【问题标题】:How do I get SwiftUI to dynamically respond to user events?如何让 SwiftUI 动态响应用户事件?
【发布时间】:2019-12-10 17:36:35
【问题描述】:

我是一位经验丰富的 iOS 开发人员,但对 SwiftUI 不熟悉。我正在尝试创建一个由数据模型支持的动态接口。以下说明了我正在尝试做的事情:

模型对象:

class Book : Hashable {
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.title == rhs.title && lhs.author == rhs.author && lhs.favorite == rhs.favorite
    }

    var title : String
    var author : String
    var favorite : Bool = false

    init(title : String, author: String) {
        self.title = title
        self.author = author
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
        hasher.combine(author)
        hasher.combine(favorite)
    }
}

内容视图:

struct ContentView: View {
    @State private var library = [
        Book(title: "Lord of The Rings", author: "J.R.R. Tolkien"),
        Book(title: "Harry Potter", author: "J.K. Rowling"),
        Book(title: "Under the Dome", author: "Steven King")
    ]

    var body: some View {
        NavigationView {
            MasterView(books: $library)
                .navigationBarTitle(Text("Library"))
        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

struct MasterView: View {
    @Binding var books: [Book]

    var body: some View {
        List {
            ForEach(books, id: \.self) { book in
                HStack {
                    Button(action: {}, label: {
                        (book.favorite ?
                            Image(systemName: "heart.fill") :
                            Image(systemName: "heart"))
                        .imageScale(.large)
                    }).onTapGesture {
                        book.favorite.toggle()
                    }
                    Text("\(book.title) by \(book.author)")
                }
            }
        }
    }
}

结果如下:

外观正确。但是,我希望 like 按钮能够工作:我希望点击更新模型对象中的收藏变量,然后按钮本身应该相应地更改。

第一个发生,第二个没有。

我做错了什么,如何获得我想要的动态 UI 更新?

【问题讨论】:

    标签: ios swift user-interface mvvm swiftui


    【解决方案1】:

    另一种解决方案是遵守 ObservableObject 协议,并使用 @Published 属性观察器,这样任何正在观看我们的类的视图都会注意到该属性已更改并重新加载。像这样。

    class Book : Hashable, ObservableObject {
    static func == (lhs: Book, rhs: Book) -> Bool {
        return lhs.title == rhs.title && lhs.author == rhs.author && lhs.favorite == rhs.favorite
    }
    
    var title : String
    var author : String
    @Published var favorite : Bool = false
    
    init(title : String, author: String) {
        self.title = title
        self.author = author
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
        hasher.combine(author)
        hasher.combine(favorite)
    }}
    

    比为每一行创建一个单独的视图

    struct Row: View {
    
    @ObservedObject var onebook: Book
    
    var body: some View {
        HStack {
            Button(action: {}, label: {
                (onebook.favorite ?
                    Image(systemName: "heart.fill") :
                    Image(systemName: "heart"))
                    .imageScale(.large)
            }).onTapGesture {
                self.onebook.favorite.toggle()
            }
            Text("\(onebook.title) by \(onebook.author)")
        }
    }}
    
    
    
    struct MasterView: View {
    
    @Binding var books: [Book]
    
    var body: some View {
        List {
            ForEach(books, id: \.self) { book in
                Row(onebook: book)
            }
        }
    }
    

    【讨论】:

    • 这比其他答案更接近我想要的——数据模型支持的重点是让单一来源直接驱动 UI。显然,@Binding@State 的对应物,@ObservedObject@ObservableObject 的对应物,但是将两者混合是行不通的。
    【解决方案2】:

    这种方法的主要问题是

    @State private var library = ...
    

    是一个引用数组,在MasterView中更改对象时不会更改,因此库的状态不会更改,因此ContentView不会刷新。

    这是使这种方法起作用所需的最低限度的更改

    1) 通过更改初始化程序使模型可复制(class Book 中的更改)

    var title : String
    var author : String
    var favorite : Bool
    
    init(title : String, author: String, favorite : Bool = false) {
        self.title = title
        self.author = author
        self.favorite = favorite
    }
    

    2) 强制更改图书导致更改库(更改struct MasterView

    ForEach(books, id: \.self) { book in
        HStack {
            Button(action: {
                // Main idea: modification of book, must modify the library
                // which is bound to state of parent view, which result in refresh
                self.books = self.books.map { $0 != book ? $0 :
                  Book(title: book.title, author: book.author, favorite: !book.favorite)}
            }) {
                (book.favorite ?
                    Image(systemName: "heart.fill") :
                    Image(systemName: "heart"))
                .imageScale(.large)
            }
            Text("\(book.title) by \(book.author)")
        }
    }
    

    这行得通。在 Xcode 11.2 / iOS 13.2 上测试。 (如果需要我的测试环境中的完整代码,请随时通知我)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-23
      • 1970-01-01
      • 2010-12-06
      • 1970-01-01
      相关资源
      最近更新 更多