Arhn - архитектура программирования

Установка значений по умолчанию для CoreData

Я работаю над своим первым проектом с CoreData, и у меня есть настройка модели данных.

У меня есть таблица с кнопкой «Добавить», которая вызывает предупреждение и позволяет пользователю ввести строку:

func handleAdd() {
    
    let alert = UIAlertController(title: "New Company", message: "Add a new company", preferredStyle: .alert)
    
    let saveAction = UIAlertAction(title: "Save", style: .default) {
        [unowned self] action in
        
        guard let textField = alert.textFields?.first,
            let newCompany = textField.text else {
                return
        }
        
        self.save(name: newCompany)
        self.tableView.reloadData()
    }

    let cancelAction = UIAlertAction(title: "Cancel", style: .default)
    
    alert.addTextField()
    alert.addAction(saveAction)
    alert.addAction(cancelAction)
    
    present(alert, animated: true)
}

Строка добавляется в массив NSManagedObject:

func save(name: String) {
    
    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    
    // 1
    let managedContext =
        appDelegate.persistentContainer.viewContext
    
    // 2
    let entity =
        NSEntityDescription.entity(forEntityName: "Company",
                                   in: managedContext)!
    
    let company = NSManagedObject(entity: entity,
                                 insertInto: managedContext)
    
    // 3
    company.setValue(name, forKeyPath: "name")

    
    
    // 4
    do {
        try managedContext.save()
        companies.append(company)
    } catch let error as NSError {
        print("Could not save. \(error), \(error.userInfo)")
    }
}

Пока все выглядит хорошо, за исключением того, что я хочу, чтобы приложение запускалось с 5 уже установленными ячейками, а затем позволяло пользователю добавлять/удалять по своему желанию. Как установить 5 значений по умолчанию, с которыми я хочу запускать приложение? Я попытался добавить приведенный ниже код в viewWillAppear.

    let entity =
        NSEntityDescription.entity(forEntityName: "Company",
                                   in: managedContext)!
    
    let company = NSManagedObject(entity: entity,
                                  insertInto: managedContext)
    
    company.setValue("Apple", forKeyPath: "name")
    company.setValue("Google", forKeyPath: "name")
    company.setValue("Facebook", forKeyPath: "name")
    company.setValue("Tesla", forKeyPath: "name")
    company.setValue("Twitter", forKeyPath: "name")

    self.save(name: "Apple")
    self.save(name: "Google")
    self.save(name: "Facebook")
    self.save(name: "Tesla")
    self.save(name: "Twitter")

Во время загрузки они все появляются. Затем, когда я ухожу со страницы и возвращаюсь, они снова добавляются. Я знаю, что это потому, что они находятся в viewWillAppear и вызываются каждый раз при загрузке страницы.

Документы Apple (Руководство по программированию основных данных) немного сложно проглотить новичку, но они говорят

Для небольших наборов данных создавайте управляемые объекты непосредственно в коде.

Если вы используете iOS или создаете приложение для OS X, которое не основано на документах, вы можете добавить проверку при запуске приложения, чтобы определить, существует ли файл в месте, указанном вами для магазина приложений. Если это не так, вам нужно импортировать данные.

Я считаю, что второй фрагмент - это то, что я ищу в своем случае (поправьте меня, если я ошибаюсь), однако я не уверен, как его реализовать, поэтому любая помощь в том, как установить эти значения по умолчанию, чтобы они добавлялись один раз (под нагрузкой) и только один раз будет очень признателен!

Вот инициализаторы, если они представляют интерес

var companies = [NSManagedObject]()
var container: NSPersistentContainer!
let managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let companyEntity = "Company"

РЕДАКТИРОВАТЬ: код добавлен в делегат приложения

    let vc = ViewController()
    let entity = NSEntityDescription.entity(forEntityName: "Company", in: managedContext)!
    let company = NSManagedObject(entity: entity,insertInto: managedContext)
    let userDefaults = UserDefaults.standard
    userDefaults.set(true, forKey: "firstRun")
    
    if userDefaults.valueForKey("firstRun") == true {

        company.setValue("Apple", forKeyPath: "name")
        company.setValue("Google", forKeyPath: "name")
        company.setValue("Facebook", forKeyPath: "name")
        company.setValue("Tesla", forKeyPath: "name")
        company.setValue("Twitter", forKeyPath: "name")
        
        vc.save(name: "Apple")
        vc.save(name: "Google")
        vc.save(name: "Facebook")
        vc.save(name: "Tesla")
        vc.save(name: "Twitter")
        
        userDefaults.set(false, forKey: "firstRun")
    }
18.01.2017

  • Это зависит от вопросов: может ли пользователь удалить любую из пяти компаний по умолчанию и должны ли они быть созданы заново? Если оба ответа равны No, зарегистрируйте логическое свойство firstRun со значением true в пользовательских настройках по умолчанию. Затем проверьте значение в applicationDidFinishLaunching. Если это true, создайте 5 компаний по умолчанию и установите значение в пользовательских настройках по умолчанию на false. Кстати: Ваш код в viewWillAppear кроме строк сохранения на самом деле ничего не делает. 18.01.2017
  • @vadian спасибо, я старался изо всех сил, хотя я определенно немного не уверен в реализации. Я отредактировал свой пост кодом, который пробовал в applicationDidFinishLaunching, но получил ошибку — Xcode не любит бинарные операторы с логическими значениями. Мой код на правильном пути, то есть то, что вы предлагали? Кроме того, ваше последнее предложение - вы имеете в виду, что setValue (s) не нужны? Спасибо за помощь, все еще разбираюсь с этим. 18.01.2017
  • Кроме того, что касается вопросов, пользователь должен иметь возможность удалить любую или все 5 компаний по умолчанию и добавить свои собственные. И они действительно должны оставаться удаленными в этот момент, хотя я также хотел бы иметь функцию отмены удаления, но это мост, который я перейду, когда дойду до этого. 18.01.2017

Ответы:


1

Согласно моему комментарию и вашему — явно неудачному — коду после EDIT это предложение, как реализовать firstRun:

Сначала зарегистрируйте значение по умолчанию в applicationDidFinishLaunching, строки необходимы, даже если значение по умолчанию было изменено за это время.

let userDefaults = UserDefaults.standard
let defaultValues = ["firstRun" : true]
userDefaults.register(defaults: defaultValues)

Затем проверьте значение (UserDefaults имеет функцию bool(forKey) и создайте компании. Крайне важно, чтобы код для регистрации значений по умолчанию выполнялся до использования значений.

if userDefaults.bool(forKey: "firstRun") {

    let defaultCompanies = ["Apple", "Google", "Facebook", "Tesla", "Twitter"]
    let entity = NSEntityDescription.entity(forEntityName: "Company", in: managedContext)!

    for companyName in defaultCompanies {
        let company = NSManagedObject(entity: entity,insertInto: managedContext)
        company.setValue(companyName, forKey: "name") // not keyPath
        companies.append(company)
    }
    do {
        try managedContext.save()
        userDefaults.set(false, forKey: "firstRun")
    } catch let error as NSError {
        print("Could not save. \(error), \(error.userInfo)")
    }
}

В своем коде вы создаете одну компанию и меняете значение названия 5 раз, не сохраняя эту конкретную компанию. Практически этот код ничего не делает.

18.01.2017
  • Хорошо, я думаю, что понимаю, где я ошибся в своей попытке, однако я попробовал ваше решение, и я просто получаю пустое представление таблицы. 18.01.2017
  • Конечно, вам нужно перезагрузить табличное представление после вставки объектов. 18.01.2017
  • У меня есть tableView.reloadData() в viewDidLoad, должен ли я вызывать его в другом месте? Я попытался поместить его после добавления массива компаний в цикл for в applicationDidFinishLaunching (после создания ссылки на мой контроллер представления) с тем же результатом. 18.01.2017
  • Лучше всего перезагружать табличное представление после сохранения контекста в области do. Используйте отладчик или вставьте строки печати, чтобы увидеть, что происходит. 18.01.2017
  • Итак, после вставки некоторых точек останова похоже, что «делать» не вызывается, что, как я предполагаю, означает, что условие if не выполняется? 18.01.2017
  • Затем удалите приложение и его содержимое в симуляторе или добавьте временную строку, чтобы сбросить firstRun в пользовательских настройках по умолчанию на true. 18.01.2017
  • Я перезагрузил симулятор и снова запустил его с vc.tableView.reloadData() и оператором печати в конце «do». Оператор печати вызывается, но табличное представление остается пустым. 19.01.2017
  • Довольно сложно что-то предложить, не зная остального кода. Компании вообще создаются? Заполняется ли табличное представление при втором запуске? Правильно ли реализованы методы источника данных и делегата табличного представления? 19.01.2017
  • Итак, я (думаю), теперь все работает правильно! Я все еще использовал keyPath при настройке cell.textLabel.text и в моей функции сохранения, поэтому я изменил их оба на key вместо keyPath, и теперь приложение загружается с 5 компаниями по умолчанию. Они не по порядку (не в том порядке, в котором они находятся в массиве defautCompanies), но это не имеет большого значения. Не могу отблагодарить вас за вашу помощь и терпение! 19.01.2017

  • 2

    Вместо того, чтобы использовать какое-то произвольное UserDefaults значение "firstRun", лучшим решением было бы просто реализовать логику дедупликации, когда вы пытаетесь вставить новые компании. Обычно для этой цели используется идентификатор. Например, объяснение псевдокода может выглядеть примерно так:

    use id, table
    if table has id {
        return table.get(id)
    } else {
        table.insert(id)
    }
    

    С реальной реализацией Swift это будет выглядеть совсем иначе, но идея останется прежней. Ниже приведен пример кода CoreData в моем собственном проекте:

    static internal func insert(at id: String?, into table: String?) -> Self?
    {
        //unwrap managed object context and id
        guard let context = self.context, let id = id else { return nil }
        let tableName = table ?? self.entityName //the table name in CoreData
        let request: NSFetchRequest<Self> = NSFetchRequest(entityName: tableName)
        request.predicate = NSPredicate(format: "id == %@", id) //filter only results matching 'id'
        if let result = try? context.fetch(request), let object = result.first
        {
            //return the existing object
            return object
        }
        else if let entity = NSEntityDescription.entity(forEntityName: tableName, in: context)
        {
            //initialize and return a new object
            return self.init(entity: entity, insertInto: context)
        }
        return nil
    }
    

    В моем случае id — это строка, но для вас это может быть Int или что-то еще. Суть приведенного выше фрагмента кода состоит в том, чтобы использовать какое-то уникальное идентифицирующее значение (id), проверить таблицу на наличие существующей записи с этим идентифицирующим значением и на основе результата либо 1) вернуть существующий объект, либо 2) вставить новый объект.

    В приложении это преимущество, потому что многократный вызов insert не приведет к созданию нескольких объектов. Предполагая, что мы никогда не вставляли в базу данных какой-либо элемент с идентификатором «abcd»:

    //object is inserted at "abcd":
    let result = SomeModel.insert(at: "abcd", into: "tablename")
    //since object at "abcd" exists now, this returns the existing object:
    let result = SomeModel.insert(at: "abcd", into: "tablename")
    

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

    18.01.2017
  • Почему id и возвращаемое значение необязательны? Вы собираетесь вставить что-то в отдельное id и ожидаете получить существующий объект или новый объект в существующей сущности. Необязательные параметры не предназначены для избежания ошибок (дизайна) во время времени компиляции. 18.01.2017
  • Что заставляет вас думать, что эти опции предназначены для предотвращения ошибок во время компиляции? Они даже не для того, чтобы избежать ошибок вообще. Суть в том, чтобы развернуть один раз в универсальной повторно используемой функции, а не разворачивать каждый раз, когда функция используется. Не стесняйтесь разворачивать json["id"] as? String каждый раз, когда вы используете его в своей программе, а не один раз в повторно используемой функции. 18.01.2017
  • @michaelfourre Спасибо за ответ и предложение разъяснить. Там определенно есть несколько вещей, с которыми я еще не знаком. Я вернусь к этому, если не смогу заставить работать решение vadian, поскольку я знаком и относительно доволен всем, что там есть. 18.01.2017
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..