I am making a simple fact app (In swift) and all the facts are in separate view controllers and when the user closes the app, it all goes to the main page and you have to start from fact 1 again.
I want to know how to save what view controller the user was on when they closed it and when they continue through the facts again,it starts on that fact. Thanks:) also if someone makes a video i think it will get enough views to start a YT channel
Updated to Swift 5
You can use UserDefaults which will remember your last ViewController.
First of all, you have to store some integer when you load any view like shown below:
FirstView.swift
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.setValue(0, forKey: "View")
// Do any additional setup after loading the view, typically from a nib.
}
SecondViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.setValue(1, forKey: "View")
// Do any additional setup after loading the view.
}
Now In your AppDelegate.swift read that stored values and load the ViewController related to that Integer this way:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let viewCount = UserDefaults.standard.integer(forKey: "View")
var VC = UIViewController()
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
println(viewCount)
if viewCount == 0 {
//FirstView should initiate
VC = storyboard.instantiateViewController(withIdentifier: "First") as! ViewController
} else if viewCount == 1 {
//SecondView should inititate
VC = storyboard.instantiateViewController(withIdentifier: "Second") as! SecondViewController
} else {
//ThirdView Should Initiate
VC = storyboard.instantiateViewController(withIdentifier: "Third") as! ThirdViewController
}
self.window?.makeKeyAndVisible()
self.window?.rootViewController = VC
return true
}
Check out THIS sample project for more Info.
Related
I am really new to Swift and have a really simple setup. There is a login screen and if the user logs in with correct credentials they go to the first VC in a tab bar.
The transition from login to the tab bar view controllers work fine but when I load the app I am not taken to the login screen if currentUser is nil.
Here is my check in the first VC in the tab bar:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if Auth.auth().currentUser == nil {
goToLogin()
}
}
And this is the function in the same VC to go to login
func goToLogin() {
let loginViewController = storyboard?.instantiateViewController(identifier: Constants.Storyboard.loginViewController) as? LoginViewController
view.window?.rootViewController = loginViewController
view.window?.makeKeyAndVisible()
}
The storyboard ID for my login VC is "loginViewController" and the Tab bar controller is my initial view controller
Update
My auth check in App Delegate looks like this:
if Auth.auth().currentUser == nil {
// considering the StoryboardID is same as View Controllers name.
self.makeRootVC(storyBoardName: "Main", vcName: Constants.Storyboard.loginViewController)
} else {
self.makeRootVC(storyBoardName: "Main", vcName: Constants.Storyboard.homeTabBarController)
}
But I am getting this error from the "homeTabBarController":
Type 'Constants.Storyboard' has no member 'homeTabBarController'
homeTabBarController is the StoryboardId for my tab bar controller in the Main storyboard. So why is it not being recognized?
You should do this check in didFinishLaunchingWithOptions in AppDelegate not in ViewController. You can use the below function to change your root view controller.
In AppDelegate.swift
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if Auth.auth().currentUser == nil {
// considering the StoryboardID is same as View Controllers name.
self.makeRootVC(storyBoardName: "Main", vcName: Constants.Storyboard.loginViewController)
} else {
self.makeRootVC(storyBoardName: "Main", vcName: Constants.Storyboard.yourTabBarController)
}
return true
}
func makeRootVC(storyBoardName : String, vcName : String) {
let vc = UIStoryboard(name: storyBoardName, bundle: Bundle.main).instantiateViewController(withIdentifier: vcName)
let nav = UINavigationController(rootViewController: vc)
nav.navigationBar.isHidden = true
self.window?.rootViewController = nav
let options: UIView.AnimationOptions = .transitionCrossDissolve
let duration: TimeInterval = 0.6
UIView.transition(with: self.window!, duration: duration, options: options, animations: {}, completion: nil)
}
All I want to do is go to the login screen if the user = nil. Otherwise it goes to the main/home section of the app which consists of the Tab Bar Controller's first view controller
Here is my app delegate:
import UIKit
import Firebase
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser == nil && !UserDefaults.standard.bool(forKey: "hasViewedWalkthrough") {
let initialViewController = storyboard.instantiateViewController(withIdentifier: "WalkthroughViewController") as? WalkthroughViewController
self.window?.rootViewController = initialViewController
} else if Auth.auth().currentUser == nil && UserDefaults.standard.bool(forKey: "hasViewedWalkthrough") {
let initialViewController = storyboard.instantiateViewController(withIdentifier: "loginViewController") as? LoginViewController
self.window?.rootViewController = initialViewController
} else {
let initialViewController = storyboard.instantiateViewController(withIdentifier: "homeTabBarController") as? MainTabBarViewController
self.window?.rootViewController = initialViewController
}
self.window?.makeKeyAndVisible()
return true
}
I am not able to get to the onboarding or login screen. I am always led to the main screen. I know the screens are rendering fine if I manually set them as initial view controllers in storyboard.
I have tried numerous solutions and none of them are working.
Update
Try this:
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyBoard.instantiateViewControllerWithIdentifier("homeTabBarController") as? TabBarViewController // whatever your swift file is called
self.window?.rootViewController = storyBoard
use that code for each statement for the if statement, obviously change the names of the view controllers to the login one respectively.
For people reading this in the future, iOS 13 introduced SceneDelegate and now all UI related stuff should be configured from that point.
I faced the same issue and solved it by using this code.
The custom window should be configured from here now.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let sceneWindow = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: sceneWindow.coordinateSpace.bounds)
window?.windowScene = sceneWindow
window?.makeKeyAndVisible()
if let navigationController = Storyboard.Main.initialViewController as? UINavigationController,
let viewController = navigationController.children.first as? CountriesViewController {
viewController.inject(CountriesViewModel(provider: CountryAPI(),
informTo: viewController),
navigator: CountriesNavigator(navigationController: navigationController))
window?.rootViewController = navigationController
}
}
I have an Onboarding screen that I'm showing to the new users when they first open the app.
In my appDelegate I check whether is the first launch or not.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var initialViewController = storyboard.instantiateViewController(withIdentifier: "OnBoarding")
let userDefaults = UserDefaults.standard
if userDefaults.bool(forKey: "onBoardingComplete") {
initialViewController = storyboard.instantiateViewController(withIdentifier: "MainApp")
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
Also I have a collectionViewCell that I have some buttons and when I click them I get an Alert with informations.
Example of one button
#IBAction func guide3Btn(_ sender: Any) {
let infoVC = infoService.info(title: "Title", body: "Information")
self.window?.rootViewController?.present(infoVC, animated: true, completion: nil)
}
When the user first launches the app if he clicks the info button gets this:
Warning: Attempt to present <MyApp.InfoViewController: 0x7f91db45cfb0> on <MyApp.OnbBoardViewController: 0x7f91db506af0> whose view is not in the window hierarchy!
If the user reopens the app everything is ok. I know that when we have first launch we have onBoarding as root controller but I can't understand how to fix this.
Update
This is the infoService class. I use a new storyboard to create the alert.
class InfoService {
func info(title: String, body: String) -> InfoViewController {
let storyboard = UIStoryboard(name: "InfoStoryboard", bundle: .main)
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
return infoVC
}
}
You can try add your storyboard instantiate code blocks to main thread using DispatchQueue.main.async like below:
I solved almost all of my whose view is not in the window hierarchy! problems.
DispatchQueue.main.async {
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoViewController
infoVC.infoBody = body
infoVC.infoTitle = title
}
return infoVC
Referenced from : https://stackoverflow.com/a/45126338/4442254
I would like to have my viewController named "DoctorInformationController" appear only when the user launches the app for the first time. Ive seen some people's answers, however i would like to know how to do this in swift please!
Thank you
This code I use that will launch a viewcontoller based on first or second launch of the app.
This in the AppDelegate.
var defaults: NSUserDefaults
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let firstLaunch = defaults.boolForKey("FirstLaunch")
if firstLaunch {
print("Not first launch.")
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewControllerWithIdentifier("navController") as! UINavigationController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
} else {
print("First launch, setting NSUserDefault.")
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewControllerWithIdentifier("firstrunVCstoryboard") as UIViewController
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
}
return true
}
You would need to adapt it to fit your storyboard and viewcontrollers names.
EDIT:
This how I set the value in the first view controller to indicate that first run has completed. So that it will not show again.
self.defaults.setBool(true, forKey: "FirstLaunch")
There is no way to tell if it's the first launch built into the UIKit framework, however you can do it another way.
In AppDelegate.swift, go to applicationDidFinishLaunchingWithOptions method (the first one, usually). Now, add this:
//Check for key "first_launch" in User Defaults
if let _ = NSUserDefaults.standardUserDefaults().objectForKey("first_launch") {
//Set your own global variable to be true. Then, when your ViewController
//loads, do a popup window with that DoctorViewController thingy if the
//variable is true
//Example:
isFirstLaunch = true
//Then, set "first_launch" to be a value so your app will never call this block again
NSUserDefaults.standardUserDefaults().setObject("", forKey: "first_launch")
}
I currently instantiate the storyboards programmatically like so:
var iphone35StoryBoard:UIStoryboard = UIStoryboard(name: "iphone35", bundle: nil)
var initialViewController:UIViewController = iphone35StoryBoard.instantiateInitialViewController() as! UIViewController
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
This is my first time working with push notifications, so I'm unsure as the best practice, but what i'm trying to do is in didReceiveRemoteNotification and while the app is in an active state(foreground), I would like to be able to determine what view the user is currently on to determine if i should A) Call a local notificaiton or B) update the view if the push notification corresponds to the visible view.
Using various other SO related questions, I have tried to access the visibleviewcontroller using these variations of code:
let navigationController = application.windows[0].rootViewController as! UINavigationController
1)
let visibleController: AnyObject? = navigationController.visibleViewController
if visibleController is ProfileViewController {
println("this works")
}
and
2)
let viewControllers: [UIViewController] = navigationController.viewControllers as! [UIViewController]
for vc in viewControllers {
if vc is ProfileViewController {
println("this works")
}
}
3)
if let wd = self.window {
var vc = wd.rootViewController
if(vc is UINavigationController){
vc = (vc as UINavigationController).visibleViewController
}
if(vc is ProfileViewController){
println("this works")
}
}
the problem i'm running into is that I can only obtain the rootviewcontroller and never the visible view controller. The rootviewcontroller in this case is a login screen, and despite me being on the profileViewController, i can only obtain the loginviewcontroller. Is this the best method for dealing with push notifications?
Move to destination view controller from storyboard when push notification will came
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main",bundle: nil)
var destViewController : destVC
destViewController = mainStoryboard.instantiateViewControllerWithIdentifier("destVC") as! destVC
var navigationController = UIApplication.sharedApplication().keyWindow!.rootViewController as! UINavigationController
navigationController.pushViewController(destViewController, animated: true)
This is working for me from appdelegate.
Use notification center for more stuff :
NSNotificationCenter.defaultCenter().postNotificationName("name", object: nil, userInfo:nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "method:", name: "name", object: nil)
func method(notification: NSNotification) {
//Do your stuff.
}
Passing data with notification center
NSNotificationCenter.defaultCenter().postNotificationName("name", object:nil, userInfo:["message":"Unable to add \(homeName) Home"])
func method(notification: NSNotification) {
//Do your stuff.
println(notification.userInfo)
}