Change initial View Controller from AppDelegate after async request - ios

I want to change the initial view controller when the user starts the app, but only after I get the information on which one to present, from the server.
It goes like this somewhat like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseManager.getUserProfile() { user in
if user?.sports.count == 0 {
// present View Controller 1
} else {
// present View Controller 2
}
}
return true
}
After the call is made, I decide which VC to be shown, but, the problem is that return true is triggered synchronously at the start.
This results in an unpleasant user experience because it shows the initial storyboard (Login.storyboard, as configured in the info.plist) and only after the request has finished (after several seconds), it changes to the correct view controller. I want it to change directly to the view controller, without showing anything else before (even if it will required more waiting from the user).
How can I avoid this? What is the best practice when dealing with such a situation?

I would suggest using Firebase remote config parameters. It stores your data locally. And you can remotely manage it.
Or store value in UserDefaults of FirebaseManager.getUserProfile() beforehand.

Related

SwiftUI - background processing in a view

I have a music player that needs to fetch the current song from a function over and over again.
up til now I have put the function inside the view (SwiftUI) using the function .onAppear{} while technically it worked I found that when a user clicks on the view to make it bigger, or even when they close and reopen the app from background mode it is re-running the code as if it has not already started.
So I thought by adding a self.timer?.invalidate() function #State var timer: Timer?
I could potentially allow the script to run while it's open and then stop it when it disappears, that worked all the way up to when I needed to add
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification), perform: { output in
self.timer?.invalidate()
})
The issue now is when I reopen the app it won't restart - not sure why.
But I am starting to feel that there must be a better way to do this function, after all it's only checking a function that is in another file on loop.
So my question: where is the best place to create a code that does this
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
//CODE HERE
}
Clearly it needs to run once and in the background but not be called again unless the app is opened for the first time.
I was thinking I need to put it inside the AppDelegate file because I know that file is called only once or when certain functions need to run.
I was thinking this section
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//INSERT CODE HEER
}
I know that I would then need to just store the values to something like
UserDefaults.standard.set
and then just get the MediaPlayerView.swift to refresh these values.
Is this the best practice and what others would recommend?, And am I right by saying a view should only be reading information not trying to run functions.

How to track the time a user listens an specific podcast

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
}

Best practice tu run code only once and only when you open an app. Swift 4, Xcode 8.

I am trying to run a function only once only when the app is opened.
Background
I have a stored array (we'll call it storedArray) in user defaults that takes its value from an array that can be modified by the user (userArray).
Problem1: Every time I open the app, storedArray has the values of the previous sessions and of course userArray is empty again. So when the storedArray takes values again from the userArray the storedArray looses its previous values.
Solution to problem1: When you open the app the userArray gets filled with the values of the storedArray (from the previous session).
No problem there. Now:
**Problem 2:**This must happen only once every time the app is opened, otherwise the userArray will start appending the storedArray values many times with repeated values. I figured out solutions 1 and 2, number 3 I tried to follow advice from other posts but failed to understand it and implement it.
Possible Solution 1: Declare a global variable
Struct globalVariables{
static var x = 0
}
Create an if statement so that the desired code runs only if globalVariables.x == 0
if globalVariables.x == 0 {
//code to append values from storedArray to userArray goes here
//Then give a new value to globalVariables.x so that the if never gets accessed again
globalVariables.x == 1
}
This works but I don't know if it as a good practice.
Possible Solution 2: Put the code to be run only once inside the application function in the AppDelegate file.
This functions looks like this.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//code to append values from storedArray to userArray goes here
}
Again this works but I don't know if it is a good practice.
Possible Solution 3: If I understood correctly, the suggested solution in other posts was to use a singleton. I could never really make this work, (or properly understood what a singleton is).
I made a new .swift file with the following code:
struct Service {
static let sharedInstance = Service(
func getStoredInfo () {
//code to append values from storedArray to userArray goes here
}
}
}
Then call this function in a viewDidLoad with:
Service.sharedInstance.getStoredInfo()
The function still gets called every time the viewDidLoad runs. If the singleton can really help me with this I am totally missing something very basic about what a singleton is and how to implement it. Totally.
Are solutions 1 and 2 good practices? Maybe there are other solutions?
Thanks!
If something need to be done only after launch then 'application didFinishLaunchingWithOptions' is definitely the way to go.
Problem : On iOS you don't control when your app is terminated. You need to store how the app was terminated, so you can restore the session or start a fresh one.

iOS - Call method when any app starts

I want to call an action which will get called every time any app start.
Example:
Showing a modal with app name saying Welcome to Twitter.
you can add that action in this method of app delegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
perfornAction()
return true
}
There are 2 methods in which you can write your code.
applicationWillEnterForeground: Gets called when you app comes into picture after INACTIVE or TERMINATED state
didFinishLaunchingWithOptions: Gets called when app gets called for first time. Also this method will get called when app is in terminated state and you open the app.
So if you want to perform it every time when app opens, prefer applicationWillEnterForeground. And for first time user call same method in didFinishLaunchingWithOptions with some flag value(Set it to true when your code runs successfully) so that your method will not get called twice.

health kit observer query always called when the app becomes active

The resultHandler of HKObserverQuery is always called when the app becomes active (background -> foreground)
But, I wrote the code of the query in didFinishLaunchingWithOptions method in AppDelegate.swift. I know the method is called when the app is launched not the app become active.
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
healthStore.authorizeHealthKit {
...
}
}
// other AppDelegate methods are empty
How do I make the handler of the query called only when my app is launched?
Why do you want to prevent the updateHandler from firing?
You can't control when the updateHandler of an HKObserverQuery fires while the query is running. You can prevent it from being called at all by stopping the query. It is designed to be called whenever there might be new HealthKit data matching your predicate. You should design your updateHandler such that it doesn't matter when it is called.
If you really wanted the observer query to not fire when your app returns to the foreground, you would need to stop the query completely with -[HKHealthStore stopQuery:] when your app enters the background, before it suspends.

Resources