Building app without storyboard easier with NSLayoutAnchor
Why not using storyboards? What are the good reasons to build your UI programmatically?
- First of all: merge conflicts, it is one of the most common occurrence when there is more than one developer working on a storyboard at the same time. Since storyboards are auto generated XML, it’s kind of difficult to diagnose and resolve conflicts.
- IBOutlet/IBAction is so annoying when you want to change the variable names, the app will crash if you do not reconnect the view to its new outlet/action name
- Identifiers used for table view cell or storyboard id are in string, so it’s unsafe (easy to make typos) when using in multiple places compared to a static constant in code.
- For nested views/stacked views: very difficult to adjust views which are placed behind a view, and for adding constraints as well
- Change layout: when you delete/move views with auto layout, even when you embed views in another view/scroll view, usually you must delete and recreate a lot of constraints
- Reuse code: it’s easier to share a source file or code for a custom view than a storyboard/xib file
- Some other minor reasons are: compile time for complicated storyboards and refactoring styles in storyboards takes longer, screen size of laptop is not big enough, easier theme support, animate views,… you name it
So in this post, I will show you how to build an table view controller with layout like this
Setup new project
Because we don’t use the storyboard created by default, we have to do a few things first before the application can run properly
Create a single view application project
- Delete
Main.storyboard
file (we don’t need this file anymore) - Select your target project, in
General
tab, scroll toDeployment Info
, delete value ofMain Interface
- Tell the application to open which view controller when the it starts, replace code inside
didFinishLaunchingWithOptions
ofAppDelegate
file with this, don’t worry we will createGamesViewController
file later
window = UIWindow(frame: UIScreen.main.bounds)
let rootViewController = GamesViewController()
let navController = UINavigationController(rootViewController: rootViewController)
window!.rootViewController = navController
window!.makeKeyAndVisible()
return true
Create GamesViewController
- This view controller display a list of Game, it contains a table view that takes up the whole screen, so in this file we have our tableView declared
var tableView: UITableView!
- and somewhere in viewDidLoad
tableView = UITableView()
- We have to set
translatesAutoresizingMaskIntoConstraints
property tofalse
first so we can apply constraints to the tableView, because by default, the property is set to true for any view you programmatically create (If you add views in Interface Builder, the system automatically sets this property to false)
tableView.translatesAutoresizingMaskIntoConstraints = false
- Add this table view to current view controller view
view.addSubview(tableView)
- Now add 4 constraints to this table view so it will take up the whole screen (leading, trailer, top, and bottom)
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
When we create contraint, it returns an inactive constraint of the form Anchor, so we have to activate it by setting isActive = true
Now, add data source for this table view, try running the app with some fixed values, you can see a screen with one table view
Create view for GameTableViewCell
From the image above, we need some views like these in our cell
fileprivate let coverImageView = UIImageView()
fileprivate let gameNameLabel = UILabel()
fileprivate let developerLabel = UILabel()
fileprivate let descriptionLabel = UILabel()
fileprivate let priceLabel = UILabel()
After we setup properties (font, color, priority, …) for those views, we can add constraints to them, but this time instead of activate each constraint one by one, we can activate a group of constraints like this
NSLayoutConstraint.activate([
// game cover
coverImageView.widthAnchor.constraint(equalToConstant: 60),
coverImageView.heightAnchor.constraint(equalToConstant: 80),
coverImageView.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor),
coverImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor),
coverImageView.bottomAnchor.constraint(lessThanOrEqualTo: marginGuide.bottomAnchor),// game name
gameNameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor),
gameNameLabel.leadingAnchor.constraint(equalTo: coverImageView.trailingAnchor, constant: 8),// developer
developerLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor),
developerLabel.leadingAnchor.constraint(equalTo: gameNameLabel.trailingAnchor, constant: 8),
developerLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor),// description
descriptionLabel.topAnchor.constraint(equalTo: gameNameLabel.bottomAnchor, constant: 8),
descriptionLabel.leadingAnchor.constraint(equalTo: coverImageView.trailingAnchor, constant: 8),
descriptionLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor),// price
priceLabel.topAnchor.constraint(greaterThanOrEqualTo: descriptionLabel.bottomAnchor, constant: 8),
priceLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor),
priceLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor)
])
Pretty easy, right?
Source code for this post can be downloaded from here: https://github.com/kentwinder/iOS-NoStoryboard-Demo