NSUserdefaults from TVTopShelfProvider conformed class: get nil - nsuserdefaults

I'm trying to get UserDefaults from ServiceProvider - the Top Shelf extension class and get nil.
class ServiceProvider: NSObject, TVTopShelfProvider {
....
var topShelfItems: [TVContentItem] {
....
item!.title = String(NSUserDefaults.standardUserDefaults().objectForKey("title"))
At the same time when AppDelegate launches i can get the value.
Is there any other way to save user-related data and then get this data from ServiceProvider?

In iOS and tvOS extensions run in their own process/sandbox and do not inherently have access to the data of the application or other extensions. The solution to this is to setup an app group for the extension and the app, which allows you to access a shared container in the file system on the device (including a shared NSUserDefaults instance). You can read more about it in the Sharing Data with Your Containing App part of the Handling Common Scenarios chapter in the App Extension Programming Guide.

Related

Xcode Project with shared CoreData for iOS & MacOS Targets

A few months ago I started to work on a MacOS Application which required CoreData implementation. Today I am beginning to work on a related iOS application that is based on the same Api, and though relies on the same model. I added my iOS target on my project and I mutualised some classes (by adding them to both targets), including the CoreData Stack:
I added my app.xcdatamodeld on both targets
I added my Object+CoreDataClass.swift & Object+CoreDataProperties.swift on both targets
I modified my ManagedObjectsController to support both iOS and MacOS implementation
by defining the appDelegate for both iOS and OSX, I can access it the same way to get my context let context = appDelegate.persistentContainer.viewContext
It works fine but I was wondering if I am doing this right. Is this the correct way to mutualise access to appDelegate instances between two targets?
Should I use some kind of Protocole & Generic Typing?
Or should I simply build a ManagedObjectController for each target?
Thanks
Declaring a protocol helps if you have multiple classes which you want to both support common functions. But in this case, UIApplication and NSApplication already support the common functions you need! The problem is that you need access two different global symbols.
One alternative worth considering is: Instead of declaring two classes IosAppDelegate and MacAppDelegate, declare a single class AppDelegate, and move that dirty #if code out of your ManagedObjectsController class and into AppDelegate. Then, this AppDelegate could be used wherever you need a reference to the shared app delegate. This is more than a few places in most projects.
But if you want to get your product out the door asap, and this ManagedObjectsController is the only place you need the shared app delegate, your code is fine.

Inter-App Data Migration (migrating data to new app version)

I currently have a Swift iOS app on Apple's App Store. I have many users and I would like to make a new version and help current users migrate to the new version. FYI: the new version is an Ionic app.
Data-wise, my app is using Core Data without any iCloud or sync support. It contains JSON data and also multiple images. So I'd need to bundle the current data and find a way of bringing it to the new ionic app version.
Basically my question is: Is there a way of writing in the app's documents directory and let the new version grab that file to import its data? Is there a way of letting both apps transmit data other than AirDrop or Custom URLs?
I don't want to upload the data remotely, I'd like to do this all locally on the device and also seamlessly so the user don't have to manually do anything.
Suggestions are welcome, thanks!
I would suggest using App Groups to get a shared container. I’m not familiar with Ionic, but this is quite straightforward in native Swift. It allows multiple apps or extensions to access a shared container of data, like the image below:
(Image from https://agostini.tech/2017/08/13/sharing-data-between-applications-and-extensions-using-app-groups/ )
This would require an update to the existing app to copy data to the shared container and then users would have to install the new app while the old one was still installed, because the shared container will be deleted when there are no installed apps using it.
It can be set up like this:
1: Enable App Groups in your project's Capabilities tab (for both apps).
2: Add a new app group and name it something like "group.appDomain.appName" or similar.
3: Now that the App Group is set up, it’s shared container can be used in several ways (User Defaults, NSCoding or Core Data).
For shared User Defaults:
let defaults = UserDefaults.init(suiteName: "group.appDomain.appName")
defaults.set("Example", forKey: "exampleKey")   
defaults.synchronize()
More info from Apple here.
For NSCoding:
let sharedContainerDirectory: URL = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.appDomain.appName")!
let sharedArchiveURL: URL = sharedContainerDirectory.appendingPathComponent("whateverYouNeed")
NSKeyedArchiver.archiveRootObject(yourObject, toFile: sharedArchiveURL.path)
For Core Data:
You can set up the container as below. I have taken this code from this answer as I have not actually tried this with Core Data myself.
You use containerURL(forSecurityApplicationGroupIdentifier: "group.appDomain.appName")! to make this work in shared container.
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: "xx")
let appName: String = "xx"
var persistentStoreDescriptions: NSPersistentStoreDescription
let storeUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.appDomain.appName")!.appendingPathComponent("xx.sqlite")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
description.url = storeUrl
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.xxx.xx.container")!.appendingPathComponent("xx.sqlite"))]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
This answer provides a way to migrate the persistent store.
As I mentioned, I am not familiar with Ionic, so I’m not sure how working in that context might change this technique.
I hope this is helpful.
I would have just left a comment but I am unable to do so.
I was able to see that after loading a native iOS application then a Ionic project with the same bundle structure that the data within Library/Application Support/DisplayName.sqlite was still there and the data within the database still intact. (Note: this is deploying using Xcode not through the App Store)
You can see this using Xcode -> Window -> Devices and Simulators -> Devices tab -> Click on your app -> settings cog -> Download container -> after saving Show package contents
I was unable to use the Ionic SQLite native plugin to open the database for some reason. That is as far as I could get. I think it might have something to do with the space of the Application Support folder.
You can do the transition without using AirDrop or Custom Url. The idea is based on how ionic works. Much of the ionic functionality depends upon the plugins developed by community like working with hardware features.
Dealing with device specific features are done in native codes then JS wrapper classes are created for making a bridge between your code and native code.
I would suggest you to write native code which will access the data and files from CoreData and then use the cordova plugin tech to setup communication between the native code and the ionic code. here is a good post on Creating Custom Plugin for ionic and a sample github project

Can't decode Swift object in framework

I had a perfectly working app. I wanted to modularize my app because I envision needing bits and pieces of it in other apps. So, I created two frameworks. The two frameworks build fine and my app with the two frameworks embedded in it also builds fine.
My problem comes when I try to unarchive data which has a class that is now in one of my frameworks. I get this error:
reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (myProjecy.MyObject) for key (NS.objects); the class may be defined in source code or a library that is not linked'
In my app I can create instances MyObject fine. All the required methods in the framework are either open or public (or the app wouldn't even build).
What an I missing?
The class in the framework is in Swift and the class trying to unarchive it in is Obj-C. I'm using Xcode 9.
Thanks.
OK. I found the problem. Re-visiting this post led me to a solution.
In my case, because the original object was archived while still part of the app and my app specified to use module name then the object was archived with the app's module name. Now, the unarchiver is trying to use the module name in the framework which is what leads to the problem.
So, I basically had to sprinkle a few of these:
NSKeyedUnarchiver.setClass(ClassName.self, forClassName: "AppModule.ClassName")
NSKeyedArchiver.setClassName("AppModule.ClassName", for: ClassName.self)
And everything works fine!
Without this code the the unarchiver tries to use "FrameworkModule.ClassName".

Swift XCTest UI tests with Realm

I'm developing my UI test with Swift 3.0 using Realm 2.4.2.
UI test target
create realm object Cat with pk "cat_01"
check if exists a Cat object with pk "cat_01" -> success
open view controller that contains a list of cats fetched from realm -> empty list
Application
CatsListViewController: a table view with cats (no Cat object are found)
How can I "see" both on test target and application same realm objects?
What I do not want to do is using the launchArguments workaround delegating app to create objects.
You cannot "see" the same Realm objects in your UITest target and your Application because those two are running as completely separated processes.
From Apple's docs:
UI testing exercises your app's UI in the same way that users do
without access to your app's internal methods, functions, and
variables. ... Your test code runs as a separate process, synthesizing
events that UI in your app responds to.
In other words: Your UITests are running in a separate App that interacts with your main App (when you run a UITest you can see the Testrunner App being launched and closed before your main App is being launched). Those two apps can not share objects.
I see two directions that you could go:
1. Create the cat objects via your app's UI
Somewhere you probably have a "Add cat" button. Press that in your UITest, add a cat like a use would and then assert that the cat has been added to the list. That is what UITests are for. Using the app like a user would and testing the results of a user's interaction with the app.
2. Use UnitTests:
If you want to test that a created Realm cat object is populating a list a UnitTest might be the better way. During a UnitTest you have full access to your app's code. So you can create a cat object in your test code and the app will "see" it.

Classes that mimic the behavior of iPhone Application Settings...but within an app?

Has anyone written iPhone classes that mimic the behavior of the Application Settings? It would be nice to be able to define settings tables for use within my app using exactly the same XML structure, etc.
See Is there a library or framework for setting preferences from within an iPhone application?
Esp. the mySettings library mentioned there:
mySettings [...] uses a plist
configuration file like the one used
by the settings app, with some added
options.
[...]
By default the settings themselves are
stored in the standard user defaults
object ([NSUserDefaults
standardUserDefaults]), but you can
use any object that supports key-value
coding. This enables you to use your
model classes directly in the settings
view.
The application settings (or NSUserDefaults) is essentially a glorified NSDictionary. You add objects to the settings and associate them with keys, so you can retrieve them later on.
If you want to do this, just create a class which wraps a singleton instance of a NSDictionary. That way you could reference it throughout your app like:
[[MyAppSettings sharedInstance] objectForKey:key];