Spritekit Scene Archiving - swift

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.

Related

SwiftUI and iCloud Documents

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?

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)
}

How can I play a sound from the asset catalog using AVAudioPlayerNode?

I'm trying to use an AVAudioPlayerNode to play sounds from the Assets.xcassets asset catalog, but I can't figure out how to do it.
I've been using AVAudioPlayer, which can be initialized with an NSDataAsset like this:
let sound = NSDataAsset(name: "beep")!
do {
let player = try AVAudioPlayer(data: sound.data, fileTypeHint: AVFileTypeWAVE)
player.prepareToPlay()
player.play()
} catch {
print("Failed to create AVAudioPlayer")
}
I want to use an AVAudioPlayerNode instead (for pitch shifting and other reasons). I can create the engine and hook up the node OK:
var engine = AVAudioEngine()
func playSound(named name: String) {
let mixer = engine.mainMixerNode
let playerNode = AVAudioPlayerNode()
engine.attach(playerNode)
engine.connect(playerNode, to: mixer, format: mixer.outputFormat(forBus: 0))
// play the file (this is what I don't know how to do)
}
It looks like the method to use for playing the file is playerNode.scheduleFile(). It takes an AVAudioFile, so I thought I'd try to make one. But the initializer for AVAudioFile wants a URL. As far as I can tell, assets in the asset catalog are not available by URL. I can get the data directly using NSDataAsset, but there doesn't seem to be any way to use it to populate an AVAudioFile.
Is it possible to play sounds from the asset catalog with an AVAudioPlayerNode? And if so, how?
OK so your problem is that you would like to get a URL from a file in your Asset catalog right?
I've looked around but only found this answer
As it says
It basically just gets image from assets, saves its data to disk and return file URL
You should probably change it to look for MP3 files (or WAV or whatever you prefer, maybe that could be an input parameter)
So you could end up with something like:
enum SoundType: String {
case mp3 = "mp3"
case wav = "wav"
}
class AssetExtractor {
static func createLocalUrl(forSoundNamed name: String, ofType type: SoundType = .mp3) -> URL? {
let fileManager = FileManager.default
let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let url = cacheDirectory.appendingPathComponent("\(name).\(type)")
guard fileManager.fileExists(atPath: url.path) else {
guard
let image = UIImage(named: name),
let data = UIImagePNGRepresentation(image)
else { return nil }
fileManager.createFile(atPath: url.path, contents: data, attributes: nil)
return url
}
return url
}
}
Maybe a bit far fetched but I haven't found any other options.
Hope that helps you.

Are audio files in DocumentDirectory deleted when Core Data entity is deleted?

I'm recording audio with AVFoundation and saving the file in the DocumentDirectory. Then in Core Data the "sounds" entity stores the url as a string. Everything's working great. However I'm wondering: when sounds entities are deleted from Core Data how do to you delete the file from the DocumentDirectory ...or does that automagically happen?
My code to save to disk:
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first
let audioFileName = NSUUID().UUIDString + ".m4a"
let audioFileURL = directoryURL!.URLByAppendingPathComponent(audioFileName)
// URL for Core Data store
audioURL = audioFileName
// Setup audio session
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: AVAudioSessionCategoryOptions.DefaultToSpeaker)
} catch _ {
print("Error creating AVAudioSession")
}
let recordSettings = [AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC), AVSampleRateKey: 44100.0, AVNumberOfChannelsKey: 2]
// Initiate and prepare recorder
audioRecorder = try?AVAudioRecorder(URL: audioFileURL!, settings: recordSettings)
audioRecorder?.delegate = self
audioRecorder?.meteringEnabled = true
audioRecorder?.prepareToRecord()
Thank you very much for any assistance! Sorry, I'm finding it a little difficult to track files in the document directory in the sim. (I'm in Swift 2.3)
Deleting a Core Data instance will affect-- at most-- things in the Core Data store. Core Data doesn't know that the string you saved points to a file, so it doesn't try to do anything special with the string.
You can remove the file using the removeItemAtURL:error: method on NSFileManager. A good place to do this would be in your managed object subclass-- override prepareForDeletion and delete the file there.

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)
}