CloudKit Public Records And Changes not Downloaded - ios

CloudKit Public Records And Changes not Downloaded
I have a CloudKit app with records for both a Public and a Custom Private
Zone. I seem to have the change token process working for the custom
private zone but am unable to get the public data to work. The code I am
using is identical for both databases except for the public/private
names and using the default zone for the public. I understand that
subscriptions do not work on default zones, but I could not find any
references to limitations on change tokens for public data. Xcode 10.1, iOS 12.0
I create my PubicData class and initialize it:
var publicDatabase : CKDatabase!
init() {
let kAppDelegate = UIApplication.shared.delegate as! AppDelegate
context = kAppDelegate.context
let container = CKContainer.default()
publicDatabase = container.publicCloudDatabase
}//init
the download function that is called from the app entry scene - a tableview:
func downloadPublicUpdates(finishClosure : # escaping(UIBackgroundFetchResult) -> Void) {
var listRecordsUpdated : [CKRecord] = []
var listRecordsDeleted : [String : String] = [:]
var publicChangeToken : CKServerChangeToken!
var publicChangeZoneToken : CKServerChangeToken!
let userSettings = UserDefaults.standard
if let data = userSettings.value(forKey: "publicChangeToken") as? Data {
if let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass : CKServerChangeToken.self, from : data) {
publicChangeToken = token
print("publicChangeToken exists")
}
} else {
print("userSettings entry for publicChangeToken does not exist")
}//if let data
if let data = userSettings.value(forKey: "publicChangeZoneToken") as? Data {
if let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, from: data) {
publicChangeZoneToken = token
}
}//if let data
let zone = CKRecordZone.default()
var zonesIDs : [CKRecordZone.ID] = [zone.zoneID]
let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: publicChangeToken)
operation.recordZoneWithIDChangedBlock = {(zoneID) in
zonesIDs.append(zoneID)
}
operation.changeTokenUpdatedBlock = {(token) in
publicChangeToken = token
}
operation.fetchDatabaseChangesCompletionBlock = {(token, more, error) in
if error != nil{
finishClosure(UIBackgroundFetchResult.failed)
} else if !zonesIDs.isEmpty {
publicChangeToken = token
let configuration = CKFetchRecordZoneChangesOperation.ZoneConfiguration()
configuration.previousServerChangeToken = publicChangeZoneToken
let fetchOperation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zonesIDs, configurationsByRecordZoneID: [zonesIDs[0] : configuration])
fetchOperation.recordChangedBlock = {(record) in
listRecordsUpdated.append(record)
}//fetchOperation.recordChangedBlock
fetchOperation.recordWithIDWasDeletedBlock = {(recordID, recordType) in
listRecordsDeleted[recordID.recordName] = recordType
}//fetchOperation.recordWithIDWasDeletedBlock
fetchOperation.recordZoneChangeTokensUpdatedBlock = {(zoneID, token, data) in
publicChangeZoneToken = token
}//fetchOperation.recordZoneChangeTokensUpdatedBlock
fetchOperation.recordZoneFetchCompletionBlock = {(zoneID, token, data, more, error) in
if let ckerror = error as? CKError {
self.processErrors(error: ckerror)
} else {
publicChangeZoneToken = token
self.updateLocalRecords(listRecordsUpdated : listRecordsUpdated)
self.deleteLocalRecords(listRecordsDeleted : listRecordsDeleted)
listRecordsUpdated.removeAll()
listRecordsDeleted.removeAll()
}//if else
}//fetchOperation.recordZoneFetchCompletionBlock
fetchOperation.fetchRecordZoneChangesCompletionBlock = {(error) in
if error != nil {
print("Error fetchRecordZoneChangesCompletionBlock")
finishClosure(UIBackgroundFetchResult.failed)
} else {
if publicChangeToken != nil {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: publicChangeToken, requiringSecureCoding: false) {
userSettings.set(data, forKey : "publicChangeToken")
}
}//if changeToken != nil
if publicChangeZoneToken != nil {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: publicChangeZoneToken, requiringSecureCoding: false) {
userSettings.set(data, forKey : "publicChangeZoneToken")
}
}
//self.updateInterface()
self.updateLocalReferences()
finishClosure(UIBackgroundFetchResult.newData)
}
}//fetchOperation.fetchRecordZoneChangesCompletionBlock
self.publicDatabase.add(fetchOperation)
} else {//else if !zonesIDs.isEmpty
finishClosure(UIBackgroundFetchResult.noData)
}//if zoneid not empty
}//fetchDatabaseChangesCompletionBlock
print("listRecordsUpdated.count is \(listRecordsUpdated.count)")
publicDatabase.add(operation)
}//downloadPublicUpdates
Outside of class: var PD = PDData()
I call the download method in viewDidLoad from the initial TableViewController:
PD.downloadPublicUpdates { (result) in
print("in ctvc viewDidLoad and downloadPublicUpdates")
switch result {
case .noData:
print("no data")
case .newData:
print("new data")
case .failed:
print("failed to get data")
}//switch
}//downloadPublicUpdates
The console output is always:
userSettings entry for publicChangeToken does not exist
listRecordsUpdated.count is 0
in ctvc viewDidLoad and downloadPublicUpdates
failed to get data
Any guidance would be appreciated.

There are no change tokens available in a public database. Those only exist in private and shared databases.
To keep things in sync, you typically have to keep a modification date on records locally, and then query for stuff that is newer on the CloudKit server using a CKQueryOperation.
Good luck!

Related

Share Data between Project and Share Extension

I would like to share data between my Main Project and my Share Extension. This is what I did:
1. enable App Groups in both Project & Share Extension
2. save data in Project inside viewDidLoad (works fine, I tested it):
DataHandler.getWishlists { (success, dataArray, dropOptionsArray) in
if success && dataArray != nil {
self.shouldAnimateCells = true
self.dataSourceArray = dataArray as! [Wishlist]
self.theCollectionView.isHidden = false
self.theCollectionView.reloadData()
self.dropOptions = dropOptionsArray as! [DropDownOption]
self.addButton.isEnabled = true
self.activityIndicator.stopAnimating()
// save dataSourceArray in UserDefaults
if let defaults = UserDefaults(suiteName: UserDefaults.Keys.groupKey) {
defaults.setDataSourceArray(data: dataArray as! [Wishlist])
defaults.synchronize()
} else {
print("error Main")
}
}
}
3. retrive data in Share Extension (error 2 fires!)
if let defaults = UserDefaults(suiteName: UserDefaults.Keys.groupKey) {
if let data = defaults.getDataSourceArray() {
dataSourceArray = data
defaults.synchronize()
}else {
print("error 2")
}
} else {
print("error 1")
}
UserDefaults + Helpers
extension UserDefaults {
public struct Keys {
public static let groupKey = "group.wishlists-app.wishlists"
public static let dataSourceKey = "dataSourceKey"
}
func setDataSourceArray(data: [Wishlist]){
set(try? PropertyListEncoder().encode(data), forKey: Keys.dataSourceKey)
synchronize()
}
func getDataSourceArray() -> [Wishlist]? {
if let data = UserDefaults.standard.value(forKey: Keys.dataSourceKey) as? Data {
if let dataSourceArray = try? PropertyListDecoder().decode(Array<Wishlist>.self, from: data) as [Wishlist] {
return dataSourceArray
}
}
return nil
}
}
I can not retrieve the data inside my Share Extension but I have no idea why. Could anyone help me out here?
Your helper function getDataSourceArray() tries to access UserDefaults.standard which is not shared between your host app and the extension app. You need to use the shared container.
UserDefaults.standard -> not shared between host and extension
UserDefaults(suiteName:) -> shared between host and extension
Try to change your function to this:
func getDataSourceArray() - > [Wishlist] ? {
if let data = UserDefaults(suiteName: UserDefaults.Keys.groupKey).value(forKey: Keys.dataSourceKey) as ? Data {
if let dataSourceArray =
try ? PropertyListDecoder().decode(Array < Wishlist > .self, from: data) as[Wishlist] {
return dataSourceArray
}
}
return nil
}

Core Data NSInvalidArgument Exception

I am following this tutorial https://medium.com/#jamesrochabrun/parsing-json-response-and-save-it-in-coredata-step-by-step-fb58fc6ce16f
But before even reaching the stage of fetching , I ran the app as the tutorial suggests to get the filePath where data is stored
My app crashes and I get the error
'NSInvalidArgumentException', reason: '+entityForName: nil is not a
legal NSManagedObjectContext parameter searching for entity name
'NewsObject''
I looked around and found this iOS: Swift: Core Data: Error: +entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name
and Core Data Reading Data
They both suggest making sure manageObjectContext is not nil. However I don't how to implement that/ make sure it's not nil
I am a complete beginner in CoreData and just started with that tutorial
Here's my Code
private func createNewsEntityFrom(dictionary: [String: Any]) -> NSManagedObject? {
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
if let newsEntity = NSEntityDescription.insertNewObject(forEntityName: "NewsObject", into: context) as? NewsObject {
newsEntity.newsAuthor = dictionary["author"] as? String ?? "default"
newsEntity.newsTitle = dictionary["title"] as? String ?? "default"
let images = dictionary["image"] as? [String: AnyObject]
newsEntity.newsImageURL = images?["link"] as? String ?? "default"
return newsEntity
}
return nil
}
private func saveInCoreDataWith(array: [[String: Any]]) {
for dict in array {
_ = self.createNewsEntityFrom(dictionary: dict)
}
do {
try CoreDataStack.sharedInstance.persistentContainer.viewContext.save()
} catch let error {
print(error)
}
}
let url = "someURL"
Alamofire.request(url, method: .get , headers: headers).responseJSON { response in
switch response.result {
case .success:
let json = response.result.value as! [String:Any]
let data = json["data"] as! [[String : Any]]
self.saveInCoreDataWith(array: data)
self.nextToken = json["nextPageToken"] as? String ?? "empty"
print("Token = "+self.nextToken!)
for dic in data{
self.news.append(News(dictionary: dic))
print(self.news.count)
}
DispatchQueue.main.async {
loadingIndicator.stopAnimating()
self.tableView.reloadData()
}
case .failure: break
}
My CoreDataStack class
import UIKit
import Foundation
import CoreData
class CoreDataStack: NSObject {
static let sharedInstance = CoreDataStack()
private override init() {}
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "TapIn")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = 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)")
}
}
}
}
extension CoreDataStack {
func applicationDocumentsDirectory() {
// The directory the application uses to store the Core Data store file. This code uses a directory named "yo.BlogReaderApp" in the application's documents directory.
if let url = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).last {
print(url.absoluteString)
}
}
}
EDIT : Here's the struct News I am using to hold the data received and perform all sorts of functionality on them
//Model to hold our news
struct News {
var image : String
var title : String
var publisherIcon : String
var publisher : String
var author : String
var time : Int
var id : String
var bookmarked : Bool
var liked : Bool
init(dictionary : [String:Any])
{
let image = dictionary["image"] as? [String:Any]
self.image = image!["link"] as! String
self.title = dictionary["title"] as? String ?? ""
self.publisherIcon = dictionary["shortenedLogo"] as? String ?? ""
self.publisher = dictionary["publisher"] as? String ?? ""
self.author = dictionary["author"] as? String ?? ""
self.time = dictionary["timeToRead"] as? Int ?? 0
self.id = dictionary["_id"] as? String ?? ""
self.bookmarked = dictionary["bookmarked"] as? Bool ?? false
self.liked = dictionary["liked"] as? Bool ?? false
}
}
And in the main VC var news = [News]()
Add this lazy instantiated property in the singleton to get the non-optional context
lazy var managedObjectContext : NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
And use the modern API to insert an object
private func createNewsEntityFrom(dictionary: [String: Any]) -> NewsObject {
let context = CoreDataStack.sharedInstance.managedObjectContext
let newsEntity = NewsObject(context: context)
newsEntity.newsAuthor = dictionary["author"] as? String ?? "default"
newsEntity.newsTitle = dictionary["title"] as? String ?? "default"
let images = dictionary["image"] as? [String: Any]
newsEntity.newsImageURL = images?["link"] as? String ?? "default"
return newsEntity
}
And according to the Naming Guidelines it's highly recommended to name entity and attributes less redundant for example
private func createNews(from dictionary: [String: Any]) -> News {
let context = CoreDataStack.sharedInstance.managedObjectContext
let news = News(context: context)
news.author = dictionary["author"] as? String ?? "default"
news.title = dictionary["title"] as? String ?? "default"
let images = dictionary["image"] as? [String: Any]
news.imageURL = images?["link"] as? String ?? "default"
return news
}
By the way the applicationDocumentsDirectory function is wrong. It must be
func applicationDocumentsDirectory() -> URL {
// The directory the application uses to store the Core Data store file. This code uses a directory named "yo.BlogReaderApp" in the application's documents directory.
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
}
Edit:
To return the inserted array you have to change saveInCoreDataWith to
private func saveInCoreDataWith(array: [[String: Any]]) -> [NewsObject] {
var newsArray = [NewsObject]()
for dict in array {
newsArray.append(self.createNewsEntityFrom(dictionary: dict))
}
do {
try CoreDataStack.sharedInstance.managedObjectContext.save()
return newsArray
} catch let error {
print(error)
}
return []
}
and in the Alamofire closure replace
self.saveInCoreDataWith(array: data)
self.nextToken = json["nextPageToken"] as? String ?? "empty"
print("Token = "+self.nextToken!)
for dic in data{
self.news.append(News(dictionary: dic))
print(self.news.count)
}
with
self.news = self.saveInCoreDataWith(array: data)
self.nextToken = json["nextPageToken"] as? String ?? "empty"
print("Token = "+self.nextToken!)
print(self.news.count)

Error nil in retrieving image from Firebase Storage

I have trouble retrieving a UIImage from Firebase Storage, the child path seems to be correct, though the image does not get "downloaded" to be displayed. The part about the Firebase Database is working fine, hence retrieving data, whereas the Storage one is now. Code and Firebase path below
I cannot understand whether the problem is in the fact that I nested the function into the .observeSingleEvent of the Database retrieving function or not.
gs://xxxyyy-xxxyyy.appspot.com/images/QhRmIcbF7AOWjZ3nrjFd7TOekrA3/FirstImage.jpg
var cells : [Cella] = []
var imageReference: StorageReference {
return Storage.storage().reference().child("images")
}
var databaseReference: DatabaseReference {
return Database.database().reference()
}
func getDataFromFirebase() -> [Cella]{
let queryRef = databaseReference.queryLimited(toLast: 1)
var appCells : [Cella] = []
queryRef.observeSingleEvent(of: .value, with: { (snapshot) in
for snap in snapshot.children {
var userPhoto : UIImage?
let userSnap = snap as! DataSnapshot
let customerUid = userSnap.key
let userDict = userSnap.value as! [String:AnyObject]
let description = userDict["description"] as! String
let title = userDict["title"] as! String
print(title)
print(String(customerUid))
print(description)
self.descriptionsArray[String(customerUid)] = description
self.titlesArray[String(customerUid)] = title
//error is here BECAUSE it can't retrive the image to be dispalyed. Title and description are fine
self.imageReference.child(String(customerUid)).child("FirstImage.jpg").getData(maxSize: 10*1024*1024, completion: { (data, error) in
if error != nil {
print("\(String(describing: error?.localizedDescription))")
}
else {userPhoto = UIImage(data: data!)}
})
let newCella = Cella(image: userPhoto!, title: title, bodyMessage: description)
appCells.append(newCella)
}
})
return appCells
}
------ UPDATE ------
As suggested I changed to using firebase Firestore and saving there the download URL as well as the other information. Still though, I cannot seem to get the image downloading. New code below.
This is the data retrieved by document.data() :
xxx.yyy#gmail.com => ["userID": QhRmIcbF7AOWjZ3nrjFd7TOekrA3, "userDescription": Route66, "userImageUrl": https://firebasestorage.googleapis.com/v0/b/shardana-61183.appspot.com/o/images%2FQhRmIcbF7AOWjZ3nrjFd7TOekrA3%2FFirstImage.jpg?alt=media&token=dea541bf-d598-414e-b4ed-a917541598d5, "userTitle": Sample]
firestoreUsersDatabase.getDocuments { (querySnapshot, error) in
if let error = error {
print("Error getting the documents: \(error)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let data = document.data()
let imageUrl = data["userImageUrl"] as! String
let title = data["userTitle"] as! String
let description = data["userDescription"] as! String
let urlDownloadReference = self.imageReference.reference(forURL: imageUrl)
urlDownloadReference.getData(maxSize: 10*2014*2014, completion: { (data, error) in
if error != nil {
print("An error occurred: \(String(describing: error?.localizedDescription))")
} else {
guard let imageDownloaded = UIImage(data: data!) else {print("Image url returned nil value ERROR"); return}
let newCell = Cella(image: imageDownloaded, title: title , bodyMessage: description )
print("NEW CELL: Image \(newCell.image)")
appCells.append(newCell)
}
})
}
}
}
yes, I think you're logic needs review. You need to store on your Firestore all the users data, including all the references to needed images. On the other hand, Firebase Storage, which is a different service within Firebase will save the images an will give you download links, but it uses a different logic than Firestore.
See the following example for clarification on what I mean:
https://firebase.google.com/docs/storage/web/download-files

Swift: Saving Firebase data to CoreData in async operation

I am attempting to pull data from Firebase and then save it to CoreData but am having trouble with the async operation. I have a custom function that returns [ConversationStruct] upon completion. I then do a forEach to save it to CoreData.
However, my current implementation saves the object multiple times, ie Firebase have 10 entries, but CoreData would somehow give me 40 over entries which most are repeated. I suspect the problem is in my completionHandler.
//At ViewDidLoad of my VC when I pull the conversations from Firebase
FirebaseClient.shared.getConversationsForCoreData(userUID) { (results, error) in
if let error = error {
print(error)
} else if let results = results {
print(results.count)
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
results.forEach({ (c) in
let conversation = Conversation(context: privateContext)
conversation.conversationStartTime = c.conversationStartTime
conversation.recipientID = c.recipientID
conversation.shoutoutID = c.shoutoutID
conversation.unreadMessagesCount = Int32(c.unreadMessagesCount!)
conversation.profileImage = c.profileImage
conversation.recipientUsername = c.recipientUsername
})
do {
try privateContext.save()
} catch let error {
print(error)
}
}
}
//At FirebaseClient
func getConversationsForCoreData(_ userUID: String, _ completionHandler: #escaping (_ conversations: [ConversationStruct]?, _ error: Error?) -> Void) {
var conversations = [ConversationStruct]()
ref.child("conversations").child(userUID).observeSingleEvent(of: .value) { (snapshot) in
for snap in snapshot.children {
let snapDatasnapshot = snap as! DataSnapshot
let snapValues = snapDatasnapshot.value as! [String: AnyObject]
let recipientUID = snapDatasnapshot.key
for (key, value) in snapValues {
//Some other logic
self.getUserInfo(recipientUID, { (results, error) in
if let error = error {
print(error.localizedDescription)
} else if let results = results {
let username = results["username"] as! String
let profileImageUrl = results["profileImageUrl"] as! String
URLClient.shared.getImageData(profileImageUrl, { (data, error) in
if let error = error {
print(error.localizedDescription)
} else if let imageData = data {
let convo = ConversationStruct(conversationStartTime: conversationStartTime, shoutoutID: shoutoutID, recipientID: shoutoutID, unreadMessagesCount: unreadMessagesCount, recipientUsername: username, profileImage: imageData)
conversations.append(convo)
}
completionHandler(conversations, nil)
})
}
})
}
}
}
}
struct ConversationStruct {
var conversationStartTime: Double
var shoutoutID: String
var recipientID: String
var unreadMessagesCount: Int?
var recipientUsername: String?
var profileImage: Data?
}
The print statement would print the count as and when the operation completes. This seems to tell me that privateContext is saving the entities when the results are consistently being downloaded which resulted in 40 over entries. Would anyone be able to point me out in the right direction how to resolve this?
Also, the implementation does not persist.

Loading saved context from AppDelegate to another ViewController

I'm trying to make an array from my Viewcontroller equal to, the objects my core data has saved. I'm using core data and created an entity named Pokemon which has 3 attributes name, id and generation. In the app delegate, I use the following function to get Pokemon from this API. This is what I do to parse the data and save the context:
typealias DownloadCompleted = () -> ()
var pokemonId: Int16 = 0
func fetchPokemon(url: String, completed: #escaping DownloadCompleted) {
let context = coreData.persistentContainer.viewContext
let url = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: url) { (data, repsonse, error) in
if error != nil {
print(error!)
}
do {
let jsonResult = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
let jsonArray = jsonResult.value(forKey: "results") as! [[String: Any]]
for pokemonData in jsonArray {
self.pokemonId += 1
if self.pokemonId > 721 {
self.coreData.saveContext()
return
}
guard let name = pokemonData["name"] as? String else {
return
}
let pokemon = Pokemon(context: context)
pokemon.name = name
pokemon.id = self.pokemonId
print("Name: \(pokemon.name) Id:\(self.pokemonId)")
if self.pokemonId <= 151 {
pokemon.generation = 1
} else if self.pokemonId <= 251 {
pokemon.generation = 2
} else if self.pokemonId <= 386 {
pokemon.generation = 3
} else if self.pokemonId <= 493 {
pokemon.generation = 4
} else if self.pokemonId <= 649 {
pokemon.generation = 5
} else if self.pokemonId <= 721 {
pokemon.generation = 6
}
}
guard let nextURL = jsonResult.value(forKey: "next") as? String else {
self.coreData.saveContext()
return
}
DispatchQueue.main.async {
self.fetchPokemon(url: nextURL, completed: {
self.coreData.saveContext()
})
completed()
}
} catch let err {
print(err.localizedDescription)
}
}
task.resume()
}
This is how I call it in the appDelegate. Really don't know what to do in the middle of the fetchPokemon or how to call it in another view controller. So I left it blank, not sure if this has something to do with the problem I'm having.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let context = self.coreData.persistentContainer.viewContext
let pokemonListVC = self.window?.rootViewController as! PokemonListVC
pokemonListVC.context = context
fetchPokemon(url: pokemonAPI) {
}
return true
}
Im using this SQL-Light read-only app from the app store. I check the data and all 721 pokemon are saving. Now, I don't know how I would be able to make the array in my view controller equal to all 721 Pokemon saved. I added this code into my viewController.
class PokemonListVC: UIViewController {
weak var context: NSManagedObjectContext! {
didSet {
return pokemon = Pokemon(context: context)
}
}
var pokemon: Pokemon? = nil
lazy var pokemons = [Pokemon]()
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
pokemons = pokemon!.loadPokemon(generation: 1, context: context)
}
}
I've created an extension of my Pokemon entity and added a function loadPokemon that filters the Pokemon by generation. Here is the code.
extension Pokemon {
func loadPokemon(generation: Int16 = 0, context: NSManagedObjectContext) -> [Pokemon] {
let request: NSFetchRequest<Pokemon> = Pokemon.fetchRequest()
request.predicate = NSPredicate(format: "generation = %#", generation)
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
do {
let pokemons = try context.fetch(request)
print("My Pokemon count: \(pokemons.count)")
return pokemons
} catch let err {
print(err.localizedDescription)
}
return []
}
}
When I call the loadData in my ViewController it crashes. The array count is 0 and so is the one in the hero extension. So I don't how to make my array equal the Pokemon saved from coreData.
Would really appreciate any help provided. :)
Here is my deleteRecords code, which is also in my appDelegate. This deletes all records when app launches. I call this method at the very beginning of didFinishLaunchingWithOption function before the fetchPokemons.
func deleteRecords() {
let context = coreData.persistentContainer.viewContext
let pokemonRequest: NSFetchRequest<Pokemon> = Pokemon.fetchRequest()
var deleteRequest: NSBatchDeleteRequest
var deleteResults: NSPersistentStoreResult
do {
deleteRequest = NSBatchDeleteRequest(fetchRequest: pokemonRequest as! NSFetchRequest<NSFetchRequestResult>)
deleteResults = try context.execute(deleteRequest)
} catch let err {
print(err.localizedDescription)
}
}
As you are saying that you have sure that all the pockemon records are stored correctly in your coredata you can simply fetch records from your codedata by providing fetch request. I have created demo for contact storing and I can get all the contact by this fetch request you can try this code in your ViewController where you want to fetch all the record.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject> (entityName: "Pokemon")
do {
arrPockemon = try managedContext.fetch(fetchRequest)
}catch let error as NSError {
showAlert(string: error.localizedDescription)
}
try to get all records first and if you get all then work for filtering extension and all. hope it will help you. you can learn from here https://code.tutsplus.com/tutorials/core-data-and-swift-core-data-stack--cms-25065
save flag on userDefault.
//check for first time when app is installed first time(first time flag is not present so)
let userDefault = UserDefaults.standard.dictionaryRepresentation()
if userDefault.keys.contains("isDataAvailable") {
//key is availebe so check it
if userDefault["isDataAvailable"] as! String == "1"{
//no need to call server for data
}else{
//fetch data from server
// once you get data from server make isDataAvailable flage as 1
UserDefaults.standard.setValue("1", forKey: "isDataAvailable")
UserDefaults.standard.synchronize()
}
}
else{
//flag is not avalable so call server for data
// once you get data from server make isDataAvailable flage as 1
UserDefaults.standard.setValue("1", forKey: "isDataAvailable")
UserDefaults.standard.synchronize()
}

Resources