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

Posted

技术标签:

【中文标题】状态更改时自定义 SwiftUI 视图不会更新【英文标题】:Custom SwiftUI view does not update when state changes 【发布时间】:2021-12-21 17:03:35 【问题描述】:

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

//
//  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,并且星星将显示正确的评分。当您选择星星时,RatingView 将显示并检查正确的评级。选择另一个评级并返回到 ContentView。评分的文字将更新,但星级仍与以前相同。

谁能指出我在这里做错了什么?我假设 StarRatingView 会在 starHolder 评级发生变化时刷新。

【问题讨论】:

【参考方案1】:

这里有几个问题。首先,在您的RatingView 中,您传递了一个Binding&lt;StarHolder&gt;,但是当您更新rating 时,该结构不会显示为已更改。要解决此问题,请传入Binding&lt;Double&gt;,更改将在ContentView 中注明。

第二个问题是StarRatingView 无法接受更改,因此需要一些帮助。我只是在ContentView 中将.id(starHolder.rating) 粘贴到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 Symbol,所以我将检查更改为 "circle" 和 "circle.fill"。无论如何,您应该为三元的两个部分提供实际图像。或者您可以使用“检查”并将.opacity() 设为三元组。

【讨论】:

您添加 .id(starHolder.rating) 的建议与上面@dudette 的回答相结合,为我提供了所需的解决方案。谢谢!【参考方案2】:

在您的StarRatingView 中更改ForEach(0..&lt;fullCount) ... 等... 到ForEach(0..&lt;fullCount, id: \.self) ...

halfFullCountemptyCount 相同。

很适合我。

【讨论】:

这是部分解决方案。我将它与下面的 Yrb 解决方案结合起来得到我需要的答案。谢谢!

以上是关于状态更改时自定义 SwiftUI 视图不会更新的主要内容,如果未能解决你的问题,请参考以下文章

空白时自定义文本输入更改标签

如何在 SwiftUI 中触发状态更改/重绘切换更改

SwiftUI 不会将状态更新为 @ObservedObject cameraViewModel 对象

SwiftUI:具有计时器样式的文本在提供的日期更改后不会更新

子视图更改SwiftUI后更新视图

基于 VIewModel 状态更新 SwiftUI 视图?