How to properly unwrap UIViewController - ios

I can't really understand how to unwrap this piece of code, I tried with if, guard and with forced unwrapping, but they all crash when the id doesn't exist. Is it possible just to make it so that it shows print() in the console and not execute any further code
guard let historyViewController = self.storyboard?.instantiateViewController(withIdentifier: "historyViewNav") else{
fatalError("No history view controller found ");
}
historyViewController.modalTransitionStyle = .flipHorizontal
present(historyViewController, animated: true, completion: nil)

I am not sure if I understand what you are trying to do. Here: fatalError("No history view controller found "); you are crashing the app if the controller can not be initialized from storyboard. If you dont want the app to crash, just use print() in your else statement. If you dont want anything else to happen there you can return afterwards:
guard let historyViewController = self.storyboard?.instantiateViewController(withIdentifier: "historyViewNav") else{
print("No history view controller found ");
return;
}
instantiateViewController(withIdentifier: "historyViewNav") does not return nil if it fails, it raises an exception. See here: link. So your guard statement does not help. Unfortunately I am not aware of straight forward way to check this in runtime, because NSExceptions are not meant to be catchable in swift, as answered here: link
However I suggest to look into the RSwift library, so you do not have to use hardcoded strings as identifiers in your code.

This works IF you put the ID in the field:
override func viewDidAppear(_ animated: Bool) {
if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "myTestID") as? UIViewController
{
vc.modalTransitionStyle = .flipHorizontal
present(vc, animated: true, completion: nil)
}else{
print("error")
}
}

Related

Swift iOS - how to properly use popToViewController

it's again me - learning swift.
Question is simple, i have control views like this:
[Initial]->[NotLogged]->[SignUp]
Now after sign up i have double dismiss, it's really ugly!
I wan't to go straight from SignUp to Initial page.
I tried this code but sadly it closing app without any error.
self.dismiss(animated: true, completion: {
let controllers = self.navigationController?.viewControllers
for vc in controllers! {
if vc is InitialViewController {
_ = self.navigationController?.popToViewController(vc as! InitialViewController, animated: true)
}
}
} )
pushViewController works same as above, there is output:
2020-01-29 20:30:38.342180+0100 BillyBill[44355:19540995] Can't end
BackgroundTask: no background task exists with identifier 10 (0xa), or
it may have already been ended. Break in
UIApplicationEndBackgroundTaskError() to debug.
Simply just pop to root view controller
self.navigationController?.popToRootViewController(animated: true)
You can just use self.navigationController?.popToRootViewController() command in place of
for vc in controllers! {
if vc is InitialViewController {
_ = self.navigationController?.popToViewController(vc as! InitialViewController, animated: true)
}
}

Swift Multiple Storyboard - How to access specific one

I had split a couple profiles into multiple storyboards to try to help with my freeze time because I heard to many storyboards can be the cause of why Xcode would just "freeze" for up to 10 mins sometimes causing a crash in my flow of work. So, I need to access each specific storyboard, yet don't know how. This current code accesses the "Main.Storyboard" file yet, I need to call other storyboard files. Here I am checking if a specific profile is signed in and pushing them to the profile. I had all the profiles in one storyboard but now I separated them. Just need to know how to push to other storyboards. Thanks
func checkIfBusinessLoggedIn() {
Auth.auth().addStateDidChangeListener({ (auth, user) in
if (user != nil) {
Database.database().reference().child("Businesses").child((user?.uid)!).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
print("Business is Signed In")
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Business Profile")
self.present(vc!, animated: true, completion: nil)
}
})
}
})
}
let storyboard = UIStoryboard(name: "your_storyboard_name", bundle: nil)
vc = storyboard.instantiateViewController(withIdentifier: "your_view_controller_name")
//to push that controller on the stack
self.navigationController?.pushViewController(vc, animated: true)
If your storyboard file is "Business.storyboard", "your_storyboard_name" name should be "Business".
If you want to access other storyboards you need to give that name.
self.storyboard will always point to your default storyboard. Instead do this :
let VC = UIStoryboard(name: "VCStoryBoardName", bundle: nil).instantiateViewController(withIdentifier: "InstructionScreenController") as! VC
Use below code for multipe storybord and create ENUM for storyboard name that is easy to specify storyboard name.
//MARK:- Enum_StoryBoard
enum enumStoryBoard:String {
case main = "Main"
case home = "HomeSB"
}
let storyBoard = UIStoryboard.init(name: enumStoryBoard. home.rawValue, bundle: nil)
let objLocationSearch = storyBoard.instantiateViewController(withIdentifier: "LocationSearch") as? LocationSearch
self.navigationController?.pushViewController(objLocationSearch!, animated: true)
Using enum and generic method to return the required object.
enum AppStoryboard : String {
case First, Second
var instance : UIStoryboard {
return UIStoryboard(name: self.rawValue, bundle: Bundle.main)
}
func instantiateVC<T : UIViewController>(viewControllerClass : T.Type) throws -> T {
let storyboardID = (viewControllerClass as UIViewController.Type).storyboardID
guard let viewObj = instance.instantiateViewController(withIdentifier: storyboardID) as? T else {
throw ExcpectedError.intantiationErro(msg:"ViewController with identifier \(storyboardID)")
}
return viewObj
}
}
use this code to instantiate your viewController
let second = try! AppStoryboard.Second.viewController(viewControllerClass: SecondViewController.self) self.present(second, animated: true, completion: nil)

Swift alert custom show and dispense

I created a custom alert in a viewcontroller, following the guidelines of the most voted answer of that question:
https://stackoverflow.com/a/37275840/6196609
I use this to display the alert, it is used as a "loading".
let pending = UIAlertController()
override func viewDidLoad() {
super.viewDidLoad()
[…]
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let pending = storyboard.instantiateViewControllerWithIdentifier("alertaLoad")
pending.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
pending.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
[…]
}
to show:
self.presentViewController(self.pending, animated: true, completion: nil)
I succeeded in showing it, but I need to terminate it by the viewcontroller that invoked it after the end of my process, not by itself as was done in the example I quoted.
I've tried this but nothing happens.
self.pending.dismissViewControllerAnimated(false, completion: { (vetor) -> Void in
[…]
})
How could I do this correctly?
Call dismisson the presenting UIViewController, not on the presented one:
self.dismiss(animated: true) {
// go on
}

Attempt to present UINavigationController whose view

AppDelegate
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController")as! UIViewController
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
Takes me to LoginViewController
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
I click on the login button takes me to CardsViewController
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
Clicking on back button which is part of the UINavigationController runs goToProfile (above) and takes me to ViewController.swift because that's where the pageController is declared.
let pageController = ViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
The error shows up here
func goToPreviousVC() {
//let currentControllers = self.navigationController?.viewControllers
if viewControllers.isEmpty {
setViewControllers([profileVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
pageController.presentViewController(profileVC, animated: true, completion: nil)
else {
let previousVC = pageViewController(self, viewControllerBeforeViewController: viewControllers[0] as! UIViewController)!
setViewControllers([previousVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
}
Error:
Warning: Attempt to present <UINavigationController: 0x15ce314e0> on <MyApp.ViewController: 0x15cd2c6f0> whose view is not in the window hierarchy!
Basically the flow is like this AppDelegate -> LoginViewController -> ViewController and I need to get to ProfileViewController.
ProfileView has ProfileNavController while CardsView has CardsNavController
ViewController has a storyboardID of pageController.
Both ProfileView and and CardsView are embedded within UINavigationControllers (hence the NavController extension).
The second time I run the app after a fresh install it works perfectly (all the controllers get loaded okay). Should I push viewControllers in AppDelegate?
I've checked your code using Xcode 7, which may not be ideal for resolving this issue because I had to covert your code to Swift 2.0, but here was what I found out.
ISSUE
First time opening the app, this block:
if currentUser() != nil {
initialViewController = pageController
}
else {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
}
self.window?.rootViewController = initialViewController
Will initialize LoginViewController and make it the current window's rootViewController.
At this point there is no pageController initialized
When user taps on the button to go to the Profile screen, this method will be called
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
At this point, pageController is initialized, and off course, there is NOTHING in the viewControllers array. Let's see what happen in the goToPreviousVC method:
Original method looks like this:
let nextVC = pageViewController(self, viewControllerAfterViewController: viewControllers[0] as UIViewController)!
setViewControllers([nextVC], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
One thing you can see obviously is: calling viewControllers[0] could give you a crash because viewControllers is an empty array.
If you use Swift 2.0, it doesn't even let you compile your code :)
SOLUTION
Let's go directly to the solution: Ensure that the pageController is available before trying to call it's viewControllers.
I blindly tried fixing you code in Swift 2.0 and found out that this method would work for you:
BEFORE: In LoginViewController.swift line 63
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
AFTER: Let's fix it like this
let navc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as! UINavigationController
if let viewControllers = pageController.viewControllers where viewControllers.count == 0 {
pageController.setViewControllers([navc.viewControllers[0]], direction: .Forward, animated: false, completion: nil)
}
self.presentViewController(pageController, animated: true, completion: nil)
It's working well here and probably I don't need to show you how the screen transition should look like :)
In case you would like to have the fixed source code as well, please find it HERE. Basically I converted your code to Swift 2.0 and ignored unnecessary parts like Facebook authentication for faster investigation.
Good luck & Happy coding!

opening a tab bar controller on successful login swift

I am extremely new to swift and storyboards. I have an initial view set which presents the user with login or register options. on the success of my login web service, I am trying to open a tab bar. I am getting into the success of the webservice as I can see the response.
My code for attempting to load the tab bar is as folllows in my initial view controller:
func loadHomeScreen()
{
emailField.text = ""
passwordField.text = ""
self.presentViewController(UIStoryboard.tabbarController()!, animated: true, completion: nil)
}
And at the very bottom of that file, I have the following:
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
class func tabbarController() -> TabbarController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("TabbarControllerID") as? TabbarController
}
}
And in my storyboard I have given the tabbarcontroller the id above. When I run the app (tested on the simulator for iphone6), I am getting the error 'found nil while unwrapping an Optional value' and this is pointing to the line of code in my loadHome func above (self.presentViewController(UIStoryboard.tabbarController()!, animated: true, completion: nil))
Any help would be appreciated
You could instead instantiate your storyboard like so and the code would look like this under loadHomeScreen():
emailField.text = ""
passwordField.text = ""
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("TabbarControllerID") as! TabbarController
self.presentViewController(vc, animated: true, completion: nil)
You may need to change the bundle to the "mainBundle" as you have in your code, but this should work.
This may not be the optimal solution if your plan is to extend UIStoryboard for instantiating your ViewControllers but I think this might be a little easier/cleaner, depending on how your project is set up.

Resources