Why do Navigation Controllers Access Views from The Storyboard? - ios

In various tutorials on how to use SegmentControllers, TabBarControllers, etc. it is configured such that the variable representing the view gets its value from an instantiation of the storyboard:
private lazy var summaryViewController: SummaryViewController = {
// Load Storyboard
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
// Instantiate View Controller
var viewController = storyboard.instantiateViewController(withIdentifier: "SummaryViewController") as! SummaryViewController
// Add View Controller as Child View Controller
self.add(asChildViewController: viewController)
return viewController
}()
Why does this code not just get an instance of SummaryViewController?

Adding an instance of a VC from your Storyboard, adds all of the logic and outlets you add in the storyboard. Let's say you have the following (obviously simple) VC:
class MyVC : UIViewController {
func viewDidLoad() {
}
#IBAction buttonPressed(sender : UIButton) {
/// Do something
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mySegue"{
var vc = segue.destinationViewController as! WhateverViewController
}
}
}
where the buttonPressed: func is connected to a button in IB, and you also have a segue with a 'mySegue' identifier. Initializing your VC from the storyboard gives you access to all of these things. You absolutely can instantiate and push a VC, without the use of the storyboard, but you should not do so, when the VC you are pushing has wired IBOutlets, IBActions, etc.... If you want to do this in code, try the following:
let myNewVC = PushedViewController()
self.navigationController?.pushViewController(myNewVC, animated : true)
This will push the myNewVC onto your navigation stack, back button and all, and without using the storyboard.

Related

Displaying and switching between ViewControllers in another ViewController

I'm basically trying to create a custom UITabBarController since I need some specific functionality. The TabBar itself is done and working, but I don't quite know how to display ViewControllers in this CustomTabBarViewController itself.
Assuming i have the following method:
func tabSelected(_ index: Int) {}
and knowing the height of my TabBar through tabbar.frame.size, how do I instantiate two ViewControllers above the TabBar and switch between them when the tabSelected method is called? A transition animation would be even nicer, but not really necessary.
NOTE: my TabBar doesn't inherit from UITabBarController, only from the regular UIViewController, to avoid further confusion.
Here I created sample project:
CustomTabBarViewController
You should have container view for child ViewControllers
Then you should have array with embed ViewControllers
You should call method in
CustomTabBarViewController which change ViewController inside
container view to ViewController from array of VCs at index which you pass as parameter of this method
Start with declaring outlet collection for your TabBar buttons and also get reference for container view where your ViewControllers will be showed
#IBOutlet var tabBarButtons: [UIButton]!
#IBOutlet weak var container: UIView!
then create array for your tab bar items
var items: [UIViewController]?
next create lazy variables for your controllers
private lazy var aVC: A = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return storyboard.instantiateViewController(withIdentifier: "a") as! A
}()
private lazy var bVC: B = {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
return storyboard.instantiateViewController(withIdentifier: "b") as! B
}()
.... this can be simplified by creating method which returns ViewController depending on VC’s identifier
After that append ViewControllers to your items array and also each add as child of your TabBarViewController
override func viewDidLoad() {
super.viewDidLoad()
items = [aVC, bVC]
items!.forEach { addChild($0) }
}
continue with declaring method for setting ViewController
private func setViewController(_ viewController: UIViewController) {
items!.forEach { $0.view.removeFromSuperview(); $0.willMove(toParent: nil) }
container.addSubview(viewController.view)
viewController.view.frame = container.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
viewController.didMove(toParent: self)
}
now add action for your tab bar buttons and get index of button. Then with this index call your tabSelected method
#IBAction func buttonPressed(_ sender: UIButton) {
if let index = tabBarButtons.index(of: sender) {
tabSelected(index)
}
}
inside tabSelected set VC from items depending on index of sender tab bar button
func tabSelected(_ index: Int) {
if let item = items?[index] {
setViewController(item)
}
}
finally in viewDidLoad set first item
override func viewDidLoad() {
...
tabSelected(0)
}
Now you can fully customize your ViewController and make other epic stuff which you know from UITabBarController
Here's another approach:
1. In your CustomTabBarViewController define an array to hold the ViewControllers:
var viewControllers: [UIViewController]
Instantiate the view controllers and add them to the array:
// If you're not using storyboard:
let homeViewController = HomeViewController()
// If using storyboard:
let searchViewController = storyboard.instantiateViewController(withIdentifier: "SearchViewController")
viewControllers = [homeViewController, searchViewController, ...]
2. Define a variable to keep track of the tab button that is selected:
var selectedIndex: Int = 0
3. Implement your tabSelected method like so. I've explained each line in code:
func tabSelected(_ index: Int) {
let previousIndex = selectedIndex
selectedIndex = index
// Use previousIndex to access the previous ViewController from the viewControllers array.
let previousVC = viewControllers[previousIndex]
// Remove the previous ViewController
previousVC.willMove(toParentViewController: nil)
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
// Use the selectedIndex to access the current ViewController from the viewControllers array.
let vc = viewControllers[selectedIndex]
// Add the new ViewController (Calls the viewWillAppear method of the ViewController you are adding)
addChildViewController(vc)
vc.view.frame = contentView.bounds
// contentView is the main view above your tab buttons
contentView.addSubview(vc.view)
// Call the viewDidAppear method of the ViewController you are adding using didMove(toParentViewController: self)
vc.didMove(toParentViewController: self)
}

Thread 1: signal SIGABRT in AppDelegate

Our App should be able to go from the main storyboard to another storyboard which contains a splitViewController that leads to a searchbar.
We created the searchbar in another storyboard and connected it to the navigation controller like this:
#IBAction func artikelButton(_ sender: Any) {
let viewController = self.storyboard!.instantiateViewController(withIdentifier:"MasterViewController")
self.navigationController?.pushViewController(viewController, animated: true)
}
If you then press that button the app crashes with the SIGABRT error.
The other 2 Buttons work fine, the difference is that they are .xib files.
Here's how we made the .xib buttons:
#IBAction func infoButton(_ sender: Any) {
let vc = InfoViewController(
nibName: "InfoViewController",bundle: nil)
navigationController?.pushViewController(vc, animated: true)
}
If you load your viewcontroller from XIB. then self.storyboard will always nil.
if you are using multiple storyboard or multiple XIB's A better solution
have abstract method practice in your every viewcontroller to access from storyboard
Just like add following (change viewcontroller name and identifier as per yours )
// MARK: - Abstract Methods
public class func viewController () -> LoginVC {
return StoryBoard.main.instantiateViewController(withIdentifier: StoryBoard.controller.LoginVC) as! LoginVC
}
// Now access it with LoginVC.viewController() and do push or present whatever your operation you requred
I manage everything in constant file
public struct StoryBoard {
static let main = UIStoryboard.init(name: "Main", bundle: nil)
struct controller {
static let LoginVC = "LoginView"
}
}

How to pass data from ViewController to a ViewController in a tab bar iOS?

I'm trying to pass data from my main ViewController to another ViewController in a Tab Bar.
I have tried using the following code , and got an error Could not cast value of type 'Test.FirstViewController' to 'Test.ViewController'
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
let tab1Controller = self.tabBarController?.viewControllers?.first as! ViewController
print(tab1Controller.test)
}
I just used the following code which just worked fine for me on Xcode 9 with swift 4.0. The following method is declared in the View Controller class which just presents the First View Controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendingToTabBar" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabVC = storyboard.instantiateViewController(withIdentifier: "tabVC") as! UITabBarController
self.present(tabVC, animated: true, completion: {
let vc = tabVC.selectedViewController as! FirstViewController
vc.dataLBL.text = self.dataTF.text!
})
}
}
You can access the tab bar controllers in your ViewController prepare method and set your values.
Prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let barViewControllers = segue.destination as! UITabBarController
let destinationViewController = barViewControllers.viewControllers?[0] as! FirstViewController
destinationViewController.test = "Hello TabBar 1"
// access the second tab bar
let secondDes = barViewControllers.viewControllers?[1] as! SecondViewController
secondDes.test = "Hello TabBar 2"
}
Then in your tab bar ViewControllers declare variables, you want to set the values to.
#IBOutlet weak var label: UILabel!
var test: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
label.text = test
}
FirstViewController
SecondViewController
You could do a number of things, what I would do is to just make a global variable so that both view controllers can access it. Another option is to give each view controller a separate global variable, and when the view is loaded, the variable is set to self, then make a variable that can be set by the other view controller.
example:
var data:Any?
viewDidLoad() {
viewControllerA = self
}

Swift3: Casting UIViewController Subclass Fails

I have a ViewController (BViewController) that's inheriting from another UIViewController Subclass (AViewController). (The reason I want to do this is I'm reusing the same view in storyboard 3+ times for different screens.)
When I call:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "AViewController") as! BViewController
self.show(vc, sender: self)
I get this error:
Could not cast value of type 'test.AViewController' (0x10d08b478) to 'test.BViewController' (0x10d08b3f0).
Here are my subclasses, they have nothing in them.
class AViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
-
class BViewController: AViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
The class in Storyboard is set to AViewController because I'm trying to share IBOutlets across all children without recreating the views. There is only one View Controller Scene in my UIStoryboard.
According to the answer in this thread, it isn't possible to reuse a single UIViewController Scene with multiple subclasses with UIStoryBoard. It is however possible with nib files.
How to use single storyboard uiviewcontroller for multiple subclass
You have to set your view controller's class to BViewController in your storyboard
You probably don't put the right view controller identifier:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "BViewController") as! BViewController
self.show(vc, sender: self)
(BViewController instead of AViewController)
EDIT:
Here's an example: I have a SignupVC view controller in my storyboard, but its storyboard ID is "signup_vc"

How to pass value to secondController

hello I want to pass value to my second Controller.The second Controller is connected to NavigationController and the storyboard Id has been set in NavigationController, not in SecondViewController
I show ViewControllers like this
var _profile : UINavigationController{
get{
return self.storyboard?.instantiateViewControllerWithIdentifier("ShowUserProfileTableViewController") as! UINavigationController
}
}
case 0:
if (self.revealViewController().frontViewController.isKindOfClass(UINavigationController)){
let nav : UINavigationController = self.revealViewController().frontViewController as! UINavigationController
if (!nav.viewControllers[0].isKindOfClass(ShowUserProfileTableViewController)){
self.revealViewController().setFrontViewController(self._profile, animated: true)
}
}
else{
self.revealViewController().setFrontViewController(self._profile, animated: true)
}
break
The simplest approach is to use segues and implement prepareForSegue, like so:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowSecondViewController" {
let navigationController = segue.destinationViewController as! UINavigationController
let targetViewController = navigationController.viewControllers.first as! SecondViewController
targetViewController.someString = "foo"
}
}
Now, this assumes that
the segue was given a storyboard identifier of ShowSecondViewController;
the destination view controller was a navigation controller whose "root view controller" had a base class of SecondViewController; and
the SecondViewController had some property called someString.
Clearly, change these identifier, class, and property names accordingly, but this is the basic approach. And you'd either programmatically call performSegueWithIdentifier or just have a segue between some UI control and the next scene (the navigation controller).

Resources