Custom Review star images not showing properly in Swift.

huangapple go评论58阅读模式
英文:

Custom Review star images not showing properly in Swift

问题

我在Storyboard中使用了5个ImageView,并连接了如下的outlets:

@IBOutlet var starRatingImageViews: [UIImageView]!

我从JSON中获取了"rating"的字符串值,例如:

rating = "4.2"

例如,rating = "4.2" 我需要显示4个满星和半颗星,但它没有正确显示。

输出:这里我有 "4.2" 的评分,但星星在输出中显示错误。请指导我如何解决这个问题。

代码: 在这里,user.rating = "4.2" 但在输出中显示错误的星星。在 fillStars 方法中我哪里错了,请指导。

@IBOutlet var starRatingImageViews: [UIImageView]!

if let rating = Float(user.rating ?? "0.0") {
    rating.fillStars(in: starRatingImageViews)
}

func fillStars(in imageViews: [UIImageView], withEmptyStar color: UIColor = .darkGray) {
    for i in (0 ..< imageViews.count) {
        imageViews[i].contentMode = .scaleAspectFit
        let review = Int(Float(self))
        if (i < review) {
            imageViews[i].image = UIImage(systemName: "star.fill")?.withRenderingMode(.alwaysOriginal).withTintColor(CommonColor.yellowColor)
        } else {
            if (self - Float(review)) > (0.5), i == review {
                imageViews[i].image = UIImage(systemName: "star.fill")?.withRenderingMode(.alwaysOriginal).withTintColor(CommonColor.yellowColor)
            } else if (self - Float(review)) <= (0.5), (self - Float(review)) > (0.0), i == review {
                imageViews[i].image = UIImage(systemName: "star.leadinghalf.fill")?.withRenderingMode(.alwaysOriginal).withTintColor(CommonColor.yellowColor)
            } else {
                imageViews[i].image = UIImage(systemName: "star")?.withRenderingMode(.alwaysOriginal).withTintColor(color)
            }
        }
    }
}
英文:

I have taken 5 imageviews in storyboard and connected outlets like this

@IBOutlet var starRatingImageViews: [UIImageView]!

and i am getting string value from json for rating like this rating = &quot;4.2&quot;

for eg. rating = &quot;4.2&quot; i need to show 4 full stars and one half star but its not showing properly

o/p: here i have "4.2" rating but its stars showing wrongly in output. please guide me to fix this issue

Custom Review star images not showing properly in Swift.

code: here user.rating = &quot;4.2&quot; but showing wrong stars in o/p. where am i wrong in fillStars method. please do guide.

 @IBOutlet var starRatingImageViews: [UIImageView]!

 if let rating = Float(user.rating ?? &quot;0.0&quot;) {
     rating.fillStars(in: starRatingImageViews)
 }

 func fillStars(in imageViews: [UIImageView], withEmptyStar color: UIColor = .darkGray) {
    for i in (0 ..&lt; imageViews.count) {
        imageViews[i].contentMode = .scaleAspectFit
        let review = Int(Float(self))
        if (i &lt; review) {
            imageViews[i].image = UIImage(systemName: &quot;star.fill&quot;)?.withRenderingMode(.alwaysOriginal).withTintColor(CommonColor.yellowColor)
        } else {
            if (self - Float(review)) &gt; (0.5), i == review {
                imageViews[i].image = UIImage(systemName: &quot;star.fill&quot;)?.withRenderingMode(.alwaysOriginal).withTintColor(CommonColor.yellowColor)
            } else if (self - Float(review)) &lt;= (0.5), (self - Float(review)) &gt; (0.0), i == review {
                imageViews[i].image = UIImage(systemName: &quot;star.leadinghalf.fill&quot;)?.withRenderingMode(.alwaysOriginal).withTintColor(CommonColor.yellowColor)
            } else {
                imageViews[i].image = UIImage(systemName: &quot;star&quot;)?.withRenderingMode(.alwaysOriginal).withTintColor(color)
            }
        }
    }
}

答案1

得分: 0

以下是您要的翻译部分:

问题在于您正在使用self - Float(review)来确定要绘制的星星类型,但self与您的循环无关,而且您甚至在使用i &lt; review作为第一个测试时也不一致。您可以让它工作,但比必要的要复杂。问题更加混乱,因为您比需要的情况更多:您只需要绘制一个填充的星星、半填充的星星或空星星。这是3种情况,但您的代码中有4种情况,因为您复制了完整的星星情况。

在评论中,您澄清了let review = Int(Float(self))是从String转换而来。因此,让我们看看当self应该是"4.2",而i为3时会发生什么,这是它错误地计算了部分星星的情况。在这种情况下,review将为4,因此if i &lt; review 应该true,因为3小于4,所以它应该将imageViews[i]设置为满星,但实际上并没有这样做。这告诉我self 不是 "4.2",因此review也不是4。要么打印self,要么在if上设置断点,查看selfreview的实际情况。这可能会显示出您的错误。基于此,我认为您关于self是什么的假设可能有误。

即使在修复这个问题之后,fillStars仍然存在一个bug,因为当i为4时,即最后一个星星,它完全填充了它,但它应该是空的。

尽管如此,fillStars可以更简单一些。

当我遇到这种问题时,我更喜欢根据当前指示器(星星在您的情况下)需要绘制的百分比来思考,然后使用该结果来决定要执行什么操作,而不是将计算和决策混合在一起。因此,我会修改您的代码,如下所示:

func clamp&lt;T: Comparable&gt;(_ value: T, to range: ClosedRange&lt;T&gt;) -&gt; T {
    return max(range.lowerBound, min(range.upperBound, value))
}

func fillStars(
    in imageViews: [UIImageView],
    withEmptyStar color: UIColor = .darkGray)
{
    let review = Float(self) // 现在是否需要这个了?

    for i in imageViews.indices
    {
        imageViews[i].contentMode = .scaleAspectFit
        let tintColor: UIColor
        let starName: String
        
        // 计算要绘制的当前星星的百分比
        let percentOfThisStar = clamp(review - Float(i), to: 0...1)

        // 现在使用百分比来选择图像/着色
        if percentOfThisStar == 0
        { // 空星星
            tintColor = color
            starName  = "star"
        }
        else
        {
            tintColor = CommonColor.yellowColor
            starName  = percentOfThisStar < 0.5
                ? "star.leadinghalf.fill" // 部分星星
                : "star.fill"             // 满星
        }
        
        imageViews[i].image = UIImage(systemName: starName)?
            .withRenderingMode(.alwaysOriginal)
            .withTintColor(tintColor)

    }
}

作为建议,我可能会将fillStars移动到代码库的另一部分。我不太确定self是什么(FloatDoubleDecimal?),除了它是某种数值类型,而且它似乎是在任何数值类型的扩展中的一个方法。我可能会将其转换为自由函数,或者可能会将其转换为包含星星的视图中的方法,并将review作为参数传递。我的理由仅仅是填充星星与self可能是什么数值类型没有太多关系。这只是您的应用程序的一个特殊用例,可能只适用于您应用程序的一个特定视图,那么为什么不将其放在该视图中呢?或者作为顶级的应用程序特定函数?显然这是一个判断的问题。编译器并不关心,所以只是根据您和您的团队的需求来决定。

为了更轻松地测试它而不必创建完整的应用程序(并且避免使用模拟器),我在命令行工具中编写了以下代码:

enum StarType: String
{
    case empty   = "[  ]"
    case partial = "[* ]"
    case filled  = "[**]"
}

func clamp&lt;T: Comparable&gt;(_ value: T, to range: ClosedRange&lt;T&gt;) -&gt; T {
    return max(range.lowerBound, min(range.upperBound, value))
}


func fillStars(
    in stars: inout [StarType],
    from rating: Float)
{
    assert(Float(stars.count) >= rating)
    
    for i in stars.indices
    {
        // 计算要绘制的当前星星的百分比
        let percentOfThisStar = clamp(rating - Float(i), to: 0...1)

        // 现在使用百分比来设置星星类型
        let starType: StarType
        if percentOfThisStar == 0
        { // 空星星
            starType = .empty
        }
        else
        {
            starType  = percentOfThisStar < 0.5
                ? .partial
                : .filled
        }
        
        // 这可以在`if`中完成,但在此处执行可以更容易地将其重构为UIImageView,如果需要的话。
        stars[i] = starType


<details>
<summary>英文:</summary>

The problem is that you&#39;re using `self - Float(review)` to figure out what kind of star to draw, but `self` is independent of your loop, and you&#39;re not even consistent about it, since you use `i &lt; review` as the first test.  You can make that work, but it&#39;s harder than it needs to be. The matter is confused somewhat more by having more cases than you need: You either need to draw a filled star, a half-filled star, or an empty star.  That&#39;s 3 cases, but you have 4 in your code, because you duplicate the full star case.

In comments you clarified that `let review = Int(Float(self))` is converting from `String`.  So let&#39;s look at what happens when `self` is supposed to be `&quot;4.2&quot;` and `i` is 3, which is where it incorrectly computes the partial star.  In this case `review` will be 4, so `if i &lt; review` *should* be `true` since 3 is less than 4, so it should set `imageViews[i]` to a full star, but it doesn&#39;t actually do that. This tells me that `self` is *not* `&quot;4.2&quot;`, and therefore `review` is not `4`.  Either print `self` or put a break-point on the `if` to see what `self` and `review` actually are.  That will probably show you your bug.  Based on that, I think your assumptions about what `self` is are likely incorrect somehow.

Even after fixing that, though, `fillStars` still has a bug, because when `i` is 4, that is on the last star, it completely fills it, but it should be empty. 

Nonetheless, `fillStars` could be simpler.

When I have problems like this, I prefer to think in terms of calculating the percent of the current indicator (star, in your case) that needs to be drawn, and then use that result to decide what to do, rather than mixing the calculation and decision-making together.  So I would modify your code to be something like this:

```swift 
func clamp&lt;T: Comparable&gt;(_ value: T, to range: ClosedRange&lt;T&gt;) -&gt; T {
    return max(range.lowerBound, min(range.upperBound, value))
}

func fillStars(
    in imageViews: [UIImageView],
    withEmptyStar color: UIColor = .darkGray)
{
    let review = Float(self) // Is this needed now?

    for i in imageViews.indices
    {
        imageViews[i].contentMode = .scaleAspectFit
        let tintColor: UIColor
        let starName: String
        
        // Calculate the percentage of the current star to draw
        let percentOfThisStar = clamp(review - Float(i), to: 0...1)

        // Now use the percentage to pick the image/tinting
        if percentOfThisStar == 0
        { // Empty star
            tintColor = color
            starName  = &quot;star&quot;
        }
        else
        {
            tintColor = CommonColor.yellowColor
            starName  = percentOfThisStar &lt; 0.5
                ? &quot;star.leadinghalf.fill&quot; // Partial star
                : &quot;star.fill&quot;             // Full star
        }
        
        imageViews[i].image = UIImage(systemName: starName)?
            .withRenderingMode(.alwaysOriginal)
            .withTintColor(tintColor)

    }
}

As a suggestion, I'd probably move fillStars to another part of your code base. I'm not exactly sure what self is (Float? Double? Decimal?), except that it's some numeric type, and it seems fillStars is a method in an extension on whatever that numeric type is. I'd probably just make it free function instead, or maybe a method in whatever view contains the stars, and pass the review in as a parameter. My reasoning is just that filling stars doesn't have much to do with being any of the numeric types that self might be. It's just a special case usage for your app, probably just for one specific view in your app, so why not put it in that view? Or as a top-level app-specific function? Obviously that's a judgement call. The compiler doesn't care, so it's just a matter of what makes the most sense for you and your team.

To make life a little easier to test it without creating a full-on app (and to avoid having to use Simulator), I wrote it like this in a command line tool:

enum StarType: String
{
    case empty   = &quot;[  ]&quot;
    case partial = &quot;[* ]&quot;
    case filled  = &quot;[**]&quot;
}

func clamp&lt;T: Comparable&gt;(_ value: T, to range: ClosedRange&lt;T&gt;) -&gt; T {
    return max(range.lowerBound, min(range.upperBound, value))
}


func fillStars(
    in stars: inout [StarType],
    from rating: Float)
{
    assert(Float(stars.count) &gt;= rating)
    
    for i in stars.indices
    {
        // Calculate the percentage of the current star to draw
        let percentOfThisStar = clamp(rating - Float(i), to: 0...1)

        // Now use the percentage to set the star type
        let starType: StarType
        if percentOfThisStar == 0
        { // Empty star
            starType = .empty
        }
        else
        {
            starType  = percentOfThisStar &lt; 0.5
                ? .partial
                : .filled
        }
        
        // This could have been done in the `if`, but doing it here allows
        // easier refactoring to UIImageView, if that&#39;s desired.
        stars[i] = starType
    }
}

I renamed review to be rating, and pass it in as a parameter. Instead of a filled star, I print [**], for a partial star I print [* ] and for an empty star I print [ ].

I drive it with this code:

var stars: [StarType] = .init(repeating: .empty, count: 5)
let rating: Float = 4.2
fillStars(in: &amp;stars, from: rating)
let starStr = stars.map { $0.rawValue }.joined()

print(&quot;rating = \(rating), stars = \(starStr)&quot;)

These are the results:

<pre>
rating = 4.2, stars = [][][][][* ]
</pre>

Which appears to be the correct result.

As mentioned above, based on what you've described, I think there is at least one bug that happens before calling fillStars.

huangapple
  • 本文由 发表于 2023年5月7日 20:35:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76193970.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定