Request Data from CoreData in Today Extension - ios

i have an app with ios 8, swift and CoreData.
all works fine :)
now i would like to add an today extion - this is my first time to work with today extions. i very proud to say, that i already can set in my app an NSUserdafault value and show it on my today extension :)
but now i would like to go a step forward.
in my app i have a CoreData Entiny LM_ITEMS with an Attribute "Hersteller"
how can i list up all data of LM_ITEMS in my today extension?
Thank you :)
in my app i do it like this way:
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var LM = [LM_ITEMS]()
func DatenAbrufen() {
let fetchRequest = NSFetchRequest(entityName: "LM_ITEMS")
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LM_ITEMS] {
LM = fetchResults
}
}
But this doesnt work in today extension ...

You should better create a core data stack singleton object and include it in both targets, your app's target and extension target.

What #Lucian said is correct and can become very useful if you want to add other targets.
However if you want access the same database of your main app you have to make both the app and the widget use the same App Group in the target capabilities and place your core data database in the shared container.

Related

CoreData + Cloudkit fetch() after app uninstall/reinstall returns no results

I am attempting to configure CoreData+CloudKit using NSPersistentCloudKitContainer to automatically sync data to/from CloudKit.
Following the Apple guide, it was trivial enough to set up an Entity, add the appropriate capabilities in Xcode, set up the persistentContainer and saveContext in my AppDelegate.
I'm making fetch() and save() calls through the NSManagedObjectContext, and I'm able to save and fetch records without issue. I can see them in the CloudKit dashboard.
However, if I uninstall the app from the simulator and rebuild/reinstall, my fetchRequest (which has no NSPredicate or sorting, just fetching all records) is always returning an empty list. I'm using the same iCloud account, and I've tried both the public and private database scope. If I create a new record and then retry my fetch request I can retrieve that newly created record, but never any of the old records. I'm 100% certain these records are still in the CloudKit database, as I can see them on the CloudKit Dashboard web app.
I took a look at Apple's CoreDataCloudKitDemo app, and it is able to fetch "Post" entities from the CloudKit database after an uninstall/reinstall, so I know it is possible. However, it is using an NSFetchedResultsController, which won't work for my application (mine is a SpriteKit game).
I attempted copying my CoreData+Cloudkit code into a brand new Xcode project and I can reproduce this issue there. Here's my code for reference:
import UIKit
import CoreData
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var persistentContainer: NSPersistentContainer = {
// Create a container that can load CloudKit-backed stores
let container = NSPersistentCloudKitContainer(name: "coredatacloudkitexample")
// Enable history tracking and remote notifications
guard let description = container.persistentStoreDescriptions.first else {
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.cloudKitContainerOptions?.databaseScope = .public
container.loadPersistentStores(completionHandler: { (_, error) in
guard let error = error as NSError? else { return }
fatalError("###\(#function): Failed to load persistent stores:\(error)")
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = "nibbler"
// Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
return container
}()
}
// ------
import UIKit
import CoreData
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let viewContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
let people: [Person]
do {
people = try viewContext.fetch(fetchRequest)
print("---> fetched People from CoreData: \(people)")
// people.isEmpty is ALWAYS true (empty array) on first install of app, even if records exist in table in CloudKit
if people.isEmpty {
let person = Person(context: viewContext)
person.name = "nibbler"
// save the data through managed object context
do {
try viewContext.save()
print("--> created Person in CoreData: \(person)")
} catch {
print("---> failed to save Person: \(error.localizedDescription)")
}
}
} catch {
print("---> error: \(error)")
}
}
}
What am I missing? Why can I only fetch the records created during this app's install and not prior ones?
UPDATE: It seems that if I wait for a few seconds and re-try my fetch on the first app install that I am able to retrieve the results from the CloudKit database. I can also see a vast number of CoreData+CloudKit log messages in the console upon first launch. Here's what I'm thinking -- even when using NSPersistentCloudKitContainer, a fetch() is reading/writing to the local CoreData store, and then a separate process is running in the background to mirror and merge the local CoreData records with the CloudKit records.
As such, I believe I need to somehow wait/be notified that this sync/merge of local CoreData and CloudKit records has completed on first launch before making my fetch() call, rather than making the fetch() call immediately as the app opens. Any ideas?
You need to use NSFetchedResultsController, why do you think it wouldn't work for your application?
The reason it is necessary is NSFetchedResultsController monitors the viewContext and when the sync process downloads new objects and inserts them into a background context the automaticallyMergesChangesFromParent merges the objects in to the viewContext and advances the generation token. The FRC's delegate methods are called to notify you if objects are inserted, updated or deleted from the fetched objects array which are objects in the context that match the fetch request's entity and predicate.
Here is the thing #professormeowingtons you mention whenever you delete the app on simulator you it won't show the previous records, so my suggestion is to try your app on a real device with an iCloud account already configured, that way you'll be able to add some records to your db then delete the app, reinstall and fetch all the previous records you did enter.
What you can try is this:
Set NSPersistentCloudKitContainerOptions
let id = "iCloud.yourid"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: id)
description?.cloudKitContainerOptions = options
Initialize your CloudKit schema (this is required at least once)
do {
try container.initializeCloudKitSchema()
} catch {
print("ERROR \(error)")
}
Edit:
Can you change lazy var persistentContainer: NSPersistentContainer
to lazy var persistentContainer: NSPersistentCloudKitContainer

Synchronizing Apple Watch and iPhone using Swift 3 and Realm

I need to Display and modify my data structure from both Apple Watch and iPhone.
The Database:
I am currently using a simple Realm Structure where I have an Object A and an Object B which can hold lots of A's.
So on iPhone the user can create a B and add A's and view of course all A's and B's.
I want the Apple watch to show all A's of the current B and give the users the chance to add new A's to their current B.
The way I have tried to do it:
I wanted to move the hole Realm file from iPhone to the watch or the other way. (That was a tip from the Internet)
iPhone Code:
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() { //makes sure it's not an iPad or iPod
let watchSession = WCSession.default()
watchSession.delegate = self
watchSession.activate()
transferRealmFile()
if watchSession.isWatchAppInstalled {
do {
try watchSession.updateApplicationContext(["foo": "bar"])
} catch let error as NSError {
print(error.description)
}
}
}
}
func transferRealmFile(){
if let path = Realm.Configuration().fileURL {
WCSession.default().transferFile(path, metadata: nil)
}
}
WathcKit Extension:
func session(_ session: WCSession, didReceive file: WCSessionFile) {
//set the recieved file to default Realm file
var config = Realm.Configuration()
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
let realmURL = documentsDirectory.appendingPathComponent("data.realm")
if FileManager.default.fileExists(atPath: realmURL.path){
try! FileManager.default.removeItem(at: realmURL)
}
try! FileManager.default.copyItem(at: file.fileURL, to: realmURL)
config.fileURL = realmURL
Realm.Configuration.defaultConfiguration = config
}
Then I call transferRealmFile() every time I write to Realm. This works but I can't solve this Problems:
Problems:
It doesn't work if only watchKit App is started.
Apple Watch to iPhone doesn't work the same way. (I think I need to change the didRecived code, but I don't know what)
Question:
Do you know who to solve this 2 Problems or do you maybe know a better way to handle the situation or will the way we interact between iPhone an Watch change in WathcOS 3?
With watchOS1 it was possible to use AppGroups to share resources (even your Realm database) between an iOS app and its Watch extension. However, Apple removed this in watchOS 2, so now the only way to share data between your iOS and watchOS apps is via WatchConnectivity. Have a look at this answer.
Sadly the WatchConnectivity framework requires the WCSession to be active on both devices for transferring data, so you can't really get around problem 1.
In my opinion it is a better solution to only communicate the changes between the two apps and not send the whole Realm file, since your Realm file can get quite big and hence sending it forward and backward can take a lot of time and resources, while just sending the changes should be way faster.

Pass data to Call Directory Extension

I cannot pass data call directory extension from main app
I've created one App group and turned it on in App capabilities for both targets (main app and extension)
I pass data with NSUser
-Create Data in main app
var userDefaults = UserDefaults(suiteName: "group.test.callapp")
userDefaults?.set("mynumber", forKey: "mykey")
-Retrieve Data in extension
var baseDescription = "test"
let newUserDefaults = UserDefaults(suiteName: "group.test.callapp")
if let testUserId = newUserDefaults?.object(forKey: "mykey") as? String
{
baseDescription = testUserId
}
When i do it in only main app - everything is ok, but when i do it in extension (i do it in 'addIdentificationPhoneNumbers' function) - it doesn't work, baseDescripton doesn't change
Your solutions shoud work, the only problem I can see is that you don't have App Groups enabled (with your "group.test.callapp" group) both for your application and the extension. Check Target->Capabilities if this is enabled for both. Another caveat is to pass the phone number in the international format (with country code).

how to keep track of reminder which is saved in iphone or ipad?

I am working on this application that will save reminder. but i do not know how to keep track of saved reminder.
In the Case of event we have eventIdentifier but for Reminder i am getting nothing like that.
I am using realm to save data and using REST API.while fetching data from server i am creating objects and at that time i am also creating realm objects and creating reminder. but i don't know how to check that reminder is added for that object or not.
Two examples. One for RealmObject, one for ReminderObject:
import Foundation
class RealmObject: NSObject {}
class ReminderObject: NSObject {}
let realmObject = RealmObject()
let reminderObject = ReminderObject()
// To save objects...
UserDefaults.standard.set(realmObject, forKey: "realmObject")
UserDefaults.standard.set(reminderObject, forKey: "reminderObject")
// To retrieve objects...
let retrievedRealm = UserDefaults.standard.object(forKey: "realmObject") as? RealmObject
let retrievedReminder = UserDefaults.standard.object(forKey: "reminderObject") as? ReminderObject

How to detect First launch of app on every new version upgrade?

I have a requirement of detecting the first launch of app after the user upgrades the app to a newer version. I need to perform certain task only on first launch of app after the user upgrades the app to a new version. Many links available online but none answer clearly to my query. How to achieve this in Swift 2 , iOS 9.
Most of the answers available says to maintain a key in NSUserDefaults and set its value to false and after first launch make it true. But the problem is after I upgrade my app the variable still will be true and thus my scenario fails on app upgrade. Any help would be much appreciated. Thanks!
Try this:
let existingVersion = NSUserDefaults.standardUserDefaults().objectForKey("CurrentVersionNumber") as? String
let appVersionNumber = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as! String
if existingVersion != appVersionNumber {
NSUserDefaults.standardUserDefaults().setObject(appVersionNumber, forKey: "CurrentVersionNumber")
NSUserDefaults.standardUserDefaults().synchronize()
//You can handle your code here
}
updating Yogesh's perfect, yet simple solution to swift 4
let existingVersion = UserDefaults.standard.object(forKey: "CurrentVersionNumber") as? String
let appVersionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
if existingVersion != appVersionNumber {
print("existingVersion = \(String(describing: existingVersion))")
UserDefaults.standard.set(appVersionNumber, forKey: "CurrentVersionNumber")
// run code here.
}

Resources