Получение данных из локального/удаленного хранилища

Сегодня мы создадим небольшое приложение, в котором используются основные концепции SwiftUI, такие как Binding и ObservableObject. Предположим, вы работаете над приложением, которое выполняет две основные функции:

  1. Получить и показать список сотрудников из локального или удаленного хранилища
  2. Редактировать личную информацию о выбранном сотруднике

Начнем с описания нашего модельного слоя.

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: "Majid", age: 27),
        .init(id: .init(), name: "John", age: 31),
        .init(id: .init(), name: "Fred", age: 25)
    ]
}

Здесь у нас есть простая структура Person, которая соответствует протоколу Identifying. Единственным требованием для Идентифицируемых является поле Hashable id. Мы реализуем его, определив id как UUID. Мы также можем использовать Int вместо UUID.

Затем мы можем реализовать класс PersonStore, который предоставляет данные для нашего представления. Тип PersonStore соответствует ObservableObject, это позволит SwiftUI обновлять представление, как только любое из полей @Published изменяется .

Теперь давайте посмотрим на PersonListView.

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

Мы используем компонент List для представления массива структур Person. Каждая строка в List содержит VStack с двумя компонентами Text, представляющими имя и возраст Person. Мы вызываем метод fetch для объекта хранилища, как только появляется список. Как вы помните, наш объект PersonStore уведомляет SwiftUI об изменениях данных, и SwiftUI перестраивает представление для представления новых данных.

Редактирование

Следующим шагом является создание нового представления, которое позволяет нам редактировать личную информацию выбранного Person. Мы будем использовать компонент Form, чтобы показать красивую форму для ввода данных. Вы можете проверить Создание форм с помощью SwiftUI», чтобы узнать больше о компоненте Form и его преимуществах. Давайте углубимся в код, который представляет режим редактирования.

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

Здесь мы используем Binding для выбранного человека. Оболочка свойств привязки позволяет передавать ссылку на тип значения. Используя свойство Binding, EditingView может читать и записывать структуру Person, но не сохраняет ее копию. Мы используем эту привязку для изменения значения внутри PersonsStore, и как только мы это сделаем, SwiftUI обновит представление обновленным списком лица.

Теперь давайте реорганизуем наш PersonsView для поддержки редактирования, передав Binding выбранному Person внутри EditingView.

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)
                        }
                    }
                }
            }
            .onAppear(perform: store.fetch)
            .navigationBarTitle(Text("Persons"))
        }
    }
}

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