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

Ios 如何让SwiftUI动态响应用户事件?,ios,swift,user-interface,mvvm,swiftui,Ios,Swift,User Interface,Mvvm,Swiftui,我是一名经验丰富的iOS开发人员,但对SwiftUI来说是新手。我试图创建一个由数据模型支持的动态接口。以下说明了我正在尝试做的事情: 模型对象: class Book : Hashable { static func == (lhs: Book, rhs: Book) -> Bool { return lhs.title == rhs.title && lhs.author == rhs.author && lhs.favorite

我是一名经验丰富的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)")
                }
            }
        }
    }
}
结果如下:

外观是正确的。但是,我希望像按钮一样的
按钮能够工作:我希望点击来更新模型对象中最喜欢的变量,然后按钮本身应该适当地更改

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


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

这种方法的主要问题是

@State private var library = ...
是一个引用数组,当您在
MasterView
中更改对象时,这些引用不会更改,因此库的状态不会更改,因此
ContentView
不会刷新

以下是使此方法起作用所需的最低限度的更改

1) 通过更改初始值设定器使模型可复制(在
类目手册中的更改

2) 强制更改书本会导致更改库(在
struct MasterView
中更改)


这很有效。在Xcode 11.2/iOS 13.2上测试。(如果需要来自我的测试环境的完整代码,请毫不犹豫地通知我)。

另一个解决方案是遵守ObserveObject协议,并使用@Published property observer,以便监视我们类的任何视图都会注意到属性已更改并重新加载。像这样

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
对应于
@observeobject
,但将两者混用是行不通的。
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)
        }
    }
}