• Tweet
  • Share 0
  • Reddit
  • +1
  • Pocket
  • Pinterest 0
  • LinkedIn 0

How many of you use freeform drawing in Xcode playgrounds? How many of you understand how drawing in playgrounds work? Xcode playgrounds can serve as great tools for prototyping your in-development apps, whether it be experimenting with algorithms or toying with ideas for app user interfaces. Granted that drawing in playgrounds is not that well documented. So the subject of this tutorial is how drawing in Xcode playgrounds works and a good number of pointers to help you start drawing in playgrounds. Here's an example of what I'm talking about:

INTRODUCTION TO DRAWING IN A PLAYGROUND

Let's create a new Xcode 9 playground. In Xcode, go to File -> New > -> Playground… and click iOS and the Single View template icon, like so:

Click the Next button, select a location for your new playground's bundle, give your playground a name, and click the Create button.

Since you chose the Single View template for your playground, you're literally going to have just one view controller (with at least one, but possibly more subviews) or just one view (with possibly one or more subviews) to work with at a time for drawing. Your drawings will be rendered almost immediately — a big plus. This is analogous to creating a new Xcode project in which you choose the Single View App template, but much more primitive.

A playground offers no storyboard and your drawings are immediately rendered to the Simulator. You have to position all user interface elements programmatically. You can even use Auto Layout (constraints) if you want, but you have to add them programmatically. For me, that's going too far and exceeds what I see as the purpose for playgrounds: prototyping, experimentation, exploring algorithms, investigating Swift language syntax and semantics, etc.

The default playground Single View template

For me, Xcode never displays the Simulator right after I create a new Single View template-based playground. Every time I re(open) my Single View template-based playgrounds, I never see the Simulator. And I've been using playgrounds since they were first introduced.

Those new to the Xcode playground's Single View template often can't find any drawings. They don't see the Simulator when they first create a new Single View template-based playground and they don't see the Simulator when subsequently reopening their Single View playgrounds.

The "secret" to viewing the Simulator

There's an extra step you need to take to see both the playground's code editor and Simulator side by side: click on the little down arrow in the bottom, right corner of the "Show the Assistant editor" button and select "Assistant Editors on Right" from the menu that appears, as shown in this video (click to enlarge image):

Note that I also adjusted the code editor (left panel) so all source code had enough room so that each line of code fit on one line without wrapping. I also made sure the "Debug Area" ("Console") was visible in case I wanted show the output from print statements.

The panel on the right with the red "Hello World!" label is the iPhone Simulator. This is where you see the fruits of your labor, i.e., where the drawing you encode gets displayed in the simulator. In all my experiences with the Xcode 9 playground, the Simulator defaults to displaying an iPhone 8 screen with dimensions in points of width: 375, height: 667.

The position of the UILabel, with CGRect(x: 150, y: 200, width: 200, height: 20), was obviously calculated so it would like nice with the foreknowledge that it would be placed on an iPhone 8 screen.

Here's what the whole Simulator looks like when you open a copy of a default Xcode playground's Single View template without making modifications to the code. Notice the default Simulator size matches an iPhone 8 screen:

The code in the default playground Single View template

Let's discuss the default Swift code provided by the Xcode 9 Single View template. First just review the code as-is. Please read the code with the intent of understanding it. I will explain it soon, but I want you to try to ingest it yourselves. Note that I made one small change to make default drawing provided by the default playground Single View template more obvious: I changed the UILabel that displays "Hello World!" to UIColor.red for the animation I made for the section above entitled "The 'secret' to viewing the Simulator." Here it is:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//: A UIKit based Playground for presenting user interface

import UIKit

import PlaygroundSupport

class MyViewController : UIViewController {

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 // .red

view . addSubview ( label )

self . view = view

}

}

// Present the view controller in the Live View window

PlaygroundPage . current . liveView = MyViewController ( )

Now, let's look at the code with some of my comments. Please read and ponder before moving forward:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

//: A UIKit based Playground for presenting user interface

import UIKit

import PlaygroundSupport

// Subclass a view controller just like in a

// an Xcode app based on the Single View App

// template.

class MyViewController : UIViewController {

// "loadView()" is kind of like

// "viewDidLoad()" in the Single View App

// template (but not the same).

override func loadView ( ) {

// Since MyViewController's "view"

// property is nil by definition

// at this point, we create

// a view so we can actually

// display something.

let view = UIView ( )

// Make MyViewController.view

// backgroud color white to show

// contrast.

view . backgroundColor = . white

// Create something that can be

// displayed on top of

// MyViewController.view.

let label = UILabel ( )

// We MUST position the label

// programmatically.

label . frame = CGRect ( x : 150 , y : 200 , width : 200 , height : 20 )

label . text = "Hello World!"

label . textColor = . black // .red

// Put the label on top of the

// new view which we'll assign

// to MyViewController.view.

view . addSubview ( label )

// We assign our custom view

// to MyViewController.view.

// "self" is declared as

//

// let `self`: MyViewController

//

self . view = view

}

}

// "Present the view controller in the Live View window"

// means display the MyViewController instance we

// just defined and customized.

PlaygroundPage . current . liveView = MyViewController ( )

My comments should be sufficient to make the code clear to you. Notice that I've highlighted lines 3, 4, and 53 in the playground code shown immediately above. I did so because we should briefly discuss those first two lines and last line — these lines:

...

import UIKit

import PlaygroundSupport

...

PlaygroundPage . current . liveView = MyViewController ( )

The PlaygroundLiveViewable protocol

The last line in the default playground Single View template makes drawing possible. Remember I just showed it to you in the last code snippet. Here's Apple's declaration and definition of liveView:

var liveView : PlaygroundLiveViewable ? { get set }

Apple continues, stating that liveView is:

The active live view in the assistant timeline or nil if there is no live view. …

Display a live view by setting liveView to an object that conforms to the PlaygroundLiveViewable protocol.

Dismiss any open live view by setting liveView to nil.

The live view is displayed in the assistant editor for the current playground page. There can be only one live view open at any time. …

As for the PlaygroundLiveViewable protocol, Apple states that it is:

A protocol for types that can be displayed as the live view for a playground. …

This protocol enables you to display any type of object in a live view. For example, a playground that presents a simplified user interface programming environment can make its view-like type conform to PlaygroundLiveViewable and appear in the live view.

By default, UIView and UIViewController conform to this protocol on iOS…

In other words, if you confine your drawing to UIView or UIViewController, which is by far the easiest path to drawing in playgrounds, you don't have to worry about conforming to anything because UIView or UIViewController are already conformant to PlaygroundLiveViewable.

Back to the code in the default playground Single View template

Please, please, please take the opportunity to review the Apple documentation on the loadView() instance method of the UIViewController class. Here are some salient quotes from the link I just asked you to review in its entirety:

You should never call this method directly. The view controller calls this method when its view property is requested but is currently nil. This method loads or creates a view and assigns it to the view property. …

If you want to perform any additional initialization of your views, do so in the viewDidLoad() method.

Notice that, in my inline playground commentary immediately above, I said:

"loadView()" is kind of like "viewDidLoad()" in the Single View App template (but not the same).

Do you understand that loadView could be used in an Xcode project (e.g., based on the Single View App template), but only if you were creating a view controller entirely programmatically, without a storyboard view controller instance corresponding to your programmatically-created view controller? Let's take a brief diversion into the world of the the Single View App template.

A UIViewController storyboard instance

I created an Xcode project based on the Single View App template. Let's look in the Main.storyboard file, specifically at the pre-existing View Controller Scene that is marked as Is Initial View Controller. Notice the items I highlight in the following video (click to enlarge image):

The default ViewController subclass of UIViewController in file ViewController.swift is the backing class of the View Controller Scene in the storyboard. The View Controller Scene's UIViewController instance has a default "View" (UIView). I don't need to use loadView().

To achieve the same results as produced by the playground based on the default Single View template we discussed above, I added the following code to my new Xcode app based on the Single View App template in method viewDidLoad in file ViewController.swift (new lines highlighted):

...

override func viewDidLoad ( )

{

super . viewDidLoad ( )

// Do any additional setup after loading the view, typically from a nib.

let label = UILabel ( )

label . frame = CGRect ( x : 150 , y : 200 , width : 200 , height : 20 )

label . text = "Hello World!"

label . textColor = . red

view . addSubview ( label )

}

...

You see that the Simulator run for the app looks exactly like the Simulator run for the playground:

Default iPhone screen size for new Xcode app Single View App templated projects and new playground Single View templates

When going into the Main.storyboard file for the first time after creating a new Xcode project based on the Single View App template, you'll find that the settings for Interface Builder specify an iPhone 8:

How do I know that an iPhone 8 has screen dimensions of width: 375, height: 667? Well, I could look these dimensions up on the web. But why not just look at the values that Interface Builder provides me in Xcode? We just established that iPhone 8 dimensions were being used.

I highlighted the View item in the View Controller Scene of the Main.storyboard file and clicked on the "Show the Size inspector" button. Here's what I saw:

CUSTOM DRAWING IN PLAYGROUNDS

Let's look at a simple example of custom drawing in a playground and then a more advanced example.

Simple experiment with the default Single View template playground

Let's see how easy it is to start doing some custom drawing in a playground based on the default Single View template. I'm going to replace the black UILabel with a red-colored square UIView. Go ahead and create a new Single View playground as I had described above, then make the modifications I highlighted in the code shown below on lines 11,12,13,15:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//: A UIKit based Playground for presenting user interface

import UIKit

import PlaygroundSupport

class MyViewController : UIViewController {

override func loadView ( ) {

let view = UIView ( )

view . backgroundColor = . white

let frame = CGRect ( x : 150 , y : 200 , width : 200 , height : 200 )

let subView = UIView ( frame : frame )

subView . backgroundColor = . red

view . addSubview ( subView )

self . view = view

}

}

// Present the view controller in the Live View window

PlaygroundPage . current . liveView = MyViewController ( )

Here's what my edits to the Single View playground code look like:

Intermediate drawing in a playground using UIView

Remember that I showed you above how, according to Apple, "By default, UIView and UIViewController conform to this protocol on iOS," where "this protocol" refers to the PlaygroundLiveViewable. Remember this last line in the default playground Single View template code:

...

// Present the view controller in the Live View window

PlaygroundPage . current . liveView = MyViewController ( )

Remember too the declaration of liveView:

var liveView : PlaygroundLiveViewable ? { get set }

Why do I bring this up? Because you can simplify drawing tremendously in a playground by using simple UIView instances, without worrying about configuring an entire UIViewController. Let me show you an example in which I draw a triangular UIView and and display it in a playground. Here's what the playground renders:

Here's the Swift code that draws the triangle:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

//: A UIKit based Playground for presenting user interface

import UIKit

import PlaygroundSupport

// My proof that we're using iPhone 8

// dimensions.

let screenWidth = 375 . 0 // points

let screenHeight = 667 . 0 // points

// Calculate the center (x, y)

// coordinate of the screen.

let centerX = screenWidth / 2.0

let centerY = screenHeight / 2.0

// Express the center (x, y) coordinate

// of the screen as a CGPoint.

let screenCenterCoordinate = CGPoint ( x : centerX , y : centerY )

// declare a view which knows how

// to draw itself

class LineDrawingView : UIView

{

// I'm overviding draw(_:) and using UIKit

// to draw 3 lines constituting a triangle.

// Follow the line coordinates to see how

// I drew the line.

override func draw ( _ rect : CGRect )

{

let currGraphicsContext = UIGraphicsGetCurrentContext ( )

// Draw a diagonal line.

currGraphicsContext ? . setLineWidth ( 2.0 )

currGraphicsContext ? . setStrokeColor ( UIColor . blue . cgColor )

currGraphicsContext ? . move ( to : CGPoint ( x : 40 , y : 600 ) ) // begin point

currGraphicsContext ? . addLine ( to : CGPoint ( x : 320 , y : 40 ) ) // end point

currGraphicsContext ? . strokePath ( )

// Draw a vertical line.

currGraphicsContext ? . setLineWidth ( 2.0 )

currGraphicsContext ? . setStrokeColor ( UIColor . blue . cgColor )

currGraphicsContext ? . move ( to : CGPoint ( x : 320 , y : 40 ) ) // begin point

currGraphicsContext ? . addLine ( to : CGPoint ( x : 320 , y : 600 ) ) // end point

currGraphicsContext ? . strokePath ( )

// Complete the triangle with a horizontal

// line.

currGraphicsContext ? . setLineWidth ( 2.0 )

currGraphicsContext ? . setStrokeColor ( UIColor . blue . cgColor )

currGraphicsContext ? . move ( to : CGPoint ( x : 320 , y : 600 ) ) // begin point

currGraphicsContext ? . addLine ( to : CGPoint ( x : 40 , y : 600 ) ) // end point

currGraphicsContext ? . strokePath ( )

}

}

// Create a UIView instance with a frame equaling the

// dimensions of an iPhone 8 screen.

let triangularView = LineDrawingView ( frame : CGRect ( x : 0 , y : 0 , width : screenWidth , height : screenHeight ) )

triangularView . backgroundColor = UIColor . white

// Present the view controller in the Live View window.

PlaygroundPage . current . liveView = triangularView

Notice that I'm setting liveView equal to a UIView instance this time, not a UIViewController instance as in the Xcode Single View template.

What if I want to do some more drawing and add another UIView on top of my triangle, like so?

I simply comment out the call to PlaygroundPage.current.liveView = triangularView, create a square UIView, set its backgroundColor property, center it in iPhone 8 screen coordinates, add it to the triangularView as a subview, and then call PlaygroundPage.current.liveView = triangularView. Here's the code I edited and added to the last snippet I showed you:

...

// Present the view controller in the Live View window.

// PlaygroundPage.current.liveView = triangularView

let squareSide = 100 . 0

let square = UIView ( frame : CGRect ( x : 0 , y : 0 , width : squareSide , height : squareSide ) )

square . backgroundColor = UIColor . red

square . center = screenCenterCoordinate

triangularView . addSubview ( square )

// Present the view controller in the Live View window.

PlaygroundPage . current . liveView = triangularView

Clearing the current playground drawings and starting new drawings

Since playgrounds are "live" in the sense that you can set them to continually recompile ("Automatically Run"), you can clear out all the code above your last call to PlaygroundPage.current.liveView = UIView/UIViewController (pseudo code) by issuing this call:

PlaygroundPage . current . liveView = nil

You can start typing in new drawing code after setting liveView to nil, and see only the fruits of your new code.

More advanced drawing in a playground

Remember the video of the rotating cube I showed you at the beginning of this tutorial? It's not that hard to create such an animation. I'm not going to go into a whole lot of detail about drawing because its an enormous subject and there are several different techniques you can use for rendering shapes on an iPhone or iPad screen.

Just as a point of interest: I've given the cube slightly rounded corners. I did so to show you how much fine-grained control you have over drawing in iOS. As a reminder, here's the animation again, rendered to the default iPhone 8 screen size:

I'm going to show you all the code in the playground that produces the rotating and color-changing square animation. I've added extensive inline commentary to help you understand what's going on. As to drawing specifics, I'll give you some hints.

Notice that I've used the center instance property of the UIView class. I find it extremely useful when drawing programmatically. Here's Apple's documentation on the property:

The center point of the view's frame rectangle. …

The center point is specified in points in the coordinate system of its superview. Setting this property updates the origin of the rectangle in the frame property appropriately.

Use this property, instead of the frame property, when you want to change the position of a view. The center point is always valid, even when scaling or rotation factors are applied to the view's transform. Changes to this property can be animated.

Speaking of transforms, CGAffineTransform is:

An affine transformation matrix for use in drawing 2D graphics. …

An affine transformation matrix is used to rotate, scale, translate, or skew the objects you draw in a graphics context. The CGAffineTransform type provides functions for creating, concatenating, and applying affine transformations…

The word "affine" refers to changing or transforming a shape into a different shape.

Here's my code:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

//: A UIKit based Playground for presenting user interface

import UIKit

import PlaygroundSupport

// My proof that we're using iPhone 8

// dimensions.

let screenWidth = 375 . 0 // points

let screenHeight = 667 . 0 // points

// Calculate the center (x, y)

// coordinate of the screen.

let centerX = screenWidth / 2.0

let centerY = screenHeight / 2.0

// Express the center (x, y) coordinate

// of the screen as a CGPoint.

let screenCenterCoordinate = CGPoint ( x : centerX , y : centerY )

// Define the dimensions of the

// square (width = height).

let squareSide = 200 . 0

// The view controller we'll set to "liveView."

class MyViewController : UIViewController

{

// Because a programmatic view controller

// initially has a nil "view" property, let's

// create a non-nil value.

override func loadView ( )

{

// Create a view that we assign to the

// the now-nil controller's "view" property.

let view = UIView ( )

view . backgroundColor = . white

// Create a subview in which we can do some

// custom drawing and animation.

let subView = UIView ( )

// Size our square view and effectively

// use its bounds since we'll position it

// using its "center" value.

subView . frame = CGRect ( x : 0.0 , y : 0.0 , width : squareSide , height : squareSide )

// Use my "proof" values to center

// the square view.

subView . center = screenCenterCoordinate

// Give a little rounding to the

// square view's corners.

subView . layer . cornerRadius = 10

// The color at which the square

// view will appear.

subView . backgroundColor = . red

// Hide the subview initially so I

// can animate its appearance.

subView . alpha = 0.0

// Add the custom subview to the

// view that we'll assign the the

// view controller's nil "view"

// property.

view . addSubview ( subView )

// Assign the new view I created,

// with custom subview, to

// MyViewController.view.

self . view = view

// Animate the appearance of the

// custom subview: 1) have its color

// change from .red to .blue while

// it appears; 2) make the subview

// rotate a full 360 degrees by providing

// pi to a CGAffineTransform.

UIView . animate ( withDuration : 5.0 , animations :

{

subView . alpha = 1.0

let affineTransform = CGAffineTransform ( rotationAngle : CGFloat ( Double . pi ) )

subView . transform = affineTransform

subView . backgroundColor = UIColor . blue

} )

} // end override func loadView()

} // end class MyViewController

//: Present the view controller in the Live View window

PlaygroundPage . current . liveView = MyViewController ( )

One last note on Simulator screen size

If you doubt my proof that the default Xcode 9 Single View playground template uses iPhone 8 dimensions of width: 375, height: 667, then substitute line 42 (highlighted) in the preceding Swift code with this line:

subView . frame = CGRect ( x : 0.0 , y : 0.0 , width : 370 . 0 , height : 660 . 0 )

All's I did was shave a few points off my screenWidth and screenHeight constants to show you some contrast between the edges of the Simulator screen and a rectangular-shaped view which is just a tad bit smaller than the full Simulator size. Here's the animation using the new line 42 I just discussed in the previous code snippet, proving the iPhone 8 dimensions:

Have fun!

  • Tweet
  • Share 0
  • Reddit
  • +1
  • Pocket
  • Pinterest 0
  • LinkedIn 0

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by http://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license. View all posts by Andrew Jaffee