How can I delay splash launch screen programmatically in Swift Xcode iOS - ios

I have put an image in imageView in LaunchStoreyboard. How can I delay the time of image programmatically?
Here is the Launch Screen Guideline from Apple.
Here is code for Launch Screen View controller:
import UIKit
class LaunshViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.delay(0.4)
}
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
}

As of today there is no predefine method from Apple to hold launch screen. Here are some Approaches which are not optimum but works
Approach #1
Create a Separate ViewController which has Launch logo & create a timer or perform some operation (Like Database/Loads some essential network call) depends on your app type this way you can ready with data before hand & hold the launch screen as well :)
Approach #2 Not Optimum
Use Sleep code which holds up the app for a while.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Thread.sleep(forTimeInterval: 3.0)
// Override point for customization after application launch.
return true
}

Would not recommending setting the entire application in a waiting state.
If the application needs to do more work before finishing the watchdog could kill the application for taking too long time to start up.
Instead you could do something like this to delay the launch screen.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
return true
}

Swift 4.x
It is Not a good practice to put your application to sleep!
Booting your App should be as fast as possible, so the Launch screen delay is something you do not want to use.
But, instead of sleeping you can run a loop during which the receiver processes data from all attached input sources:
This will prolong the launch-screen's visibility time.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
RunLoop.current.run(until: NSDate(timeIntervalSinceNow:1) as Date)
return true
}

Swift 5.x, iOS 13.x.x
Modifying the following function in the AppDelegate class does not work in Swift 5.x/iOS 13.x.x.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
Instead, you will have to modify the scene function in SceneDelegate class as following. It will delay the LaunchSceen for 3 seconds.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
window?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
guard let _ = (scene as? UIWindowScene) else { return }
}
The window variable should already be there in SceneDelegate class like the following.
var window: UIWindow?

Definitely your app should not be put to sleep as it may be killed by the OS for being unresponsive for so long.
If you're using a static image for your launch screen, what works for me is to use the image in the LaunchScreen.storyboard, and then when your main controller launches, modally present a VC with the same image as the background in the ViewDidAppear of your main controller (with animated set to false).
You can then use your logic to know when to dismiss the launch screen (dismiss method in the VC with animated set to false).
The transition from the actual LaunchScreen to my VC presenting the same screen looks to me imperceptible.
PS: the ViewDidAppear method might be called more than once, in which case you need to use logic to not present the VC with the launch screen a second time.

Create a ViewController and use NSTimer to detect the delay time. and when the timer ends push the first UIViewcontroller.
In ViewDidLoad method..
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(fireMethod) userInfo:nil repeats:NO];
-(void)fireMethod
{
// push view controller here..
}

Put one line of code in AppDelegate Class;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Thread.sleep(forTimeInterval: 3.0)
return true
}

SwiftUI
For SwiftUI, you can put a very similar code to the accepted answer into ContentView.onAppearing:
struct ContentView: View {
var body: some View {
Text("Hello")
.onAppear {
Thread.sleep(forTimeInterval: 3.0)
}
}
}

Putting a thread to sleep is not a good idea.
I would suggest you go to SceneDelegate's "willConnectTo" function and paste this piece of code and you are good to go.
window?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
guard let _ = (scene as? UIWindowScene) else { return }

Related

3D Touch quick actions not working when called from didFinishLaunchingWithOptions

I'm implementing 3D touch quick actions in my app and I have the following problem:
When the app is already running and so the quick action is initiated through perform Action For Shortcut Item, it works perfectly fine. However, when the app is killed and then launched through a quick action (so didFinishLaunchingWithOptions) it does not take me to the desired view controller, but rather to the home screen of the app.
Here is my code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//... other stuff I'm doing
if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
shortcutItemToProcess = shortcutItem
}
return true
NOTE: I've read previous SO answers where they said that I need to return false in didFinishLaunchingWithOptions when the app was launched through a quick action, so that performAction won't get called. I need to always return true however in my didFinishLaunching method because of the other things I'm handling there. I tried however to return false just to see if that causes the problem and the app still behaved the same way.
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
shortcutItemToProcess = shortcutItem
}
Here is how I present the view controller:
func applicationDidBecomeActive(_ application: UIApplication) {
if let shortcutItem = shortcutItemToProcess {
if shortcutItem.type == "com.myName.MyApp.myQuickAction" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myViewController = storyboard.instantiateViewController(withIdentifier: "myViewController") as! MyViewController
if let navVC = window?.rootViewController as! UINavigationController? {
navVC.pushViewController(myViewController, animated: true)
}
}
So this works fine when app is already running, but it lands me on the home page of my app when the app is killed. What am I doing wrong and how can I solve this?

iOS state restoration issue with DrawerController

I have an app written in Swift 3.1, using Xcode 8.3.3.
I am currently trying to implement state preservation/restoration.
To do this I have implemented shouldSaveApplicationState and willFinishLaunchingWithOptions methods in AppDelegate.swift and set to return true:
// AppDelegate.swift
// STATE RESTORATION CALLBACKS
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
debug_print(this: "shouldSaveApplicationState")
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
debug_print(this: "shouldRestoreApplicationState")
restoringState = true
return true
}
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
debug_print(this: "willFinishLaunchingWithOptions")
return true
}
I’ve also provided restoration IDs for all involved viewcontrollers and navigationcontrollers.
I'm using a 3rd party library to handle side drawer navigation container (https://github.com/sascha/DrawerController). The initial viewcontroller is set programmatically inside the didFinishLaunchingWithOptions method, see below.
// AppDelegate.swift
var centerContainer: DrawerController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let centerViewController = mainStoryboard.instantiateViewController(withIdentifier: "RootViewControllerNav") as! UINavigationController
let leftViewController = mainStoryboard.instantiateViewController(withIdentifier: "SideDrawerViewController") as! UITableViewController
centerContainer = DrawerController(centerViewController: centerViewController, leftDrawerViewController: leftViewController)
centerContainer?.restorationIdentifier = "DrawerControllerView"
window = UIWindow(frame: UIScreen.main.bounds)
window?.restorationIdentifier = "MainWindow"
window?.rootViewController = centerContainer
window?.makeKeyAndVisible()
return true
}
When app opens and attempts to restore state, it displays the correct viewcontroller (last controller before app closed) temporarily, then once app becomes active it reverts back to the initial viewcontroller.
For example, the following happens:
Open app Navigate to the “settings” view via the side menu
Navigate to the home screen
Stop running xcode and start it again
App will open showing settings view, then revert back to home view
Can anyone tell me what is causing this, or where I am going wrong? Let me know if you need any more code examples.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let leftSideDrawerViewController = mainStoryboard.instantiateViewController(withIdentifier: "SideDrawerViewController") as! UITableViewController
let centerViewController = mainStoryboard.instantiateViewController(withIdentifier: "RootViewControllerNav") as! UINavigationController
let navigationController = UINavigationController(rootViewController: centerViewController)
navigationController.restorationIdentifier = "navigationControllerKey"
let leftSideNavController = UINavigationController(rootViewController: leftSideDrawerViewController)
leftSideNavController.restorationIdentifier = "LeftNavigationController"
self.drawerController = DrawerController(centerViewController: navigationController, leftDrawerViewController: leftSideNavController, rightDrawerViewController: nil)
self.drawerController.openDrawerGestureModeMask = .all
self.drawerController.closeDrawerGestureModeMask = .all
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = self.drawerController
return true
}

performSegue before the View is visible

I have two views
ViewController-> Main view
LoginVC -> LogIn View
My initial view is ViewController which contains buttons and some text.
What i want to achive
Perform a segue that will transfer the view to Login if the user is not yet log in.
What i have done
Inside my ViewController I did a check if USER_ID is nil then if its a nil then i will perform a segue.
override func viewWillAppear(animated: Bool) {
if Globals.USER_ID == nil{
self.performSegueWithIdentifier("goto_login", sender: nil)
// dispatch_async(dispatch_get_main_queue(), {
// self.performSegueWithIdentifier("goto_login", sender: nil)
// })
}
}
What is my problem
My Problem is whether I use viewWillAppear or viewDidLoad or viewDidAppear to transfer view from ViewController to LoginVC.ViewController will be visible in a single second before the the login screen appears and i want to get rid of it.Can someone help me solve this issue.
I have same functionality in my App. I achieved it by checking for userID in AppDelegate.swift inside function func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
So, delete the segue and your modified code will look like as follows:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
if Globals.USER_ID == nil{
let loginScreen = storyBoard.instantiateViewControllerWithIdentifier("LoginVC")
self.window?.rootViewController = loginScreen
}else{
let mainScreen = storyBoard.instantiateViewControllerWithIdentifier("ViewController")
self.window?.rootViewController = mainScreen
}
return true
}
This way you will not need any of the method like viewDidLoad(), viewDidAppear() or viewWillAppear()to be used for this purpose.
Just as an addition to this answer - Use the instance of AppDelegate again after logout happens. For example you should do following in logout method:
func logout() {
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
Globals.USER_ID = nil
appDelegate.window?.rootViewController = loginScreen
}
}
This way you can clear the userID and navigate back to login screen after the user logouts.

iOS Swift splash screen from Storyboard

In my iPhone app need to display a splash screen (displays company logo and some info) for 2-3 seconds before load the first page.
Also the app needs to decide which page should load as the first page in here (according the 'initial setup completion' level by the user).
I am using 'Swift' as the programing language and 'Universal StoryBoard' to design interfaces...
I have seleted the Main.storyboard as the Launch Screen File. In the ViewController class have implemented following logic
override func viewDidAppear(animated: Bool) {
NSLog("Before sleep...")
sleep(2)
NSLog("After sleep...")
self.controllNavigation()
}
func controllNavigation() -> Void {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var nextViewController
if (Condition1)
{
nextViewController = storyBoard.instantiateViewControllerWithIdentifier("MainMenu") as! MainMenuViewController
}
else
{
nextViewController = storyBoard.instantiateViewControllerWithIdentifier("UserSetup") as! UserSetupViewController
}
self.presentViewController(nextViewController, animated: true, completion: nil)
}
All works ok but While waiting with sleep(2), refresh page sort of a thing happens. I am not sure if this is the best way to do. Like to hear ideas. Thanks
Use the delay in app delegate instead of viewController's viewDidAppear.
Use as:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
NSLog("Before Sleep");
sleep(2);
NSLog("After sleep");
return true
}
Doing this will allow your splash screen to stay till 2 seconds and then you can use following in your view controller:
override func viewDidAppear(animated: Bool) {
self.controllNavigation()
}
Hope this helps :)
I came across similar answers which mentioned sleep(), however it doesn't seem appropriate to use as it blocks the main thread.
I came up with this solution if you are using the default LaunchScreen.storyboard in Xcode 7.2:
func showLaunchScreen() {
let launchScreen = UIStoryboard(name: "LaunchScreen", bundle: nil)
let launchView = launchScreen.instantiateInitialViewController()
self.view.addSubview(launchView!.view)
let timeDelay = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC)))
dispatch_after(timeDelay, dispatch_get_main_queue()) {
UIView.animateWithDuration(0.5, animations: { _ in
launchView?.view.alpha = 0.0
}) { _ in
launchView!.view.removeFromSuperview()
}
}
This will show the launch screen for another 2 seconds, then fade it out over 0.5 seconds. You can call you other function to load the 'default' VC in the completion handler.
I have solved this issue with this:
On my project I set the desired storyboard of splash (in my case Splash.storyboard) as Default Screen -> Targets/ (your target)/ Info/ Launch Screen Interface File Base Name.
Field on Project Settings
When you've done that, you can insert your logic on method "func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {} " placed on AppDelegate.swift .
If you want to make clean code, you can insert all your navigation related methods on another Class, and call them from AppDelegate.swift .
With this solution, the splash is visible only the required time, and when it finish, you can navigate to the screen you need.
Swift 4 Update 100% working
Just write one line of code
Thread.sleep(forTimeInterval: 3.0)
in the method of didfinishLauching.... in appdelegate class.
Example
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Thread.sleep(forTimeInterval: 3.0)
// Override point for customization after application launch.
return true
}

How to check NsUserDefaults from launch screen in IOS?

I am new in iOS application development. In my application I have a launch screen,login page and a home page. The launch screen is in separate storyboard and I want to check NSUserDefaults from launch screen to decide the user already logged in or not or how to check NSUserDefaults to bypass login screen to home screen.
If with launch screen you mean the a storyboard that is displayed while the app is starting then you are out of luck.
Since your app is starting, no code is run and you can not launch anything. You will have to do this is UIApplicationDelegate.
What are you looking for is the application(_:didFinishLaunchingWithOptions:) method in the AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// check whatever, so I can decide which ViewController should be the rootViewController
return true
}
you can access the NSUserDefaults in AppDelegate Class, and also you need to access your ViewController from storyboard to show as home screen.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if (NSUserDefaults.standardUserDefaults().objectForKey("login") == nil)
{
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "login")
}
if (NSUserDefaults.standardUserDefaults().boolForKey("login") == true)
{
do {
let arr1 = try UserProfileDataHelper.find("1")
if arr1?.Type == "Customer" {
currentUser = "Customer"
screenlaunch("MENU")
}else{
currentUser = "Store"
screenlaunch("HOME")
}
} catch _{}
}
return true
}
func screenlaunch(str : String )
{
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = mainStoryboard.instantiateViewControllerWithIdentifier(str)
let navigationController :UINavigationController = UINavigationController(rootViewController: homeViewController)
navigationController.navigationBarHidden = true
window!.rootViewController = nil
window!.rootViewController = navigationController
window?.makeKeyWindow()
}

Resources