16 Layout Prototyping with Playgrounds¶
With a Swift playground, developers can quickly prototype their ideas and layouts using a minimal amount of code, allowing for instant feedback. In this chapter, you’ll look at some of the benefits of using playgrounds over a full Xcode project.
There are three main issues when using full Xcode projects for layout prototyping:
- Boilerplate code and extra files: When you create a new single view project, Xcode adds boilerplate code along with additional files that you often don’t need.
- Lack of Speed: Iteration is a vital component while building new apps, especially for layout prototyping. With full Xcode projects, you need to repeatedly build and run your project to see how everything works. With playgrounds, you can immediately see the results.
- Dependency: When you build UIs on top of existing projects, you’re dealing with existing code, which means there’s a good chance your new code will influence the existing code and vice versa.
When you prototype your layout, you iterate. Because your time is valuable, playgrounds are useful for prototyping; they also give you some additional benefits, such as:
- Documentation: Playground pages provide clear, convenient step-by-step documentation.
- Xcode integration: You can integrate a playground with an Xcode project. This makes the project’s documentation convenient and accessible. This is something you can find in many of Apple’s latest sample projects.
- Mobility: With the newer iPads, developers can create and run code right on the iPads using Swift Playgrounds. In doing so, they’ll have direct access to features not available on a simulator such as a camera.
These are just some of the benefits of using playgrounds. By the end of this chapter, you’ll have learned how to prototype layouts and provide markup documentation using playgrounds.
Getting started with playgrounds¶
Open the starter project and select the Working with Live View page.
At the top of the page, add the following code:
import PlaygroundSupport
This code imports the PlaygroundSupport
framework, giving you access to the live view.
A live view allows Xcode to interact with a playground to display the executed code. A live view is the view you’ll be able to see and interact with. You can put almost any UI component into a live view.
Setting up the live view¶
Working with a live view requires minimal setup — all you need to do is give the live view a view to display. You can have the live view show UIView
, UIButton
, UIViewController
and more.
At the bottom of the playground page, add the following code:
// 1
let size = CGSize(width: 400, height: 400)
let frame = CGRect(origin: .zero, size: size)
let view = UIView(frame: frame)
// 2
PlaygroundPage.current.liveView = view
With this code, you:
- Initialize a 400×400
UIView
. - Set the playground page’s live view to the initialized
UIView
.
Now that you’ve attached a view to the live view, you’ll learn how to execute code within a playground.
Executing playground code¶
To execute your code, click Execute Playground, which you’ll find below the standard editor.
To execute your code up to a certain line, hover over the line number and click or press Shift-Return.
With the line execution feature, you can run the code line-by-line and gain the following benefits:
- Understanding: You can break down mysterious presentation or business logic behind lengthy code.
- Save time: You can quickly execute either the next line, the next few lines or the remaining lines of code; you don’t have to run everything.
Now that you know how to execute code within a playground, you’ll learn how to display the live view.
Displaying the live view¶
When you execute your playground code, Xcode shows the live view on the right by default. If Xcode disabled the live view, click the Adjust Editor Options menu and select Live View:
With the live view in the Assistant editor, execute the playground. You’ll see a view with a black background.
At this point, you’re ready to use markup formatting to document your playground.
Markup formatting¶
Markup is a computer language made for document annotations. Swift playgrounds support markup, so you can use it to document your playground, which helps organize and clarify your code.
To tell Xcode you’re writing a line of markup, start your code comment followed by a colon, //:
.
Make sure Xcode is set to show you the raw markup code. Choose Editor ▸ Show Raw Markup from the menu. Then, add the following markup text to the top of the page:
//: # Sampling Pads
//: ## Featuring rock, jazz and pop samples.
//: ### By: Your Name
Here, you declare three single-line markup comments. The first has the largest heading font size. Each line that follows has a smaller font size due to the number of consecutive #
.
To see the markup annotation, you need the playground to render the markup text. If you open the Editor menu, you’ll see the Show Raw Markup option has changed to Show Rendered Markup.
Afterward, your page will render something like this:
That’s a great start to your playground documentation, but you’ve got more work to do.
Replace the following markup text:
//: # Sampling Pads
//: ## Featuring rock, jazz and pop samples.
//: ### By: Your Name
With this:
/*:
# Working with [live view](https://developer.apple.com/documentation/playgroundsupport/playgroundpage/1964506-liveview)
## Featuring rock, jazz and pop samples.
### By: Your Name
*/
By starting with /*:
and ending with */
, you’ve declared multiline markup. You also added a link reference to the live view documentation.
After import PlaygroundSupport
, add the following markup text:
//: `view` is used for experimental purposes on this page.
This text describes the role of view
. When rendered, you see this:
To declare inline code, you can put the code between two backticks.
Next, add the following to the top of your playground page:
//: [Previous Page](@previous)
When you’re done with that, at the bottom of the playground page, add this:
//: [Next Page](@next)
These two markup formatting syntaxes are rather straightforward. The first link navigates to the previous playground page, and the second link navigates to the next playground page.
Open the Table of Contents page and add the following markup text:
/*:
# Table of Contents
1. [Working with Live View](Working%20with%20Live%20View)
2. [Music Button](Music%20Button)
3. [Sampling Pad](Sampling%20Pad)
*/
This block of code creates links to the specific playground pages. When adding links, always encode the playground page name according to the URI rules in RFC 3986. Essentially, replace any whitespace with %20
.
Documentation is an essential part of code maintenance and sharing. Now that you know how to document your playground, it’s time to fire up your creativity and start prototyping.
Experimenting with layout¶
In this section, you’ll go through a layout experimentation workflow in a playground.
Open the Working with Live View page, and below PlaygroundPage.current.liveView = view
, add the following code:
view.backgroundColor = .lightGray
view.backgroundColor = .blue
view.backgroundColor = .red
view.backgroundColor = .magenta
With the code above in the playground, you can see incremental changes. In this experimentation stage, suppose you want to see how view
looks with different background colors. To do this, you can run the code above line-by-line, which helps you iterate through the different background colors and immediately see the results.
Execute the code you added, and your live view will look like this:
Below the code you already added, add this:
view.layer.cornerRadius = 50
view.layer.masksToBounds = true
view.layoutIfNeeded()
Now, let’s say you want to alter the view
shape. As you execute your code line-by-line, you need to call layoutIfNeeded()
. In doing so, the view knows to take the latest layout changes into effect.
Execute the code you added, and your live view now looks like this:
Now, you’re going to add a label to customize your view further. Add the following code:
// 1
let label = UILabel()
label.backgroundColor = .white
view.addSubview(label)
// 2
label.translatesAutoresizingMaskIntoConstraints = false
let labelLeadingAnchorConstraint =
label.leadingAnchor.constraint(
equalTo: view.leadingAnchor,
constant: 8)
let labelTrailingAnchorConstraint =
label.trailingAnchor.constraint(
equalTo: view.trailingAnchor,
constant: -8)
let labelTopAnchorConstraint =
label.topAnchor.constraint(
equalTo: view.topAnchor,
constant: 8)
labelLeadingAnchorConstraint.isActive = true
labelTrailingAnchorConstraint.isActive = true
labelTopAnchorConstraint.isActive = true
// 3
label.text = "Hello, wonderful people!"
view.layoutIfNeeded()
With this code, you:
- Add a label with a white background to
view
. - Create and activate constraints programmatically, keeping references to the constraints.
- Set the label’s text and update any
view
’s pending layout changes.
Execute the code, and your live view now looks like this:
Notice the label is clipped. You can update the label’s position by updating the constraints. In addition to that change, you can also make a few more UI adjustments to give the label a facelift.
Next, you’re going to set up the layout and user interface for the label. Below the code you just added, add the following:
// 1
label.font = UIFont.systemFont(ofSize: 64, weight: .bold)
label.adjustsFontSizeToFitWidth = true
// 2
labelLeadingAnchorConstraint.constant = 24
labelTrailingAnchorConstraint.constant = -24
labelTopAnchorConstraint.constant = 24
// 3
view.layoutIfNeeded()
// 4
label.textAlignment = .center
label.backgroundColor = .clear
label.textColor = .white
label.text = "WONDERFUL PEOPLE!"
With this code, you:
- Set the label’s font and tell the label to adjust its font to fit the available width.
- Update the leading, trailing and top anchor constraint constants.
- Update any
view
’s pending layout changes. - Update the label’s text alignment, background color, text and text color.
Execute the code, and your live view now looks like this:
Hmm, not bad. But suppose you want to center the label’s y position in the container and animate the vertical axis constraint. To do that, add the following code below what you’ve just added:
// 1
label.removeConstraint(labelTopAnchorConstraint)
// 2
let labelCenterYAnchorConstraint =
label.centerYAnchor.constraint(
equalTo: view.centerYAnchor,
constant: -32)
labelCenterYAnchorConstraint.isActive = true
// 3
view.layoutIfNeeded()
// 4
UIView.animate(
withDuration: 3,
delay: 1,
usingSpringWithDamping: 0.1,
initialSpringVelocity: 0.1,
options: [.curveEaseInOut, .autoreverse, .repeat],
animations: {
labelCenterYAnchorConstraint.constant -= 32
view.layoutIfNeeded()
},
completion: nil)
With this code, you:
- Remove
labelTopAnchorConstraint
fromlabel
. - Add and activate
labelCenterYAnchorConstraint
, which centerslabel
in its container’s on the vertical axis. - Update any
view
’s pending layout changes. - Animate
label
’s vertical center constraint.
Execute the code you added, and your live view now looks like this:
Great! It looks like you’ve got the hang of experimentation using a playground. Next, you’ll create a custom button.
Creating a custom button¶
With structured and focused playground pages, developers visiting your playground can understand your code with less cognitive load. In this section, you’ll up your game and create a custom button: MusicButton
.
Open the Music Button page. Inside this page, you’ll see MusicButton
, a subclass of UIButton
. MusicButton
contains MusicGenre
, which dictates MusicButton
’s appearance and its associated audio track.
At the end of the page, add the following code:
let size = CGSize(width: 200, height: 300)
let frame = CGRect(origin: .zero, size: size)
let musicButton = MusicButton(frame: frame)
PlaygroundPage.current.liveView = musicButton
//: Different music genre gives `MusicButton` a different look.
musicButton.musicGenre = .rock
First, you initialize musicButton
with a frame. You then set musicButton
to the live view. After that, you create some documentation explaining the code. Finally, you set the music genre of the music button to rock
.
Execute the playground, and you’ll see this:
When developers visit your playground, they may want to understand the effect a music genre has on MusicButton
. At the same time, you may want to let developers know that the button’s music genre is what makes it special.
Below the code you added, add the following to see the jazz
effect:
musicButton.musicGenre = .jazz
Execute the next line of code, and you’ll see this:
Now, add the following code to see the pop
effect:
musicButton.musicGenre = .pop
Execute the next line of code, and you’ll see this:
Next, add the following code to document the audio playing feature of musicButton
:
/*:
Each music genre is associated with an audio track from the **Resources** folder.
You can prepare the audio player by calling `makeAudioPlayer()`.
Afterward, you can call `play()` on the audio player to play the associated audio track`.
*/
let audioPlayer = musicButton.makeAudioPlayer()
audioPlayer?.play()
Here, you add some documentation informing developers about audio tracks, creating an audio player, and getting the audio player to play.
Execute the playground to the final line of code.
You’ll hear a song, specifically an audio track that’s associated with the button’s music genre, so make sure you turn up your sound.
To integrate assets like image, video and audio, you can drag them into the playground’s Resources folder; you’ll find that folder in the Project navigator.
To decrease and increase the audio player’s volume in real-time, add the following code:
audioPlayer?.volume -= 0.5
audioPlayer?.volume += 0.5
Execute the code above line-by-line. Notice how the audio player first decreases and then increases the volume.
Now that you have a grasp on how to prototype in a playground, you’ll put what you’ve learned into creating a more fun and complex view. How about a sampling pad?
Moving code to the Sources folder¶
Playgrounds allow you to share code between playground pages. In this section, you’re going to make MusicButton
accessible to all playground pages.
Open the Music Button page. Comment out MusicButton
’s class declaration.
In the playground’s main Sources folder, open MusicButton.swift, and then uncomment all of the code inside that file.
The MusicButton
code you’ve uncommented is a refactored version of the previous MusicButton
with two main differences:
- It uses
public
access modifiers to make the code accessible to all playground pages. - There’s a new delegate property, and when
touchesEnded(_:with:)
triggers, it notifies that delegate.
Now that you’ve made your code reusable across all playground pages, you’ll create a sampling pad.
Working with more complex custom views¶
When views get complex, it’s a good idea to include documentation on how a complex view works. With clear documentation, other developers can understand your views. In addition to documentation, the live view can make any custom view interactive for developers. They can test, experiment and observe empirical evidence.
Open the Sampling Pad page. Below the frameworks import, add the following code to begin creating the sampling pad:
final class SamplingPad: UIView {
private var audioPlayer: AVAudioPlayer?
init() {
let size = CGSize(width: 600, height: 400)
let frame = CGRect(origin: .zero, size: size)
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func set(_ audioPlayer: AVAudioPlayer) {
audioPlayer.enableRate = true
self.audioPlayer = audioPlayer
}
func playAudioPlayer() {
audioPlayer?.play()
}
func update(_ volume: Float) {
audioPlayer?.volume = volume
}
}
The code declares SamplingPad
, a custom UIView
. SamplingPad
has an audio player and methods that play sound and adjust the audio player’s volume.
Immediately below the code you just added, add the following to customize the sampling pad with music buttons:
//: ### Embed sampling pad and music buttons in a horizontal stack view.
let samplingPad = SamplingPad()
PlaygroundPage.current.liveView = samplingPad
let rockMusicButton = MusicButton(type: .system)
rockMusicButton.musicGenre = .rock
let jazzMusicButton = MusicButton(type: .system)
jazzMusicButton.musicGenre = .jazz
let popMusicButton = MusicButton(type: .system)
popMusicButton.musicGenre = .pop
let horizontalStackView = HorizontalStackView(arrangedSubviews:
[rockMusicButton,
jazzMusicButton,
popMusicButton])
horizontalStackView.distribution = .fillEqually
With this code, you set the live view to SamplingPad
. SamplingPad
initializes with the default 600×400 frame size. You then create the rock, jazz and pop music buttons. Afterward, you embed the buttons into a horizontal stack view with its distribution set to fill equally.
Next, add the following code:
//: ### Embed the horizontal stack view into a vertical stack view.
let verticalStackView = VerticalStackView(
arrangedSubviews: [horizontalStackView])
verticalStackView.translatesAutoresizingMaskIntoConstraints =
false
verticalStackView.axis = .vertical
samplingPad.addSubview(verticalStackView)
Here, you embed the horizontal stack view into a vertical stack view. You also prepare verticalStackView
for Auto Layout, set its axis to vertical, and then add it to samplingPad
.
Below that code, add the following:
//: ### Set up vertical stack view layout.
let verticalSpacing: CGFloat = 16
let horizontalSpacing: CGFloat = 16
NSLayoutConstraint.activate(
[verticalStackView.leadingAnchor.constraint(
equalTo: samplingPad.leadingAnchor,
constant: horizontalSpacing),
verticalStackView.topAnchor.constraint(
equalTo: samplingPad.topAnchor,
constant: verticalSpacing),
verticalStackView.trailingAnchor.constraint(
equalTo: samplingPad.trailingAnchor,
constant: -horizontalSpacing),
verticalStackView.bottomAnchor.constraint(
equalTo: samplingPad.bottomAnchor,
constant: -verticalSpacing)])
samplingPad.layoutIfNeeded()
This code adds and activates constraints for verticalStackView
. It also gets samplingPad
to update any pending layout changes.
Execute the playground, and you’ll see this:
Immediately below the code you already added, add the following to begin integrating VolumeButton
s into the sampling pad:
//: ### Create and set up layouts for volume controls.
// 1
let decreaseVolumeButton = VolumeButton(type: .system)
decreaseVolumeButton.volumeButtonType = .decrease
let increaseVolumeButton = VolumeButton(type: .system)
increaseVolumeButton.volumeButtonType = .increase
let volumeSlider = VolumeSlider()
// 2
let volumeButtonsStackView =
HorizontalStackView(
arrangedSubviews: [
decreaseVolumeButton,
increaseVolumeButton])
volumeButtonsStackView.distribution = .fillEqually
let volumeControlsStackView =
HorizontalStackView(
arrangedSubviews: [
volumeSlider,
volumeButtonsStackView])
verticalStackView.insertArrangedSubview(
volumeControlsStackView,
at: 0)
// 3
volumeButtonsStackView.widthAnchor.constraint(
equalToConstant: 120).isActive = true
samplingPad.layoutIfNeeded()
With this code, you:
- Create an increase
VolumeButton
, decreaseVolumeButton
andVolumeSlider
. - Arrange the controls inside of the stack views, and insert the final stack view into
verticalStackView
. - Give the volume buttons stack view a width. You also update any pending layout changes in
samplingPad
.
Execute to the last of line code, and you’ll see this:
Now, add the following code to customize the volume control’s stack view layout:
//: ### Add spacer view to `volumeControlsStackView`.
let leftSpacerView = UIView()
let rightSpacerView = UIView()
volumeControlsStackView.insertArrangedSubview(
leftSpacerView,
at: 0)
volumeControlsStackView.addArrangedSubview(rightSpacerView)
//: ### Add width constraints to spacer views.
NSLayoutConstraint.activate(
[leftSpacerView.widthAnchor.constraint(equalToConstant: 8),
rightSpacerView.widthAnchor.constraint(
equalTo: leftSpacerView.widthAnchor)])
samplingPad.layoutIfNeeded()
With this code, you push the custom volume controls inward using spacer views, 8 points from the left and the right.
Execute to the last line of code, and you’ll see this:
To notify the sampling pad of a MusicButton
touch action, add the following code to the end of the page:
//: ### Set up `samplingPad` with `MusicButtonDelegate`.
extension SamplingPad: MusicButtonDelegate {
func touchesEnded(_ sender: MusicButton) {
guard let audioPlayer = sender.makeAudioPlayer()
else { return }
set(audioPlayer)
playAudioPlayer()
volumeSlider.setValue(1, animated: true)
}
}
Here, you extend SamplingPad
to handle setting up the audio player for the music buttons. You also adjust the audio player’s volume back to 1
anytime a music button triggers didTouchUpInside(_:)
.
Add the following code to the end of the page:
//: ### Set up `SamplingPad` with `MusicButtonDelegate` adoption.
[rockMusicButton, jazzMusicButton, popMusicButton]
.forEach { $0.delegate = samplingPad }
This code sets the delegate of all rock, jazz and pop music buttons to samplingPad
.
Execute to the last line of code. Click a music button in the live view, and you’ll hear some music playing.
You’re getting close, but you’re not done yet, so add the following code to the end of the page:
// ### Update volume slider to associate with volume button action.
extension SamplingPad {
@objc func volumeSliderValueDidChange(
_ sender: VolumeSlider) {
audioPlayer?.volume = sender.value
}
}
volumeSlider.addTarget(
samplingPad,
action: #selector(
SamplingPad.volumeSliderValueDidChange),
for: .valueChanged)
Now, when you slide the volume knob across the slider, the sample pad’s audio player will adjust its volume accordingly.
Next, add the following code to the end of the page:
// ### Set up `SamplingPad` with volume buttons.
extension SamplingPad {
@objc func volumeButtonDidTouchUpInside(
_ sender: VolumeButton) {
let change: Float =
sender.volumeButtonType == .increase ? 0.2 : -0.2
volumeSlider.value += change
audioPlayer?.volume = volumeSlider.value
}
}
Here, you extend SamplingPad
to make VolumeButton
increase or decrease the volumeSlider
’s value and audioPlayer
’s volume. The change in volume and slider is either an increase or decrease of 0.2
depending on the volume button’s type.
Below the code you just added, add the following:
[increaseVolumeButton, decreaseVolumeButton].forEach {
$0.addTarget(
samplingPad,
action: #selector(
SamplingPad.volumeButtonDidTouchUpInside(_:)),
for: .touchUpInside) }
This code sets the volume button’s delegate
to samplingPad
, which enables the volume increase/decrease features using the plus or minus volume buttons in samplingPad
.
Finally, execute to the last line of code — and there you have it, your own sampling pad. Go crazy, mix-up some sick tracks and blow some minds!
You’ve gone a long way in this chapter. You started by prototyping a simple magenta view. You then took what you learned early on and developed a sampling pad — and you’ve done it all using playgrounds.
Playgrounds help you get your ideas quickly off the ground. They also let you run code line-by-line, get responsive and immediate feedback and help you to create wonderful documentation for your code, which is perhaps one of the most overlooked gems in a development process. Documentation is vital for clear communication, productivity, code sharing.
Key points¶
- Playgrounds are arguably the quickest way to go from idea to prototype.
- In comparison to an Xcode project, playgrounds reduce the amount of boilerplate code developers must see. Developers also won’t need to build and run in the prototype phase continuously and will no longer need to risk having existing code unintentionally influencing new code and vice versa.
- Use markup formatting to structure and format your playground content. Aim for clarity, focus, and ease of experimentation.
- Playgrounds have a live view to display interactive content.
- Playgrounds allow developers to run code up to a particular line; then, write and execute new code without having to stop and execute the playground entirely again.
- Place playground assets in the Resources folder.
- Place code you’d like to make accessible to all playground pages in the Sources folder.