[英]How to change item of array in Swiftui
How to change item of List in Swift?如何更改 Swift 中的列表项? (struct item)
(结构项目)
@State var people: [Person] = [
.init(firstName: "Steve",
lastName: "Jobs", image: #imageLiteral(resourceName: "jobs"), jobTitle: "Founder of Apple"),
.init(firstName: "Tim", lastName: "Cook", image: #imageLiteral(resourceName: "cook"), jobTitle: "Apple CEO"),
.init(firstName: "Jony", lastName: "Ive", image: #imageLiteral(resourceName: "ive"), jobTitle: "Head of Design")
]
I want to pass the item of this array to another view and that view can modify item (function like class)我想将此数组的项目传递给另一个视图,并且该视图可以修改项目(类函数)
I try more way like:我尝试更多方式,例如:
@State Person struct
Observable Object (not working) Passthrough (not working)可观察到的 Object(不工作)直通(不工作)
You have to provide a Binding
.您必须提供一个
Binding
。 Eg:例如:
struct MySecondView: View {
@Binding var people: [People]
var body: some View { ... }
func modifyArray() { /* Do whatever you need here */ }
}
And pass it to that view when initializing it in your other view like:并在您的其他视图中初始化它时将其传递给该视图,例如:
MySecondView(people: self.$people)
ObservableObject
should work if used correctly, the core concept of SwiftUI is having a single source of truth.如果使用正确,
ObservableObject
应该可以工作,SwiftUI 的核心概念是具有单一的事实来源。 That was where the Binding
came in, however if you iterate trough a list, you got the value typed Person
not the desired Binding<Person>
.这就是
Binding
的用武之地,但是如果您遍历一个列表,您会得到类型为Person
的值,而不是所需的Binding<Person>
。 You may use indexes through the iteration and pass it to TextForm
so it can get the original array.您可以通过迭代使用索引并将其传递给
TextForm
以便它可以获取原始数组。
To make a code more readable I suggest to have a viewmodel like为了使代码更具可读性,我建议使用类似的视图模型
class PeopleViewModel: ObservableObject {
@Published var people: [Person] = [
.init(lastname: "Some", firstname: "Dude"),
.init(lastname: "Other", firstname: "Dude"),
]
}
Which you must watch in the view using the @ObservedObject
wrapper.您必须使用
@ObservedObject
包装器在视图中观看。
struct PeopleList: View {
@ObservedObject var viewModel = PeopleViewModel()
var body: some View {
NavigationView {
List(viewModel.people.indices) { index in
TextForm(viewModel: self.viewModel, index: index)
}
}
}
}
And have the TextForm
have the index end the viewmodel instance.并让
TextForm
的索引结束 viewmodel 实例。
struct TextForm: View {
@ObservedObject var viewModel: PeopleViewModel
var index: Int
var body: some View {
VStack {
TextField("textField", text: self.$viewModel.people[index].firstname)
Text(self.viewModel.people[index].firstname)
}
}
}
If you really want to omit the viewmodel just pass the binding trough indexing.如果您真的想省略视图模型,只需通过绑定槽索引即可。
List(people.indices) { index in
TextForm(item: self.$people[index])
}
struct TextForm: View {
@Binding var item: Person
var body: some View {
VStack {
TextField("textField", text: self.$item.firstname)
Text(self.item.firstname)
}
}
}
one thing that almost no one writes about in the ObservableObject solution - it is best that the item in the array will be struct instead of class,在 ObservableObject 解决方案中几乎没有人写过的一件事 - 数组中的项目最好是 struct 而不是 class,
when a struct member (var) change it's value it is considered as a change to the containing struct, and achange to the containing struct (the array element) is considered as a change to the whole array (and therefor a change to the published object) - and this small change will cause the view to re render当结构成员 (var) 更改其值时,它被视为对包含结构的更改,对包含结构(数组元素)的更改被视为对整个数组的更改(因此对已发布对象的更改) - 这个小改动会导致视图重新渲染
so to make a short story long, here are 2 fully working code examples, one does not work (where Episode is Class) and the other is working (where Episode is Struct)所以长话短说,这里有 2 个完全工作的代码示例,一个不工作(其中 Episode 是 Class),另一个工作(其中 Episode 是 Struct)
this code will not update the View as expected only because the Episode entity is defined as Class:此代码不会按预期更新视图,因为 Episode 实体被定义为 Class:
struct Test_Previews: PreviewProvider {
static var previews: some View {
SeasonScreen()
}
}
// model class that represents episode data
class Episode: Identifiable
{
let id: UUID
let name: String
var watched: Bool
init(name: String, watched: Bool)
{
id = UUID()
self.name = name
self.watched = watched
}
}
// dummy server api that returns the episodes array
struct ServerAPI
{
static func getAllEpisodes(showId: String, seasonId: String, completion: @escaping([Episode]) -> ())
{
let dummyResponse: [Episode] = [Episode(name: "Episode 1", watched: true), Episode(name: "Episode 2", watched: false)]
completion(dummyResponse)
}
}
// class that holds (and publish changes on) the episodes array
class SeasonEpisodes: ObservableObject
{
@Published var episodesRow: [Episode] = []
init()
{
ServerAPI.getAllEpisodes(showId: "Friends", seasonId: "Season 2", completion: { episodes in
self.episodesRow = episodes
})
}
}
// main screen view that contains episodes array object and observe changes on it
struct SeasonScreen: View
{
@ObservedObject var seasonEpisodes: SeasonEpisodes = SeasonEpisodes()
var body: some View
{
VStack
{
if(self.seasonEpisodes.episodesRow.count > 0)
{
ScrollView(.horizontal, showsIndicators: false)
{
HStack(alignment: VerticalAlignment.center, spacing: 60.0, content: {
ForEach(self.seasonEpisodes.episodesRow.indices) { episodeIndex in
EpisodeButton(seasonEpisodes: self.seasonEpisodes, episodeIndex: episodeIndex)
.padding(40)
}
})
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
}
}
// ui view to represent episode button
struct EpisodeButton: View
{
@ObservedObject var seasonEpisodes: SeasonEpisodes
let episodeIndex: Int
var body: some View
{
let watchedText = self.seasonEpisodes.episodesRow[episodeIndex].watched ? "Watched" : ""
Button(action: {
// although the change occurr on the observed object and it causes the SeasonScreen
// to re render, and therefor re render this button view, that should receive the
// new watched value - it doesn't work
self.seasonEpisodes.episodesRow[episodeIndex].watched = !self.seasonEpisodes.episodesRow[episodeIndex].watched
print("episode new watched value: \(self.seasonEpisodes.episodesRow[episodeIndex].watched)")
}, label: {
Text("\(self.seasonEpisodes.episodesRow[episodeIndex].name) \(watchedText)")
.frame(width: 420, height: 224)
.background(Color.blue)
.foregroundColor(Color.white)
})
.buttonStyle(PlainButtonStyle())
.padding()
}
}
tapping on the buttons will produce these log lines that confirm the value is changing (although view is not re-render):点击按钮将生成这些日志行,确认值正在更改(尽管视图不会重新渲染):
episode new watched value: false
episode new watched value: true
episode new watched value: false
and this code will work as expected and reflect the new changed value on the view just because i replaced the Episode entity from Class to Struct:并且此代码将按预期工作并在视图上反映新的更改值,因为我将 Episode 实体从 Class 替换为 Struct:
struct Test_Previews: PreviewProvider {
static var previews: some View {
SeasonScreen()
}
}
// model class that represents episode data
struct Episode: Identifiable // <--- this is the only change to make it work
{
let id: UUID
let name: String
var watched: Bool
init(name: String, watched: Bool)
{
id = UUID()
self.name = name
self.watched = watched
}
}
// dummy server api that returns the episodes array
struct ServerAPI
{
static func getAllEpisodes(showId: String, seasonId: String, completion: @escaping([Episode]) -> ())
{
let dummyResponse: [Episode] = [Episode(name: "Episode 1", watched: true), Episode(name: "Episode 2", watched: false)]
completion(dummyResponse)
}
}
// class that holds (and publish changes on) the episodes array
class SeasonEpisodes: ObservableObject
{
@Published var episodesRow: [Episode] = []
init()
{
ServerAPI.getAllEpisodes(showId: "Friends", seasonId: "Season 2", completion: { episodes in
self.episodesRow = episodes
})
}
}
// main screen view that contains episodes array object and observe changes on it
struct SeasonScreen: View
{
@ObservedObject var seasonEpisodes: SeasonEpisodes = SeasonEpisodes()
var body: some View
{
VStack
{
if(self.seasonEpisodes.episodesRow.count > 0)
{
ScrollView(.horizontal, showsIndicators: false)
{
HStack(alignment: VerticalAlignment.center, spacing: 60.0, content: {
ForEach(self.seasonEpisodes.episodesRow.indices) { episodeIndex in
EpisodeButton(seasonEpisodes: self.seasonEpisodes, episodeIndex: episodeIndex)
.padding(40)
}
})
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
}
}
// ui view to represent episode button
struct EpisodeButton: View
{
@ObservedObject var seasonEpisodes: SeasonEpisodes
let episodeIndex: Int
var body: some View
{
let watchedText = self.seasonEpisodes.episodesRow[episodeIndex].watched ? "Watched" : ""
Button(action: {
// this time the new watched value reflects on the view
self.seasonEpisodes.episodesRow[episodeIndex].watched = !self.seasonEpisodes.episodesRow[episodeIndex].watched
print("episode new watched value: \(self.seasonEpisodes.episodesRow[episodeIndex].watched)")
}, label: {
Text("\(self.seasonEpisodes.episodesRow[episodeIndex].name) \(watchedText)")
.frame(width: 420, height: 224)
.background(Color.blue)
.foregroundColor(Color.white)
})
.buttonStyle(PlainButtonStyle())
.padding()
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.