currently I'm working on an update for an already existing App (migration to Swift 3). I'm having targets for Today-, Search-, Message- and Watch Extensions. Every target needs to access the Core Data Model of my App, so I created an AppGroup and enabled the Capability for every target. Although I've subclassed the NSPersistentStoreCoordinator, so everything is stored in a shared folder:
import CoreData
class PFPersistentContainer: NSPersistentContainer {
override open class func defaultDirectoryURL() -> URL {
if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "add-groupname-here") {
return url
}
// Fallback
return super.defaultDirectoryURL()
}
}
This class-file, as well as my Core-Data-Model and the following class are all in target membership of all of the mentioned targets. The Codegen of the Entities is set to Class Definition
. So far I'm using the default implementation for the Core Data Stack:
class DataManager {
/**
* Singleton Implementation
*/
internal static let sharedInstance:DataManager = {
let instance = DataManager()
return instance
}()
// MARK: - Core Data stack
lazy var persistentContainer: PFPersistentContainer = {
let container = PFPersistentContainer(name: "Data")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
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 {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Now the weird part: Accessing this data from the Today- and Message-Extension seems to work gracefully (I'm assuming the Search-Extension is working too, but so far as a developer I'm not able to test this). Trying to fetch it with the same Request from the Watch App's Extension results in an empty Array. Here is my fetch code:
internal func getLimitedPOIsWithCalculatedDistance(andCurrentLocation currentLocation:CLLocation) -> Array<POI> {
var poiArray:Array< POI > = []
guard let context = self.backgroundContext else { return [] }
do {
// Initialize Fetch Request
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "POI")
// Create Entity Description
let entityDescription = NSEntityDescription.entity(forEntityName: "POI", in: context)
// Configure Fetch Request
request.entity = entityDescription
// Configure Predicate
let predicate = NSPredicate(format: "(disabled == NO) AND (locationDistanceCalculated <= \(SETTINGS_MAXIMUM_FETCH_DISTANCE))")
request.predicate = predicate
if let result = try context.fetch(request) as? Array<POI> {
for poi in result {
let poiLocation = CLLocation(latitude: poi.locationLatitude, longitude: poi.locationLongitude)
let distance = currentLocation.distance(from: poiLocation)
poi.locationDistanceTransient = Double(distance)
}
poiArray = result.filter({ (poi) -> Bool in
return poi.locationDistanceTransient > 0.0
})
poiArray.sort { (first, second) -> Bool in
return first.locationDistanceTransient < second.locationDistanceTransient
}
}
} catch {
print("Error in WatchDataInterface.getLimitedPOIsWithCalculatedDistance(): \(error.localizedDescription)")
}
return poiArray
}
A bit more context for better understanding: the POI Entitie contains latitude and longitude of a location. When starting the app, I'm pre-calculating the distance to each point in the database from the current user's position. When the Today-Extension tries to get the nearest x (in my case 8) POIs to the current users position, fetching all 15k POIs and calculating their distance is to much memory wise. So I had to pre-calculate the distance (stored in
locationDistanceCalculated
), then fetch in a given radius (the staticSETTINGS_MAXIMUM_FETCH_DISTANCE
) and calculate a precise distance during the fetch process (stored into a transient propertylocationDistanceTransient
).
In the Watch App's ExtensionDelegate
Class a CLLocationManagerDelegate
is implemented and the code is called, when the User's Location is updated:
extension ExtensionDelegate: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
OperationQueue.main.addOperation{
if let watchDataManager = self.watchDataManager {
// getNearestPOIs() calls the getLimitedPOIsWithCalculatedDistance() function and returns only a numberOfPOIs
self.nearbyPOIs = watchDataManager.getNearestPOIs(numberOfPOIs: 4)
}
}
}
}
It was tested in Simulator and on a Device. The context.fetch
always returns an empty array (Yes, core data contains values and yes I've tested it without the predicate). Am I missing anything new in Core Data, which I haven't considered yet or are their any limitations in WatchOS3 why this isn't working out? Does anyone has a clue? Thanks for your help.
Update: when using a Watch Framework target to access Core Data, like it's described in this project, the fetch keeps being empty. Maybe this could be the right path, but a Watch Framework is the wrong selection. Will keep you up to date.
Update 2: I've already checked the App Programming Guide for WatchOS and the transferFile:metadata:
function in the API References, but it doesn't seem to be a suitable way to send these large amounts of data to the AppleWatch. I just can't rely on the circumstance, that the user is checking the app more often than he is traveling out of the SETTINGS_MAXIMUM_FETCH_DISTANCE
radius and for locations with a high density of POIs this data is even more extensive.
Update 3: I've reimplemented the nearby Feature for POIs to fetch only in a given radius. If this solves a problem for you, check out this public Gist.
Since Watch OS3, you cannot access a CoreData
base using App Group trick ( because the watch is supposed to work without a phone.
So you have to use WatchConnectivity
to fetch your data