SwiftUI and iCloud Documents - swift

I am trying to use iCloud with SwiftUI. I just want to be able to read/write json files stored there. I believe I have taken all the proper steps to get there, including the necessary changes to Signings & Capabilities and the addition of settings in info.plist. I have set up iCloud on the simulator. But the following code always returns nil for the URL:
let driveURL = FileManager.default .url(forUbiquityContainerIdentifier:
nil)?.appendingPathComponent("Documents")
if driveURL != nil {
print("iCloud available")
let fileURL = driveURL!.appendingPathComponent("test.txt")
try? "Hello word".data(using: .utf8)?.write(to: fileURL)
} else {
print("iCloud not available")
}
What am I missing?

Related

Unexpectedly found nil while implicitly unwrapping an Optional value AVAUDIO Player SWIFT

I am currently downloading an m4a file from firebase and trying to play the file with AVAudio Player.
How the system works
Get path of downloaded file as String
let pathForAudio: String = UserDefaults.standard.string(forKey: "path") ?? "There is no path for the audio"
Convert to URL
let url = URL(string: pathForAudio)
Pass URL into AVAUDIOPLAYER Function
soundPlayer = try AVAudioPlayer(contentsOf: url!)
When doing soundPlayer.play() I get "Unexpectedly found nil while implicitly unwrapping an Optional value"
I have seen this problem on Stack Before and they just enable permissions on a static file. Here the file path always changes so I cannot perform their solution.
Any help is much appreciated, let me know if you need other code blocks. Thanks so much!
You define your path this way:
let pathForAudio: String = UserDefaults.standard.string(forKey: "path") ?? "There is no path for the audio"
This could result in:
A valid file path stored in UserDefaults
An invalid file path stored in UserDefaults
Nothing stored in UserDefaults, which would then cause it to be "There is no path for the audio"
Unless you get scenario #1, then your next call (let url = URL(string: pathForAudio)) will fail, returning a nil value (which is what's happening right now. Then, upon calling try AVAudioPlayer(contentsOf: url!), because you're force unwrapping with !, you'll have a crash because you have a nil value that you're telling the system isn't nil by using !
In short, you need to put a valid path into UserDefaults in order for this system to work.
I'd also do some error checking along the way. Something like:
guard let pathForAudio = UserDefaults.standard.string(forKey: "path") else {
//handle the fact that there wasn't a path
return
}
guard let url = URL(string: pathForAudio) else {
//handle the fact that a URL couldn't be made from the string (ie, invalid path
return
}
//see if the file exists
guard FileManager.default.fileExists(atPath: pathForAudio) else {
//handle no file
return
}
do {
soundPlayer = try AVAudioPlayer(contentsOf: url) //notice there's now no !
} catch {
//handle error
print(error)
}

Why can't I get the filePath although I save my file properly and see it?

I am trying to write/read to the bundle and I use the below reference from StackOverflow:
How to access file included in app bundle in Swift?
which looks like good code and should work fine.
if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
let fileURL = documentsDirectory.appendingPathComponent("file.txt")
do {
if try fileURL.checkResourceIsReachable() {
print("file exist")
} else {
print("file doesnt exist")
do {
try Data().write(to: fileURL)
} catch {
print("an error happened while creating the file")
}
}
} catch {
print("an error happened while checking for the file")
}
}
To get filepath:
var filePath = Bundle.main.url(forResource: "file", withExtension: "txt")
I can save the file but somehow I can never find the file path by
var filePath = Bundle.main.url(forResource: "file", withExtension: "txt")
I cannot get any of the extensions. I even can see the saved file in my app's data under Devices in XCode which looks great but I can't find it by filePath​. Can you please help me?
Well, let's look at it this way:
Bundle.main is your app; Bundle.main.url looks for the file inside your app. (That's why the linked answer is called "How to access file included in app bundle in Swift?")
The documentDirectory is outside your app, namely (get this), it's the Documents directory.
So you are saving the file outside your app and then looking for the file inside your app. That's not where it is, so that's not going to work.

Reading in array of elements from macOS directory

I can read in elements (images or videos) uploaded in Xcode's project this way:
let photos = (1...9).map {
NSImage(named: NSImage.Name(rawValue: "000\($0)"))
}
or like this:
let videos = (1...100).map { _ in
Bundle.main.urls(forResourcesWithExtension: "mov", subdirectory: nil)![Int(arc4random_uniform(UInt32(100)))]
}
But how to read in files (as array) from macOS directory using .map method?
/Users/me/Desktop/ArrayOfElements/
First of all your second way is very expensive, the array of movie URLs is read a hundred times from the bundle. This is more efficient:
let resources = Bundle.main.urls(forResourcesWithExtension: "mov", subdirectory: nil)!
let videos = (1...100).map { _ in
resources[Int(arc4random_uniform(100))]
}
Reading from /Users/me/Desktop is only possible if the application is not sandboxed, otherwise you can only read form the application container.
To get all files (as [URL]) from a directory use FileManager:
let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Desktop/ArrayOfElements")
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
let movieURLs = fileURLs.filter{ $0.pathExtension == "mov" }
print(movieURLs)
} catch { print(error) }
Rather than using map I recommend to implement an Array extension adding shuffle()

Spritekit Scene Archiving

The SpriteKit programming guide contains this useful little tidbit:
Store a game level as an archive of a scene node. This archive includes the scene, all of its descendants in the node tree, and all of their connected physics bodies, joints, and actions.
But it doesn't have any info on where or how to store this archive. This is my current code, though it doesn't work:
// Save Data
let fileManager = FileManager.default
let URL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
let path = URL.appendingPathComponent("stage1.dat")
if NSKeyedArchiver.archiveRootObject(stage1, toFile: path.path) != false {
print("stage saved")
}
else {
print("save failed")
}
// Load Data
var data = Data()
do{
data = try Data(contentsOf: path)
}
catch{
print(error)
}
let stage = NSKeyedUnarchiver.unarchiveObject(with: data) as? GameScene
edit: Turns out I don't have permission to write to local domain. oops.
I switched it to user domain and now the write is working. However, attempting to unarchive that same file is now throwing an NSException. I added the unarchiving code to the block above.
I ended up using unarchiveTopLevelObjectWithData instead to generate an error I could catch.
Turns out the NSexception was due to trying to decode an optional using something that's not decode object.
This question pretty much sums it up.

Swift Realm, load the pre-populated database the right way?

I'm pretty new to ios development.
I follow this migration example to use pre-populated database and change the code a little bit
here is the final code I use on AppDelegate -> func application
let defaultPath = Realm.Configuration.defaultConfiguration.path!
let path = NSBundle.mainBundle().pathForResource("default", ofType: "realm")
if let bundledPath = path {
print("use pre-populated database")
do {
try NSFileManager.defaultManager().removeItemAtPath(defaultPath)
try NSFileManager.defaultManager().copyItemAtPath(bundledPath, toPath: defaultPath)
} catch {
print("remove")
print(error)
}
}
I'm testing this in a real device.
It works but according to the code logic, it'll always be reset to the pre-populated database. This is verified: the data is reset after app restart.
I tried moveItemAtPath instead of copyItemAtPath. permission error
I tried to delete the pre-populated database file after copy. permission error
I tried to use the pre-populated database file as the realm default configuration path. error occurs too.
In Swift 3.0, try this:
let bundlePath = Bundle.main.path(forResource: "default", ofType: "realm")
let destPath = Realm.Configuration.defaultConfiguration.fileURL?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
//File exist, do nothing
//print(fileManager.fileExists(atPath: destPath!))
} else {
do {
//Copy file from bundle to Realm default path
try fileManager.copyItem(atPath: bundlePath!, toPath: destPath!)
} catch {
print("\n",error)
}
}
Yeah, your logic is correct. Every time this code gets executed, the default Realm file in the Documents directory is deleted and replaced with the static copy that came with the app bundle. This is done by design in the Realm sample code in order to demonstrate the migration process each time the app is launched.
If you only want that to happen one time, the easiest way to do it would be to check beforehand to see if a Realm file already exists at the default path, and then perform the copy only when it isn't already there. :)
let alreadyExists = NSFileManager.defaultManager().fileExistsAtPath(defaultPath)
if alreadyExists == false && let bundledPath = path {
print("use pre-populated database")
do {
try NSFileManager.defaultManager().removeItemAtPath(defaultPath)
try NSFileManager.defaultManager().copyItemAtPath(bundledPath, toPath: defaultPath)
} catch {
print("remove")
print(error)
}
}
try this
let realm_db_path = Realm.Configuration.defaultConfiguration.fileURL!
let bundle_realm_path = Bundle.main.url(forResource: "default", withExtension: "realm")!
if !FileManager.default.fileExists(atPath: realm_db_path.absoluteString){
do {
try FileManager.default.copyItem(at: bundle_realm_path, to: realm_db_path)
}catch let error {
NSLog(error as! String)
}