I create a fitness app and I use Realm as local database. During first launch I want to replace default realm with realm file which contains initial data (names of exercises, equipment, muscles engaged etc.). This initial data won't change in future. I wonder if exists some way which can help me to create reference in main class to another smaller classes. I need this to make filtering and getting data easier.
It's my main realm class
class Exercise: Object {
#Persisted var exerciseID: Int = 0
#Persisted var name: String = ""
#Persisted var category: Int
#Persisted var equipment: String
#Persisted var instruction: String
#Persisted var muscle: String
#Persisted var gif: String?
#Persisted var image: String? = nil
convenience init(name: String, category: Int, equipment: String, instruction: String, muscle: String, gif: String?, image: String?) {
self.init()
self.name = name
self.category = category
self.equipment = equipment
self.instruction = instruction
self.muscle = muscle
self.gif = gif
self.image = image
}
override static func primaryKey() -> String? {
return "exerciseID"
}
}
When I want to get all exercises and assigned equipment and muscles it is really a lot of code to retrieve this data especially when string contains few references to object.
var exercises = [Exercise]()
var equipments = [Equipment]()
func getAllExercises() {
let data = RealmService.shared.realm.objects(Exercise.self)
exercises = data.compactMap({$0})
let equipment = exercises.compactMap({$0.equipment})
for eq in exercises.compactMap({$0.equipment}) {
let numberOfEquipment = eq.components(separatedBy: ",")
for number in numberOfEquipment {
guard let intNumber = Int(number) else { return }
guard let finalEquipment = RealmService.shared.realm.object(ofType: Equipment.self, forPrimaryKey: intNumber) else { return }
equipments.append(finalEquipment)
}
}
Maybe the better option is to just insert values instead of object references?
You need to set up one-to-many relationships to take advantage of quicker queries and lazy loading.
I've simplified the models, but the magic is in the equipmentObjects property:
class Exercise: Object {
#Persisted(primaryKey: true) var exerciseID = 0
#Persisted var name: String = ""
#Persisted var equipment: String
#Persisted var equipmentObjects: List<Equipment>
convenience init(exerciseID: Int, name: String, equipment: String) {
self.init()
self.exerciseID = exerciseID
self.name = name
self.equipment = equipment
}
}
class Equipment: Object {
#Persisted(primaryKey: true) var equipmentID = 0
#Persisted var equipment: String = ""
convenience init(equipmentID: Int, equipment: String) {
self.init()
self.equipmentID = equipmentID
self.equipment = equipment
}
}
You can go ahead and initialize realm with your csv file. But when the app begins you would want to go ahead and establish the relationships between Exercise, Equipment, and Muscles. You should only do this once.
Here I've created a small utility to link the realm objects. Notice how it uses UserDefaults to check and see if relationships were already built. It is also building the relationships on a specified queue. You would want to pass in a background queue rather than the main queue so the UI doesn't lock up.
struct RealmRelationshipBuilder {
let configuration: Realm.Configuration
let userDefaults: UserDefaults
let queue: DispatchQueue
func buildRelationshipsIfNeeded(completion: #escaping() -> Void) {
guard userDefaults.didBuildRealmRelationships == false else { return completion() }
queue.async {
autoreleasepool {
defer { completion() }
do {
let realm = try Realm(configuration: configuration)
try realm.write {
realm.objects(Exercise.self).forEach { exercise in
let equipment = exercise
.equipment
.components(separatedBy: ",")
.compactMap(Int.init)
.compactMap { realm.object(ofType: Equipment.self, forPrimaryKey: $0) }
exercise.equipmentObjects.append(objectsIn: equipment)
}
}
} catch {
print("RealmRelationshipBuilder error: \(error)")
}
userDefaults.didBuildRealmRelationships = true
}
}
}
}
extension UserDefaults {
enum Key {
static let didBuildRealmRelationships = "didBuildRealmRelationshipsKey"
}
var didBuildRealmRelationships: Bool {
get { bool(forKey: Key.didBuildRealmRelationships) }
set { set(newValue, forKey: Key.didBuildRealmRelationships) }
}
}
Then to test the builder here is a small test case. But in reality you would probably want to show the user an status indicator while the relationships are being built in the background.
enum InitialData {
static let exercises: [Exercise] = {
[
Exercise(exerciseID: 1, name: "Bench press", equipment: "1,3,5"),
Exercise(exerciseID: 2, name: "Butterfly", equipment: "6"),
]
}()
static let equipment: [Equipment] = {
[
Equipment(equipmentID: 1, equipment: "Barbell"),
Equipment(equipmentID: 2, equipment: "Bench"),
Equipment(equipmentID: 3, equipment: "Bodyweight"),
Equipment(equipmentID: 4, equipment: "Cable"),
Equipment(equipmentID: 5, equipment: "Not sure"),
Equipment(equipmentID: 6, equipment: "Unknown"),
]
}()
}
class RealmExerciseTests: XCTestCase {
let realmConfiguration = Realm.Configuration.defaultConfiguration
override func setUpWithError() throws {
let realm = try Realm(configuration: realmConfiguration)
try realm.write {
realm.deleteAll()
realm.add(InitialData.exercises)
realm.add(InitialData.equipment)
}
}
func testInitialize() throws {
let relationshipBuilder = RealmRelationshipBuilder(
configuration: realmConfiguration,
userDefaults: .init(suiteName: UUID().uuidString) ?? .standard,
queue: DispatchQueue(label: "realm.init.background")
)
let expectation = expectation(description: "realm.init")
relationshipBuilder.buildRelationshipsIfNeeded {
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
let realm = try Realm(configuration: realmConfiguration)
realm.refresh()
guard let exercise1 = realm.object(ofType: Exercise.self, forPrimaryKey: 1) else {
return XCTFail("Missing exercise with primary key 1")
}
guard let exercise2 = realm.object(ofType: Exercise.self, forPrimaryKey: 2) else {
return XCTFail("Missing exercise with primary key 2")
}
XCTAssertEqual(exercise1.equipmentObjects.count, 3)
XCTAssertEqual(exercise2.equipmentObjects.count, 1)
}
}
Related
Im trying to delete a realm object which contains a realm list of objects, but when I do that I receive this error: "Thread 1: RLMArray has been invalidated or the containing object has been deleted.". I've tried all the solutions I've managed to find but none of them helped me to fix my issue. Thank you for helping in advance!
Parent object creation code :
#MainActor
func addWordSet(name : String){
Task {
do {
// let realm = try await realmService.getRealm()
if let realm = realmService.getRealm() {
let user = realmService.getCurrentUser()
let set = WordSet(name: name, ownerId: user?.id ?? "")
try realm.write {
realm.add(set, update: Realm.UpdatePolicy.modified)
}
}
}catch {
print(error)
}
}
}
Child object creation code
#MainActor
func addTranslationToSet(set : WordSet, term : String, meaning : String, completion: #escaping () -> Void){
Task {
do {
if let realm = realmService.getRealm() {
let user = realmService.getCurrentUser()
let translation = Translation(word: term, translation: meaning, ownerId: user?.id ?? "")
try realm.write {
set.translations.append(translation)
}
completion()
}
}catch {
print(error)
}
}
}
Parent object deletion code:
#MainActor
func deleteWordSet(set : WordSet){
Task {
do {
if let realm = realmService.getRealm() {
try realm.write {
realm.delete(set)
}
}
} catch{
print(error)
}
}
}
#MainActor
func deleteTranslationFromSet(set : WordSet, translation : Translation, completion: #escaping () -> Void){
Task {
do {
if let realm = realmService.getRealm() {
let index = set.translations.firstIndex{
$0._id == translation._id
}
try realm.write {
if let index = index {
set.translations.remove(at: index)
}
}
completion()
}
}catch {
print(error)
}
}
models code :
class Translation : Object {
#Persisted(primaryKey: true) var _id : ObjectId
#Persisted var owner_id : String
#Persisted var word : String
#Persisted var translation : String
convenience init(word : String, translation: String, ownerId: String){
self.init()
self.word = word
self.translation = translation
self.owner_id = ownerId
}
}
class WordSet : Object {
#Persisted var name : String
#Persisted(primaryKey: true) var _id : ObjectId
#Persisted var owner_id : String
#Persisted var translations : List<Translation>
convenience init(name : String, ownerId : String){
self.init()
self.name = name
self.owner_id = ownerId
}
}
I'm trying to get list of toys from Firestore and put it into array
But when I call function, it returns empty array, and just after returning it prints Toy object, so order is broken.
I thought that closures would help me, but I think I don't know how to use them, and examples from Google don't help me
Here is my code (I use SwiftUI so I created swift file with variable)
let db = Firestore.firestore()
class DataLoade {
func loadFirebase(completionHandler: #escaping (_ toys: [Toy]) -> ()){
var toysar: [Toy] = []
let toysRef = db.collection("Toys")
toysRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
var name: String = document.get("name") as! String
var id: Int = document.get("id") as! Int
var description: String = document.get("description") as! String
var imageName: String = document.get("imageName") as! String
var price: String = document.get("price") as! String
var category: String = document.get("category") as! String
var timeToy = Toy(id: id, name: name, imageName: imageName, category: category, description: description, price: price)
toysar.append(timeToy)
}
}
}
completionHandler(toysar)
// print(toysar)
}
}
that's what it prints out:
[] // it prints empty array, but it is in the end of the code
Toy(id: 1001, name: "Pikachu", imageName: "pikachu-plush", category: "lol", description: "kek", price: "350₽") // and now it prints Toy object, however it is in the start of the code
Ok, so I tried to make completion handler for my function, like in "duplicated" answer, but that doesn't work: array is returning before completion handler works
ContentView.swift
func updateArray() -> [Toy]{
dl.loadFirebase() { toys in
ll = toys
}
print("lol \(datas)") // prints «lol []»
return ll
}
You can wait for an asynchronous task using a DispatchGroup. But the trick is NOT to associate asynchronous tasks with return statements. Instead, use closures to do an action after the task is done.
Disclaimer: I wrote this on SO, I apologize in advance for syntax issues.
let toyData = loadFirebase( { (toys) in
print(toys)
//Do something with toys when done
//You could add another completionHandler incase it fails.
//So 1 for pass and 1 for fail and maybe another for cancel. W/e u want
} )
let db = Firestore.firestore()
func loadFirebase(completionHandler:#escaping ((toys: [Toy]?) -> Void)) {
//Create Group
let downloadGroup = DispatchGroup()
var toysar: [Toy] = []
let toysRef = db.collection("Toys")
//If you had multiple items and wanted to wait for each, just do an enter on each.
downloadGroup.enter()
toysRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
var name: String = document.get("name") as! String
var id: Int = document.get("id") as! Int
var description: String = document.get("description") as! String
var imageName: String = document.get("imageName") as! String
var price: String = document.get("price") as! String
var category: String = document.get("category") as! String
var timeToy = Toy(id: id, name: name, imageName: imageName, category: category, description: description, price: price)
toysar.append(timeToy)
print(timeToy)
}
//We aren't done until AFTER the for loop, i.e., each item is grabbed.
downloadGroup.leave()
}
}
//Once the queue is empty, we notify the queue we are done
downloadGroup.notify(queue: DispatchQueue.main) {
completionHandler(toys)
}
}
import SwiftUI
var dl = DataLoade()
var ll: [Toy] = []
let semaphore = DispatchSemaphore(value: 1)
struct ContentView: View {
var items: [Toy]
var body: some View {
NavigationView{
ScrollView(){
VStack(alignment: .leading){
ToyRow(category: "Наш выбор", toys: items)
Spacer()
ToyRow(category: "Акции", toys: items)
}
}.navigationBarTitle(Text("Игрушки г.Остров"))}
}
}
func upe(completionHandler:#escaping ((toys: [Toy]?){
dl.loadFirebase(completionHandler: { toy in
ll.append(contentsOf: toy!)
completionHandler(ll)
} )
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
upe(completionHandler: { (toys) in
DispatchQueue.main.async {
ContentView(items: toys)
}
})
}
}
Initially I had the following classes:
#objcMembers public class NormalObjectRealm: Object {
// Shared
dynamic public var id: String?
dynamic public var title: String?
dynamic public var subTitle: String?
dynamic public var imageInfo: ImageInfoRealm?
dynamic public var descriptionString: String?
public var categories = List<String>()
public var count = RealmOptional<Int>()
public var episodes = List<String>()
public static let realmPrimaryKey: String = "id"
public override class func primaryKey() -> String? {
return NormalObjectRealm.realmPrimaryKey
}
}
#objcMembers public class ImageInfoRealm: Object {
dynamic public var id: String?
dynamic public var url: String?
public static let realmPrimaryKey: String = "id"
public override class func primaryKey() -> String? {
return ImageInfoRealm.realmPrimaryKey
}
}
but now NormalObjectRealm is kind of incorporated into a new class like so:
#objcMembers public class MediaObjectRealm: Object {
// Shared
dynamic public var id: String?
dynamic public var title: String?
dynamic public var subTitle: String?
dynamic public var imageInfo: ImageInfoRealm?
dynamic public var descriptionString: String?
public var categories = List<String>()
dynamic public var type: String?
// NormalObjectRealm
public var episodeCount = RealmOptional<Int>()
public var episodes = List<String>()
// OtherObjectRealm
dynamic public var urlOne: String?
dynamic public var urlTwo: String?
dynamic public var urlThree: String?
public var isExplicit = RealmOptional<Bool>()
public static let realmPrimaryKey: String = "id"
public override class func primaryKey() -> String? {
return MediaObjectRealm.realmPrimaryKey
}
}
I'm currently trying to write the migration for the transition here where the idea basically is to transfer most of the fields over from NormalObjectRealm to MediaObjectRealm.
This is what my migration-block currently looks like
Realm.Configuration(schemaVersion: schemaVersion, migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < temp {
print("RealmMigration: Applying migration from \(oldSchemaVersion) to \(temp)")
migration.enumerateObjects(ofType: "NormalObjectRealm") { oldObject, newObject in
guard let oldObject = oldObject else {
return
}
guard let id = oldObject["id"] as? String else {
return
}
guard let title = oldObject["title"] as? String else {
return
}
guard let subTitle = oldObject["subTitle"] as? String else {
return
}
guard let imgInfo = oldObject["imageInfo"] else {
return
}
guard let count = oldObject["count"] as? RealmOptional<Int>? else {
return
}
guard let descriptionString = oldObject["descriptionString"] as? String? else {
return
}
let item = migration.create("MediaObjectRealm")
item["id"] = id
item["title"] = title
item["subTitle"] = subTitle
item["descriptionString"] = descriptionString
item["type"] = "myType"
item["episodeCount"] = episodeCount // Doesn't work either...
migration.enumerateObjects(ofType: "ImageInfoRealm") { oldImg, newImg in
guard let oldImg = oldImg else {
return
}
let inf = oldObject.value(forKey: "imageInfo")
print(inf)
let t = migration.create("ImageInfoRealm", value: inf)
print("doing it")
// print(t)
item.setValue(t, forKey: "imageInfo")
}
}
}
})
id, title, subTitle etc. (String? and Date? variables) are set fine and appear inside the newly created MediaObjectRealm DB-Entries. However imageInfo of type ImageInfoRealm does not... setting it directly like so: item.setValue(oldObject.value(forKey: "imageInfo"), forKey: "imageInfo") (or item["imageInfo"] = oldObject.value(forKey: "imageInfo")) results in realm crashing and telling me that this object is from another realm and I have to copy it over.
'Object is already managed by another Realm. Use create instead to
copy it into this Realm.'
Creating it like in the code above results in not even having any items of type MediaObjectRealm at all i.e. loosing all the data (as NormalObjectRealm is also not present anymore).
Am I missing something? What I basically want is to to take the link/reference from the NormalObjectRealm and copy it to the new MediaObjectRealm.
After long testing and trying different possibilities I managed to migrate the data.
Here is what I did to accomplish this.
I used this as a base:
class RealmMigrationObject {
let migration: () -> ()
init(migration: #escaping () -> ()) {
self.migration = migration
}
}
and derived classes from that. Something like:
class MigrationObjectToThree: RealmMigrationObject {
init() {
super.init(migration: MigrationObjectToThree.migration)
}
private static func migration() {
print("Migration to three | migration")
var imageInfos: [ImageInfo] = []
let config = Realm.Configuration(schemaVersion: 3, migrationBlock: { migration, oldSchemaVersion in
print("Migration to three | migrationBlock")
print("RealmMigration: Applying migration from \(oldSchemaVersion) to 3")
migration.deleteData(forType: "ExploreSectionObjectRealm")
migration.enumerateObjects(ofType: "ImageInfoRealm") { oldInfo, newObject in
guard let oldInfo = oldInfo else {
return
}
guard let id = oldInfo["id"] as? String,
let url = oldInfo["url"] as? String,
let url500 = oldInfo["url500"] as? String,
let url400 = oldInfo["url400"] as? String,
let url300 = oldInfo["url300"] as? String,
let url200 = oldInfo["url200"] as? String,
let url100 = oldInfo["url100"] as? String,
let colorString = oldInfo["color"] as? String,
let color = UIColor(hexString: colorString) else {
return
}
imageInfos.append(ImageInfo(id: id,
url: url,
url500: url500,
url400: url400,
url300: url300,
url200: url200,
url100: url100,
color: color))
}
})
Realm.Configuration.defaultConfiguration = config
do {
let realm = try Realm(configuration: config)
print("Realm is located at: \(realm.configuration.fileURL?.description ?? "")")
print(realm.configuration.fileURL?.description ?? "") // Printing here on purpose as it's easier to copy
} catch {
print("Realm Error: \(error), trying to rebuild realm from scratch")
let deleteMigrationConfig = Realm.Configuration(schemaVersion: RealmHelper.schemaVersion,
deleteRealmIfMigrationNeeded: true)
do {
_ = try Realm(configuration: deleteMigrationConfig)
} catch {
print("Failed to instantiate: \(error.localizedDescription)")
}
}
RealmHelper.removeRealmFiles()
Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 3)
imageInfos.forEach({ $0.save() })
}
}
From that I just created all migration for the difference between the current schema version and target schema version on looped over all migrations simply executing the migration function of that given object.
I'm making an app for airports and I'm getting an array of data from one api, like so:
"data":[
{"id":"001","code":"ABZ","name":"Aberdeen","country":"United Kingdom"},
{"id":"002","code":"AUH","name":"Abu Dhabi","country":"United Arab Emirates"},
.
.
.
]
AND :
"airports":[
{"from":"001",
"to":["1","3","11","13","12","20","23","27","29","31","33"]
},
.
.
.
]
I have created realm model classes:
class AirportsDataRealm: Object {
#objc dynamic var name: String = ""
#objc dynamic var id: Int = 0
#objc dynamic var code: String = ""
#objc dynamic var country: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
class AirportsFromToRealm: Object {
#objc dynamic var fromID: Int = 0
var toID = List<Int>()
override static func primaryKey() -> String? {
return "fromID"
}
}
now I want to save it into realm, I'm using swiftyJSON and I have used for-loop to do it and it is working fine but I think it's taking long time since the array is very long, here is what I've done:
// Airports Data
let countData = json["data"].count
for i in 0...countData - 1{
let airportsDataModel = AirportsDataRealm()
airportsDataModel.code = json["data"][i]["code"].stringValue
airportsDataModel.name = json["data"][i]["name"].stringValue
airportsDataModel.country = json["data"][i]["country"].stringValue
airportsDataModel.id = Int(json["data"][i]["id"].stringValue)!
try! realm.write {
realm.add(airportsDataModel, update: true)
}
}
//Airports FROM-TO
let countFromTo = json["airports"].count
for i in 0...countFromTo - 1{
let fromToDataModel = AirportsFromToRealm()
fromToDataModel.fromID = Int(json["airports"][i]["from"].stringValue)!
let arrayTo = json["airports"][i]["to"].arrayValue.map{ $0.intValue }
fromToDataModel.toID.append(objectsIn: arrayTo)
try! realm.write {
realm.add(fromToDataModel, update: true)
}
}
is there any way to save the whole array in realm in one shot without for-loop?
P.S
"there should be a relation between the two tables because each from 'id' has a list of 'to' id's and the id's are from the data table, for now I managed to create this relations when fetching the data using filters ,, so just ignore this"
Thank you
Simply use map method,
First I needed to add initializers to my object classes and pass json array as a parameter, like so:
class AirportsDataRealm: Object {
#objc dynamic var name: String = ""
#objc dynamic var id: Int = 0
#objc dynamic var code: String = ""
#objc dynamic var country: String = ""
convenience required init(withJSON json : JSON) {
self.init()
self.name = json["name"].stringValue
self.id = json["id"].intValue
self.code = json["code"].stringValue
self.country = json["country"].stringValue
}
override static func primaryKey() -> String? {
return "id"
}
}
class AirportsFromToRealm: Object {
#objc dynamic var fromID: Int = 0
var toID = List<Int>()
convenience required init(withJSON json : JSON) {
self.init()
self.fromID = json["from"].intValue
let toArray = json["to"].arrayValue.map{ $0.intValue }
self.toID.append(objectsIn: toArray)
}
override static func primaryKey() -> String? {
return "fromID"
}
}
Then by using map method the code will look like this:
func updateAirport(json: JSON) {
// Airports Data
let airportsData : [AirportsDataRealm]
let airportsDataJsonArray = json["data"].array
airportsData = airportsDataJsonArray!.map{AirportsDataRealm(withJSON: $0)}
//Airports FROM-TO
let airportsFromTo : [AirportsFromToRealm]
let airportsFromToJsonArray = json["airports"].array
airportsFromTo = airportsFromToJsonArray!.map{AirportsFromToRealm(withJSON: $0)}
//Write To Realm
try! realm.write {
realm.add(airportsData, update: true)
realm.add(airportsFromTo, update: true)
}
}
No for loops anymore ^_^
I´m trying to save an array of structs into coredata. I did a lot of research, but i cannot find the solution.
Here´s what i´ve got:
import Cocoa
import CoreData
class ViewController: NSViewController {
struct StudentsStruct {
let firstName: String
let lastName: String
let age: Int
}
let Studentsdata: [StudentsStruct] = [StudentsStruct(firstName: "Albert", lastName: "Miller", age: 24), StudentsStruct(firstName: "Susan", lastName: "Gordon", age: 24), StudentsStruct(firstName: "Henry", lastName: "Colbert", age: 24)]
override func viewDidLoad() {
super.viewDidLoad()
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
for items in Studentsdata {
student.firstName = StudentsStruct.firstName
student.lastName = StudentsStruct.lastName
student.age = StudentsStruct.age
}
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
print (student)
}
}
The DatabaseController is solution i´ve got from this tutorial:
https://www.youtube.com/watch?v=da6W7wDh0Dw
It´s not so important, it´s just making the "getContext" function.
Whats important, in teh commandline "student.firstName = StudentsStruct.firstName" i´m getting the error "instance member "firstName" cannot be used on type ViewController.StudentStruct.
After trying and trying, i´m running out of ideas how to get the array of structs into coredata.
This is the DatabaseController file:
import Foundation
import CoreData
class DatabaseController {
private init() {
}
class func getContext() -> NSManagedObjectContext {
return DatabaseController.persistentContainer.viewContext
}
// MARK: - Core Data stack
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "StudentCoreFile")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
})
return container
}()
class func saveContext () {
let context = DatabaseController.persistentContainer.viewContext
if context.hasChanges {
do {
try context.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)")
}
}
}
}
for any help thanks in advance!
Ok, you are right, i forgot to execute the fetchrequest. Here´s my current code:
import Cocoa
import CoreData
class ViewController: NSViewController {
struct StudentsStruct {
let firstName: String
let lastName: String
let age: Int
}
let Studentsdata: [StudentsStruct] = [StudentsStruct(firstName: "Albert", lastName: "Miller", age: 24), StudentsStruct(firstName: "Susan", lastName: "Gordon", age: 24), StudentsStruct(firstName: "Henry", lastName: "Colbert", age: 24)]
override func viewDidLoad() {
super.viewDidLoad()
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
for item in Studentsdata {
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
student.firstName = item.firstName
student.lastName = item.lastName
student.age = Int16(item.age)
}
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
do {
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
print("number of results: \(searchResults.count)")
for result in searchResults as [Student] {
print(student)
}
} catch {
print ("error")
}
}
}
It´s running without errors. Now i´m getting 32 search results. Every entry is: age = 0; firstName = nil; lastName = nil;
For comparison, this code, without the loop is working:
import Cocoa
import CoreData
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
student.firstName = "henry"
student.lastName = "miller"
student.age = 22
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
do {
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
print("number of results: \(searchResults.count)")
for result in searchResults as [Student] {
print(student)
}
} catch {
print ("error")
}
}
}
You need to access the item in your for loop also you are currently accessing the same object Student object in for loop instead of that you need to create a new Student in every iteration of for loop.
for item in Studentsdata {
//Create new student in for loop
let student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
//To get firstName, lastName and age access the item
student.firstName = item.firstName
student.lastName = item.lastName
student.age = item.age
}
//Save context now
DatabaseController.saveContext()
In case someone is interested, I found the solution:
You first have to set up the struct in the CoredataEntity Class like that:
import Foundation
import CoreData
struct StudentsStruct {
let firstName: String
let lastName: String
let age: Int
}
#objc(Student)
public class Student: NSManagedObject {
#NSManaged public var firstName: String?
#NSManaged public var lastName: String?
#NSManaged public var age: Int16
var allAtributes : StudentsStruct {
get {
return StudentsStruct(firstName: self.firstName!, lastName: self.lastName!, age: Int(self.age))
}
set {
self.firstName = newValue.firstName
self.lastName = newValue.lastName
self.age = Int16(newValue.age)
}
}
}
Then use the same struct to paste the data:
import Cocoa
import CoreData
class ViewController: NSViewController {
let studentsdata: [StudentsStruct] = [StudentsStruct(firstName: "Albert", lastName: "Miller", age: 24), StudentsStruct(firstName: "Susan", lastName: "Gordon", age: 24), StudentsStruct(firstName: "Henry", lastName: "Colbert", age: 24)]
override func viewDidLoad() {
super.viewDidLoad()
for items in studentsdata {
let student: Student = NSEntityDescription.insertNewObject(forEntityName: "Student", into: DatabaseController.getContext()) as! Student
student.allAtributes = items
}
DatabaseController.saveContext()
let fetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
do {
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
print("number of results: \(searchResults.count)")
for result in searchResults as [Student] {
print("student: \(firstName), \(lastName), \(age)" )
}
} catch {
print ("error: \(error)")
}
}
}
Thats it.
Perhaps this is lazy. You could also just encode your array as a json object and then create a field on your NSManagedObject for it as a transformable. When you want to retrieve you'd just decode and downcast to the proper type. That's what I did in one of my projects; worked fine.