Adding a Swift File to project

Глава 6. Data Persistence – все о хранении данных

Оглавление

Введение

Сохранение настроек в UserDefaults

Data Persistence – чтение и запись файлов

Использование Core Data

Создание файла модели Data Model

Добавление поддержки Core Data в существующий проект

Заключение

Введение

В любом реальном приложении, даже самом простом, возникает необходимость хранения данных. Представим приложение Stocks, предназначенное для инвестиций в акции. Наше приложение должно отслеживать курс интересующих нас акций, а для этого необходимо где то хранить список акций, которые выбрал пользователь. Каждый раз, когда пользователь запускает приложение Stocks, оно отображает список акций, которые пользователь выбрал ранее. Если пользователь в процессе работы с приложением, удалит или добавит акции из списка отслеживаемых, приложение должно обновить сохраненный список акций, чтобы при следующем запуске отобразить актуальный список.

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

Cохранение или извлечение данных между запусками приложения, известно как Data Persistence(сохранение данных). Мне не нравится дословный перевод – хранение или сохранение данных. Поэтому в дальнейшем я буду использовать термин Data Persistence. Мы рассмотрим 3 варианта для хранения и извлечения данных в iOs приложении:

  • UserDefaults
  • Reading and writing files
  • Core Data

Каждый метод предоставляет как свои преимущества, так и недостатки, в зависимости от вида тех данных, какие мы хотим сохранять. Именно на основании поставленной задачи – с какими именно данными наше приложение должно работать, мы и выбираем способ хранения данных, который мы должны использовать в приложении.

UserDefaults в основном используется для хранения небольших данных, такие как пользовательские настройки. Этот метод использует структуру данных типа словарь и хранит данные в .plist файле, похожем на Info.plist, который использует каждый XCode проект. Это простейший метод для хранения общих типов данных, такие как строки, числа, даты и структуры данных, такие как массивы словарей и лучше всего подходит для небольшого объема данных.

Если Вы хотите хранить большой объем данных, Вы можете использовать API для хранения и записи данных в файл. Он лучше подходит для хранения медиа файлов или неупорядоченных строк, например большого текста. Используя API работы с файлами, Вы должны не забывать про то, что поиск специфичных данных в большом файле может быть весьма продолжительным по времени.

CoreData позволяет вам сохранять различные типы данных в группах, называемых сущностями. Сущности похожи на записи в таблицах базы данных. Если Вам нужно сохранить большое количество структурированных данных, лучше использовать CoreData, чем предыдущие два типа хранения данных.

Сохранение настроек в UserDefaults

UserDefaults –  предназначен для хранения небольших данных, таких как числа, тип Boolean или строки. Поэтому использование UserDefaults прекрасно подходит для сохранения настроек приложения, таких как цвет фона или наименование используемого шрифта в чатах вашего приложения. Использование UserDefaults включает в себя:

  • Сохранение данных в UserDefaults
  • Извлечение данных из UserDefaults

Для сохранения данных в UserDefaults, необходимо определить ключ и данные, которые Вы будете ассоциировать с данным ключом. Это очень похоже по своему смыслу и назначению на сохранение настроек в SharedPreferences Android SDK. Если мы хотим сохранить данные dataToSave, которые будут ассоциированы с ключом “keyString“, все что нам необходимо сделать:

UserDafaults.standart.set(dataToSave, forKey: "keyString")

Команда set сохраняет ключ и ассоциированные с ним данные. Для извлечения данных, которые Вы ранее сохранили, Вы должны знать наименование ключа и тип извлекаемых Вами данных, например Integer, Boolean или Double. Зная тип данных, который вы будете извлекать, Вы можете использовать один из следующих участков кода:

  • integer(forKey:”keyString”) – возвращает целочисленное число, если ключ keyString существует, или 0 в противном случае
  • bool(forKey:”keyString”) – возвращает логическое значение, если ключ существует или false в противном случае
  • float(forKey:”keyString”) – возвращает число с плавающей точностью типа float, если ключ существует или 0.0 в противном случае
  • string(forKey:”keyString”) – возвращает значение типа String если ключ существует или nil в противном случае
  • double(forKey:”keyString”) – возвращает плавающее число с двойной точностью, типа double если ключ существует или 0.0 в противном случае
  • object(forKey:”keyString”) – возвращает тип AnyObject? который Вам надо привести с условного типа к требуемому, или возвращает nil если ключ не присутствует в хранилище
  • url(forKey:”keyString”) – возвращает тип URL если ключ присутствует в хранилище данных или nil в противном случае

Посмотрим как сохранять данные в UserDefaults на практике:

  1. Создадим новый проект Chapter6 для iOs приложения и убедимся что мы выбрали тип интерфейса SwiftUI 
  2. Откроем исходный файл ContentView в панели Навигатора
  3. Добавим 3 State переменные:
    @State private var myText = ""
    @State private var myToggle = true
    @State private var mySlider = 0.0
  4. Добавим контейнер VStack в тело var body: Some View. Также добавим отступ .padding у компонента VStack и определим отступаемое пространство в 25:
    var body: some View {
        VStack(spacing: 25) {
                
        }
        .padding()
    }
  5.  Добавим UI компоненты TextField, Toggle и Slider внутри контейнера VStack. Эти компоненты будут отвечать за изменение значений и сохранение их в UserDefaults:
    var body: some View {
         VStack(spacing: 25) {
            TextField("Change text here", text: $myText)
            Toggle(isOn: $myToggle, label: {
               Text("Toggle here")
            })
            Slider(value: $mySlider)
                
        }
        .padding()
    }

     

  6.  Под компонентом Slider добавим 3 кнопки. Первая кнопка сохраняет данные, вторая кнопка возвращает пользовательский интерфейс к первоначальному состоянию, а третья извлекает сохраненные данные.
    Button(action: {
         UserDefaults.standard.set(myText, forKey: "Text")
         UserDefaults.standard.set(myToggle, forKey: "Toggle")
         UserDefaults.standard.set(mySlider,forKey: "Slider")
    }){
         Text("Save data")
    }
         Button(action: {
             myText = ""
             myToggle = true
             mySlider = 0.0
         }){
             Text("Clear data")
         }
         Button(action: {
             myText = UserDefaults.standard.string(forKey: "Text") ?? ""
             myToggle = UserDefaults.standard.bool(forKey: "Toggle")
             mySlider = UserDefaults.standard.double(forKey: "Slider")
         }){
         Text("Retrieve data")
    }

     

    Обратите внимание, что первая кнопка использует команду UserDefaults.standard.set для сохранения данных. А третья кнопка использует команды UserDefaults.standard.string, .bool или .double для извлечения данных, сохраненных при нажатии на первую кнопку.

  7.  Нажмем на кнопку предварительного просмотра на панели Canvas
  8.  В компоненте TextView введем любой набор слов или букв
  9. Нажмем на Toggle
  10. Переместим слайдер вправо
  11. Нажмем на кнопку Save Data для сохранения текущих значений переменных  в компонентах TextField, Toggle и Slider
  12. Теперь нажмем на кнопку Clear Button для сброса состояния всех компонентов UI к первоначальному
  13. Нажмем на кнопку Retrieve Data для восстановления значений переменных и связанными с ними компонентами UI

Data Persistence iOs UserDefaults

Полный исходный код файла ContentView:

import SwiftUI

struct ContentView: View {
    
    @State private var myText = ""
    @State private var myToggle = true
    @State private var mySlider = 0.0
    
    var body: some View {
        VStack(spacing: 25) {
            TextField("Change text here", text: $myText)
            Toggle(isOn: $myToggle, label: {
                Text("Toggle here")
            })
            Slider(value: $mySlider)
            Button(action: {
                UserDefaults.standard.set(myText, forKey: "Text")
                UserDefaults.standard.set(myToggle, forKey: "Toggle")
                UserDefaults.standard.set(mySlider,forKey: "Slider")
            }){
                Text("Save data")
            }
            Button(action: {
                myText = ""
                myToggle = true
                mySlider = 0.0
            }){
                Text("Clear data")
            }
            Button(action: {
                myText = UserDefaults.standard.string(forKey: "Text") ?? ""
                myToggle = UserDefaults.standard.bool(forKey: "Toggle")
                mySlider = UserDefaults.standard.double(forKey: "Slider")
            }){
                Text("Retrieve data")
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Исходный код проекта Вы можете скачать с нашего GitHub’a

Data Persistence – чтение и запись файлов

В обычных компьютерах, например в Macintosh или под управлением ОС Linux/Windows мы ежедневно выполняем сотни операций с файлами, даже не задумываясь об этом. Конечно такие же возможности нам дает и iOs. Мы точно также можем создавать файлы, читать и записывать данные.

И хотя iOs сильно инкапсулирует от пользователя работу с файлами, это не так уж и сложно в исполнении. Во первых при работе с файлами мы должны создать объект FileManager:

let fm = FileManager.default

Далее мы должны определить местоположение файл, в нашем случае это папка с документами в домашней директории

let urls = fm.urls(for: .documentDirectory, in: .userDomainMask)

И наконец мы создаем файл, который называется file.txt для хранения данных

let url = urls.last?.appendingPathComponent("file.txt")

После того как мы сохраним текст в файле, мы можем также его извлечь используя все тот же FileManager снова и найти сторонними приложениями наш файл в папке document домашней директории. Теперь посмотрим на практике, как сохранять и читать данные в файл в ОС iOs:

  1. Создадим новый проект – Chapter6ReadAndWriteFile
  2. Откроем для редактирования файл с исходным кодом ContentView в панели Навигатора
  3. Добавим 2 State переменные
    @State var createText = ""
    @State var displayText = ""
  4. Внутри компонента VStack разместим 2 компонента TextEditor, разделенные пустым контейнером HStack:
    var body: some View {
        VStack {
            TextEditor(text: $createText)
            HStack{
                    
            }.padding()
            TextEditor(text: $displayText)
        }
    }
  5. И добавим внутрь нашего пока еще пустого контейнера HStack 2 кнопки, разделенные пустым элементом Spacer:
    Button(action: {
        let fm = FileManager.default
        let urls = fm.urls(for: .documentDirectory, in: .userDomainMask)
        let url = urls.last?.appendingPathComponent("file.txt")
        do {
            try createText.write(to: url!, atomically: true, encoding: String.Encoding.utf8)
        }catch{
            print("File writing error")
        }
    }){
        Text("Write file")
    }
    Spacer()
    Button(action: {
        let fm = FileManager.default
        let urls = fm.urls(for: .documentDirectory, in: .userDomainMask)
        let url = urls.last?.appendingPathComponent("file.txt")
        do{
            let fileContent = try String(contentsOf: url!, encoding: String.Encoding.utf8)
            displayText = fileContent
        }catch{
            print("File reading error")
        }
    }){
        Text("Read file")
    }

    Итоговый исходный файл выглядит следующим образом:

    import SwiftUI
    
    struct ContentView: View {
        
        @State var createText = ""
        @State var displayText = ""
        
        var body: some View {
            VStack {
                TextEditor(text: $createText)
                HStack{
                    Button(action: {
                        let fm = FileManager.default
                        let urls = fm.urls(for: .documentDirectory, in: .userDomainMask
                        )
                        let url = urls.last?.appendingPathComponent("file.txt")
                        do {
                            try createText.write(to: url!, atomically: true, encoding: String.Encoding.utf8)
                        }catch{
                            print("File writing error")
                        }
                    }){
                        Text("Write file")
                    }
                    Spacer()
                    Button(action: {
                        let fm = FileManager.default
                        let urls = fm.urls(for: .documentDirectory, in: .userDomainMask)
                        let url = urls.last?.appendingPathComponent("file.txt")
                        do{
                            let fileContent = try String(contentsOf: url!, encoding: String.Encoding.utf8)
                            displayText = fileContent
                        }catch{
                            print("File reading error")
                        }
                    }){
                        Text("Read file")
                    }
                }.padding()
                TextEditor(text: $displayText)
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

     

  6. Нажмите на иконку Preview на панели Canvas
  7. В верхнем UI элементе TextEditor введите любой текст
  8. Нажмите на кнопку WriteFile, после этого текст сохранится в файле
  9. Теперь нажмите на кнопку Read File. После этого во втором TextEditor появится текст, прочитанный из файла file.txt

Проект Вы можете посмотреть или скачать по адресу: https://github.com/timur-gaysin/Chapter6ReadAndWrireFile

iOs Data Persistance. Reading and Writing to Files

Использование Core Data

Для хранения небольших данных, идеально подходит использование UserDefaults. Если требуется хранить большие объемы неструктурированных данных, можно хранить их в файле. Однако если у Вас стоит задача хранения больших массивов данных, лучшим решением будет использовать Core Data. По своей архитектуре это сродне работе с SQLData в AndroidSDK. 

Core Data это фреймворк, который помогает выполнять весь спектр задач по хранению и извлечению данных в приложении. Он позволяет легко организовывать доступ к структурированным данным и отношениям внутри этих данных, без заучивания непростых SQL запросов.

Core Data организовывает хранилище, опираясь на термины сущностей и аттрибутов. Атрибуты определяют одиночную запись в таблице, такие как ее имя, адрес, возраст, пол, email и телефонный номер. Сущность представляет все эти атрибуты как определение единицы данных, такой как например персона. Проще всего представлять сущность Core Data как запись в таблице базы данных, а аттрибут как поле БД, для понимания мы представили рисунок 6.1

Core Data iOs SwiftUI

 

Рисунок 6.1 Core Data хранит данные в атрибутах. сгруппированных вместе в единой сущности

Для того чтобы использовать Core Data необходимо выполнение 2-х условий:

  • Создание сущностей и определение атрибутов в редакторе XCode данных модели
  • Написание Swift кода для работы с данными

Есть 2 пути для добавления Core Data в проект. Первый – мы добавляем CoreData после создания проекта. Второй – мы добавляем Core Data во время создания проекта. Теперь рассмотрим подробнее оба варианта.

Создание файла модели Data Model

Файл Core Data – это модель данных, которая описывает структуру данных и атрибуты, представляющие единицы объекта, такие как имя, номер телефона, адрес и т.д.  Мы можем вручную добавить файл с данными модели в любой проект или позволить XCode добавить модель  данный когда создаете новый проект.

Посмотрим как добавить файл Core Data при создании проекта:

  1. Создадим новый iOs проект в среде XCode. Назовем проект CoreDataApp. При создании обратите внимание чтобы CheckBox был снят напротив Core Data. 
  2. Создадим новый файл
  3. При создании файла выбираем iOs категорию
  4. Среди шаблонов выбираем Data Model  в секции Core DataCreate a Data Model File
  5. Нажмем кнопку Next
  6. Выберем имя файла DataModel и нажмем кнопку Create. XCode отобразит Ваш созданный Core Data файл в панели Навигатор

После создания файла Core Data, следующий шаг это определение одной или нескольких сущностей. Сущность содержит множество атрибутов, такие как имя, цена, возраст или телефонный номер. Думайте о сущности, как о записи в базе данных и атрибутах, какие как поля, как указано на Рисунке 6.3

Entity contain multiply

Рисунок 6.3 Сущность может содержать множество атрибутов

Если Вы перед началом работы с приложением уже знаете, что будете использовать Core Data для хранения данных приложения, в этом случае при создании проекта, XCode автоматически сгенерирует файл DataModel и код для доступа к данным. Посмотрим как это выглядит на практике:

  1. Создадим новый iOs проект
  2. Дадим ему имя CoreDataProject
  3. Убедимся, что Checkbox установлен напротив надписи Use Core Data

    Create new Project with Checkbox Use Core Data
    Рис 6.4 Устанавливаем CheckBox напротив Use Core Data
  4. Нажимаем Next и далее Create
  5. После создания проекта, откроем файл Persistence в панели Навигатора. Когда Вы создаете новый проект с Core Data, XCode  всегда генерирует следующий код:
//
//  Persistence.swift
//  CoreDataProject
//
//  Created by Timur on 20.03.2023.
//

import CoreData

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
        }
        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "CoreDataProject")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

Обратите внимание, строка container = NSPersistentContainer(name: “CoreDataProject”) относится к имени файла модели данных, который определяет сущности и атрибуты

Добавление поддержки Core Data в существующий проект

Вы можете добавить поддержку Core Data при создании проекта. Но иногда Вы понимаете, что лучше организовать систему хранения данных через использование Core Data уже во время разработки Вашего приложения. В нашем приложении будет весьма простой пользовательский интерфейс который содержит два поля для ввода текста, две кнопки и две текстовые метки. Поля для ввода текста позволяют нам вводить данные, которые мы собираемся сохранить, две текстовые метки будут отображать сохраненный нами текст и две кнопки для удаления или добавления данных.

Давайте разберемся на практике, как добавить поддержку Core Data в уже существующий проект.

  1. Создадим новый iOs проект. Дадим ему имя Chapter6CoreDataUI. Обязательно!!! проследите, чтобы Вы не включали поддержку Core Data в наш создаваемый проект(см. Рисунок 6.4)
  2. Создадим новый файл Data Model и дадим ему имя DataModel
  3. Выберем созданный нами файл DataModel в панели Навигатора. XCode отобразит редактор данных, как показано на рис. 6.5

    XCode Data Editor
    Рис 6.5 Редактор данных позволяет нам смотреть и редактировать сущности и атрибуты
  4. Нажмем на кнопку Add Entity. XCode отобразит созданную сущность Entity в раздели Entities
  5. Теперь выберем нашу сущность и нажмем Enter для изменения ее имени
  6. Введем Item и снова нажмем Enter. Имена сущностей должны начинаться с заглавной буквы, например Item, Person, Customer и т.д.

После того, как мы создали одну или более сущностей, нам необходимо добавить атрибуты сущности. Атрибут включает в себя описательное имя(прописными буквами) и тип содержащихся данных, такие как string, integer  или date.

Чтобы добавить атрибуты в созданную сущность, проделаем следующее:

  1. Откроем созданный нами файл Core Data – DataModel. XCode отобразит редактор модели(см. Рис 6.5)
  2. Выберем сущность, которую мы хотим изменить. XCode предоставляет нам 2 варианта добавления атрибутов:
    iOs swiftUI add attributes to Core Data file
    Рис. 6.6 Добавление атрибутов к выбранной сущности

     

  3. Нажмем на кнопку Add attribute и увидим следуюшее:
    Add attributes to XCode iOs Swift Entity Core Data
    Рис. 6.7 Создание нового атрибута

     

  4. Введем имя атрибута – name. Не забывайте что имена атрибутов должны начинаться с маленькой буквы
  5. Если нажать на всплывающее меня type – то мы увидим на рисунке 6.8 список доступных нам типов.
    Defining the type of data to store in an attribute
    Рис 6.8 Определение тип данных аттрибута

     

  6. Мы выберем тип string
  7. Добавим еще один атрибут
  8. Введем его имя – price
  9. В выпадающем меню выберем тип данных String. Получившиеся 2 атрибута нашей сущности Item вы можете увидеть на рисунке 6.9
Defining a name and price attribute for an Item entity
Рис 6.9 Определение атрибутов name и price для сущности Item

Теперь, после того как мы добавили модель Core Data в наш проект и определили модель данных с нашей сущностью, пришло время добавить  отдельный Swift файл для добавления, извлечения и удаления данных из Core Data следуя инструкции:

  1. Создайте сами новую сущность с именем Animal и двумя атрибутами breed и name, типа String, как указано на рисунке 6.10

    Defining a Core Data Model with animal entity SWiftUI
    Рис 6.10 Определение модели данных с сущностью Animal
  2. Создадим новый Swift файл:
    Adding a Swift File to project
    Рис 6.11 Добавляем новый Swift файл в проект

     

  3. Дадим ему имя CoreDataManager
  4. После создания файла, откроем его
  5. Добавим следующий импорт для работы с фреймворком CoreData
    import CoreData
    
  6. Добавим следующий код в класс CoreDataManager:
    class CoreDataManager{
        let persistenceContainer: NSPersistentContainer
        
        init(){
            persistenceContainer = NSPersistentContainer(name: "DateModel")
            persistenceContainer.loadPersistentStores {(description, error) in
                if let error = error{
                    fatalError("Core Data failed to initialize \(error.localizedDescription)")
                }
            }
        }
        
    }
    

    Этот простой класс загружает файл Core Data. В нашем случае файл это Core Data файл DataModel. Если же Вы дали файлу другое имя, необходимо заменить “DataModel” на другое название. Мы написали простой код для загрузки Core Data, но мы также должны добавить функции для добавления, удаления и доступа к данным.

  7. Теперь добавим следующую функцию для сохранения данных в модели Core Data
    func savePet(name: String, breed: String){
        let pet = Animal(context: persistenceContainer.viewContext)
        pet.name = name
        pet.breed = breed
            
        do{
            try persistenceContainer.viewContext.save()
            print("Pet saved!")
        }catch{
            print("Failed to save movie \(error)")
        }
    }

    Эта функция принимает 2 входных параметра name и breed(это 2 атрибута определенные в сущности Animal) и сохраняет их уже в виде сущности Animal

  8. Добавим следующую функцию для извлечения данных
    func getAllPets() -> [Animal]{
            
        let fetchRequest: NSFetchRequest<Animal> = Animal.fetchRequest()
        do{
            return try persistenceContainer.viewContext.fetch(fetchRequest)
        }catch{
            return[]
        }
    }

    Эта функция извлекается все данные из сущности Animal, определенные в файле модели Core Data

  9. Добавим следующую функцию для удаления данных:
    func deletePet(animal: Animal){
            
        persistenceContainer.viewContext.delete(animal)
            
        do{
           try persistenceContainer.viewContext.save()
        }catch{
           persistenceContainer.viewContext.rollback()
           print("Failed to save context \(error.localizedDescription)")
        }
    }

    Эта функция принимает единицу сущности, которую мы хотим убрать из хранилища и затем удаляет ее из Core Data. Итоговый код файла CoreDataManager выглядит следующим образом:

//
//  CoreDataManager.swift
//  Chapter6CoreDataUI
//
//  Created by Timur on 20.03.2023.
//

import Foundation
import CoreData

class CoreDataManager{
    let persistenceContainer: NSPersistentContainer
    
    init(){
        persistenceContainer = NSPersistentContainer(name: "DataModel")
        persistenceContainer.loadPersistentStores {(description, error) in
            if let error = error{
                fatalError("Core Data failed to initialize \(error.localizedDescription)")
            }
        }
    }
    
    func savePet(name: String, breed: String){
        let pet = Animal(context: persistenceContainer.viewContext)
        pet.name = name
        pet.breed = breed
        
        do{
            try persistenceContainer.viewContext.save()
            print("Pet saved!")
        }catch{
            print("Failed to save movie \(error)")
        }
    }
    
    func getAllPets() -> [Animal]{
        
        let fetchRequest: NSFetchRequest<Animal> = Animal.fetchRequest()
        do{
            return try persistenceContainer.viewContext.fetch(fetchRequest)
        }catch{
            return[]
        }
    }
    
    func deletePet(animal: Animal){
        
        persistenceContainer.viewContext.delete(animal)
        
        do{
            try persistenceContainer.viewContext.save()
        }catch{
            persistenceContainer.viewContext.rollback()
            print("Failed to save context \(error.localizedDescription)")
        }
    }
    
}

Следующий этап у нас – это разработка пользовательского интерфейса, который позволяет пользователям ввести 2 строки(наши поля name и breed), сохранение данных, отображение данных списком и удаление выбранных нами данных. Для этого выполним следующее:

  1. Откроем исходный файл ContentView в панели Навигатора
  2. Добавим 3 State переменные и определим константу, определяющую созданный нами выше CoreDataManager. Первые 2 переменные это строки, отвечающие за значения имени и породы(name and breed), третья переменная отвечает за массив сущностей Animal, определенных в файле модели Core Data. Все это мы добавляем после struct ContentView: View {
    let coreDM: CoreDataManager
    @State var petName = ""
    @State var petBreed = ""
    @State var petArray = [Animal]()
  3. Теперь добавим 2 текстовых поля и кнопки внутри контейнера VStack:
    VStack {
         TextField("Enter pet name", text: $petName).textFieldStyle(RoundedBorderTextFieldStyle())
         TextField("Enter pet breed", text: $petBreed).textFieldStyle(RoundedBorderTextFieldStyle())
         Button("Save") {
            coreDM.savePet(name: petName, breed: petBreed)
            displayPets()
            petName = ""
            petBreed = ""
         }
    }.padding()
    .onAppear(perform:{
        displayPets()
    })

    Не обращайте внимание на ошибку с отсутствием функции displayPets() – к ней мы вернемся уже в следующем пункте. Мы видим 2 текстовых поля с именем и породой. И кнопка, которая сохраняет эти данные в Core Data. Также мы добавили в onAppear() отображение питомцев. onAppear – срабатывает когда UI появляется на экране девайса.

  4. Теперь добавим функцию displayPets() сразу после последней закрывающей скобки блока кода var body: some View
    func displayPets(){
        petArray = coreDM.getAllPets()
    }

    Эта функция извлекает все данные из Core Data и передает их переменной petArray

  5. Теперь добавим следующий код сразу после блока кола кнопки Save
     List{
          ForEach(petArray, id: \.self){pet in
              VStack{
                  Text(pet.name ?? "")
                  Text(pet.breed ?? "")
              }
          }.onDelete(perform: { indexSet in
              indexSet.forEach { index in
                  let pet = petArray[index]
                  coreDM.deletePet(animal: pet)
                  displayPets()
              }
          })
    }
    Spacer()

    Этот код создает List, в котором в теле цикла ForEach извлекаются данные из Core Data и затем отображаются в отдельном контейнера VStack. И обратите внимание на модификатор .onDelete – он позволяет свайпнуть влево элемент отображаемой сущности и удалить из Core Data выбранный элемент данных и потом снова отобразить актуальный список сущностей. Элемент Spacer() добавлен для того, чтобы наш список всегда “прижимался” к кнопке save

  6. Последний штрих – необходимо дать превью доступ к CoreDataManager, для этого чуть подкорректируем блок struct ContentView_Previews: PreviewProvider {
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView(coreDM: CoreDataManager())
        }
    }

    Итоговый код ContentView:

    //
    //  ContentView.swift
    //  Chapter6CoreDataUI
    //
    //  Created by Timur on 20.03.2023.
    //
    
    import SwiftUI
    
    struct ContentView: View {
        
        let coreDM: CoreDataManager
        @State var petName = ""
        @State var petBreed = ""
        @State var petArray = [Animal]()
        
        var body: some View {
            VStack {
                TextField("Enter pet name", text: $petName).textFieldStyle(RoundedBorderTextFieldStyle())
                TextField("Enter pet breed", text: $petBreed).textFieldStyle(RoundedBorderTextFieldStyle())
                Button("Save") {
                    coreDM.savePet(name: petName, breed: petBreed)
                    displayPets()
                    petName = ""
                    petBreed = ""
                }
                List{
                    ForEach(petArray, id: \.self){pet in
                        VStack{
                            Text(pet.name ?? "")
                            Text(pet.breed ?? "")
                        }
                    }.onDelete(perform: { indexSet in
                        indexSet.forEach { index in
                            let pet = petArray[index]
                            coreDM.deletePet(animal: pet)
                            displayPets()
                        }
                    })
                }
                Spacer()
            }.padding()
                .onAppear(perform:{
                    displayPets()
                })
        }
        
        func displayPets(){
            petArray = coreDM.getAllPets()
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView(coreDM: CoreDataManager())
        }
    }
    

     

Этот код предельно простой. Мы видим 2 текстовых поля, где вводим имя и породу питомца. А также кнопку Save – которая сохраняет введенные нами данные о питомце в наш Core Data. После сохранения данных, они тут же отображаются в списке, расположенном под кнопкой Save. Также мы добавили функционал по удалению добавленных нами домашних питомцев: достаточно свайпнуть влево питомца и появится кнопка Delete, которая при нажатии удалит выбранную сущность из Core Data и обновит пользовательский интерфейс.Посмотрим как работает наш проект:

  1. Нажмем на иконку Preview
  2. В верхнем текстовом поле введем имя Fedel
  3. Во втором текстовом поле введем породу Teriere
  4. Нажмем на кнопку Save – обратим внимание что наша порода появилась теперь под кнопкой Save
  5. Добавим еще одно домашнее животное, чтобы мы получили скриншот, похожий на Рис. 6.12

    Preview simulator ios16 XCode
    Рис. 6.12 Отображение всех сохраненных данных
  6. Попробуем свайпнуть влево второе домашнее животное – Nick и у нас появится красная кнопка для удаления питомца из Core Data, как на рисунке 6.13

    Delete swift button ios red
    Рис 6.13 Свайп влево для отображения кнопки Delete
  7. Нажмите на кнопку Delete и обратите внимание что интерфейс тут же обновится
  8. Попробуйте резко свайпнуть влево оставшегося питомца – наше приложение удалит без предупреждения нашего терьера по имени Fedel из Core Data

Заключение

В любом более или менее серьезном приложении Вы столкнетесь с необходимостью сохранения данных. Для хранения простых настроек приложения или небольших данных, лучше использовать UserDefaults – этот подход отличается простотой и легкостью в исполнении.

Для хранения больших данных, лучше выбрать решение по хранению их в файлах. Файлы могут хранить большие объемы текста или медийной информации(музыка, фильмы – но к этой теме мы вернемся только через несколько глав). Только необходимо помнить, что поиск нужной текстовой информации в большом файле может быть весьма продолжительным по времени.

Если же Вы хотите хранить большие объемы структурированных данных, например список клиентов, домашних питомцев, календарь дел и т.д. – Вам лучше использовать возможности фреймворка Core Data. Когда Вы храните ваши данные в любом из описанных нами способов – UserDefaults, файлы или Core Data, Вы можете легко извлекать Ваши данные и отображать их в Вашем UI автоматически без необходимости ручного управления кодом для отображения загруженной информации.

Leave a Reply

Please disable your adblocker or whitelist this site!