如何让 SwiftUI 动态响应用户事件?
Posted
技术标签:
【中文标题】如何让 SwiftUI 动态响应用户事件?【英文标题】:How do I get SwiftUI to dynamically respond to user events? 【发布时间】: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 更新?
【问题讨论】:
【参考方案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 上测试。 (如果需要我的测试环境中的完整代码,请随时通知我)。
【讨论】:
以上是关于如何让 SwiftUI 动态响应用户事件?的主要内容,如果未能解决你的问题,请参考以下文章