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

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

Related

Implementing Sharing with NSPersistentCloudKitContainer in SwiftUI

Is it possible?
I've got a new iOS 14 app set up with Core Data and Cloudkit. I had to make a few changes to my Persistence.swift file to get it working but it's working without a hitch.
I'm interested in implementing sharing with other iCloud Users, but a lot of the documentation is out of date and confusing and it seems like it might not have been possible at one point but it is now?
I think the first step is to make my database shared? I'm adding the following line to my Persistence.swift file
container.persistentStoreDescriptions.first!.cloudKitContainerOptions?.databaseScope = .shared
(Here's the whole thing):
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Shopmatic")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.persistentStoreDescriptions.first!.cloudKitContainerOptions?.databaseScope = .shared
}
}
but when I run the app I get the following error
Thread 1: "CKDatabaseScopeShared is not supported with NSPersistentCloudKitContainer"
Which is not encouraging, but maybe it's possible to implement sharing with a public database scope?
I'm not really sure what the next steps are? Implementing my own NSPersistentContainer that both syncs to cloudkit and allows sharing?
I believe the answer is no based on this thread on the Apple dev forums (I'm MasonAndMuse there), but there's one Apple engineer in there who was saying yes but wouldn't elaborate. I stopped trying soon after, not finding any way around the opaque nature of the system as it is now. Nothing I've seen indicates someone else has gotten it working but I haven't looked very closely after giving up 8 months ago. Of course there might be a way - I'd love to be proven wrong!
I assume they introduce sharing in iOS 15, but that's not very helpful now... Too bad because it's so close now and it will save SO much time if/when they add it in...
The Apple engineer's answer in the forum reads quite clearly to me:
NSPersistantCloudKitContainer does not support sharing!
You need to implement CloudKit sharing through CK APIs.
I have not implemented such a feature yet, but I would approach it like this:
Get the NSPersistantCloudKitContainer implementation working.
Implement CloudKit sharing on the same container.
Start passing specific records from NSPersistantCloudKitContainer into the sharing feature implemented in step 2.
To access the shared Cloudkit database with CKDatabase.Scope = shared:
If this is the NSPersistentCloudKitContainer
:
container = NSPersistentCloudKitContainer(name: "AppName")
I would try to access the shared DB of this container like so
:
let myAppsCloudKitContainer = CKContainer(identifier: "iCloud.com.name.AppName")
let myContainersSharedDatabase = myAppsCloudKitContainer.database(with: .shared)
To access data from NSPersistantCloudKitContainer for use in the CloudKit sharing feature use NSPersistantCloudKitContainer instance method
record(for managedObjectID: NSManagedObjectID)
(as of macOS 11 / Xcode 12)

Is there a way to use CoreML models in capacitor-plugins?

I am currently working on a capacitor plugin, that should allow me to run a CoreML-model on the ios-version of my Ionic-App.
Even though I used the common terminology to access the model-file, the model is somehow not found in my ios-plugin-script. Is there a different way I can access the model besides VNCoreMLModel or is there maybe in general a problem with using CoreML models in capacitor plugins?
I also tried to load the model, using the same lines of code in a full/native swift app, what worked fine.
The model is already located in the Plugins' Directory (together with the files Plugin.swift, Plugin.m and so on...) and is accessed via calling it as //VNCoreMLModel(for: "modelname".model).
The error message in particular is : "Cannot find 'Resnet50' in scope"
code snippet:
guard let model = try? VNCoreMLModel(for: Resnet50().model) else {return}
(I personally think, that when integrating the plugin into my app, the model file is maybe not transferred into the 'Development Pods' for any reason.)
I don't know what capacitor is, but Resnet50 is a class that is automatically generated by Xcode. You either need to copy the source code for that class into your own project, or not use that class and instantiate an MLModel object for your model instead.

How to stop tokbox screen sharing in SWIFT

I am working on an IOS application(SWIFT) in which i have used tokbox for screensharing, i am able to share the screen but not able to stop screensharing.
This is the code I have used for screensharing.
publisher?.videoType = .screen
publisher?.audioFallbackEnabled = false
let cap = ScreenCapturer(withView:view)
publisher?.videoCapture = cap
session?.publish(publisher, error: &error)
Can anyone guide to stop screensharing in swift.
To stop screen sharing will need to stop the publisher from streaming. To do that you can call:
[OTSession unpublish:error:]
More info is available on the Video API guides
For your case, where you are adding screen sharing to an existing call, you will need to create an additional publisher for the screen sharing rather than editing the existing one. To use the existing publisher it will require the publisher to be reinitialised to switch between publishing a camera feed vs a screen which will stop publishing audio too.
In addition to creating a new publisher, you need to create a new subscriber for the other user, you can do that in the subscriberDidConnect delegate function on the OTSubscriberDelegate.
Additionally, you will need to handle the destruction of both the new publisher and subscriber. This will be done in the delegate functions are you using already on the OTSessionDelegate and OTPublisherDelegate.
I have created a demo app which demonstrates this behaviour.

How to stop ScreenSharing manually with tokbox sdk in Swift [duplicate]

I am working on an IOS application(SWIFT) in which i have used tokbox for screensharing, i am able to share the screen but not able to stop screensharing.
This is the code I have used for screensharing.
publisher?.videoType = .screen
publisher?.audioFallbackEnabled = false
let cap = ScreenCapturer(withView:view)
publisher?.videoCapture = cap
session?.publish(publisher, error: &error)
Can anyone guide to stop screensharing in swift.
To stop screen sharing will need to stop the publisher from streaming. To do that you can call:
[OTSession unpublish:error:]
More info is available on the Video API guides
For your case, where you are adding screen sharing to an existing call, you will need to create an additional publisher for the screen sharing rather than editing the existing one. To use the existing publisher it will require the publisher to be reinitialised to switch between publishing a camera feed vs a screen which will stop publishing audio too.
In addition to creating a new publisher, you need to create a new subscriber for the other user, you can do that in the subscriberDidConnect delegate function on the OTSubscriberDelegate.
Additionally, you will need to handle the destruction of both the new publisher and subscriber. This will be done in the delegate functions are you using already on the OTSessionDelegate and OTPublisherDelegate.
I have created a demo app which demonstrates this behaviour.

New parse framework doesn't have PFFacebookUtils?

Guys I am in desperate need of help!
All I want to do is have a login with Facebook and have that data uploaded to Parse. But the issue I am having is, when I use the old Parse framework, PFFacebookUtils works, but doesn't upload anything to Heroku via Parse. And when I use the new Parse framework, it allows me to upload data to Heroku, but it doesn't let me use PFFacebookUtils. Can anyone please shed some light on this?!
Here is the code that works with the old framework and lets me use PFFacebookUtils, but doesn't upload anything to Heroku.
Parse.setApplicationId("My App ID",
clientKey: "My Client Key")
Here is the code that works with the new Parse framework and lets me upload to Heroku, but with this new framework PFFacebookUtils no longer works.
let parseConfiguration = ParseClientConfiguration(block: { (ParseMutableClientConfiguration) -> Void in
ParseMutableClientConfiguration.applicationId = "My App ID"
ParseMutableClientConfiguration.clientKey = "My Client Key"
ParseMutableClientConfiguration.server = "My Client URL"
})
Parse.initializeWithConfiguration(parseConfiguration)
I could honestly use any help provided, I've been running around in circles trying to find a solution. It seems like if I want to use Facebook login via PFFacebookUtils I have to use the old Parse framework but that doesn't upload to Heroku anymore.
If there is a different way to use Facebook login without PFFacebookUtils then I'd be happy to use that because then I can use this new Parse framework that actually uploads to Heroku.
Many thanks for any help!
I don't know how you're doing your package management, but you need to include other Frameworks as well. For instance, if using cocoapods, these are the frameworks you would want to include
pod 'FBSDKCoreKit’
pod ‘FBSDKLoginKit’
pod ‘FBSDKShareKit’
pod 'ParseFacebookUtilsV4’
I figured it out. I deleted all the old frameworks and found a ParseFacebookUtils framework that works. After adding the new framework, I just had to:
import ParseFacebookUtils