How to Debug Swift Playgroundbook

How Do You Debug Swift PlaygroundBook?

From a bug report / suggestion I sent to Apple, I got the following reply:

We’ve actually built tools to help debug the auxilliary sources and we did a presentation at WWDC 2018 that demonstrates it. Please view the presentation and get access to the tools here: https://developer.apple.com/videos/play/wwdc2018/413/

Upon further research, I found that they have recently released a Playgrounds Author Template:

The Swift Playgrounds Author Template is a starter Xcode project that will help you create, debug, and produce a Playground book. Using the template you can step through the code for your live view as if it were an app so that you can identify bugs more easily and develop an efficient workflow for developing your Playground books.

This template, requiring Swift 4.1 to run, includes three different targets:

  • PlaygroundBook
  • Book_Sources
  • LiveViewTestApp

Sample Image

You can use the LiveViewTestApp to fully debug your Playground Book right on your Mac with Xcode.

How can I print to console in Swift Playgrounds on iPad?

In order to write debug messages from the iPad to the console, NSLog() has to be used.

public func wannaDebugThis() {
let x = 42
let text = "Debug message with useful information: x = \(x)"
NSLog(text)
}

The output of NSLog() can be found under the process named ExecutionExtension in Console.app on macOS. The output of print messages is only shown as <private>, which can be seen on attached screenshot.

Sample Image

Swift Playgrounds and Playground Books

Swift 5, Xcode 11, Catalina

Swift Playgrounds can now run on either iPad or macOS (10.15.3 or newer).

Related: Swift Playgrounds Release Notes

Caveat: However, the most-recently available "Swift Playgrounds Author Template for Xcode 11.1 (for Swift Playground 3.3)" does not build "as-is" with the current Xcode 11.4.1 release.

Sample Image


Xcode 8

Creating and Running a Playground Book indicates that…

You use both a Mac running Xcode and an iPad to create a playground book.

The only recommended (supported?) workflow for a *.playgroundbook appears to be "edit in Xcode, run on iPad" with Starter.playgroundbook provided as a starting point.

The suggested workflow is:

  1. Make targeted changes to the book’s content and structure in Xcode
  2. Transfer the updated book into Swift Playgrounds using iCloud or AirDrop
  3. Open the updated book and test the changes
  4. Note any additional changes that are needed, and return to step 1

Note that an iPad Playgrounds +New PlaygroundBlank creates a non-book *.playground that can be run both on iPad and in Xcode.

Pages can be added although *.playground is not a *.playgroundbook.

Note: iPad currently uses a newer version of contents.xcplayground than Xcode 9, so CREATE a blank *.playground and ADD new *.playground pages on the iPad to be seen in both iPad or Xcode.

contents.xcplayground

<playground version='6.0' target-platform='ios'>
<pages>
<page name='Page One'/>
<page name='Page Two'/>
</pages>
</playground>

How do I address the “There was a problem running this page” message in Swift Playgrounds?

I have found a way to address this issue. However, I can only offer you a round-ish idea of what the issue is and how to fix it.

The problem seems to be with the variable declarations at the top of the class.

var primaryView: SCNView!
var primaryScene: SCNScene!
var cameraNode: SCNNode!

The exclamation mark following the type indicates that the variable is an implicitly-unwrapped optional (I am quoting from this Stack Overflow response that explains what this is in more depth), but more-or-less the variable will automatically force unwrap whenever it is used. I have a more of a working knowledge of Swift than a technical one, so my guess is that when a GameScene object is created a synthetic initializer tries to set up these variables, it, for some reason, needs to read from the variables, gets nil, and cannot continue.

The solution appears to be putting the values of the variables in their initial declaration (There might be a better solution, but this at least works in my testing.), so that they are not nil for the initializer. Now the reason that I cannot fully explain the issue here is that this only seems to be necessary for the SCNScene variable. I don't really know why this is the case.

Code (*** mark important comments):

import UIKit
import SceneKit
import QuartzCore
import PlaygroundSupport

class GameScene: UIViewController, SCNSceneRendererDelegate {
var primaryView: SCNView!// = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300)) //***This seems to work for reasons beyond me with the implicitly-unwrapped optional***
var primaryScene = SCNScene() //***The implicitly unwrapped optional is removed here***
var cameraNode = SCNNode() //***The implicitly unwrapped optional is removed here (I am not sure if that is necessary but it probably good practice not to use it anywhere intros code. I use it earlier to demonstrate its weird complexities.)***

override func viewDidLoad() {
sceneAndViewInit()
cameraInit()
createGround()
//moonInit() ***I removed this for simplicity in the answer***
}

func createGround() {
var ground = SCNBox(width: 200, height: 1, length: 200, chamferRadius: 0)
var groundPhysicsShape = SCNPhysicsShape(geometry: ground, options: nil)
var groundNode = SCNNode(geometry: ground)
ground.firstMaterial?.diffuse.contents = UIColor.orange
ground.firstMaterial?.specular.contents = UIColor.white
groundNode.position = SCNVector3(0, -6, 0)
groundNode.physicsBody = SCNPhysicsBody(type: .static, shape: groundPhysicsShape)
primaryScene.rootNode.addChildNode(groundNode)
}

func sceneAndViewInit() {
primaryView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
primaryView.allowsCameraControl = true
primaryView.autoenablesDefaultLighting = true

//primaryScene = SCNScene() ***This is not necessary when it is declared above***

self.primaryView.scene = primaryScene
//primaryView.isPlaying = true

self.view = primaryView //***I realized later you need this bit of code or the program will only display a blank screen***
}

func cameraInit() {
//var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)
cameraNode.camera?.fieldOfView = 65
cameraNode.camera?.wantsHDR = false
primaryScene.rootNode.addChildNode(cameraNode)
}

func moonInit() {
let moonScene = SCNScene(named: "Moon.scn")
var moonNode = moonScene?.rootNode.childNode(withName: "Sphere", recursively: true)
var moonPhysicsShape = SCNPhysicsShape(node: moonNode!, options: nil)
moonNode?.position = SCNVector3(0, 0, 0)
moonNode?.scale = SCNVector3(1, 1, 1)
moonNode?.name = "Moon"
moonNode?.physicsBody = SCNPhysicsBody(type: .static, shape: moonPhysicsShape)
primaryScene.rootNode.addChildNode(moonNode!)
}

func renderer(_ aRenderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
print(time)
}
}

let gameScene = GameScene()
PlaygroundPage.current.liveView = gameScene

One final note:
The self.view = primaryView code is really important. I make a note about in the full code, but I have found this way too hard to locate on the internet. I think this has to do with UIViewController (I don't know much about UIKit at all.), but either SceneKit or UIKit just displays a blank screen if you do now have that bit of code. If you don't figure out that these are separate issues they are really hard to decipher.

book example not working in SWIFT Playground

As far as I know, the println() function takes only one parameter.

You either do:

println((num1, num2)) // for printing as a Tuple object

or:

println("\(num1), \(num2)") // for printing as a String object

Reference: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID309


When in a normal project rather than Playground, the code you provided actually works. However, you will have to call doSomething(5, num2: 6) instead. (Swift 1.2/2.0)

Fixing low FPS in Swift Playground

On the Mac, Xcode's "Playgrounds" are super useful for quick experiments but, due to their nature, terribly slow for "real" tasks.

If your code is more than a few pages long, and/or involves working with the UI, as you do with SpriteKit, the Playground may become really slow, sometimes even unresponsive.

"Playgrounds" are part of Xcode and run on top of the iOS simulator - that's how they display graphics and UI in the "Assitant Editor". The iOS simulator is not really known to be fast either.

On the other hand, "Swift Playgrounds" on iOS is a completely different app, even if it shares a lot with its Mac cousin.

Most importantly, it runs in iOS on the real device, with the real hardware processing, not emulation, which makes it ideal to use for SpriteKit, as Apple themselves often shows in their demos.

I would therefore say that your code should indeed run faster/better/properly on the iPad version.

Even if of course, I can't really know, since I don't know your code - you will probably be the one telling us later if using the iPad version made a difference.

Xcode autocomplete does not work in Sources folder of Swift playgrounds

In order to enable autocompletion, you can embed your Playground in a regular Xcode project (e.g. an iOS application). I recommend creating a dummy project for that purpose. Simply drag and drop your playground in this dummy project and make sure to check "Add to target".

Then you can navigate to

Target -> Build Phases -> Compile Sources -> + -> Add other

and add all the files from your source folder. Please note, that you don't need to actually copy the files, a reference is enough for this purpose.

After this process all your source files are built against this dummy target and you can use autocompletion as usual. As far as I know, this is the best practice for debugging Playgrounds right now. Anyway I am curious, if there is an easier way to achieve that.



Related Topics



Leave a reply



Submit