Swift, Playgrounds, and XCPlayground

http://www.codeschool.com/blog/2014/12/12/swift-playgrounds-xcplayground/

Swift, Playgrounds, and XCPlayground

Swift Playgrounds are a great feature of Xcode 6 that allow you to create a single file to test out code before adding it to your app’s main codebase. Prior to Playgrounds, if you wanted to test out code, you had a couple options — you could create a new git branch off of master of the app you’re working on, or, if you’re like me, you could open a brand new project with the Single View Application template and try to build enough of a base to start testing the feature you want.

Playgrounds reduce the amount of steps between having an idea and prototyping it, but there’s certain things they can’t do right out of the box, like network requests or showing rendered views when you try to draw UIViews.

Fortunately, these features are just a module import away.

XCPlayground

XCPlayground is a new module that has a few methods to help you get the most out of Playgrounds. Adding it to your playground is as simple as adding an import statement right near the default UIKit import, like this:

import UIKit // there by default
import XCPlayground // add this in

XCP EXECUTION SHOULD CONTINUE INDEFINITELY

Let’s say you want to prototype some code that requests an API endpoint and returns some JSON data. The setup for that scenario might look like this:

// create a session object
let session = NSURLSession(
    configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

// make a network request for a URL, in this case our endpoint
session.dataTaskWithURL(NSURL(string: "http://localhost:8080/notes")!, 
    completionHandler: { (taskData, taskResponse, taskError) -> Void in

    // create an NSArray with the JSON response data
    var jsonReadError:NSError?

    let jsonArray = NSJSONSerialization.JSONObjectWithData(
        taskData, options: nil, error: &jsonReadError) as [AnyObject]

}).resume()

The problem you’ll run into when you run this code, though, is that your playground execution happens so fast the completion handler won’t have enough time to complete before execution stops. That means that none of the code inside of the handler will run, and you won’t have that JSON response data.

The solution? Call XCPSetExecutionShouldContinueIndefinitely() at the top of your file just below the imports, and set the continueIndefinitely boolean argument to true. This tells your playground it shouldn’t stop right away, and you’ll now see the jsonArrayobject has data that’s been returned from the API.

image00

XCP SHARED DATA DIRECTORY PATH

It’s common to prototype apps with local data first. Often the design of the API that eventually returns real data is being built alongside the app, and not having to immediately account for all of the edge cases that come with asynchronous callbacks can help developers focus on view layout and other core features.

In a normal Xcode project you can easily import files and folders directly, but playgrounds don’t offer the same support. Instead, each time a playground is opened, it’s assigned a new random container buried deep in the /var folder. That container holds a Shared Playground Data folder that is symbolically linked to/Users/HOME_FOLDER/Documents/Shared Playground Data, so anything you put in that folder will be available in your playground.

The XCPSharedDataDirectoryPath string constant always holds a reference to that shared folder, so any files you want to access in your project can be put in that Shared Playground Data folder and accessed in the playground.

A SMARTER SHARED DIRECTORY PATH

Putting all of your files in that shared /Documents subfolder is fine until you’ve got multiple playgrounds, so why not organize all of your files per-playground just like you usually do per-project? There’s no direct support out of the box for this, but you can create a helper method that makes things easier. Here’s the one I use:

func pathToFileInSharedSubfolder(file: String) -> String {
    return XCPSharedDataDirectoryPath +
           "/" +
           NSProcessInfo.processInfo().processName +
           "/" +
           file
}

NSProcessInfo().processInfo() creates an object that contains tons of information about what Xcode is currently running, which right now is your playground! The processNameproperty of that process is the same as the file name of your playground, so if I haveNetworkPrototype.Playground, then processName will be NetworkPrototype. That gets appended along with a / to the end of that Shared Playground Data folder path. Finally, I append the file name string I passed into the function as an argument called file.

image02

Now, grabbing the contents of a file inside a playground is a little easier. For example, like grabbing a JSON data object from a local JSON file:

let jsonData = NSFileManager.defaultManager().contentsAtPath(
    pathToFileInSharedSubfolder("data.json"))!

Or, loading a locally stored image into a UIImageView:

let imageView = UIImageView()
imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder("code-school.png"))

XCP CAPTURE VALUE

Prototyping data operations is nice, but you’re also probably going to want to build up some actual views at some point. For example, take the following playground code:

let view = UIView()
view.frame = CGRectMake(0,0,320,568)
view.backgroundColor = UIColor.lightGrayColor()

let imageView = UIImageView()
imageView.frame = CGRectMake(20, 20, 280, 51)
imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder("code-school.png"))
view.addSubview(imageView)

let label = UILabel()
label.frame = CGRectMake(0, 100, 320, 30)
label.textAlignment = .Center
label.text = "Welcome!"
view.addSubview(label)

This creates a view and sets the background color to gray, adds an image view that’s sourced from a local file (see the above section), and adds a label with the text “Welcome!”. One way to see that rendered view is to click on the tiny eyeball icon in the debug panel in the playground, but even then you only get to see the view preview once before you have to collapse the panel and get back to coding.

By calling XCPCaptureValue() and passing it an identifier string and a view object, you’ll be able to see that view in the timeline, like this:

...
label.text = "Welcome!"
view.addSubview(label)

XCPCaptureValue('mainView', view)

view there is that gray UIView object we created above. If you pair this withXCPExecutionShouldContinueIndefinitely like I described above, you’ll have a continuously updating UIView preview right inside of your playground!

image01

Here’s a zip file that contains the Code School logo and a playground that has all of the code in this post: CodeSchoolProtoPlayground.zip. Let us know what you think about Swift Playgrounds, and if you’d like to see more blog posts on iOS, in the comments section below!

SHARE THIS POST

ABOUT THE AUTHOR

Jon Friskics

Content Producer and Developer at Code School. Enjoys building iOS and web apps, and then figuring out the best way to teach people how to build their own.

posted on 2015-07-02 17:32  沉淀2014  阅读(694)  评论(0编辑  收藏  举报

导航