Apr 1st, 2021

🐀 SwiftUI Meets Swift Playgrounds

Continuing the long-standing tradition, together with this year's WWDC, Apple has announced the Swift Student Challenge. To participate, you'll have to prepare an interactive Swift playground (that can be experienced in three minutes).

I participated in this year's challenge and wrote up my experience here

Swift playgrounds allow to create interactive learning experiences in a digital book format. Each book contains chapters and pages which include documentation, guides, and a live preview that executes the code on a page.

Viewers of a Swift playground can view and edit code, use smart code suggestions, and learn to code the a playful way.

While it has been supported for some time now, SwiftUI is available in Swift playgrounds, too, enabling you to build powerful user interfaces and experiences with the framework you're used to for building applications for the Apple ecosystem.

Unfortunately, there are some pitfalls you can sink a lot of time into when using SwiftUI with Swift Playgrounds, I've collected some issues I noticed in this post.

I hope that all problems I listed below will be improved upon eventually, because I can see a lot of potential in explaining code, libraries, and all kinds of projects in a combination of code and prose writing.

Playground Book Platform and Version

By far the biggest factor of whether your playground will work as expected is where you run it. Swift playgrounds can be run in the dedicated application on iPadOS or macOS. You can also run playgrounds in Xcode.

Where it gets a bit complicated, though, is that there are multiple versions of Swift playgrounds. Earlier versions use a different file structure and support different features. Xcode will create playgrounds of earlier versions, whereas the dedicated application will use the latest version.

I noticed that SwiftUI would crash a lot in Xcode, and features such as NavigationViews or Lists would not work at all. This might be fixed in upcoming versions of Xcode, however. Until that, if you really want to be sure your playground works, use the Swift Playgrounds app.

And if you know your guide would work best on a specific device (say you include a lot of touch-based controls, for which you would prefer usage on iPad), make sure to test and run your playground on those devices.

If you want to test compatibility for some common features, add the code below to a playground page and run the code

import SwiftUI
import PlaygroundSupport

PlaygroundPage.current.setLiveView(VStack {
    NavigationView {
        VStack {
            Text("Hello world")
            List(0..<5) {
                i in
                Text("\(i) works")
            }
        }
    }
    .navigationViewStyle(StackNavigationViewStyle())
})

Performance

By default, Swift playgrounds will display results of each line of code (variable values, how often a specific line was invoked, etc.). With many SwiftUI views, this can pile up and make your experience really slow.

There are two possible ways to improve performance, depending on your use case:

The first is pretty simple: If there's no requirement for some code to be on a page, move it to a shared module or file. Once the code is out of the page, it is no longer evaluated constantly and displayed in the results view.

If you do want to show code on a page but want to decrease performance hits, you can disable the results view altogether. You might have noticed the gauge icon next to the button to run your code. If you tap this, you'll see a toggle to hide results. You can try this to see if your performance issues would be fixed by deactivating results.

If this is the case, you can go one step further and disable results for a page. This will hide the option as well, so users are no longer able to show results. Note, however, that this is only possible for playgrounds created with recent versions, supporting manifest files.

Navigate to the place your playground book is stored, and enter the .playgroundbook directory. Then head over to Contents/Chapters/<Your Chapter>.playgroundchapter/Pages/<Your Page>.playgroundpage. In addition to the main.swift file, which contains your page code, you can create a Manifest.plist file, if not already present.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Name</key>
	<string>My Playground</string>
	<key>LiveViewEdgeToEdge</key>
	<true/>
	<key>LiveViewMode</key>
	<string>VisibleByDefault</string>
	<key>PlaygroundLoggingMode</key>
	<string>Off</string>
</dict>
</plist>

Make sure to include PlaygroundLoggingMode set to Off. This disables logging/results for the page.

Persistence Across Pages

There might be a case where you want to persist specific data across all playground pages. Let's say you're building a tutorial where some piece of data is set on the first page and then reused on the following page.

In this case, you can use the PlaygroundKeyValueStore, which is only available in the dedicated playgrounds app.

import PlaygroundSupport

PlaygroundKeyValueStore.current.keyValueStore["someValue"] = .string("Lorem")

There's also a shared data directory, but that is only available in Xcode.


I think my biggest gripe with Swift playgrounds is the combination of lacking up-to-date documentation, a clear explanation of which platforms use which versions, and inconsistencies. Apart from that, sure, the tooling could be a bit better (there's no search feature in the Playgrounds app), but I think having working and well-documented core features is much more important for now.