How storyboards works under the hood? - swift

i wonder how storyboards and class are connected.
I deleted all "main" mentions in target/project and info.plist. and set rootViewController in SceneDelegate:
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Dad")
window?.makeKeyAndVisible()
my storyBoard:
ViewController:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .brown
}
}
what I get:
Question is: If I initializing Root ViewController from Storyboard in SceneDelegate. How it knows that background should be brown? I know that I picked ViewController as a Custom Class in Identity Inspector, but How it works under the hood?
like when wrote some code in
class ViewController
it goes into nib file too? How this interaction between storyboard and class works?
p.s: sorry for my english

it goes into nib file too?
No. The nib file only has what you put in the storyboard.
I know that I picked ViewController as a Custom Class in Identity Inspector, but How it works under the hood?
When you did this in your code:
UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Dad")
the nib is read, and iOS now sees that the VC with the identifier "Dad" has the class ViewController, so it makes an instance of that class by calling the init(coder:) initialiser, so that the things stored in the nib can be decoded, followed by a bunch of other lifecycle methods including but not limited to awakeFromNib, viewDidLoad, viewWillAppear, viewDidAppear, etc.
Since viewDidLoad of your class is called at some point, the background is set to brown.

Related

Xcode Moving from Storyboard

I have a Xcode project using storyboards.
I have the typical setup,
Segue in storyboard
Call self.perform("..."....) from view controller
This then calls 'prepare(for segue: UIStoryboardSegue, sender: Any?)' allowing me to write variables in the new view controller before 'viewdidload' is called
Now the project is growing making using storyboards impractical.
We are taking a code approach loading views from code. The issue we are facing is how to pass data to new view before 'viewdidload' is called or what's the correct process to use.
We are loading view using
let nib = UINib(nibName: "TestView", bundle: nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! TestViewController
view.delegate = self
view.dataModel = TestViewDataModel()
present(view, animated: true, completion: nil)
Issue we have is 'instantiate' calls viewdidload but ideally it needs data from 'datamodel'
Thanks
Add a new initializer to TestViewController that takes accepts whatever data you need as parameters.
From within it, call super.init(nibName: "TestView", bundle: nil). UIViewController's initializer will take care of finding the nib, instantiating itself from it, and settings itself as the owner.
class TestViewController: UIViewController {
let dataModel: TestViewDataModel
init(dataModel: TestViewDataModel) {
super.init(nibName: "TestView", bundle: nil)
self.dataModel = dataModel
}
//...
}

IBOutlet nil - button

I have a HomeController and a registerController. The HomeController is embedded in a navigation controller and is the root view controller. If I present the registerController modally in HomeController's ViewWillAppear:
let reg = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "signup") as! RegisterController
reg.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
reg.modalTransitionStyle = UIModalTransitionStyle.flipHorizontal
reg.view.frame = self.view.frame
self.view.addSubview(reg.view)
There is no problem. BUT if I present it by pushing it on the navigation controller stack:
let v = RegisterController()
self.navigationController?.pushViewController(v, animated: true)
the app crashes and says the IBOutlet for the signUp button (which is in registerController) is nil. I've re-created the outlet and cleaned the project and restarted xcode and nothing has worked...
It’s not a “bug”. Your code is what’s at fault. It has nothing to do with pushing vs presenting. It’s that this line is wrong:
let v = RegisterController()
That creates a barebones view controller with no outlets hooked up. The outlets are hooked up in the storyboard instance of this class. Create the view controller from the storyboard as you did in the first code, and all will be well.
let v = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "signup") as! RegisterController

Swift 3+: Multiple Storyboards with TabBarController. Set TabBarController Tab, Segue to desired ViewController

Working in multiple storyboards. Getting from point A->TabBarController(tab 2)->NavigationControllerA->StoryboardReference->NavigationControllerB->ViewControllerA->ViewControllerB. No need for data passing.
My tab bar controller is set up in its own storyboard, with each tab going through a navigation controller to a storyboard reference. Then the storyboard reference links to another storyboard (obv.), and through a navigation controller to ViewControllerA. I then want to go to performSegue to ViewControllerB. This is all done from a UIViewController Extension.
From what I've been reading, I should approach like this:
let tab = 2
let sbOne = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = sbOne.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
tabBarController.selectedIndex = tab
let sbTwo = UIStoryboard(name: "sbTwo", bundle: nil)
let viewControllerA = sbTwo.instantiateViewController(withIdentifier: "ViewControllerA")
performSegue(withIdentifier: "toViewControllerB", sender: self)
I receive NSInvalidArgument: Point A "has no segue with identifier 'toViewControllerB'"
Swift 3+
I’d be surprised if this is the best answer but this is the only solution that worked. A bit of my problem stemmed from lack of understanding. But the concept I arrived at was to separate the functions into two parts. Setting the tabbarcontroller tab, then segueing to the destination using a variable struct.
I ended up setting a struct at Point A.
struct Destination {
static var currentID = Int()
}
And on button press I would set the variable with this and then and call the function below:
Destination.currentID = currentID
goToViewControllerA()
Then using part of the code I had before I could set the tabbar Controller as the root view controller and go to ViewControllerA (the first view controller) on the selected tab. This is done in an uiviewcontroller extension but can also be done in a function in Point A.
goToViewControllerA(){
let tab = 2
let sbOne = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = sbOne.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
tabBarController.selectedIndex = tab
}
It’s then just a matter of checking if the struct static var is set, and segueing to the destination controller (ViewControllerB). This is done in ViewControllerA, or if you have to segue through multiple view controllers you could model your function on each viewcontroller using this in the viewdidload.
If Destination.currentID != Int() {
performSegue(withIdentifier:“toViewControllerBSegue”, sender:self)
}
I’d love to learn a better way of doing this if it exists.

signal SIGABRT on instantiateViewController

I have create a second view controller with a storyboard. I have specified a StoryBoard ID. I have created a class for this controller and specified this class in the story board as well:
import UIKit
import Foundation
class SecondViewController: UIViewController {
// It is really empty
}
Then I am trying to activate this controller doing quite a standard operation:
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let secondViewController = storyBoard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.present(secondViewController, animated:true, completion:nil)
However, when execution reaches and invokes storyBoard.instantiateViewController - I get Thread 1: signal SIGABRT without any description (clicking on it gives nothing):
I have already looked at this QA: Swift error : signal SIGABRT how to solve it - clean & build doesn't solve the problem.
I guess I am missing something in the configuration of my second view controller. But I can not find what exactly. Any advices?
Verify your storyboard filename and use it in the line:
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)

how to call existing navigationcontroller in Swift

I have 3 items in my storyboard. 1. viewController (A) connected to 2. Navigation controller and 3. viewController (B) is NOT connected to anything
All 3 items have restoration identifiers set in the storyboard.
viewController (B) is the initial view controller.
I am trying in B to get the navigation controller that A is attached to, but always returns nil:
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewControllerWithIdentifier("viewControllerA") as! ViewControllerA
print(vc.navigationController?) // always prints nil
why!?
UPDATE#1
I can't declare a UINavigationController like a view controller. I've tried setting navigationcontroller with the id of 'myNavigationController' as storyboardID:
When storyboard, I get this error
let navigationController = storyBoard.instantiateViewControllerWithIdentifier("myNavigationController") as! UINavigationController
print(self.navigationController!) // fatal error: unexpectedly found nil while unwrapping an Optional value
I've also tried setting the id in restoration identifier, It bombed earlier at the instantiating line
#ColdLogic, see what hits I get when I search the entire project for that identifier:
Because you never instantiated the navigation controller, you instantiated view controller A. You would want something like this if you want both the nav controller and the view controller to be setup like they are in the storyboard
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyBoard.instantiateViewControllerWithIdentifier("YourNavControllerIdentifier") as! UINavigationController
let vc = navigationController.topViewController as! ViewControllerA
Your code directly instantiates an object of type ViewControllerA. Which, unless you setup logic to do it, does not have a navigation controller by default.
This Worked for me!
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = storyBoard.instantiateViewController(withIdentifier: "HomeNavigationContoller") as! UINavigationController
let viewController = storyBoard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
navigationController.pushViewController(messageVC, animated: true)
self.present(navigationController, animated: true, completion: nil)
Use NavigationController identifier to access the NavigationController and ViewController A is already attached to it, so it will automatically get loaded to NavigationController