Key data not storing to iCloud with NSUbiquitousKeyValueStore.defaultStore - swift

So I have a simple goal, just to get this working. So theoretically if you ruined this.
Triggered save players
waited a bit
Deleted the app
Rebuilt (downloaded)
Triggered restore then the word "MEDO" would print to the console
But instead it is null, making me pretty sure that it is not saving to iC cloud some reason. I followed this tutorial. Perhaps it is too outdated to work?
func c_restoreCharecters()
{
let icloud = NSUbiquitousKeyValueStore.defaultStore()
println(icloud.objectForKey("username"))
}
func c_savePlayers()
{
let icloud = NSUbiquitousKeyValueStore.defaultStore()
icloud.setObject("MEDO", forKey: "username")
println(icloud.synchronize())
}
Obviously I am going to use this concept for something completely different in the future, but I have to get the basics down first!
Some other stuff:
iCloud set up in my settings:
My Entitlements file:

Related

Process to change data model

How to change the .xcdatamodeld file i.e. the data model?
Since the program has already been run and the Persistent Store Coordinator (PSC) contains a url to .sqlite, .sqlite-shm and .sqlite-wal files on disk I think the process is as follows but am unsure. Any input would be appreciated.
Run code below to delete the url from PSC.
Delete sqlite files from disk.
Change .xcdatamodeld file.
CodeGen is set to manual so create new managed object subclasses.
Make appropriate changes to code.
Run program which I assume will enter a url into the PSC and create the 3 sqlite files on disk but now based on the new .xcdatamodeld file.
func deletePersistentStore() {
guard let persistentStoreURL = container.persistentStoreCoordinator.persistentStores.first?.url
else {
print("URL Missing")
return
}
do {
try container.persistentStoreCoordinator.destroyPersistentStore(
at: persistentStoreURL,
ofType: "SQLite",
options: nil)
} catch {
print("Persistent Store Not Deleted: \(error) - \(error.localizedDescription)")
}
print("\(container.persistentStoreCoordinator.persistentStores.count)")
// prints 0
print("\(String(describing: container.persistentStoreCoordinator.persistentStores.first?.url) )")
// prints nil
}
It sounds like you’re still developing this app and that it’s not released yet. If that’s true, you could do something like this. But it would be easier to delete the app from your phone (or simulator), change the model, and then install a new copy of the app.
That would let you skip steps 1 and 2.
If your app is already released, you should look into Core Data model migration. It’s a process that lets you update the data model without deleting existing data. In most cases it’s nearly automatic, but it depends on how much your model is changing.

Ffmpeg for use in iOS application coded in Swift

I have been browsing the web, even this website... bu cannot find a good option to implement ffmpeg functionality in an iOS application made in swift.
Options looked at and reasons why they are not solutions:
SwiftFFmpeg - I am not sure it can run on iOS, plus I don't see an option to run my desired Ffmpeg command.
MobileFFmpeg - Not maintained anymore, and a new project by the same founders created a "better altenrative" called ffmpeg-kit.
ffmpeg-kit - looks amazing, but their API only allows for interaction in the Objective-C language.
Any solutions anyone can give me?
First...
Make sure you understand what "packages" are available. You can build it yourself, but to be honest, unless you have a very specific reason, I'd just use the pre-build packages. See "8. Packages" of the ffmpeg-kit README.MD to see what's on offer
Second
Go to the Releases and find the version you're interested (I used FFmpegKit Native v4.5.1), scroll down and expand the "Assets" list for the release you're interested in.
For this experiment I used ffmpeg-kit-full-4.5.1-macos-xcframework.zip. If you're doing this in iOS, the workflow is basically the same, you just need to take into account that your file access is sandboxed (and yes, I've done this, and yes, it takes a long time to transcode video, compared to a desktop)
Third
Create a new Xcode project. Again, for this experiment, I created a "MacOS App" using "Storyboards" as the UI (you could try using SwiftUI, but that's another layer of complexity this example didn't need right now)
Unzip the *xcframework.zip file you download from the last step.
In Xcode, select the "project" node, select the MacOS target (🤞 there's only one target).
Select "General", drag the *.xcframework folders from Finder to the "Frameworks, Libraries and Embedded Content" section of your project
Forth
For this experiment, I opened the ViewController class (which was automatically created by Xcode) and simply added...
func syncCommand() {
guard let session = FFmpegKit.execute("-i file1.mp4 -c:v file2.mp4") else {
print("!! Failed to create session")
return
}
let returnCode = session.getReturnCode()
if ReturnCode.isSuccess(returnCode) {
} else if ReturnCode.isCancel(returnCode) {
} else {
print("Command failed with state \(FFmpegKitConfig.sessionState(toString: session.getState()) ?? "Unknown") and rc \(returnCode?.description ?? "Unknown").\(session.getFailStackTrace() ?? "Unknown")")
}
}
func asyncCommand() {
FFmpegKit.executeAsync("-i file1.mp4 -c:v file2.mp4") { session in
guard let session = session else {
print("!! Invalid session")
return
}
guard let returnCode = session.getReturnCode() else {
print("!! Invalid return code")
return
}
print("FFmpeg process exited with state \(FFmpegKitConfig.sessionState(toString: session.getState()) ?? "Unknown") and rc \(returnCode).\(session.getFailStackTrace() ?? "Unknown")")
} withLogCallback: { logs in
guard let logs = logs else { return }
// CALLED WHEN SESSION PRINTS LOGS
} withStatisticsCallback: { stats in
guard let stats = stats else { return }
// CALLED WHEN SESSION GENERATES STATISTICS
}
}
The code above is basically the "2. Execute synchronous FFmpeg commands." and "4. Execute asynchronous FFmpeg commands by providing session specific execute/log/session callbacks." examples from the ffmpeg-kit/apple documentation
!! Important !! - don't forget to add import ffmpegkit to the start of the file!
At this point, this should now compile (you'll get a couple of warnings about logs and stats not been used, you can ignore those).
After thoughts...
You should realise by now that the code I've provided won't actually run, for two reasons.
I've not actually called either func from anywhere (I tested it by placing it in the viewDidLoad func of the ViewController class)
The input file, used in the execute command, doesn't exist. You will need to provide an actual reference to an actual file, preferably with an absolute path. This, how ever, may require you to change the "App Sandbox" settings under the targets "Signing and Capabilities"
Xcodes auto code suggestions aren't bad and I mostly filled out the above using it, and the Obj-c code as a starting point.
Also, beware, SO is not a "tutorial" site, Xcode is a complex beast and you may need to spend some time exploring other resources to overcome issues you encounter

UserDefaults/NSUserDefaults removeObject(forKey:) mysteriously failing in app and between apps

I am working on an "uninstaller" for an macOS app we've had for several years now. The purpose for the uninstaller is to allow us to put a given system into a nascent state as if the original app had never been installed so that we can more reliably test the install process.
The original app has an extensive array of preferences stored in UserDefaults. In the original app there is a resetToDefaults() method which works just fine resetting all the defaults however for the uninstaller we'd wanted to remove the values completely. It looks to be straight-forward and this is what I came up with...
func flushPreferences() {
let defaults = getDefaultPreferences()
for preferenceName in defaults.keys.sorted() {
UserDefaults.standard.removeObject(forKey: preferenceName)
}
UserDefaults.standard.synchronize()
}
... which does not work at all.
I read in the documentation
Removing a default has no effect on the value returned by the objectForKey: method if the same key exists in a domain that precedes the standard application domain in the search list.
and I don't really understand what "domain" relates to and thought it might be app so tried the code as a test in the original app and that does nothing either.
Someone else suggested this, which also does nothing
let appDomain = Bundle.main.bundleIdentifier!
UserDefaults.standard.removePersistentDomain(forName: appDomain)
I also found some test code which works absolutely fine... which looks to be nearly identical to what I'm doing. I even tried using it with hard-coding one of our pref keys and that fails as well.
func testRemoveObject() {
let myKey:String = "myKey"
UserDefaults.standard.set(true, forKey: myKey)
let beforeVal = UserDefaults.standard.value(forKey: myKey)
print("before: \(beforeVal ?? "nil")")
UserDefaults.standard.removeObject(forKey: myKey) // Note: This is the only line needed, others are debugging
let afterVal = UserDefaults.standard.value(forKey: myKey)
print("after: \(afterVal ?? "nil")")
}
What am I missing? It looks like this one (based on what I've been able to find on the web) can be somewhat mysterious but I'm thinking it must be something obvious that I'm not seeing.
Well, thanks to red_menace's suggestion I found one article that led to another that suggested that the following command will reset the user preferences cache:
killall -u #USER cfprefsd
which seemed to work (yay) but upon further investigation it appears that simply closing the app is what updates the actual preference in the .plist and that changing it in the app will not show up until you exit.
This makes sense as it explains why you can create a preference on the fly save it, confirm it saved, delete it and confirm it deleted but cannot delete a previously saved preference — as similar to the persistent prefs perhaps the new preference is not added to the cache until the application exits.
This could also explain the various odd behaviors that other posters were finding (only worked every other time, had to do it asynchronously, etc.). As for NSUserDefaults.synchronize() has been depreciated and developer.apple.com indicates that it is unneeded and does nothing.
So one problem solved...
As it turns out my initial instinct was accurate as well and you cannot access prefs from one app in another using the removeObject(forKey: preferenceName)
// Will not work cross-application, though will work locally (inter-app)
UserDefaults.standard.removeObject(forKey: preferenceName)
To get it to work cross applications you have to use CFPreferencesSetAppValue(_ key:, value:, applicationID:) which is part of the "Preferences Utilities" section of the Core Library which requires that you know the appDomain of the initial app. So, the final solution is:
In the source app:
let appDomain = Bundle.main.bundleIdentifier! // Note, needed by uninstaller
will give you the domain for the stored preference in the source app.
And in the app doing the changing — the final working code:
func flushPreferences() {
let defaults = getDefaultPreferences()
let sourceAppDomain = "{THE_BUNDLE_ID_FROM_SOURCE_APP}"
for preferenceName in defaults.keys {
print("Preference name: \(preferenceName)")
CFPreferencesSetAppValue(preferenceName as CFString,
nil,
sourceAppDomain as CFString)
}
}
Hope this helps someone save some time at some point - thanks to everyone who contributed. This one was a BEAR!

Deleting, adding and updating Firestore documents when offline

I have been using the following code in my iOS Swift app:
class ProfileController
{
func remove(pid: String, completion: #escaping ErrorCompletionHandler)
{
guard let uid = self.uid else
{
completion(Errors.userIdentifierEmpty)
return
}
let db = Firestore.firestore()
let userDocument = db.collection("profiles").document(uid)
let collection = userDocument.collection("profiles")
let document = collection.document(pid)
document.delete()
{
error in
completion(error)
}
}
}
When the device is online, everything works fine. The completion handler of the deletecall is properly executed. However, when I am offline, I have noticed that the completion handler will not be executed as long as I am offline. As soon as I get back online, the completion handler will be called almost immediately.
I don't want to wait until the user is back online (which could take forever), so I changed the code a little bit and added a ListenerRegistration:
class ProfileController
{
func remove(pid: String, completion: #escaping ErrorCompletionHandler)
{
guard let uid = self.uid else
{
completion(Errors.userIdentifierEmpty)
return
}
let db = Firestore.firestore()
let userDocument = db.collection("profiles").document(uid)
let collection = userDocument.collection("profiles")
let document = collection.document(pid)
var listener: ListenerRegistration?
listener = document.addSnapshotListener(includeMetadataChanges: false)
{
snapshot, error in
listener?.remove() // Immediately remove the snapshot listener so we only receive one notification.
completion(error)
listener = nil
}
document.delete()
}
}
Although this works fine, I am not sure if this is the right way to go. I have read online that a snapshot listener can be used in real-time scenarios, which is not really what I am looking for (or what I need).
Is this the right approach or is there another (better) way? I only want to get notified once (thus I added the includeMetadataChanged property and set it to false). I also remove the ListenerRegistration once the completion handler was called once.
If the first approach does not work properly when being offline - what are the use cases of this approach? Before I change my entire codebase to use listeners, is there any way of executing the completion handler of the first approach when the device is offline?
TL;DR: The second implementation works fine, I am simply unsure if this is the proper way of receiving notifications when the device is offline.
If the first approach does not work properly when being offline - what are the use cases of this approach?
It depends on what you mean by "work properly". The behavior you're observing is exactly as intended. Write operations are not "complete" until they're registered at the server.
However, all writes (that are not transactions) are actually committed locally before they hit the server. These local changes will eventually be synchronized with the server at some point in the future, even if the app is killed and restarted. The change is not lost. You can count on the synchronization of the change to eventually hit the server as long as the user continues to launch the app - this is all you can expect. You can read more about offline persistence in the documentation.
If you need to know if a prior change was synchronized, there is no easy way to determine that if the app was killed and restarted. You could try to query for that data, if you know the IDs of the documents written, and you could check the metadata of the documents to find out the source of the data (cache or server). But in the end, you are really supposed to trust that changes will be synchronized with the server at the earliest convenience.
If your use case requires more granularity of information, please file a feature request with Firebase support.

Possible to get latest data with observeSingleEvent + persistence enabled?

I am trying to get some data from firebase. Any idea how can I get the latest data (not from cache) when I have persistence enabled? I tried keepSynced; I still get stale data. Is this the correct usage?
userRef = FIRDatabase.database().reference().child("<path>")
userRef.keepSynced(true)
userRef.observeSingleEvent(of: .value, with: { snapshot in
...stale data here...
})
Or the only option is to use observe instead of observeSingleEvent? I don't like the fact that with observe I get the cache data first, and then the event triggers a second time with data from the server. So with observe, when I navigate to this screen, first I see a blank table, then I see the table with stale data, and then I see the table with latest data.
Thanks.
EDIT:
https://stackoverflow.com/a/34487195/1373592 -
This post says keeySynced should work. But it's not working for me. I would like to know if I am doing something wrong.
I retrieve some explanation, I think it might help you in your case :
ObserveSingleEventType with keepSycned will not work if the Firebase
connection cannot be established on time. This is especially true
during appLaunch or in the appDelegate where there is a delay in the
Firebase connection and the cached result is given instead. It will
also not work at times if persistence is enabled and
observeSingleEvent might give the cached data first. In situations
like these, a continuous ObserveEventType is preferred and should be
used if you absolutely need fresh data.
I think you don't have the choice to use a continuous listener. But to avoid performance issues why you don't remove yourself your listeners when you don't it anymore.
Here is an example on how to ALWAYS get latest data from firebase when persistence is turned on. Use observe event, keepSynced on your ref and terminate listener if you don't want to keep it always. After several trials, I came up with this and it is working.
func readFromFB() {
let refHandle: DatabaseHandle?
let ref: DatabaseReference? = firebase.child(nodeName)
ref?.keepSynced(true)
refHandle = ref!.observe(.value, with:
{ snapshot in
if snapshot.exists() {
for item in ((snapshot.value as! NSDictionary).allValues as Array) {
//do whatever tasks
}
}
})
if let rf = ref {
rf.removeObserver(withHandle: refHandle!)
}
}