tl;dr I don’t like using storyboards in bigger projects (more than 4-5 screens). When I start a new project from scratch I ussualy do the steps from this tutorial.
I won’t talk about the issues that storyboards have, but I will recommend this post for anyone interested in this topic.
In this tutorial we are going to remove the storyboard from the Single View Application template, setup a navigation controller and present new screens modaly or by pushing them on top of the navigation stack.
Create a new project
Create a new project and select the Single View Application template. Name the project NoStoryboards
and select swift as the programming language.
Remove the storyboard
In the project navigator right click Main.storyboard
then select delete(or select and press delete) and move to trash.
In the project navigator select your project file.
A list of options should display now. Go to the Deployment Info category and remove the Main Interface.
Optional: Remove the Launch Screen
Remove the LaunchScreen.xib
file. In the project options remove the launch screen.
Create the interface file for the main controller
Right click on the projects main group and select New File…. Select the User Interface category and the View template. Name it ViewController
.
Open ViewController.xib
. Select the File's Owner
placeholder. Open the Identity Inspector and change the class to ViewController
.
Open the Connections Inspector and connect the view
outlet.
Create a new Window
Open AppDelegate.swift
and create a new window.
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.makeKeyAndVisible()
return true
}
...
}
Load the main controller
Create an instance of ViewController
and set it as the root view controller of the window.
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
var mainViewController = ViewController(nibName: "ViewController", bundle: nil)
window?.rootViewController = mainViewController
window?.makeKeyAndVisible()
return true
}
...
}
Modal Controller
Right click on the projects main group and select New File…. Select the Cocoa Touch Class template.
Name the class ModalController
and set the subclass to UIViewController
.
Presenting the modal
Open ViewController.xib
and add a button from the Components Library. Set the button’s title to Modal
.
Write a method that creates and presents a ModalController
.
class ViewController: UIViewController {
@IBAction func didTapModal() {
var modalController = ModalController(nibName: "ModalController", bundle: nil)
presentViewController(modalController, animated: true, completion: nil)
}
}
In ModalController
write the dismiss
method.
class ModalController: UIViewController {
@IBAction func dismiss() {
dismissViewControllerAnimated(true, completion: nil)
}
}
Open ModalController.xib
and add a button from the components library. Set the button title to Dismiss
. Connect the Touch Up Inside event with the dismiss
action.
Setup Navigation
Inside applicationDidFinishLaunchingWithOptions
create a UINavigationController
with mainController
as root view controller. Make the navigation controller the root view controller of the window.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
var mainViewController = ViewController(nibName: "ViewController", bundle: nil)
var navigationController = UINavigationController(rootViewController: mainViewController)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
return true
}
...
}
If you run the project now you will see an empty navigation bar on the screen.
Override the viewDidLoad
method and change the title of the screen to Main
.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Main"
}
...
}
Now the bar should display a title.
Secondary Controller
Create a new view controller called SecondaryController
using the Cocoa Touch Class template.
Open ViewController.xib
and add a button from the components library. Set the button title to Next
.
In ViewController
write a method that creates a SecondaryController
and then pushes it on the navigation stack.
class ViewController: UIViewController {
...
@IBAction func didTapNext() {
var secondaryViewController = SecondaryViewController(nibName: "SecondaryController", bundle: nil)
navigationController?.pushViewController(secondaryViewController, animated: true)
}
}
In Interface Builder connect the Next
button to the didTapNext
action.
Add a button called Next
in SecondaryController
that has the same behaviour as the previous one.
class SecondaryViewController: UIViewController {
...
@IBAction func didTapNext() {
var secondaryViewController = SecondaryViewController(nibName: "SecondaryController", bundle: nil)
navigationController?.pushViewController(secondaryViewController, animated: true)
}
}
Implement a method called didTapBack
that pops the current view controller from the navigation stack.
class SecondaryViewController: UIViewController {
...
@IBAction func didTapBack() {
navigationController?.popViewControllerAnimated(true)
}
}
Add a button called Back
in SecondaryController
and connect it to the didTapBack
action.
Add a property named level
to SecondaryController
. We are going to use this property to name the secondary controllers.
class SecondaryViewController: UIViewController {
var level = 1
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Secondary \(level)"
}
...
}
Change the didTapNext
method.
class SecondaryViewController: UIViewController {
@IBAction func didTapNext() {
var secondaryViewController = SecondaryViewController(nibName: "SecondaryController", bundle: nil)
secondaryViewController.level = level + 1
navigationController?.pushViewController(secondaryViewController, animated: true)
}
...
}
Now we can push an infinite number of secondary controller
Conclusions
You don’t have to use storyboards if you don’t like them. It takes about 5 minutes to remove them from your project.
If you want to learn more about View Controllers and Navigation Controllers you should read the View Controller Programming Guide for iOS.
Why you don’t use storyboards? what’s the point?
Some like chocolate, some like vanilla. It’s just life
I like storyboards, but in some projects with a complex navigation, the connections are awful.
Also, if you have too much views controllers, and You works in a old Mac, load all the views controllers in a single Storyboard file is too slow.
I’ve seen this in many projects that I’ve taken over. Solution is to actually use multiple Storyboards. One for each functional domain of the app. Then you can put a category on UIStoryboard to load the appropriate storyboard that you want to use (storyboardWithName: is sort of ugly to see all over the place), so it would be something like [[UIStoryboard domainStoryboard] instantiateInitialViewController]. If a segue doesn’t suit your needs then don’t use one. didSelectRowAtIndexPath still works fine. Another trick is to name your storyboard identifier the same as your view controller class, then introspection will clearly work.
It’s a nice alternative to folders with a bunch of NIBs in them, and can give you some sense of flow of the app with the layout.
I never know if I start a project with or without the storyboard. I like both methods and both methods have their interests as disadvantages. What criteria do you choose?
I’ve started using storyboards in almost all my projects. I think I would say no to storyboards only in projects that don’t target iOS8.
Storyboards are horrible when working in a larger team – the git conflicts are plain awful.
Also, autolayout is starting to become that bad side effect of having storyboards
The team at apple made a lot of improvement to Storyboards and xibs since iOS 7 – they are diffable – if that’s a word..
Also Autolayout improved a lot since it appeared. Since iOS 8 I’ve started using it with joy
Since IOS 5, 6 & 7 I’ve always programmatically created my views (maybe thats because I read Erica Sadun’s books & picked up my coding style there).
But with AutoLayout, Swift, multiple screen sizes and yearly ios updates I’m started to wonder whether I should just follow Apple’s guidelines & use storyboards. I’ve also noticed how hard this gets when the client wants a custom UI.
Are Storyboards the way to go for large & complex Apps with swift?
Yes, storyboards are the way to go. If the project is to big – split into more storyboars. Or at least use a nib for each view controller. Writing code to generate the UI is generally a bad idea.
doing exact you’ve wrote and won’t push secondaryController.
error: 015-07-28 18:21:13.890 no_storyboard[1396:25818] pushViewController:animated: called on while an existing transition or presentation is occurring; the navigation stack will not be updated.
2015-07-28 18:21:14.347 no_storyboard[1396:25818] Warning: Attempt to present on which is already presenting (null)
Both are a matter of taste. However, these are the reasons why I prefer xibs over storyboards:
1. Storyboards break good programming paradigms by disabling you from calling needed designated initializers. For example, let say a screen has subscribed and is observing for some state change (realtime socket communication or other). Then some data arrives in callback, and it needs to be passed as params to a new view controller. With storyboards, we are forced to create a separate property for storing received data, then performSegue, then grab stored data and pass to new view controller… That sucks. Instead, with xib approach, you just pass received data via custom designated initializer to the new view controller, and that’s it. No need to putting received data somewhere in temporary property.
2. With storyboards there are no construct time dependency injection (only via properties). Anyone who is writing unit tests knows construct time dependency injection is better that property injection because of less bugs.
3. Storyboards break MVC pattern. According to MVC, every screen has to consist of 3 parts: Controller (ViewController), Model (…), and a View (Xib). Storyboards with multiple Views clearly violate this pattern. This means several devs might work on the same file, and that could lead to unneeded merge conflicts.
4. There’s no big benefit in capturing app flow in one/several storyboard files. If a person is smart enough to write code then he’s probably smart enough to have a clean modular project structure, and understand app flow implicitly from project structure. Because as soon as app gets bigger, storyboards become messier and provide less immediate flow.
5. It’s impossible to do navigating part just with storyboards for non-trivial apps. You will still need to code some custom transitioning (custom segues). Also, sometimes you need to open some screens by instantiating them directly from code. This means, navigation flow inevitably becomes a mix of storyboards and a code, and that makes storyboard to lose their primary purpose – to show app navigation flow. That only works for simple <10 screens apps.