【问题标题】:Unable to change color of the button无法更改按钮的颜色
【发布时间】:2022-01-22 19:53:40
【问题描述】:

下面是我编写的代码,用于在按下按钮时更改按钮的颜色。我一直在使用灵活的网格布局。出于某种原因,当我单击任何按钮时,颜色不会改变。似乎 StudentRegister 类没有被更新。感谢任何帮助。

struct StudentView: View {
    
    @State var students: [StudentRegister] = [student1, student2]
    
    let layout = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    var body: some View {
        LazyVGrid(columns: layout, spacing: 20) {
            ForEach(students, id: \.self) { student in
                VStack() {
                    Button(action: {
                        student.status = Color.green
                    }) {
                        Text(student.name!)
                    }
                    .foregroundColor(student.status!)
                }
            }
        }
    }
}


class StudentRegister: ObservableObject, Hashable, Equatable {
    var name: String?
    @Published var status: Color?
    
    static func == (lhs: StudentRegister, rhs: StudentRegister) -> Bool {
        return lhs.name == rhs.name
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
    }
    
}

【问题讨论】:

    标签: ios swiftui


    【解决方案1】:

    解决方案

    import SwiftUI
    
    struct StudentsView: View {
        // Here, we make the `StudentsView` instance subscribe to the
        // `StudentRegister` instance's `objectWillChange` publisher, by wrapping
        // the `studentRegister` instance-property with the `@StateObject` (**not**
        // the `@State`) property wrapper.
        //
        // This, combined with the fact that the `students` instance-property of the
        // `StudentRegister` class has been wrapped with the `@Published` property
        // wrapper, will cause the `StudentsView` instance to be re-rendered
        // whenever we add, remove, or re-order the **references** to `Student`
        // instances that are being stored in the `StudentRegister` instance's
        // `students` Array instance-property.
        @StateObject var studentRegister = StudentRegister()
        
        @State private var isLoading = true
        
        let layout = [
            GridItem(.flexible()),
            GridItem(.flexible()),
            GridItem(.flexible())
        ]
        
        var body: some View {
            Group {
                if isLoading {
                    Text("Loading...")
                } else {
                    LazyVGrid(columns: layout, spacing: 20) {
                        ForEach(studentRegister.students, id: \.self) { student in
                            StudentView(student: student)
                                // Changes to the values of properties of the
                                // `Student` instances to which **references** are
                                // being stored in the `StudentRegister` instance's
                                // `students` Array instance-property **won't**
                                // cause the **references** that are being stored in
                                // that Array to change.
                                //
                                // Consequently, changes to the value of the
                                // `status` instance-property of any of the
                                // `Student` instances **won't** cause the
                                // `StudentsView` View to be re-rendered.
                                //
                                // Thus, it would be **unsafe** for us to set the
                                // `foregroundColor` here in this View, given the
                                // fact that its value is dependent on the `status`
                                // property of one of the `Student` instances.
                                // .foregroundColor(student.status) // **Don't** do this.
                        }
                    }
                }
            }
            .onAppear {
                // Note: This could obviously be improved with
                // asynchronous-loading in the future.
                studentRegister.load()
                isLoading = false
            }
        }
    }
    
    class StudentRegister: ObservableObject {
        @Published var students = [Student]()
        
        func load() {
            students = [.init(name: "Bob Smith", status: .blue), .init(name: "Alice Davidson", status: .yellow)]
        }
    }
    
    struct StudentView: View {
        // The use of the `@ObservedObject` property wrapper here, **will** cause
        // the `StudentView` instance to subscribe to the `Student` instance's
        // `objectWillChange` publisher.
        @ObservedObject var student: Student
        
        var body: some View {
            VStack() {
                Button(action: {
                    student.status = Color.green
                }) {
                    Text(student.name)
                }
                // Given that changes to the value of `student.status` will cause
                // **this** `StudentView` instance to be re-rendered, it's safe for
                // us to set the `foregroundColor` (which depends on the value of
                // `student.status`) here in **this** View.
                .foregroundColor(student.status)
            }
        }
    }
    
    
    class Student: ObservableObject, Hashable, Equatable {
        var name: String
        
        @Published var status: Color
        
        init(name: String, status: Color) {
            self.name = name
            self.status = status
        }
        
        static func == (lhs: Student, rhs: Student) -> Bool {
            return lhs.name == rhs.name
        }
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(name)
        }
    }
    
    struct StudentsView_Previews: PreviewProvider {
        static var previews: some View {
            StudentsView()
        }
    }
    

    解决方案说明

    ObservableObject 的实例存储在“拥有”该实例的视图中

    您应该将@StateObject 用于最高级别视图中的属性,该属性包含符合ObservableObject 协议的类的特定实例。那是因为包含该属性的视图“拥有”该实例。

    在低级视图中接收ObservableObject 的实例

    您应该将@ObservedObject 用于该实例直接传递到的低级视图中的属性,或者如果您选择通过传递实例将实例间接传递到低级视图作为调用“拥有”实例的视图的body 计算属性变量中的environmentObject 视图方法的参数,您应该将@EnvironmentObject 用于需要接收的较低级别视图中的属性它。

    哪些变化会导致哪些 ObservableObject 的 objectWillChange Publishers 被触发,哪些视图将因此被重新渲染。

    如果您在studentRegister.students 数组中添加、删除或重新排序元素,则会导致StudentRegister 实例的objectWillChange Publisher 触发,因为它的students 属性是@Published 属性,并且在它存储的数组中添加、删除或重新排序元素会导致该数组包含的 references/pointersStudent 实例发生变化。这反过来将触发 StudentsView 视图被重新渲染,因为它订阅了 StudentRegister 实例的 objectWillChange 发布者,因为它将对该实例的引用存储在 @StateObject@ObservedObject 中或@EnvironmentObject 属性(它专门将其存储在@StateObject 中,因为它恰好“拥有”该实例)。

    请务必注意,studentRegister.students 数组将 references/pointers 存储到 Student 实例,因此,对任何 Student 实例的属性的更改赢了t 导致studentRegister.students 数组的元素发生变化。由于这些Student 实例的status 属性之一的更改不会导致studentRegister.students 数组更改,它也不会导致studentRegister 对象的objectWillChange Publisher 被触发,因此不会触发StudentsView 视图被重新渲染。

    更改Student 实例的status 属性之一 导致Student 实例的objectWillChange Publisher 被触发,因为status 属性是@Published 属性,因此对该属性的更改将触发 Student 实例对应的 StudentView 视图重新渲染。请记住,就像StudentsView 视图订阅StudentRegister 实例的objectWillChange 发布者一样,StudentView 视图订阅了它的Student 实例的objectWillChange 发布者,因为它将对该实例的引用存储在@ 987654368@ 或 @ObservedObject@EnvironmentObject(它专门将其存储在 @ObservedObject 中,因为它不“拥有”Student 实例,而是由其直接传递给它直接父视图)。

    【讨论】:

      猜你喜欢
      • 2023-01-28
      • 1970-01-01
      • 1970-01-01
      • 2020-09-07
      • 1970-01-01
      • 2020-01-01
      • 1970-01-01
      • 2020-08-03
      • 2014-07-11
      相关资源
      最近更新 更多