Dub Dub ah llegado a su fin y SwiftUI ha evolucionado para convertirse en el rey de los frameworks, ahora podemos usarlo para crear apps a través de todo el ecosistema. Sin embargo la nueva arquitectura de las apps multi plataforma tiene un problema, hemos perdido la casilla de CoreData 😨, así que ahora tendremos que agregarlo a mano.
En este blog voy a documentar la manera de añadir CoreData a las nuevas apps multiplataforma de SwiftUI.
Paso 1
Creamos nuestra nueva app multi plataforma para eso solo seleccionamos crear nuevo proyecto, seleccionamos Multiplataforma > App y Nueva

Con eso tenemos una nueva aplicación multi plataforma que corre tanto en iOS como en MacOS

Paso 2
Añadir nuestro nuevo modelo de datos, esto venia por default cuando creábamos un app con CoreData en iOS Anteriormente pero ahora tendremos que hacerlo a mano.
Para añadir nuestro modelo de datos solo presionamos ⌘ + N y elegimos Data Model en la sección de Core Data


Ahora creamos nuestro modelo de datos como siempre con sus entidades y relaciones
En este caso estoy haciendo una App para guardar reglas de tres


Paso 3
Crear las Clases de nuestro modelo de datos, esto no es algo obligatorio pero nos permite crear wrappers para no tener problemas con los optionals, principalmente en los Strings ademas nos permitirá agregar un mejor manejo de nuestras relaciones entre entidades, como explicare a continuación.
Primero que nada, no olvidemos en el inspector de la entidad definir que el codegen sera manual/none si no tendremos un error como el que se muestra a continuación

Esto ocurre cuando generamos nuestras subclases de manera manual y olvidamos decírselo al modelo de datos.
Una ves hecho eso, nos dirigimos al menu Editor > Create NSManagedObject Subclass

Seleccionamos nuestro modelo de datos

Seleccionamos las entidades de las cuales queremos crear su subclases

y de la misma forma que lo hicimos anteriormente nos aseguramos de que nuestros targets estén seleccionados y que la carpeta sea shared

Voilà con eso ya tenemos listas nuestras subclases ahora podemos deshacernos de los valores opcionales

Para eliminar los valores opcionales en las strings es tan fácilmente como crear una nueva variable
public var wname : String {
name ?? "No name"
}
y para facilitar el manejo de nuestra relaciones agregamos otra variable con un Array de nuestra relación
public var elementArray: [Element] {
let set = elements as? Set<Element> ?? []
return set.sorted {
$0.wName > $1.wName
}
}
Lo mismo haremos en la otra entidad que creamos, si prestas atención al código a continuación veras que añadí una nueva variable para que Date no sea opcional
//
// Element+CoreDataProperties.swift
// 3Rule
//
// Created by Francisco Misael Landero Ychante on 02/06/20.
// Copyright © 2020 Francisco Misael Landero Ychante. All rights reserved.
//
//
import Foundation
import CoreData
extension Element {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Element> {
return NSFetchRequest<Element>(entityName: "Element")
}
@NSManaged public var decimals: Int16
@NSManaged public var direct: Int16
@NSManaged public var name: String?
@NSManaged public var plurarlLabel: String?
@NSManaged public var unitLabel: String?
@NSManaged public var unitLabelExponent: String?
@NSManaged public var usePlural: Bool
@NSManaged public var useUnitLabel: Bool
@NSManaged public var value: Double
@NSManaged public var rule: Rule?
@NSManaged public var date: Date?
@NSManaged public var id: UUID?
public var wDate : Date {
date ?? Element.defaultTime
}
public var wName : String {
name ?? "Sin Nombre"
}
public var wPlurarlLabel : String {
plurarlLabel ?? ""
}
public var wUnitLabel : String {
unitLabel ?? ""
}
public var wUnitLabelExponent : String {
unitLabelExponent ?? ""
}
static var defaultTime: Date {
var components = Calendar.current.dateComponents([.hour, .minute, .month, .day, .year], from: Date())
components.year = 2014
components.month = 3
components.day = 3
return Calendar.current.date(from: components) ?? Date()
}
}
Paso 4
Conectando SwiftUI y Core Data
ok esta la parte importante, primero nos dirigimos a donde nuestra app es declarada
import SwiftUI
@main
struct ruleOfThreeApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
La nueva estructura de SwiftUI es Elegante y simple
Aquí añadiremos «import CoreData» y nuestro context «let context = PersistentCloudKitContainer.persistentContainer.viewContext» ademas de que lo añadimos a nuestro ContentView con «ContentView().environment(.managedObjectContext, context)»
Esto que antes iba en AppDelegate ahora se incorpora en @main que es la declaración de nuestra app.
import SwiftUI
import CoreData
@main
struct ruleOfThreeApp: App {
let context = PersistentCloudKitContainer.persistentContainer.viewContext
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, context)
}
}
}
Muy bien finalmente ahora añadiremos una clase para conectar Core Data y nuestro modelo de datos.
Lo mismo que antes nuevo archivo ⌘ + N y nos aseguramos que sea en shared y que seleccionemos nuestros targets de manera apropiada

Ahora hay dos puntos importantes en el siguiente código
El primero en la linea 18
let container = NSPersistentContainer(name: “ruleOfThree”)
Donde definimos el nombre de nuestro modelo de datos previamente creado
y el segundo en lineas 24 y 25
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
Donde definimos nuestras políticas de merge
//
// PersistentCloudKitContainer.swift
// ruleOfThree
//
// Created by Francisco Misael Landero Ychante on 27/06/20.
//
import CoreData
public class PersistentCloudKitContainer {
// MARK: - Define Constants / Variables
public static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
// MARK: - Initializer
private init() {}
// MARK: - Core Data stack
public static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "ruleOfThree")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// MARK: - Core Data Saving support
public static func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Paso 5
Ahora solo resta probar que nuestro modelo funciona y que podemos guardar datos en él, para simplemente cree una lista y un botón para añadir elementos de prueba.
import SwiftUI
struct ContentView: View {
// MARK: - Core Data
@Environment(\.managedObjectContext) var moc
@FetchRequest(entity: Rule.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Rule.order, ascending: true)] ) var rules: FetchedResults<Rule>
var body: some View {
List{
ForEach(self.rules, id: \.self){ rule in
Text("\(rule.order) \(rule.wname)")
}
Button(action: {
saveTheRule()
}) {
Text("Add Rule")
}
}
}
func saveTheRule(){
//New Rule
let newRule = Rule(context: self.moc)
newRule.baseLabel = "test"
newRule.name = "Test name \(Int.random(in: 1..<100))"
//simple rule
let newElement = Element(context: self.moc)
newElement.name = "parameter name"
newElement.rule = newRule
try? self.moc.save()
}
}

En el siguiente post vamos a añadir sincronización con CloudKit
Para todo este proceso me base en las siguiente entradas del foro de desarrolladores de apple
https://developer.apple.com/forums/thread/117655