Categories
Swift Development

CoreData en SwiftUI

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

Así que aquí es donde comenzamos el hermoso nacimiento de una app multi plataforma, ahora vemos que necesitamos para añadir CoreData

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

Un punto importante al crear el modelo es poner dentro de Shared y elegir como targets tanto nuestros test como nuestras apps para iOS y macOS

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

Nota: no olvides definir tus constraints para evitar que se te dupliquen tus valores

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

}
y bueno ahi esta en lo que agregan la casilla de CoreData otra vez esta es la forma manual de implementarlo.

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

https://developer.apple.com/forums/thread/117655

https://developer.apple.com/forums/thread/650309

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *