状态更改时自定义 SwiftUI 视图不会更新

Custom SwiftUI view does not update when state changes

我修改了自定义 5 星评级视图 (https://swiftuirecipes.com/blog/star-rating-view-in-swiftui) 以满足我的需要。我在我的应用程序的多个位置使用该视图来显示结构的当前评级并通过 selectable 列表更改评级。我遇到的问题是,当我通过 NavigationLink select 一个新的星级评分值时,基础评分值会发生变化,但显示不会。我创建了一个显示问题的示例应用程序并将其包含在下面。

//
//  StarTestApp.swift
//  StarTest
//
//  Created by Ferdinand Rios on 12/20/21.
//

import SwiftUI

@main
struct StarTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct StarHolder {
    var rating: Double = 3.5
}


struct ContentView: View {
    
    @State private var starHolder = StarHolder()

    var body: some View {
        NavigationView {
            NavigationLink {
                RatingView(starHolder: $starHolder)
            } label: {
                HStack {
                    Text("Rating: \(starHolder.rating, specifier: "%.1f")")
                    Spacer()
                    StarRatingView(rating: starHolder.rating, fontSize: 15)
                }
                .padding()
            }
            .navigationTitle("Test")
        }
    }
}

struct RatingView: View {
    @Binding var starHolder: StarHolder

    var body: some View {
        List {
            ForEach(0..<11, id: \.self) { index in
                HStack {
                    StarRatingView(rating: Double(index) * 0.5, fontSize: 15)
                    Spacer()
                    Image(systemName: starHolder.rating == Double(index) * 0.5 ? "checkmark" : "")
                }
                .contentShape(Rectangle()) //allows to tap whole area
                .onTapGesture {
                    starHolder.rating = Double(index) * 0.5
                }
            }
        }
        .navigationBarTitle(Text("Rating"))
    }
}

struct StarRatingView: View {
    private static let MAX_RATING: Double = 5 // Defines upper limit of the rating
    
    private let RATING_COLOR = Color(UIColor(red: 1.0, green: 0.714, blue: 0.0, alpha: 1.0))
    private let EMPTY_COLOR = Color(UIColor.lightGray)

    private let fullCount: Int
    private let emptyCount: Int
    private let halfFullCount: Int
    
    let rating: Double
    let fontSize: Double

    init(rating: Double, fontSize: Double) {
        self.rating = rating
        self.fontSize = fontSize
        fullCount = Int(rating)
        emptyCount = Int(StarRatingView.MAX_RATING - rating)
        halfFullCount = (Double(fullCount + emptyCount) < StarRatingView.MAX_RATING) ? 1 : 0
    }
    
    var body: some View {
        HStack (spacing: 0.5) {
            ForEach(0..<fullCount) { _ in
                self.fullStar
            }
            ForEach(0..<halfFullCount) { _ in
                self.halfFullStar
            }
            ForEach(0..<emptyCount) { _ in
                self.emptyStar
            }
        }
    }
    
    private var fullStar: some View {
        Image(systemName: "star.fill").foregroundColor(RATING_COLOR)
            .font(.system(size: fontSize))
    }
    
    private var halfFullStar: some View {
        Image(systemName: "star.lefthalf.fill").foregroundColor(RATING_COLOR)
            .font(.system(size: fontSize))
    }
    
    private var emptyStar: some View {
        Image(systemName: "star").foregroundColor(EMPTY_COLOR)
            .font(.system(size: fontSize))
    }
}

如果您 运行 该应用程序,初始评分将为 3.5,星星将显示正确的评分。当您 select 星星时,RatingView 将显示正确的评级。 Select 另一个评级和 return 到 ContentView。评分文本会更新,但星级仍与之前相同。

任何人都可以指出我在这里做错了什么吗?我假设当 starHolder 评级改变时 StarRatingView 会刷新。

这里有几个问题。首先,在您的 RatingView 中,您传递的是 Binding<StarHolder>,但是当您更新 rating 时,该结构并未显示为已更改。要解决此问题,请传入 Binding<Double>,更改将记录在 ContentView.

第二个问题是 StarRatingView 无法接受更改,因此需要一些帮助。我只是将 .id(starHolder.rating) 粘贴到 ContentView 中的 StarRatingView 上,当 StarRatingView 发生变化时,它会向 SwiftUI 发出信号,因此它会更新。

struct ContentView: View {
    @State private var starHolder = StarHolder()

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink {
                    RatingView(rating: $starHolder.rating)
                } label: {
                    HStack {
                        Text("Rating: \(starHolder.rating, specifier: "%.1f")")
                        Spacer()
                        StarRatingView(rating: starHolder.rating, fontSize: 15)
                            .id(starHolder.rating)
                    }
                    .padding()
                }
                .navigationTitle("Test")
            }
        }
    }
}

struct RatingView: View {
    @Binding var rating: Double

    var body: some View {
        List {
            ForEach(0..<11, id: \.self) { index in
                HStack {
                    StarRatingView(rating: Double(index) * 0.5, fontSize: 15)
                    Spacer()
                    Image(systemName: rating == Double(index) * 0.5 ? "circle.fill" : "circle")
                }
                .contentShape(Rectangle()) //allows to tap whole area
                .onTapGesture {
                    rating = Double(index) * 0.5
                }
            }
        }
        .navigationBarTitle(Text("Rating"))
    }
}

最后一件事。 SwiftUI 不喜欢将“”作为 SF 符号,因此我将检查更改为“circle”和“circle.fill”。无论如何,您应该为三元组的两个部分提供实际图像。或者你可以使用“检查”并使 .opacity() 三元。

在您的 StarRatingView 中更改 ForEach(0..<fullCount) {...} 等... 至 ForEach(0..<fullCount, id: \.self) {...}.

halfFullCountemptyCount 相同。

很适合我。