【SwiftUI】如何优雅的构建列表数据模型

602 阅读1分钟

比如我们要实现两个简单的页面:一个列表页展示学生列表,一个学生编辑页面。点击列表的每一条跳转到对应的编辑页面,编辑并保存后列表数据更新。

构建model

import SwiftUI
import Combine

struct Person : Identifiable {
    let id : UUID
    var name : String
    var age : Int
}

final class PersonStore : ObservableObject {
    @Published var persons : [Person] = [
        .init(id: .init(), name: "张三", age: 1),
        .init(id: .init(), name: "李四", age: 2),
        .init(id: .init(), name: "王五", age: 3)
    ]
}

构建列表及编辑页View层(不具有跳转功能,纯展示)

struct PersonsView : View {
    @ObservedObject var store: PersonStore

    var body: some View {
        NavigationView {
            List(store.persons) { person in
                VStack(alignment: .leading) {
                    Text(person.name)
                        .font(.headline)
                    Text("Age: \(person.age)")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
            }
        }.navigationBarTitle(Text("Persons"))
    }
}

接下来编写编辑页面:

struct EditingView: View {
    @Environment(\.presentationMode) var presentation
    @Binding var person: Person

    var body: some View {
        Form {
            Section(header: Text("Personal information")) {
                TextField("type something...", text: $person.name)
                Stepper(value: $person.age) {
                    Text("Age: \(person.age)")
                }
            }

            Section {
                Button("Save") {
                    self.presentation.wrappedValue.dismiss()
                }
            }
        }.navigationBarTitle(Text(person.name))
    }
}

在编辑页面声明了person属性,并使用@Binding修饰。@Binding修饰符允许传递一个引用类型到值类型。这样在EditView的子View里我们可以使用$来传递person的引用而不是值的copy。

自定义数据结构IndexedCollection

我们知道列表List对应的数据需实现RandomAccessCollection,PersonStore中的数组已经满足要求。但是在跳转时需要获取当前选中的数据的索引,从而从store中获取并传递引用,因此我们需要自定义RandomAccessCollection类型,其中Element类型为原组(Index,Element)

struct IndexedCollection<Base: RandomAccessCollection>: RandomAccessCollection {
    typealias Index = Base.Index
    typealias Element = (index: Index, element: Base.Element)

    let base: Base

    var startIndex: Index { base.startIndex }

    var endIndex: Index { base.endIndex }

    func index(after i: Index) -> Index {
        base.index(after: i)
    }

    func index(before i: Index) -> Index {
        base.index(before: i)
    }

    func index(_ i: Index, offsetBy distance: Int) -> Index {
        base.index(i, offsetBy: distance)
    }

    subscript(position: Index) -> Element {
        (index: position, element: base[position])
    }
}

extension RandomAccessCollection {
    func indexed() -> IndexedCollection<Self> {
        IndexedCollection(base: self)
    }
}

重构列表View

重构列表View:增加跳转编辑页逻辑和数据传递逻辑。

1.使用NavigationLink实现跳转。

2.传递选中条目对应的Person引用,通过前一步的准备,现在很好实现了。

struct PersonsView : View {
    @ObservedObject var store: PersonStore

    var body: some View {
        NavigationView {
            List {
                ForEach(store.persons.indexed(), id: \.1.id) { index, person in
                    NavigationLink(destination: EditingView(person: self.$store.persons[index])) {
                        VStack(alignment: .leading) {
                            Text(person.name)
                                .font(.headline)
                            Text("Age: \(person.age)")
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                        }
                    }
                }
            }
            .navigationBarTitle(Text("Persons"))
        }
    }
}