Designing User Interfaces in Playgrounds
Note: Updated for Swift 2.0 and Xcode 7 beta 4
I have an on-off relationship with the Interface Builder. In the last projects I tried to use it and I like it a lot for prototyping. But the more complex a project gets the more I hate it. It does take to long to load and often I have to search for the one setting in the different inspectors to get what I want.
One of the thinks I miss, when I don't use the Interface Builder is the interface preview one get while working on the interface. I have started now to use Playgrounds for this.
Let's for this post assume you want to create a login screen for an app. Open a Playground and put in the code:
import UIKit
import XCPlayground
let hostView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
hostView.layer.borderWidth = 1
hostView.layer.borderColor = UIColor.grayColor().CGColor
hostView.backgroundColor = .whiteColor()
XCPShowView("Host View", view: hostView)
Go to View/Assistant Editor/Show Assistant Editor. You should see something like this:
The app is going to have a navigation bar on the login screen. So add the following code at the end of the Playground:
let navigationBar = UINavigationBar(frame: CGRect.zeroRect)
navigationBar.setTranslatesAutoresizingMaskIntoConstraints(false)
navigationBar.setItems([UINavigationItem(title: "Login")], animated: false)
hostView.addSubview(navigationBar)
let views = ["navigationBar": navigationBar]
var layoutConstraints = [NSLayoutConstraint]()
layoutConstraints += NSLayoutConstraint.constraintsWithVisualFormat("|[navigationBar]|", options: [], metrics: nil, views: views)
layoutConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[navigationBar(64)]", options: [], metrics: nil, views: views)
NSLayoutConstraint.activateConstraints(layoutConstraints)
In the Assistant Editor you should see this:
To add a text field add the code
let usernameTextField = UITextField(frame: CGRect.zeroRect) usernameTextField.setTranslatesAutoresizingMaskIntoConstraints(false) usernameTextField.placeholder = "Username" usernameTextField.borderStyle = .Line hostView.addSubview(usernameTextField)
before the definition of the constrains. Change the constraints to
let views = ["navigationBar": navigationBar, "usernameTextField": usernameTextField]
hostView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[navigationBar]|", options: nil, metrics: nil, views: views))
hostView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[usernameTextField]-|", options: nil, metrics: nil, views: views))
hostView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[navigationBar(64)]-20-[usernameTextField(30)]", options: nil, metrics: nil, views: views))
The preview then looks like this:

Uh!?! That looks really bad! In fact this is a bug in Playground. To fix this, either don't use Auto Layout, add the statement usernameTextField.contentMode = .Redraw or add usernameTextField.setNeedsDisplay() at the end of the Playground.
If you have to use different code in the Playground than later in the real app the Playground is kind of useless. For a quick and very dirty fix add usernameTextField.setNeedsDisplay() at the end of the Playground.
The textfield now looks like it should:

After adding a second text field for the password and a button for the login action the code looks like this:
import UIKit
import XCPlayground
let hostView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
hostView.layer.borderWidth = 1
hostView.layer.borderColor = UIColor.grayColor().CGColor
hostView.backgroundColor = .whiteColor()
XCPShowView("Host View", view: hostView)
let navigationBar = UINavigationBar(frame: CGRect.zeroRect)
navigationBar.translatesAutoresizingMaskIntoConstraints = false
navigationBar.setItems([UINavigationItem(title: "Login")], animated: false)
let usernameTextField = UITextField(frame: CGRect.zeroRect)
usernameTextField.translatesAutoresizingMaskIntoConstraints = false
usernameTextField.placeholder = "Username"
usernameTextField.borderStyle = .Line
let passwordTextField = UITextField(frame: CGRect.zeroRect)
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.placeholder = "Password"
passwordTextField.borderStyle = .Line
passwordTextField.secureTextEntry = true
let loginButton = UIButton(type: .System)
loginButton.translatesAutoresizingMaskIntoConstraints = false
loginButton.setTitle("Login", forState: .Normal)
hostView.addSubview(navigationBar)
hostView.addSubview(usernameTextField)
hostView.addSubview(passwordTextField)
hostView.addSubview(loginButton)
let views = ["navigationBar": navigationBar, "name": usernameTextField, "password": passwordTextField, "button": loginButton]
var layoutConstraints = [NSLayoutConstraint]()
layoutConstraints += NSLayoutConstraint.constraintsWithVisualFormat("|[navigationBar]|", options: [], metrics: nil, views: views)
layoutConstraints += NSLayoutConstraint.constraintsWithVisualFormat("|-[name(password,button)]-|", options: [], metrics: nil, views: views)
layoutConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[navigationBar(64)]-20-[name(30)]", options: [], metrics: nil, views: views)
layoutConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:[name(30)]-10-[password(30)]-20-[button]", options: [.AlignAllLeading, .AlignAllTrailing], metrics: nil, views: views)
NSLayoutConstraint.activateConstraints(layoutConstraints)
hostView.subviews.map { $0.setNeedsDisplay() }


