Nested Table in coreData ios swift - ios

I need to set a nested table in core data. For eg, I have MenuList Table in which I have attributes (let's say they are name, id, address, email). So I want to add one attribute which childMenu which is array of custom objects, and again in it I have another attribute called subchildMenu with custom array objects. So I need to save all this data using coredata and manage functions for CRUD.
Below is the code, which I have tries, but stucked at saving childMenu.
import Foundation
import CoreData
extension MenuList {
#NSManaged public var sIcon : String?
#NSManaged public var sId: String?
#NSManaged public var sName : String?
//#NSManaged public var sChildMenu : NSSet?
#nonobjc public class func nsfetchRequest() -> NSFetchRequest< MenuList > {
return NSFetchRequest< MenuList >(entityName: "MenuList")
}
//------------------------------------------------------------------------------
// MARK:- Fetch Record
//------------------------------------------------------------------------------
class func fetchAllRecords() -> [MenuList]? {
let fetchRequest = MenuList.nsfetchRequest()
do {
let arr = try AppDelegate.shared.persistentContainer.viewContext.fetch(fetchRequest)
return arr
} catch {
Logger.error("\(self) - \(error.localizedDescription)")
return nil
}
}
//------------------------------------------------------------------------------
class func fetchAllRecords(forId: String) -> [MenuList]? {
let fetchRequest = MenuList.nsfetchRequest()
fetchRequest.predicate = NSPredicate(format: "sId = %#", forId)
do {
let arr = try AppDelegate.shared.persistentContainer.viewContext.fetch(fetchRequest)
return arr
} catch {
Logger.error("\(self) - \(error.localizedDescription)")
return nil
}
}
//------------------------------------------------------------------------------
// MARK:- Save Record
//------------------------------------------------------------------------------
class func saveRecord(_ tblData: [ListData]) {
for data in tblData {
if let tempId = data.id {
if let arrData = MenuList.fetchAllRecords(forId: tempId),arrData.count > 0 {
let tblData = arrData.first!
tblData.sId = tempId
if let childMenu = data.childMenu,childMenu.count > 0 {
// tblData.sChildMenu = NSSet.init(array: childMenu)
TblChildMenu.saveRecord(childMenu)
}
if let name = data.name {
tblData.sName = name
}
do {
try AppDelegate.shared.persistentContainer.viewContext.save()
Logger.log("\(self) Updated Here => =>")
} catch {
Logger.error("\(self) Update Error Here => \(error.localizedDescription)")
}
} else {
let viewContext = AppDelegate.shared.persistentContainer.viewContext
let tblStatusEntity = NSEntityDescription.entity(forEntityName: "MenuList", in: viewContext)
let tblStatusObj = NSManagedObject(entity: tblStatusEntity!, insertInto: viewContext) as! MenuList
tblStatusObj.sId = tempId
if let childMenu = data.childMenu,childMenu.count > 0 {
// tblStatusObj.sChildMenu = NSSet.init(object: childMenu)//NSSet.init(array: childMenu)
TblChildMenu.saveRecord(childMenu)
}
if let name = data.name {
tblStatusObj.sName = name
}
do {
try viewContext.save()
Logger.log("\(self) Saved Here => =>")
} catch {
Logger.error("\(self) Save Error Here => \(error.localizedDescription)")
}
}
}
}
}
//------------------------------------------------------------------------------
// MARK:- Delete Record
//------------------------------------------------------------------------------
class func deleteRecord(forId: String) {
let fetchRequest = MenuList.nsfetchRequest()
fetchRequest.predicate = NSPredicate(format: "sId = %#", forId)
do {
let arrData = try AppDelegate.shared.persistentContainer.viewContext.fetch(fetchRequest)
if arrData.count > 0 {
AppDelegate.shared.persistentContainer.viewContext.delete(arrData.first!)
}
} catch {
Logger.error("\(self) Delete = \(error.localizedDescription)")
}
}
}
Any Help will be appreciated.

Related

Creating example of Core Data entity

Creating an example for a struct is very easy and straightforward. For example,
import Foundation
struct User: Identifiable, Codable {
let id: UUID
let isActive: Bool
let name: String
let age: Int
let company: String
static let example = User(id: UUID(), isActive: true, name: "Rick Owens", age: 35, company: "Rick Owens Inc.")
}
Now, how can I create an example if I made this an entity in core data? I can't just put let example = CachedUser(id: UUID(), ...) like I did with the struct. I want this example to be part of my core data automatically without having to manually create it by using forms, buttons, etc... Thanks in advance!
You can simply check if your default user exists in database. If it does not then you need to create one and save it. Something like the following would work if you have synchronous operations:
class CachedUser {
static var example: CachedUser = {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
if let existingUser = Database.fetchUser(id: exampleUUID) {
return existingUser
} else {
let newUser = CachedUser()
// TODO: apply example values to user
Database.saveUser(newUser)
return newUser
}
}()
}
This will lazily return existing or generate a new user for you. This user will then be persistent in your database.
The code will only be executed once per session, first time you call CachedUser.example.
If you have your database setup asynchronous then with closures it should look something like this:
class User {
static private(set) var example: User!
static func prepareExampleUser(_ completion: () -> Void) {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
Database.fetchUser(id: exampleUUID) { user in
if let user = user {
example = user
completion()
} else {
let newUser = User()
newUser.id = exampleUUID
// TODO: apply example values to user
Database.saveUser(newUser) {
example = newUser
completion()
}
}
}
}
But in this case it makes sense to warmup your application before you show screens that require this user to be present. You can for instance have a loading screen when your app first starts and continue to next screen once this method has finished...
// Loading screen enters
self.startLoading()
User.prepareExampleUser {
self.navigateToNextScreen()
self.stopLoading()
}
In both cases you now hold a static property to your example entry such as User.example which can be used anywhere.
But in both cases you may stumble to issue if user (if able to) deletes this entry from database. You would need to handle that case. Either prevent that action or create a new example user once the old one is deleted.
To access this manager put
let mgr = CachedUserPersistenceManager()
In a ViewModel or a View
/// Manager for the Item entity
class CachedUserPersistenceManager: PersistenceManager<CachedUser>{
let sampleUUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
init(isTest: Bool = false) {
super.init(entityType: CachedUser.self, isTest: isTest)
//Preloads the user
preloadSample()
}
///Preloads a sample object to the context
func preloadSample(){
let list = retrieveObjects(sortDescriptors: nil, predicate: NSPredicate(format: "%K == %#", #keyPath(CachedUser.uuid), sampleUUID as CVarArg)
)
if list.isEmpty{
let sampleItem = createObject()
sampleItem.uuid = sampleUUID
save()
}
}
override func addSample() -> CachedUser {
let new = super.addSample() as CachedUser
//add any sample code
return new
}
override func createObject() -> CachedUser {
super.createObject()!
}
override func updateObject(object: CachedUser) -> Bool {
//Replace the uuid if needed
if object.uuid == sampleUUID{
object.uuid = UUID()
}
return super.updateObject(object: object)
}
}
The generic classes that are a part of this code are below. You don't need them per say it just makes some of the code reusable through the app.
//Manager for any Entity
class PersistenceManager<T : NSManagedObject>{
let serviceSD: CoreDataPersistenceService<T>
internal init(entityType: T.Type, isTest: Bool = false) {
self.serviceSD = CoreDataPersistenceService(isTest: isTest, entityType: entityType)
}
//MARK: convenience
func addSample() -> T {
let newItem = createObject()
return newItem!
}
//MARK: Persistence Service Methods
func createObject() -> T? {
let result = serviceSD.createObject()
return result
}
func updateObject(object: T) -> Bool {
return serviceSD.updateObject(object: object)
}
func deleteObject(object: T) -> Bool {
return serviceSD.deleteObject(object: object)
}
func deleteAllObjects(entityName: String, isConfirmed: Bool) -> Bool {
return serviceSD.deleteAllObjects(isConfirmed: isConfirmed)
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) -> [T]{
return serviceSD.retrieveObjects(sortDescriptors: sortDescriptors, predicate: predicate)
}
func retrieveObject(id: String) -> T? {
return serviceSD.retrieveObject(sortDescriptors: nil, id: id).first
}
func resetChanges() {
serviceSD.resetChanges()
}
func save() {
_ = serviceSD.save()
}
}
//Service for Any Entity
class CoreDataPersistenceService<T: NSManagedObject>: NSObject {
var persistenceController: PersistenceController
let entityType: T.Type
required init(isTest: Bool = false, entityType: T.Type) {
if isTest{
self.persistenceController = PersistenceController.preview
}else{
self.persistenceController = PersistenceController.previewAware
}
self.entityType = entityType
super.init()
}
//MARK: CRUD methods
func createObject() -> T? {
let result = entityType.init(context: persistenceController.container.viewContext)
return result
}
func updateObject(object: T) -> Bool {
var result = false
result = save()
return result
}
func deleteObject(object: T) -> Bool {
var result = false
persistenceController.container.viewContext.delete(object)
result = save()
return result
}
func deleteAllObjects(isConfirmed: Bool) -> Bool {
var result = false
//Locked in so only the Generic "Item" can be deleted like this
if entityType == Item.self && isConfirmed == true{
let deleteRequest = NSBatchDeleteRequest(fetchRequest: entityType.fetchRequest())
do {
try persistenceController.container.persistentStoreCoordinator.execute(deleteRequest, with: persistenceController.container.viewContext)
} catch {
print(error)
result = false
}
}
return result
}
func resetChanges() {
persistenceController.container.viewContext.rollback()
_ = save()
}
func save() -> Bool {
var result = false
do {
if persistenceController.container.viewContext.hasChanges{
try persistenceController.container.viewContext.save()
result = true
}else{
result = false
}
} catch {
print(error)
}
return result
}
func retrieveObject(sortDescriptors: [NSSortDescriptor]? = nil, id: String) -> [T]{
return retrieveObjects(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "id == %#", id))
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) -> [T]
{
let request = entityType.fetchRequest()
if let sortDescriptor = sortDescriptors
{
request.sortDescriptors = sortDescriptor
}
if let predicate = predicate
{
request.predicate = predicate
}
do
{
let results = try persistenceController.container.viewContext.fetch(request)
return results as! [T]
}
catch
{
print(error)
return []
}
}
}
The previewAware variable that is mentioned goes with the Apple standard code in the PersistenceController
It automatically give you the preview container so you don't have to worry about adapting your code for samples in Canvas. Just add the below code to the PersistenceController
static var previewAware : PersistenceController{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
return PersistenceController.preview
}else{
return PersistenceController.shared
}
}

Can't save custom class array to UserDefaults

I'm trying to save a custom class array to UserDefaults but it doesn't work. I get nil back on if let. I looked everywhere online. I'm using Swift 4.2
extension UserDefaults {
func saveReciters(_ reciters: [Reciter]) {
do {
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: reciters, requiringSecureCoding: false)
self.set(encodedData, forKey: UD_RECITERS)
} catch {
debugPrint(error)
return
}
}
func getReciters() -> [Reciter] {
if let reciters = self.object(forKey: UD_RECITERS) as? Data {
return NSKeyedUnarchiver.unarchiveObject(with: reciters) as! [Reciter]
} else {
print("EMPTY RECITERS")
return [Reciter]()
}
}
}
UserInfo={NSDebugDescription=Caught exception during archival: -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600001babcc0
Thats my class:
class Reciter: NSCoding {
private(set) public var name: String
private(set) public var image: UIImage?
private(set) public var surahs: [Surah]
private(set) public var documentID: String
private let quranData = QuranData()
init(name: String, image: UIImage?, surahCount: Int?, documentID: String) {
self.name = name
self.image = image
self.documentID = documentID
if let surahCount = surahCount {
surahs = Array(quranData.getAllSurahs().prefix(surahCount))
} else {
surahs = quranData.getAllSurahs()
}
}
func encode(with aCoder: NSCoder) {
}
required init?(coder aDecoder: NSCoder) {
}
}
On my Surah class i get nil back. All other properties i get back succesfully
Most often I see developer's use codeable, here I am using user as an example:
YourDataModel.swift
struct User: Codable {
var userId: String = ""
var name: String = ""
var profileImageData: Data? }
UserDefaults.swift
import Foundation
extension UserDefaults {
/// The current user of the application, see `./Models/User.swift`
var currentUser: User? {
get {
guard let userData = self.object(forKey: #function) as? Data else { return nil }
return try? JSONDecoder().decode(User.self, from: userData)
}
set {
guard let newuser = newValue else { return }
if let userData = try? JSONEncoder().encode(newuser) {
self.set(userData, forKey: #function)
}
}
}
}
Transform the data into json data... #function is the function or value name i.e.
// For the case the user doesn't yet exist.
if ( UserDefaults.standard.currentUser == nil ) {
// Create a new user
user = User()
// Generate an id for the user, using a uuid.
user?.userId = UUID().uuidString
} else {
// otherwise, fetch the user from user defaults.
user = UserDefaults.standard.currentUser
}

Why the first completionHandler return the data before all methods are called?

Here is my API code
import UIKit
import Alamofire
import SwiftyJSON
class ItunesApi: NSObject
{
var artistArray:Array <Artist> = []
func downloadData(name:String, _ completionHandler: #escaping (_ result: Array<Artist>) -> Void)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=musicArtist").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.findDiscography(data: JsonResult)
}
completionHandler(self.artistArray)
}
}
func findDiscography (data: JSON)
{
for (subJson):(String, JSON) in data["results"]
{
if let artistName = subJson.1["artistName"].string
{
print(artistName);
self.downloadDiscography(name: artistName)
}
}
}
func downloadDiscography (name: String)
{
Alamofire.request("https://itunes.apple.com/search?term=U2&entity=album").response
{ response in
if let data = response.data
{
let JsonResult = JSON(data)
self.createDataModel(name: name, data: JsonResult)
}
}
}
func createDataModel (name: String, data: JSON)
{
var albums:Array <Album> = []
for (subJson):(String, JSON) in data["results"]
{
var thumbnail:String = ""
var title:String = ""
var year:String = ""
if let thumbImage = subJson.1["artworkUrl60"].string
{
thumbnail = thumbImage;
}
if let titleString = subJson.1["collectionName"].string
{
title = titleString;
}
if let releaseDate = subJson.1["releaseDate"].string
{
year = releaseDate;
}
let album = Album(_thumbnail: thumbnail, _title: title, _year: year)
albums.append(album)
}
let artist = Artist(_name: name, _musicStyle: "Rock", _albums: albums as NSArray);
self.artistArray.append(artist);
}
}
And I call here in MyClassTableView.m
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
if let artist = searchBar.text
{
self.itunesApi.downloadData(name: artist, { (array) in
print(array);
})
}
}
Why the copmletionHandler return before all method are called? I want to return in first completionHandeler the result of all method but it return before. The self.itunesApi.downloadData return [] instead of an array filled

Swift extra argument when initialising

I'm trying to create an app that saves data to CloudKit, I have that all sorted but when trying to initialise a new object I get an Extra Argument when initialising error.
Code with error:
func submitTask(){
var task: Task
task = CloudKitTask(task: Task){
task.shortDescription = self.txtShortDesc.text!
task.type = self.txtType.text!
task.reminder = self.txtReminder.text!
task.priority = self.txtPriority.text!
task.dueTime = self.txtDueTime.text!
task.dueDate = self.txtDueDate.text!
task.completed = "False"
task.className = self.txtClass.text!
task.additionalDetails = self.txtDetails.text
}
print(task)
}
Error: Extra argument 'task' in call on the task = CloudK... line
Task Class:
protocol Task {
var id: String? { get }
var shortDescription: String { get set }
var className: String { get set }
var type: String { get set }
var dueDate: String { get set }
var dueTime: String { get set }
var priority: String { get set }
var reminder: String { get set }
var completed: String { get set }
var additionalDetails: String { get set }
}
class CloudKitTask: Task {
let record: CKRecord
init(record: CKRecord){
self.record = record
}
init(task: Task){
record = CKRecord(recordType: "Task")
shortDescription = task.shortDescription
className = task.className
type = task.type
dueDate = task.dueDate
dueTime = task.dueTime
priority = task.priority
reminder = task.reminder
completed = task.completed
additionalDetails = task.additionalDetails
}
var className: String {
get {
return record.objectForKey("ClassName") as! String
}
set {
record.setObject(newValue, forKey: "ClassName")
}
}
var completed: String {
get {
return record.objectForKey("Completed") as! String
}
set {
record.setObject(newValue, forKey: "Completed")
}
}
var dueDate: String {
get {
return record.objectForKey("DueDate") as! String
}
set {
record.setObject(newValue, forKey: "DueDate")
}
}
var dueTime: String {
get {
return record.objectForKey("DueTime") as! String
}
set {
record.setObject(newValue, forKey: "DueTime")
}
}
var priority: String {
get {
return record.objectForKey("Priority") as! String
}
set {
record.setObject(newValue, forKey: "Priority")
}
}
var reminder: String {
get {
return record.objectForKey("Reminder") as! String
}
set {
record.setObject(newValue, forKey: "Reminder")
}
}
var shortDescription: String {
get {
return record.objectForKey("ShortDescription") as! String
}
set {
record.setObject(newValue, forKey: "ShortDescription")
}
}
var type: String {
get {
return record.objectForKey("Type") as! String
}
set {
record.setObject(newValue, forKey: "Type")
}
}
var additionalDetails: String {
get {
return record.objectForKey("AdditionalDetails") as! String
}
set {
record.setObject(newValue, forKey: "AdditionalDetails")
}
}
var id: String? {
return record.recordID.recordName
}
var createdAt: NSDate {
return record.creationDate!
}
var lastModifiedAt: NSDate {
return record.modificationDate!
}
}
I'm fairly new to programming with Swift and can't seem to find an answer that fixes my problem, if you know what I am doing wrong or what I can do to fix it please do point me in the right direction your help is very much appreciated.
If you need to see any more of my code please do ask...
What you probably meant here was to construct a CloudKitTask without first creating a CKRecord. In that case you should have an initializer for that:
convenience init() {
self.init(record: CKRecord(recordType: "Task"))
}
Then you can use it:
func submitTask(){
let task = CloudKitTask()
task.shortDescription = self.txtShortDesc.text!
task.type = self.txtType.text!
task.reminder = self.txtReminder.text!
task.priority = self.txtPriority.text!
task.dueTime = self.txtDueTime.text!
task.dueDate = self.txtDueDate.text!
task.completed = "False"
task.className = self.txtClass.text!
task.additionalDetails = self.txtDetails.text
print(task)
}

Can I serialize a RealmObject to JSON or to NSDictionary in Realm for Swift?

I'm testing Realm, but I cant find a easy way to convert my object to JSON.
I need to push the data to my REST interface.
How can I do it using swift?
class Dog: Object {
dynamic var name = ""
}
class Person : Object {
dynamic var name = ""
let dogs = List<Dog>()
}
I'm trying something like this, but I can't iterate unknown objects (List)
extension Object {
func toDictionary() -> NSDictionary {
let props = self.objectSchema.properties.map { $0.name }
var dicProps = self.dictionaryWithValuesForKeys(props)
var mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeysWithDictionary(dicProps)
for prop in self.objectSchema.properties as [Property]! {
if let objectClassName = prop.objectClassName {
if let x = self[prop.name] as? Object {
mutabledic.setValue(x.toDictionary(), forKey: prop.name)
} else {
//problem here!
}
}
}
return mutabledic
}
}
**sorry for ugly code.
I am also new to Realm but I think the easiest way is to reflect on Object's schema:
class Person: Object {
dynamic var name = ""
dynamic var age = 0
}
let person = Person()
let schema = person.objectSchema
let properties = schema.properties.map() { $0.name }
let dictionary = person.dictionaryWithValuesForKeys(properties) // NSDictionary
println(properties)
println(dictionary)
I think that I found the solution.
I'm not reliant about performance.
extension Object {
func toDictionary() -> NSDictionary {
let properties = self.objectSchema.properties.map { $0.name }
let dicProps = self.dictionaryWithValuesForKeys(properties)
var mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeysWithDictionary(dicProps)
for prop in self.objectSchema.properties as [Property]! {
if let objectClassName = prop.objectClassName {
if let nestedObject = self[prop.name] as? Object {
mutabledic.setValue(nestedObject.toDictionary(), forKey: prop.name)
} else if let nestedListObject = self[prop.name] as? ListBase {
var objects = [AnyObject]()
for index in 0..<nestedListObject._rlmArray.count {
if let object = nestedListObject._rlmArray[index] as? Object {
objects.append(object.toDictionary())
}
}
mutabledic.setObject(objects, forKey: prop.name)
}
}
}
return mutabledic
}
}
Here is my solution. use unsafeBitCast to avoid cast fail warning.
extension Object {
func toDictionary() -> [String:AnyObject] {
let properties = self.objectSchema.properties.map { $0.name }
var dicProps = [String:AnyObject]()
for (key, value) in self.dictionaryWithValuesForKeys(properties) {
if let value = value as? ListBase {
dicProps[key] = value.toArray()
} else if let value = value as? Object {
dicProps[key] = value.toDictionary()
} else {
dicProps[key] = value
}
}
return dicProps
}
}
extension ListBase {
func toArray() -> [AnyObject] {
var _toArray = [AnyObject]()
for i in 0..<self._rlmArray.count {
let obj = unsafeBitCast(self._rlmArray[i], Object.self)
_toArray.append(obj.toDictionary())
}
return _toArray
}
}

Resources