Is there an easy way to store my app's data in Swift 3?
I would like to store a single object in some way. I tried UserDefaults.standard.set(my_object, forKey: "my_object") but it doesn't seem to work.
I put this code in AppDelegate.swift in func applicationWillTerminate(_ application: UIApplication)
Also, I would like to read this object back in func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool, when the application starts.
UserDefaults is the simplest way.
I put this code in AppDelegate.swift in func applicationWillTerminate(_ application: UIApplication)
Well, that's pretty silly, because that method is basically never called. Save information out to UserDefaults somewhere such that your code will actually run. applicationDidEnterBackground(_:) and applicationWillResignActive(_:) are possible choices, but the way to be certain is simply to save the information out whenever the information changes.
Also, I would like to read this object back in func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool, when the application starts.
Well, 99.9% of the time your application starts, that method won't be called. But if that method is called, fine, go ahead and read the object back in.
Related
I have an iOS app that is about podcasts and I want to track how long a user listens every podcast. I have tried the basic - when a user plays I save the timestamp and when stops it sends an event with the timestamp difference but it obviously doens't work because there's many edge cases.
I have issues to know when a user has the app in background and stops listening at some point through the the system controls. Also when the user or the system kills the app without tapping on "pause" or "stop". I think these 2 cases are my main non-tracked cases so far.
Any idea how can I build a working solution? I don't want/can't pay an external service - I am merely relying on Firebase.
Thanks!
You can override applicationWillTerminate method in your app, and save a current user progress to UserDefaults.
As docs say, you have few seconds to do it:
This method lets your app know that it is about to be terminated and
purged from memory entirely. You should use this method to perform any
final clean-up tasks for your app, such as freeing shared resources,
saving user data, and invalidating timers. Your implementation of this
method has approximately five seconds to perform any tasks and return.
Your code can look like this:
var player: AVPlayer!
func applicationWillTerminate(_ application: UIApplication) {
UserDefaults.standard.setValue(player.currentTime().seconds, forKey: "curPlayerTime")
}
Then, on application launch, you can restore it:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let lastPlayerTime = UserDefaults.standard.value(forKey: "curPlayerTime") as? Double {
// update your player
}
return true
}
I have this code that show a message "Msg Background" when the application is in Background. What I need is that as long as the application continues in the background show that message every 2 minutes (just an example of time). So far the code I have shows the message only once, apparently this sentence is not working properly.
UIApplication.shared.setMinimumBackgroundFetchInterval (UIApplication.backgroundFetchIntervalMinimum)
I also have this warning: 'setMinimumBackgroundFetchInterval' was deprecated in iOS 13.0: Use a BGAppRefreshTask in the BackgroundTasks framework instead
I am using swift 5 and Xcode 11
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GMSServices.provideAPIKey("AIzaSyBSpAt5zqvbh73FmG_Kb6xpiFMkWRmHppg")
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
return true
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Msg Background")
}
}
As Paulw11 said, it's not called in fixed interval. The OS controls when it is going to be executed. We cannot expect the specific time because of it.
You probably know this, but I'm going to add this bit just in case, setMinimumBackgroundFetchInterval has deprecated on iOS 13 and later. You might want to consider to use BackgroundTasks Framework instead.
but if you want to test performFetchWithCompletionHandler, you can use XCode Navigation Bar > Debug > perform Background Fetch.
screenshot of XCode navigation bar to find option
I hope it can help!
I'm using Firebase's deep linking to try and provide a certain function in my app when a user clicks a link that was sent to them. The deep link is setup properly on the Firebase web portal.
Clicking the link sent to the user DOES open my app, but does not trigger the continue userActivity method in my App Delegate, as none of the code contained in there is every executed.
I've tried the solution suggested in this StackOverflow post but no success, that's switching [UIUserActivityRestoring] to [Any] in the method declaration.
In Xcode, I have setup the associated domain to match what I set on Firebase: applinks:myappname.page.link; and I have added a "URL type" with identifier "Bundle ID", and URL scheme of ca.mycompany.myappname, with role editor.
All this is running on-device, of course, as I don't expect this to work in the simulator.
This is the method in my app delegate which should be called, however is not.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
print ("The link worked")
}
I made sure didFinishLaunchingWithOptions returns true always as well.
I expect the "The link worked" to actually print in debugging console, but it does not. Breakpoints indicate the method isn't being called at all, as the breakpoints are never reached.
There is a good change that somebody facing this problem deals with iOS13 / SwiftUI, too. Just like me.
I have found this article which solved all my problems.
Basically you have to be aware that some methods from the new SceneDelegate replace what was called in AppDelegate before iOS 13. Additionally there seem to be edge cases where methods from the AppDelegate still might be needed to handle deeplinks processing.
For anyone who needs it:
you need to use the below code to handle deep links from the link to the app that is already installed(other func application are for different things)
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
// ...
}
return handled
}
https://firebase.google.com/docs/dynamic-links/ios/receive #6
For scene application try to add your logic as part of
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
//...
}
}
I'm creating a framework and using AWS to upload data in the background.
I need to have code in the method that gets called in the projects AppDelegate,
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
AWSS3TransferUtility.interceptApplication(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
My current solution is to just call my framework when this gets called in the AppDelegate, but I do not want the users to have to include any code other than the code that starts the framework running. Is there anyways to have my line of code called with all the parameters from my framework without the person needing to call a method in my framework?
After some thought I don't think there is a way to do this.
As far as I know there are two different ways to implement what you looking for, the one you are currently using, or create your own class of the AppDelegate that inherits UIApplicationDelegate
1.- Call your method from the App delegate, as you are doing, the cons of this approach as you are well aware are that you depend on Developers to Implement the method, and know where to implement it in the App Delegate.
2.- Create a Base AppDelegate class, You would need to tell the developers to Inherit their app delegate from your class.
class CustomAppDelegate: UIResponder,UIApplicationDelegate {
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
AWSS3TransferUtility.interceptApplication(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
}
Their AppDelegate would inherit from it:
class AppDelegate: CustomAppDelegate{
}
The problem is that developers might not always follow this guideline, and even if they do, if they need to override that function, they would need to call the super method call in order for it to work.
Hope this helps!
Basically, whenever the app launches or opens I have a check to see if there's a link on the clipboard, and if so I ask the user if he/she wants to add it.
However, I recently added the ability to use x-callback-urls to add URLs into the app. If they use an app like LaunchPad to open a URL in my app, the "do you want to add the URL from your clipboard?" notification still opens, which doesn't make much sense as they've already launched it to add that URL.
Problem is, the openURL: method in my AppDelegate is called to handle the URL scheme after appDidFinishLaunching and appWillEnterForeground, so I can't put a check in those to see if the openURL method was already called. I could use a dispatch_after, but that seems lazy and hacky.
Is there a way to check how the app was launched, i.e.: via a URL scheme or via a manual open?
Don't implement applicationDidFinishLaunching:. Use application:didFinishLaunchingWithOptions: instead. Likewise don't use application:handleOpenURL:, use application:openURL:sourceApplication:annotation: instead.
If you are launched from a URL the options dictionary will contain a UIApplicationLaunchOptionsURLKey key, and the value for that key will be the URL.
However, if your app is already running but is in the background and the user invokes a URL that re-opens your app, you will get a application:openURL:sourceApplication:annotation: message instead.
What you really need to do is implement a handleURL method, and then call that method from both application:didFinishLaunchingWithOptions: and application:openURL:sourceApplication:annotation:
EDIT:
Note that in iOS 9, Apple deprecated application:openURL:sourceApplication:annotation: and added the new method application:openURL:options:.
If your app is iOS 9 and later only, you should implement the new application:openURL:options: method. If you need to support iOS 9 and earlier versions, you should probably implement both application:openURL:sourceApplication:annotation: and the new iOS 9 method application:openURL:options:. The OS will call the correct version for the OS version you are running. I would then create a common method that both of those call. That way you get called from both OS versions, but your code to handle opening an URL is only in one place.
applicationDidBecomeActive is called after openURL and continueUserActivity (and after appWillEnterForeground), so is a good place to determine how the app was opened.
Cache the url in openURL and continueUserActivity (depending on the source, deep links may come from continueUserActivity. Anecdotally, I always see that method called when opening a link from Messages. Then check that url in applicationDidBecomeActive
private var launchURL:URL?
static func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
launchURL = userActivity.webpageURL
// Do something with userActivity
return true
}
static func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
launchURL = url
// Do something with URL
return true
}
static func applicationDidBecomeActive(_ application: UIApplication) {
defer {
launchURL = nil // Just cleaning up
}
let didLaunchFromURL = launchURL != nil
}