简体   繁体   English

如何让 SwiftUI 动态响应用户事件?

[英]How do I get SwiftUI to dynamically respond to user events?

I'm an experienced iOS developer, but new to SwiftUI.我是一位经验丰富的 iOS 开发人员,但不熟悉 SwiftUI。 I'm trying to create a dynamic interface backed by a data model.我正在尝试创建一个由数据模型支持的动态接口。 The following illustrates what I'm trying to do:以下说明了我正在尝试做的事情:

The model object:模型对象:

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)
    }
}

The content view:内容视图:

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)")
                }
            }
        }
    }
}

Here's what the result looks like:结果如下所示:

预览

The appearance is correct.外观是正确的。 However, I want the like buttons to work: I want the tap to update the favorite variable in the model object, and then the buttons themselves should change appropriately.但是,我希望like按钮能够工作:我希望点击更新模型对象中的收藏变量,然后按钮本身应该适当地改变。

The first happens, the second doesn't.第一个发生,第二个没有。

What am I doing wrong, and how would I get the dynamic UI updates I want?我做错了什么,如何获得我想要的动态 UI 更新?

Another solution would be to conform to ObservableObject protocol, and use @Published property observer so any views that are watching our class notice that the property changed and reload.另一种解决方案是遵守 ObservableObject 协议,并使用 @Published 属性观察器,以便任何正在观看我们类的视图都会注意到该属性已更改并重新加载。 Like this.像这样。

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)
}}

Than create a separate View for each row比为每一行创建一个单独的视图

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)
        }
    }
}

The main issue of this approach is that这种方法的主要问题是

@State private var library = ...

is an array of references, which are not changed when you change object in MasterView , so state of library is not changed, so ContentView is not refreshed.是一个引用数组,当你改变MasterView对象时MasterView不会改变,所以库的状态不会改变,所以ContentView不会刷新。

Here are minimum required changes to make this approach work以下是使此方法起作用所需的最少更改

1) make model duplicatable, by changing initialiser (changes in class Book ) 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) force changing of book result in changed library (changes in struct MasterView ) 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)")
    }
}

This works.这有效。 Tested on Xcode 11.2 / iOS 13.2.在 Xcode 11.2 / iOS 13.2 上测试。 (If needed full code from my test env, don't hesitate to notify me). (如果需要来自我的测试环境的完整代码,请随时通知我)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM