ObjectBox Database sharing between main app and its extension - ios

I was trying to share the Objectbox created database between the app and its extension.
I used the below code to share the database between the app and its extension.
static func storeURL() -> URL {
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "AppGroupName") else {
fatalError("Shared file container could not be created.")
}
return fileContainer.appendingPathComponent("\(databaseName)")
}
The app is working fine when the app is running in foreground. When the app goes to the background then the app is crashing. When I remove the above code and used the below code
let appSupport = try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
.appendingPathComponent(Bundle.main.bundleIdentifier!)
let directory = appSupport.appendingPathComponent(databaseName)
The app does not crash and started working fine without Database sharing.
Note: If we ignore this crash, I was able to use the database from the app and its extension.

Related

Preventing/Detecting iCloud migration of app data in UserDefaults and KeyChain

When a user gets a new iPhone, iCloud can restore app data from a different device, which copies info from UserDefaults and the Keychain.
This presents problems for my app when a user migrates from iPhone A -> iPhone B, because the app stores a device-specific security key that changes irregularly.
The restored security key may be expired (an old backup).
The user may continue using both iPhone A and iPhone B, causing their stored security keys get out-of-sync with rotations.
This would be easy to fix if I could detect the iCloud data restore, or an upgrade to a new device. This would allow me to reset the persisted device identifier and clear out the persisted old security key.
But I can find no way to do so, because Apple blocks accessing any unique device identifier so you can't tell if the app has moved to a new device. It also gives no callbacks about when an iCloud restore happened. I could check the hardware device model for changes, but sometimes a user replaces a phone with identical hardware when a phone is damaged or lost.
Is there any way to detect migration of an app to a new device and/or prevent cloning of iCloud backups of my app data from one device to another?
You can detect if an app is installed from iCloud backup by saving a file in the .applicationSupportDirectory. That directory is not backed up, so if your app crates a file there and doesn't see it, then that means it is (a) the first time your app has run or (b) the app was restored from backup.
You can use this as a flag to perform any special cleanup when a restore is detected.
And if you need to discern between a first time install and a restore, just save a second flag to UserDefaults. If the flag exists in UserDefaults but the flag file does not exist in .applicationSupportDirectory then you know it was an iCloud restore.
This technique has passed App Store review once as of this writing.
class RestoredAppDetector {
func saveInstallationFlagFile() {
if let applicationSupportDirectory = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) {
var flagFile = applicationSupportDirectory.appendingPathComponent("app_installed.txt", isDirectory: false)
if (!FileManager.default.createFile(atPath: flagFile.path, contents: "true".data(using: .utf8)) ) {
NSLog("Filed to create flag file")
}
var values = URLResourceValues()
values.isExcludedFromBackup = true
do {
try flagFile.setResourceValues(values)
}
catch {
NSLog("Failed to set resource value")
}
}
else {
NSLog("Could not create application support directory.")
}
}
func installationFlagFileExists() -> Bool {
if let applicationSupportDirectory = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
let flagFile = applicationSupportDirectory.appendingPathComponent("app_installed.txt", isDirectory: false)
if (FileManager.default.fileExists(atPath: flagFile.path)) {
NSLog("Flag file exists")
return true
}
else {
NSLog("Flag file does not exist")
}
}
else {
NSLog("Could not find application support directory.")
}
return false
}
}
As far as I know and tested, .applicationSupportDirectory folder is definitely backed up with no problem including all the folders/files hierarchy unless you explicitly excluded some specific items from the backup.

File Manager create file on iOS simulator

I am currently working on a functionality to store files. I am testing the application on an iOS simulator. When I call the File Manager to create a file I get a false response. am I able to save files to the simulator?
code:
let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.absoluteString
let response = FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
print(response)

Using an iPhone or iPad, how can I access a folder created by FileManager.default.createDirectory(...)?

I've created an iOS app that creates a "Photos" folder using FileManager, like this:
let appPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let photosPath = appPath.appendingPathComponent("Photos", isDirectory: true)
try FileManager.default.createDirectory(at: photosPath, withIntermediateDirectories: true, attributes: nil)
I know I can access that directory in the simulator by using Finder and going to:
Users/xxxx/Library/Developer/Devices/[my device GUID]/data/Containers/Data/Application/[my app GUID]/Documents/Photos
But how can I access that folder using the real device itself? (iPad or iPhone)
The native browser doesn't show my app directory

Loading document in shared documents folder into WKWebView

I am having problems trying to load a document into a WKWebView when the document has been added to the app using iTunes file sharing.
If I include the file inside the app I can load it fine.
I am using this code to get the load the file:
let documentsURL = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fooURL = documentsURL.appendingPathComponent(docFileName)
let docURL = URL(fileURLWithPath: fooURL.path)
let req = URLRequest(url:docURL)
docView!.load(req)
docURL looks like this:
file:///var/mobile/Containers/Data/Application/432E716E-F70D-4985-814C-FFE7ECE53EF8/Documents/filename.pdf
I have tried to check the file exists using this code:
FileManager().fileExists(atPath: fooURL.path)
This returns true. I have also tried to copy the file from the documents folder into the app folder but this returns an error of file not found (again this is even after checking the file exists)
Should WKWebView be able to load from this location? Or have I missed something here?
Perhaps you are looking for loadFileURL(_:allowingReadAccessTo:)
Though I didn't see it explicitly stated in the docs, it wouldn't surprise me if the security policies of WKWebView are getting in your way, and the presence of this method alone seems to confirm that ;-)
Happy coding!

How to find Apple App Group shared directory

We are currently developing an iOS10 app, including "Messages Extension".
To share CoreDatas persistant store.sqlite inbetween App and Extension, we are using a shared "Apple App Group" directory, which is working fine.
Now we have to get our hands on the store for debug reasons and are unable to find the directory. The Apps container directories are completely empty, which makes sense. But how to download our database? Do we have to somehow copy it programmatically to a reachable place?
To sum it up:
We already use CoreData which stores model.sqlite in our shared directory.
Everything is up and running.
What we want to archive is to download the database to our computer.
Without a shared directory we can simply download the App container from the device, using Xcode->Devices. But as we do use a shared directory, the .sqlite database is not within the container.
Question:
How can we download the .sqlite database from the device to our computer?
EDIT on 2018-10-12: Updated code for Swift 4.x (Xcode 10). (Older version retained for reference.)
In Swift 4.x:
let sharedContainerURL :URL? = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.etc.etc")
// replace "group.etc.etc" above with your App Group's identifier
NSLog("sharedContainerURL = \(String(describing: sharedContainerURL))")
if let sourceURL :URL = sharedContainerURL?.appendingPathComponent("store.sqlite") {
if let destinationURL :URL = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("copyOfStore.sqlite") {
try! FileManager().copyItem(at: sourceURL, to: destinationURL)
}
}
In older version of Swift (probably Swift 2.x):
let sharedContainerURL :NSURL? = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.etc.etc") // replace "group.etc.etc" with your App Group's identifier
NSLog("sharedContainerURL = \(sharedContainerURL)")
if let sourceURL :NSURL = sharedContainerURL?.URLByAppendingPathComponent("store.sqlite")
{
if let destinationURL :NSURL = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0].URLByAppendingPathComponent("copyOfStore.sqlite")
{
try! NSFileManager().copyItemAtURL(sourceURL, toURL: destinationURL)
}
}
Something like the above will get a file from the app group's shared container to the app's Documents directory. From there, you could use Xcode > Window > Devices to get it to your computer.
You could also use iTunes file sharing to retrieve the file from the app's Documents directory after setting UIFileSharingEnabled to YES in the Info.plist file, but bear in mind that this will expose the directory's contents to the user as well. Should be okay for development/debugging purposes, though.

Resources