Изучение того, как хранить данные в SwiftUI
Ранее я писал статью, объясняющую, что такое FileManager
и как его можно использовать с UIKit. Я рекомендую прочитать его, прежде чем перейти к сегодняшней статье:
В этой статье мы узнаем, что такое FileManager
и как его использовать. Мы узнаем, как использовать FileManager
в простом проекте SwiftUI. Начнем с того, что такое FileManager
.
Примечание. Вы можете скачать пример проекта и исходные коды этой статьи на GitHub.
Что такое FileManager?
FileManager
позволяет нам создавать и редактировать данные для типов файлов JSON или plist, а также использовать эти данные в наших приложениях.
FileManager
очень похож на Core Data, но FileManager более понятен, чем Core Data. Короче говоря, хотя вы можете создавать более сложные структуры данных с помощью Core Data, вы можете создавать более простые структуры данных в FileManager
более быстрым способом. Итак, FileManager
отлично подходит для небольших инди-приложений.
Как использовать FileManager в SwiftUI
Чтобы хранить и редактировать данные в FileManager
, нам нужно использовать декодеры и кодеры. Очевидно, что все файловые менеджеры в основном используют декодеры и кодировщики.
Обычно мы сохраняем значения в FileManager
прямо из типа Data
. Тип Data
можно считать самым основным типом данных, потому что, когда вы храните данные с типом Data
, вы напрямую сохраняете 0
и 1
эквивалент этих данных. Вы знаете, что все данные на вашем компьютере в основном состоят из нулей и единиц.
С FileManager
мы можем делать в основном две вещи: декодировать и кодировать данные. Например, мы можем декодировать настраиваемый массив объектов, используя PropertyListDecoder
:
let decoder = PropertyListDecoder()
let data = try Data(contentsOf: dataSourceURL)
let decodedNotes = try! decoder.decode([Note].self, from: data)
Точно так же мы можем добавить настраиваемый объект в массив настраиваемых объектов, используя объект PropertyListEncoder
:
let encoder = PropertyListEncoder()
let data = try encoder.encode(allNotes)
try data.write(to: dataSourceURL)
В отличие от UIKit, мы должны выполнять эти операции отдельно, потому что Swift не позволяет нам использовать оболочку свойства в вычисляемом свойстве. Мы увидим это при разработке типового проекта.
Использование FileManager в простом проекте SwiftUI
Чтобы понять, как использовать FileManager в SwiftUI, мы сделаем проект, аналогичный тому, что я создал в этой статье. SwiftUI работает немного иначе, чем UIKit. Вот почему нам нужно будет внести некоторые изменения в класс FileManager
.
Создание проекта Xcode
Давайте создадим новый проект Xcode. Конечно, интерфейс нашего проекта должен быть SwiftUI.
Вы можете дать проекту любое имя. После этого остается только приступить к созданию своего проекта.
Мой совет вам при разработке проекта - разделить файлы по папкам. При выполнении этого проекта вы можете использовать папки так же, как и я:
Построение модели данных
В этой статье мы создадим приложение для заметок. Таким образом, наша модель данных должна представлять один объект заметки. Эта модель имеет три свойства: id
, title
, description
.
struct Note: Codable, Identifiable {
// MARK: - Properties
var id = UUID()
let title: String
let description: String
}
В этой модели данных мы используем протоколы Codable
и Identifiable
.
Codable - это псевдоним типа для протоколов Encodable и Decodable. Когда вы используете Codable в качестве типа или общего ограничения, он соответствует любому типу, который соответствует обоим протоколам . - Разработчик Apple
Используйте протокол« Идентифицируемый , чтобы обеспечить устойчивое представление об идентичности для класса или типа значения. Например, вы можете определить тип User со свойством id, которое будет стабильным для вашего приложения и хранилища базы данных вашего приложения. Вы можете использовать свойство id для идентификации конкретного пользователя, даже если изменяются другие поля данных, такие как имя пользователя ». - Разработчик Apple
Подготовка класса FileManager
Теперь, когда наша модель данных готова, мы можем приступить к созданию нашего класса FileManager
. Для этого создадим новый файл Swift с именем DataProvider
:
class DataProvider: ObservableObject {
}
Как видите, мы используем протокол ObservableObject
для передачи данных.
По умолчанию
ObservableObject
синтезируетobjectWillChange
издателя, который передает измененное значение до того, как любое из его@Published
свойств изменится. - Разработчик Apple
В нашем классе свойство @Published
будет массивом объектов Note
:
class DataProvider: ObservableObject {
// MARK: - Properties
@Published var allNotes = [Note]()
}
Определите значение с именем shared
для вызова класса DataProvider
вне себя:
class DataProvider: ObservableObject {
// MARK: - Properties
static let shared = DataProvider()
@Published var allNotes = [Note]()
}
Для хранения данных с FileManager
нам нужен объект URL. Мы определяем для этого свойство:
Как я сказал ранее, значение, использующее оболочку свойства @Published
, не может быть вычисляемым свойством. Вот почему мы должны использовать два дополнительных метода для операций получения и установки данных.
Сначала напишите метод под названием getAllNotes
, чтобы вернуть массив Note
, который мы сохранили ранее. Если мы не сохранили ни одного Note
объекта, метод должен вернуть пустой массив. Это очень важно. В противном случае наше приложение выйдет из строя.
Мы будем использовать метод getAllNotes
внутри метода init
. В противном случае элемент allNotes
будет пустым, и наше приложение выйдет из строя. Следовательно, после определения элемента dataSourceURL
в методе init
, мы выравниваем элемент allNotes
с getAllNotes
:
Вы можете спросить, почему он таким образом уравнивает эти два значения. Это потому, что allNotes
имеет _
в начале, а метод getAllNotes
преобразуется в свойство @Published
.
Теперь мы можем определить метод, который позволяет нам сохранять данные. Этот метод должен быть частным, потому что мы хотим, чтобы этот метод находился за пределами класса DataProvider
.
Чтобы создать новый объект Note
, напишите метод ниже. Как видите, после вставки нового объекта Note
в allNotes
мы вызываем метод saveNotes
только для сохранения изменений:
func create(note: Note) {
allNotes.insert(note, at: 0)
saveNotes()
}
Напишите метод для замены ранее определенного объекта Note
. Точно так же после внесения изменений мы вызываем метод saveNotes
:
func changeNote(note: Note, index: Int) {
allNotes[index] = note
saveNotes()
}
Напишите новый метод удаления объекта Note
. Чтобы удалить элемент в List
объекте в SwiftUI, нам нужна offsets
информация об этом элементе. Вот почему у этого метода есть параметр offsets
:
func delete(_ offsets: IndexSet) {
allNotes.remove(atOffsets: offsets)
saveNotes()
}
Наконец, напишите метод под названием move
для перемещения List
ячейки. Источником информации о ячейках в объекте List
, который мы создадим, будет массив allNotes
. Следовательно, когда мы меняем положение ячейки, нам нужно изменить смещения объекта Note
этой ячейки:
func move(source: IndexSet, destination: Int) {
allNotes.move(fromOffsets: source, toOffset: destination)
saveNotes()
}
С этим методом мы закончили с классом DataProvider
!
Подготовка пользовательского интерфейса
Чтобы получить доступ к свойствам и методам в классе DataProvider
, давайте определим элемент, используя оболочку свойств @ObservedObject
ниже в основном объекте View
:
Таким образом, мы можем в основном создать наш основной пользовательский интерфейс:
Чтобы сделать код более надежным, создайте собственный объект с объектами, которые мы пишем в объекте ForEach
:
Как видите, наш главный View
объект намного удобнее:
В настоящее время мы не можем добавлять, редактировать или удалять новый Note
объект в нашем приложении. Поэтому, когда вы запускаете проект прямо сейчас, в приложении нет функциональности. Приступим к созданию элементов пользовательского интерфейса для взаимодействия.
Чтобы создать элементы пользовательского интерфейса, нам нужно создать две переменные: alertShowing
и editMode
. alertShowing
- это значение типа Bool
, и если это true
, объект Alert
может появиться. editMode
- это значение типа EditMode
, и если оно установлено на .active
, ячейки объекта List
будут доступны для редактирования и удаления.
// MARK: - Properties
@ObservedObject var dataProvider: DataProvider
@State private var alertShowing = false
@State private var editMode: EditMode = .inactive
Наш новый объект, который добавляет и редактирует кнопки, будет находиться в объекте NavigationView
. Мы используем модификатор navigationBarItems
для размещения кнопок внутри NavigationView
:
Неправильно иметь активную кнопку добавления при редактировании объекта List
. Вот почему мы преобразуем кнопку добавления в пользовательский объект с именем AddButton
, а когда элемент editMode
равен .active
, он превратится в объект EmptyView
:
Таким образом, модификатор navigationBarItems
также стал совсем маленьким:
.navigationBarItems(
leading: EditButton(),
trailing: AddButton(editMode: $editMode, alertShowing: $alertShowing)
)
Написав модификатор environment
в конце объекта List
, мы можем сделать объект List
редактируемым при щелчке по EditButton
:
.listStyle(InsetListStyle())
.environment(\.editMode, $editMode)
Наконец, нам нужно использовать модификаторы onDelete
и onMove
, чтобы ячейки в объекте списка можно было удалять и редактировать. Здесь мы используем методы move
и delete
, которые мы написали ранее в параметрах perform
.
ForEach(dataProvider.allNotes) { note in
NoteListCell(note: note)
}
.onDelete(perform: dataProvider.delete)
.onMove(perform: dataProvider.move)
В настоящее время мы можем редактировать и удалять List
объектные ячейки, но мы не можем добавить новый Note
объект. Вот почему наше приложение сейчас не имеет смысла, поскольку к нему не прикреплен Note
объект. Если мы создадим элементы пользовательского интерфейса для добавления новых Note
объектов, наше приложение станет тем, что мы хотим.
Мы будем использовать объект Alert
, чтобы добавить новый объект Note
. Вам это может показаться простым, но в SwiftUI по-прежнему нет Alert
объекта с TextField
объектами в нем. Следовательно, нам нужно создать собственный объект Alert
с UIViewControllerRepresentable
.
Первое, что мы сделаем, это создадим новый UIViewController
, который аналогичен определению пользовательского UIAlertViewController
в UIKit. Для этого вы можете создать файл Swift с именем TextFieldAlertViewController
и импортировать фреймворки SwiftUI и Combine.
import SwiftUI
import Combine
После этого мы можем создать класс TextFieldAlertViewController
. Код, который мы напишем ниже, поможет нам создать объект UIAlertController
:
Создайте метод с именем presentAlertController
и напишите этот метод в viewDidAppear
:
В качестве нашего настраиваемого объекта Alert
, который мы будем использовать в нашем основном объекте View
, мы создаем настраиваемый объект пользовательского интерфейса с именем TextFieldAlert
:
Мы используем протокол UIViewControllerRepresentable
для использования пользовательских элементов UIKit в SwiftUI. Точно так же мы будем использовать его с объектом TextAlert
:
Затем создайте элемент View
с именем TextFieldWrapper
, чтобы определить, что нужно сделать, когда объект TextAlert
будет и не будет отображаться в нашем основном объекте View
:
Как и в случае с модификатором alert
, мы создаем модификатор с именем textAlert
, чтобы использовать объект TextAlert
как объект Alert
в нашем приложении:
Теперь мы можем использовать наш объект TextAlert
в нашем приложении с помощью модификатора textFieldAlert
:
Наше приложение готово. Теперь мы можем это проверить! Как видите, SwiftUI позволяет нам выполнять множество действий автоматически:
Заключение
Хотя SwiftUI не поддерживает многие функции, благодаря таким инструментам, как UIViewControllerRepresentable
, мы можем делать почти все с помощью SwiftUI. Вот почему я думаю, что каждый может разработать собственное инди-приложение с помощью только SwiftUI.
Надеюсь, вам понравилась эта статья. Если у вас есть вопросы, оставьте их в комментариях.
Другие мои статьи: