Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Rapid, Interactive Prototyping With Xcode Playgrounds

$
0
0
Final product image
What You'll Be Creating

Introduction

Since their introduction in Xcode 6 alongside Swift, to their current iteration in Xcode 7.3.1, playgrounds have come a long way. With new features and better stability, they are evolving into a viable tool for rapid prototyping or quickly hacking together a proof of concept.

As a developer, sometimes you have a flash of inspiration in the form of an interesting idea for an app and you want to quickly code up a prototype that represents the bare essence of your idea. Or you just want to verify your understanding of how some piece of UIKit code will behave. If you are like me, you would rather avoid the hassle and mental overhead of creating an Xcode project and having to deal with a myriad of factors, such as device types and resolutions, and build settings. These decisions can be deferred until after you have made up your mind that the core idea is worth pursuing.

In this tutorial, we create a card-based memory game all within the confines of a playground. It is a common, well-known game, so no credit for originality there. The game consists of eight pairs of identical cards (so a total of 16 cards) placed face down in a 4x4 grid.

The player needs to flip two cards whose faces are briefly revealed and then quickly turned back over. The objective of the game is for the player to try and remember the positions of the cards and uncover identical pairs, which are then removed from the game. The game is over when the grid is cleared.

The game is touch-based and incorporates simple view animations. You learn how you can make modifications to your app and see the result of your changes live.

1. Getting Started

Fire up Xcode and select New > Playground... from Xcode's File menu. Give the playground a name, such as MemoryGameXCPTut, set Platform to iOS, and save the playground. I am using Xcode 7.3.1 for this tutorial.

Create A New Playground

Finding Your Way Around the Playground

Let's spend some time familiarizing ourselves with the playground interface. Feel free to skim this section if you are already familiar with playgrounds.

A playground can have multiple pages, each associated with its own live view and its own sources/resources folders. We won't be using multiple pages in this tutorial. Playgrounds support markup formatting that allows you to add rich text to a playground and link between playground pages.

The first thing you see after creating a playground is the playground source editor. This is where you write code, which has an immediate effect on the live view. One of the ways to toggle the (dis)appearance of the Project Navigator is using the shortcut Command-0. In the Project Navigator, you can see two folders, Sources and Resources.

Sources

In the Sources folder, you can add auxiliary code in one or more Swift files, such as custom classes, view controllers, and views. Even though the bulk of the code that defines your prototype's logic goes there, it is auxiliary in the sense that it is tucked in the background when you are viewing your app live.

The advantage of putting the auxiliary code in the Sources folder is that it is automatically compiled every time you modify and save the file. This way, you get faster feedback in the live view from changes made in the playground.  Back in the playground, you are able to access public properties and methods that you expose in the auxiliary code affecting how your app behaves.

Resources

You can add external resources, such as images, in the Resources folder.

In this tutorial, you frequently need to jump between a Swift file we create in the Sources folder and the playground file (technically also a Swift file, except you won't refer to it by its file name). We also make use of the Assistant Editor in the tutorial, having it display the Timeline, to view the live output side-by-side with the playground code. Any changes you make in the playground are reflected instantly (well, within a few seconds) in the live output. You are also able to touch-interact with the live view and its user interface elements. To ensure you can do all this, take a quick glance at the figure below.

Sources and Resources Folders

Corresponding to the green numbers I have added to the figure:

  1. This button hides the Assistant Editor so that only the main editor is visible.
  2. This button reveals the Assistant Editor. The Assistant Editor is visible on the right of the main editor. This editor can help by showing us relevant files, such as the counterpart of the file in the main editor.
  3. From left to right, these two buttons are respectively used to toggle the appearance of the Project Navigator and the debug console. In the console, we can inspect the output of print statements among other things.
  4. The jump bar at the top of the main editor can also be used to navigate to a particular file. Clicking the project name twice brings you back to the playground. Alternatively, you can also use the Project Navigator.

Sometimes, when viewing the playground, you need to ensure that the Assistant Editor is displaying the Timeline instead of some other file. The below figure shows how to do this. In the Assistant Editor, select Timeline, the counterpart of the playground, instead of Manual, which allows you to show any file in the Assistant Editor.

Accessing the Timeline

When you are editing a source file from the Sources folder, as its counterpart, the Assistant Editor shows the interface of your code, that is, declarations and function prototypes without their implementations. I prefer to hide the Assistant Editor when I am working on a file in the Sources folder and only expose the Assistant Editor in the playground to see the live view.

To access the special abilities of playgrounds, you need to import the XCPlayground module.

You set the liveView property of the currentPage of the XCPlaygroundPage object to an object that conforms to the  XCPlaygroundLiveViewable protocol. This can be a custom class or it can be a UIView or UIViewController instance.

Adding Files to the Sources/Resources Folder

I have added a few images we can work with in this tutorial. Download the images, extract the archive, and add the images in the Images folder to the Resources folder of the playground in the Project Navigator.

Make sure to drag just the images so that each image file resides in the Resources folder, not in Resources/Images.

Delete the code in the playground. Right-click the Sources folder and select New File from the menu. Set the name of the file to Game.swift.

Importing Images and Creating a New Source File

2. Writing Helper Classes & Methods

Add the following code to Game.swift. Make sure you save the file after every code addition.

I have added a few numbered comments to explain some sections of the implementation:

  1. In addition to UIKit and XCPlayground, we are also importing GamePlayKit. This framework includes a convenient method that will help us implement a method to randomly shuffle an array.
  2. This extension on UIImage allows us, with the help of UIKit methods, to make images with a solid color of any size we want. We will use this to set the initial background image of the playing cards.
  3. The cardHeight and cardWidth constants represent the card image sizes based on which we will compute other sizes.
  4. The Card class, inheriting from UIImageView, represents a card. Even though we set a few properties in the Card class, the main purpose of creating this class is to help us identify and iterate over the subviews that correspond to playing cards in the game. The cards also have properties x and y to remember their position in the grid.

3. View Controller

Add the following code to Game.swift, immediately after the previous code:

  1. The two properties, padding and backImage, are declared public so that we can access them in the playground later. They represent the blank space surrounding the cards on the grid and the image displayed on the back of each card respectively. Note that both properties have been given initial values, representing a padding of 20 and a solid red color for the card's non-face side image. You can ignore the commented-out code for now.
  2. We are calculating the desired width and height of the views by means of computed properties. To understand the viewWidth calculation, remember that there are four cards in each row and we also need to take the padding of each card into account. The same idea applies to the viewHeight calculation.
  3. The code (1...8).flatMap{[$0, $0]} is a concise way of producing the array [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8]. If you aren't familiar with functional programming, you could also write a for-loop to generate the array. Using methods from the GamePlayKit framework, we shuffle the numbers in the array.  The numbers correspond to the eight pairs of cards. Each number represents the card image of the same name (for example, a value of 1 in shuffledArray corresponds to 1.png).
  4. We wrote a method that maps the location of a card on the 4x4 grid to its location in the shuffledNumbers array of length 16. The factor 4 in the arithmetic calculation reflects the fact that we have four cards per row.
  5. We also have a method that figures out the position of a card (its center property) in the grid based on card dimensions and padding.
  6. The setupGrid() method is called during the view controller's initialization. It lays out the 4x4 Card grid. It also assigns each card's identity based on the shuffledNumbers array and stores it in the tag property inherited from the card's base class, UIView. In the game logic, we compare the tag values to figure out whether two cards match or not. This rather rudimentary modeling scheme serves well enough for our current needs.
  7. This currently unused piece of code will help us reposition the cards in case the padding changes. Remember that we declared the padding property as a public property so we can access it in the playground.
  8. The code in viewDidAppear(_:) runs immediately after the view controller's view becomes visible. We iterate through the view's subviews and, if the subview is an instance of the Card class, (checked through the as? failable downcasting operator) the body of the if-statement defines the transition to perform. This is where we will change the image being displayed on the cards, from the cartoon image defining the face of each card to the (common) backImage of all the cards. This transition is accompanied by a left-to-right flip animation giving the appearance of the cards being physically turned over. If you aren't familiar with how UIView animations work, this may look a bit odd. Even though we added each card's animation sequentially in a loop, the animations are batched into a single animation transaction and executed concurrently, that is, the cards flip together.

Revisit the playground and replace any text in the editor with the following:

Make sure the timeline is visible. The view controller's view should spring to life and show us a 4x4 grid of cards with cute cartoons animals that flip over to show us the back of the cards. Right now, we can't do much with this view because we haven't programmed any interaction into it yet. But it's definitely a start.

First Output

4. Modifying Variables in the Playground

Let's now change the back faces of the cards from solid red to an image, specifically b.png in the Resources folder. Add the following line to the bottom of the playground.

After a second or two, you'll see that the back sides of the cards have changed from plain red to a cartoon hand.

Changing the Back Image of the Cards From the Playground

Let's now try to alter the padding property, which we assigned a default value of 20 in Game.swift. The space between the cards should increase as a result. Add the following line to the bottom of the playground:

Wait for the live view to refresh and see that ... nothing has changed.

5. A Brief Detour

To understand what is going on, you have to keep in mind that entities, such as view controllers and their associated views, have a complex life cycle. We are going to focus on the latter, that is, views. Creating and updating of a view controller's view is a multistage process. At specific points in the life cycle of the view, notifications are issued to the UIViewController, informing it of what is going on. More importantly, the programmer  can hook into these notifications by inserting code to direct and customize this process.

The loadView() and viewDidAppear(_:) methods are two methods we used to hook into the view life cycle. This topic is somewhat involved and beyond the scope of this discussion, but what matters to us is that the code in the playground, after the assignment of the view controller as the playground's liveView, is executed some time between the call to viewWillAppear(_:) and the call to viewDidAppear(_:). You can verify this by modifying some property in the  playground and add print statements to these two methods to display the value of this property.

The issue with the value of padding not having the expected visual effect is that, by that time, the view and its subviews have already been laid out. Keep in mind that, whenever you make a change to the code, the playground is rerun from the beginning. In that sense, this issue isn't specific to playgrounds. Even if you were developing code to run on the simulator or on a physical device, often times you would need to write additional code to ensure that the change in a property's value has the desired effect on the view's appearance or content.

You might ask why we were able to change the value of the backImage property and see the result without doing anything special. Observe that the backImage property is actually used for the first time in viewDidAppear(_:), by which time it has already picked up its new value.

6. Observing Properties and Taking Action

Our way to deal with this situation will be to monitor changes to the value of padding and resize/reposition the view and subviews. Fortunately, this is easy to do with Swift's handy property observing feature. Start by uncommenting the code for the resetGrid() method in Game.swift:

This method recomputes the position of the view's frame and that of each Card object based on the new values of viewWidth and viewHeight. Recall that these properties are computed based on the value of padding, which has just been modified.

Also, modify the code for padding to use the didSet observer whose body, as the name indicates, executes whenever we set the value of padding:

The resetGrid() method kicks in and the view is refreshed to reflect the new spacing. You can verify this in the playground.

Padding Successfully Updated

It appears we were able to fix things quite easily. In reality, when I first decided I wanted to be able to interact with the padding property, I had to go back and make changes to the code in Game.swift. For example, I had to abstract out the Card center calculation in a separate function (centerOfCardAt(_:_:)) to cleanly and independently (re)compute the positions of the cards whenever they needed to be laid out.

Making computed properties for viewWidth and viewHeight also helped. While this kind of rewrite is something you should be prepared for as a trade-off of not doing much upfront design, it can be reduced with some forethought and experience.

7. Game Logic & Touch Interaction

It is now time to implement the game's logic and enable ourselves to interact with it through touch. Begin by uncommenting the firstCard property declaration in the GameController class:

Recall that the logic of the game involves revealing two cards, one after the other. This variable keeps track of whether a card flip performed by the player is the first of the two or not.

Add the following method to the bottom of the GameController class, before the terminating curly brace:


That is a lengthy method. That is because it packs all the required touch handling, game logic as well as associated animations in one method. Let's see how this method does its work:

  • First, there is a check to ensure that the user actually touched a Card instance. This is the same as? construct that we used earlier.
  • If the user did touch a Card instance, we flip it over using an animation similar to the one we implemented earlier. The only new aspect is that we use the completion handler, which executes after the animation completes, to temporarily disable touch interactions for that particular card by setting the userInteractionEnabled property of the card. This prevents the player from flipping over the same card. Note the _ in construct that is used several times in this method. This is just to say that we want to ignore the Bool parameter that the completion handler takes.
  • We execute code based on whether the firstCard has been assigned a non-nil value using optional binding, Swift's familiar if let construct.
  • If firstCard is non-nil, then this was the second card of the sequence that the player turned over. We now need to compare the face of this card with the previous one (by comparing the tag values) to see whether we got a match or not. If we did, we animate the cards fading out (by setting their alpha to 0). We also remove these cards from the view. If the tags are not equal, meaning the cards don't match, we simply flip them back facing down and set userInteractionEnabled to true so that the user can select them again.
  • Based on the current value of firstCard, we set it to either nil or to the present card. This is how we switch the code's behavior between two successive touches.

Finally, uncomment the following two statements in the GameController's initializer that adds a tap gesture recognizer to the view. When the tap gesture recognizer detects a tap, the handleTap() method is invoked:

Head back to the playground's timeline and play the memory game. Feel free to decrease the large padding we assigned a bit earlier.

The code in handleTap(_:) is pretty much the unembellished version of what I wrote the first time. One might raise the objection that, as a single method, it does too much. Or that the code isn't object-oriented enough and that the card flipping logic and animations should be neatly abstracted away into methods of the Card class. While these objections aren't invalid per se, remember that quick prototyping is the focus of this tutorial and since we did not foresee any need to interact with this part of the code in the playground, we could afford to be a bit more "hack-ish".

Once we have something working and we decide we want to pursue the idea further, we would certainly have to give consideration to code refactoring. In other words, first make it work, then make it fast/elegant/pretty/...

8. Touch Handling In the Playground

While the main part of the tutorial is now over, as an interesting aside, I want to show you how we can write touch handling code directly in the playground. We will first add a method to the GameController class that allows us to peek at the faces of the cards. Add the following code to the GameController class, immediately after the handleTap(_:) method:

Suppose we want the ability to activate or deactivate this "quick peek" feature from within the playground. One way to do this would be to create a public Bool property in the GameController class that we could set in the playground. And of course, we would have to write a gesture handler in the GameController class, activated by a different gesture, that would invoke quickPeek().

Another way would be to write the gesture handling code directly in the playground. An advantage of doing it this way is that we could incorporate some custom code in addition to calling quickPeek(). This is what we will do next. Add the following code to the bottom of the playground:

To activate the quick peek feature, we will use a long press gesture, that is, the player holds their finger on the screen for a certain amount of time. We use two seconds as the threshold.

For handling the gesture, we create a class, LPGR (long press gesture recognizer abbreviated), with a static variable property, counter, to keep track of how many times we peeked, and a static method longPressed(_:) to handle the gesture.

By using the static qualifier, we can avoid having to create an LPGR instance because the entities declared static are associated with the LPGR type (class) rather than with a particular instance.

Apart from that, there is no particular advantage to this approach. For complicated reasons, we need to mark the method as @objc to keep the compiler happy. Note the use of LPGR.self to refer to the type of the object. Also note that in the gesture handler, we check if the state of the gesture is .Began. This is because the long press gesture is continuous, that is, the handler would execute repeatedly as long as the user kept their finger on the screen. Since we only want the code to execute once per finger press, we do it when the gesture is first recognized.

The incrementing counter is the custom code that we introduced, which doesn't rely on functionality provided by the GameController class. You can view the output of the print(_:) function (after having peeked a couple of times) in the console at the bottom.

Gesture Recognizer With External Behavior

Conclusion

Hopefully, this tutorial has demonstrated an interesting example of rapid, interactive prototyping in Xcode playgrounds. Apart from the reasons for using playgrounds I mentioned earlier, you could think up other scenarios where they could be useful. For instance:

  • demonstrating prototype functionality to clients and letting them choose options and make customizations with live feedback and without having to dig into the nitty-gritty details of the code.
  • developing simulations, such as for physics, where students can play with some parameter values and observe how the simulation is affected. In fact, Apple has released an impressive playground that showcases their interactivity and the incorporation of physics via the UIDynamics API. I encourage you to check it out.

When using playgrounds for demonstration/teaching purposes such as these, you will probably want to make liberal use of the markup capabilities of playgrounds for rich text and navigation.

The Xcode team seems committed to improving playgrounds as new versions of the IDE are rolled out. The breaking news is that Xcode 8, currently in beta, will feature playgrounds for iPad. But, obviously, playgrounds are not meant to substitute the full blown Xcode IDE and the need to test on actual devices when developing a complete, functional app. Ultimately, they are just a tool to be used when it makes sense, but a very useful one.

2016-06-24T19:05:25.000Z2016-06-24T19:05:25.000ZAkiel Khan

Viewing all articles
Browse latest Browse all 1836

Trending Articles