how to draw a playground
- Tweet
- Share 0
- +1
- 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 Dismiss any open live view by setting liveView
to an object that conforms to the PlaygroundLiveViewable
protocol.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
- +1
- 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
how to draw a playground
Source: http://iosbrain.com/blog/2018/09/03/how-drawing-works-in-an-xcode-playground/
Posted by: breedingalliat.blogspot.com
0 Response to "how to draw a playground"
Post a Comment