0%

Tips for Developing a Swift Playground

If you have experience in iOS development with Swift, making a Swift Playground application would not be a tough job for you. However, there are still some differences between developing an iOS application and a Swift Playground application, for example, using Storyboard and Xib files is almost impossible, and the code execution queue might be a little bit different. Here are some tips that can help iOS developers to quickly hands-on Swift Playground application development. Briefly speaking, here are the things that you should know about developing a Swift Playground:

  • Create files in ./Sources/ folder to write your primary code (classes definition, instance method, struct definition, etc.), instead of writing everything directly in Contents.swift.
  • Use proper layout with NSLayoutConstraint, as it is too difficult to introduce the interface builder to your Swift Playground application.
  • Use Mark Up grammar with special indicators to write a good looking and interactive playground instruction.
  • Write, AirDrop, test run, and again.

Note that the sample code in this article can be found in this repository.

Now You Hear Me

Adding Source File to Your Project

Even though the single view playground template defines a UIViewController class in the file Contents.swift, the ./Sources/ folder is capable of placing the source code files, and it is strongly recommended to create the classes in separate files instead of writing everything in Contents.swift.

I personally noticed that there would be unreasonable dispatch queue issues with the code execution. In brief, the code execution queue in Contents.swift may not be identical to the queue that is supposed to be executed, this could cause run time error, and of course, iPad would not indicate anything about the error: it would just suggest that there is something going wrong, and you need to check your code. Sometimes the error would be eliminated with DispatchQueue.main.async method, others are more complicated, and the performance varies from the version of iPad, and from each run to other runs. For instance, the Playground workes fine on iPad 2018 would fail on iPad Pro, and the code that works well this time might fail the next time you run it. Thus, to make your life easier, simply place most of the code on the files in ./Sources/, instead of writing everything in a single file.

By the way, do not forget to use public notation in front of the shared variable definition and override function definition since the code in ./Sources/ is actually a package. Here is some sample code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public func delay(for seconds: Double, block: @escaping ()->()) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: block)
}

public class SequenceViewController: UIViewController {
public var backgroundView: UIView = UIView()

public override func loadView() {
super.loadView()
// do something
}

@objc public func startButtonTapped() {
// do something
}

public func playNote(at index: Int, min: Int, max: Int) {
// do something
}
}

Layout Your Interface

For Swift Playground projects, applying storyboard files or xib files is extremely complicated and could cause lots of problems. Besides, considering the functionality of the Swift Playground, the interface is usually simple. Thus, applying layout with code is a good option. NSLayoutConstraint is used for this purpose.

Where to Add the Code

By creating an iOS playground with the Single View template, you will notice that the layout process is done in layoutView, given as follows.

1
2
3
4
5
6
7
8
9
10
11
12
override func loadView() {
let view = UIView()
view.backgroundColor = .white

let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black

view.addSubview(label)
self.view = view
}

However, as far as I've noticed, writing the codes for layout within layoutView works fine for the older version of Swift Playground in iOS 12, it causes layout problems in iOS 13, with a newer version of Swift Playground. You can try this older version of Swift Playground and see whether the layout works fine on your device. To fix the layout problem that the newer version brings, I'd suggest you only write the code for instantiating view instance of the UIViewController in loadView, and leave any other layout code for viewDidLoad(), as we usually do for normal iOS projects.

1
2
3
4
5
6
7
8
9
10
11
public override func loadView() {
super.loadView()

self.view = backgroundView
backgroundView.backgroundColor = backgroundColor
}

public override func viewDidLoad() {
super.viewDidLoad()
// Layout code here
}

Applying Constraint Programmatically

Since we've figured out where we should add the code for layout, now it's time to implement them.

Anchors of UIView

The essence of the layout is to place the views to the right location in its container, and the so-called 'right location' is usually defined by the view's height and width, the distance to the view next to it, or the horizontal or vertical centering. NSLayoutAnchor, as one of the member variables of UIView, is the thing that forms the relation. There are anchors on each boundary of the view, width, and height, as well as the center. The name of the anchors including topAnchor, bottomAnchor, leadingAnchor, trailingAnchor, widthAnchor, heightAnchor, centerXAnchor and centerYAnchor.

Before applying the constraint, do remember to set the view's translatesAutoresizingMaskIntoConstraints property to false, or the constraint would not take effect.

1
nView.translatesAutoresizingMaskIntoConstraints = false

Activating the Constraint

For static layout, you just need to activate the NSLayoutConstraint in loadView(). By accessing the member method constraint of different anchors, it is easy to set up the relationship between anchors, say, constraints. Here are the sample codes.

1
2
3
4
5
6
NSLayoutConstraint.activate([
nView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 64),
nView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -64),
nView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64),
nView.heightAnchor.constraint(equalToConstant: 32)
])
1
2
3
4
5
6
NSLayoutConstraint.activate([
sButton.widthAnchor.constraint(equalToConstant: 160),
sButton.heightAnchor.constraint(equalToConstant: 48),
sButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0),
sButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -32)
])

Note that the constant parameter defines the deviation of the layout, the positive direction of the X and Y axis are right and down.

Using Constraint as Member Variable

In some conditions, the layout needs to be changed (e.g. progress bars). The NSLayoutConstraint can be initialized and set active later on, as follows.

1
2
3
4
5
6
public var vConstraint: NSLayoutConstraint!
public override init(frame: CGRect) {
super.init(frame: frame)
vConstraint = lView.heightAnchor.constraint(equalToConstant: 16)
vConstraint.isActive = true
}

To change the constraint (even with animation), use the following code.

1
2
3
4
5
6
7
8
9
10
UIView.animate(
withDuration: timeInterval,
delay: 0,
options: .curveEaseInOut,
animations: {
vConstraint.constant = 32
self.layoutIfNeeded()
},
completion: nil
)

Write a Mark Up

The mark up is used to instruct the users to use your Swift Playground, sometimes modify your code a little. Basically, variables that can be assigned to new values in Contents.swift are global variables, say, variables defined outside the class or structure and marked as public in files in ./Sources/ folder.

For the details of the mark up language, you can refer to Apple's Document.

Debugging Tips

The debug process is tough for Swift Playground projects since you cannot add breakpoints1. Using the 'Step Though My Code' or 'Step Slowly' could be a solution, but they are still not fancy. Most of the time, the developing process is simply write -> AirDrop to your iPad -> run and find error -> again, it is not odd to see almost a hundred duplicated playground files in your Swift Playgrounds app when you are debugging. Sometimes, methods such as commenting the doubtful code or use UITextField as an output console (stupid option, but it is true that the console is not available on iPad, which means you could not simply print and check the errors) would help a little. Anyway, good luck with that!

Reference


  1. https://stackoverflow.com/questions/29710286/debug-breakpoint-in-swift-playground↩︎