我在故事板中拍摄了 5 个图像视图并连接了这样的插座:
@IBOutlet var starRatingImageViews: [UIImageView]!
我从 JSON 中获取 rating 的字符串值,如下所示:
rating = "4.2"
例如。
rating = "4.2"
我需要显示 4 颗全星和一颗半星,但显示不正确。
o/p:这里我的评级为“4.2”,但其星星在输出中显示错误。
代码:在这里
user.rating = "4.2"
但在o/p中显示错误的星星。我的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)
}
}
}
}
问题是您使用
self - Float(review)
来确定要绘制什么样的星星,但是 self
独立于您的循环,并且您甚至对此不一致,因为您使用 i < review
作为第一次测试。你可以做到这一点,但这比需要的更难。由于案例数量超出了您的需要,问题变得更加混乱:您要么需要绘制一个实心星形,一个半实心星形,要么一个空心星形。这是 3 个案例,但您的代码中有 4 个案例,因为您复制了全星案例。
在评论中您澄清了
let review = Int(Float(self))
是从 String
转换而来。那么让我们看看当 self
应该是 "4.2"
并且 i
是 3 时会发生什么,这是它错误地计算部分星的地方。在这种情况下,review
将为4,因此if i < review
应该为true
,因为3小于4,所以它应该将imageViews[i]
设置为全星,但实际上并没有这样做。这告诉我 self
是 不是 "4.2"
,因此 review
不是 4
。打印 self
或在 if
上放置断点以查看 self
和 review
实际上是什么。这可能会告诉你你的错误。基于此,我认为您对 self
的假设可能在某种程度上是不正确的。
即使修复了这个问题,
fillStars
仍然有一个错误,因为当i
为4时,即在最后一个星星上,它完全填满了它,但它应该是空的。
尽管如此,
fillStars
还可以更简单。
当我遇到这样的问题时,我更喜欢考虑计算需要绘制的当前指标(在您的情况下为星号)的百分比,然后使用该结果来决定要做什么,而不是混合计算与决策相结合。所以我会将您的代码修改为如下所示:
func clamp<T: Comparable>(_ value: T, to range: ClosedRange<T>) -> 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 = "star"
}
else
{
tintColor = CommonColor.yellowColor
starName = percentOfThisStar < 0.5
? "star.leadinghalf.fill" // Partial star
: "star.fill" // Full star
}
imageViews[i].image = UIImage(systemName: starName)?
.withRenderingMode(.alwaysOriginal)
.withTintColor(tintColor)
}
}
作为建议,我可能会将
fillStars
移至代码库的另一部分。我不太确定 self
是什么(Float
?Double
?Decimal
?),除了它是某种数字类型,而且 fillStars
似乎是任何该数字类型的扩展中的方法是。我可能只是让它成为免费函数,或者可能是包含星星的任何视图中的方法,并将 review
作为参数传递。我的推理是,填充星星与 self
可能属于的任何数字类型没有太大关系。这只是您的应用程序的一种特殊情况用法,可能仅适用于您应用程序中的一个特定视图,那么为什么不将其放在该视图中呢?或者作为顶级应用程序特定功能?显然这是一个判断。编译器并不关心,所以这只是什么对您和您的团队最有意义的问题。
为了让测试变得更容易,而无需创建完整的应用程序(并避免必须使用模拟器),我在命令行工具中这样编写:
enum StarType: String
{
case empty = "[ ]"
case partial = "[* ]"
case filled = "[**]"
}
func clamp<T: Comparable>(_ value: T, to range: ClosedRange<T>) -> 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
{
// 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 < 0.5
? .partial
: .filled
}
// This could have been done in the `if`, but doing it here allows
// easier refactoring to UIImageView, if that's desired.
stars[i] = starType
}
}
我将
review
重命名为rating
,并将其作为参数传入。我打印 [**]
,而不是实心星形,对于部分星形,我打印 [* ]
,对于空星形,我打印 [ ]
。
我用这个代码驱动它:
var stars: [StarType] = .init(repeating: .empty, count: 5)
let rating: Float = 4.2
fillStars(in: &stars, from: rating)
let starStr = stars.map { $0.rawValue }.joined()
print("rating = \(rating), stars = \(starStr)")
这些是结果:
评分 = 4.2,星星 = [**][**][**][**][* ]
这似乎是正确的结果。
如上所述,根据您的描述,我认为在调用 fillStars
之前
至少发生了一个错误。
import SwiftUI
private func startIcon(rate: Float, position: Int) -> String {
let rate10 = Int(rate * 10)
let position10 = position * 10
if rate10 > position10 || rate10 == position10 {
return "star.fill"
}
if rate10 < position10 && rate10 > position10 - 10 {
return "star.leadinghalf.filled"
}
return "star" /// rate10 < position10
}
struct StarsRating: View {
var rate: Float
var body: some View {
HStack {
Label("", systemImage: startIcon(rate: rate, position: 1))
Label("", systemImage: startIcon(rate: rate, position: 2))
Label("", systemImage: startIcon(rate: rate, position: 3))
Label("", systemImage: startIcon(rate: rate, position: 4))
Label("", systemImage: startIcon(rate: rate, position: 5))
}
}
}
#Preview {
StarsRating(rate: 3.5).font(.title)
}
如果你想以你正在做的方式循环,我会:
struct StarsRating: View {
var rate: Float
var body: some View {
HStack {
ForEach(1 ... 5, id: \.self) { index in
Label("", systemImage: startIcon(rate: rate, position: index))
}
}
}
}