Explore the intricacies of iOS development in this detailed tutorial that covers topics such as connecting the UI to the View Controller, and adding shuffle functionality. Learn about modeling a single card by adding a Card class, and modeling all the cards by adding a Deck class.
Key Insights
- This tutorial covers the process of iOS development, with a focus on connecting UI elements to the code and creating data models for deck and cards.
- It involves launching Xcode, opening relevant files and completing exercises to understand the process.
- The tutorial provides detailed instructions for connecting the UI to the View Controller, including deleting contents within the View Controller to make it empty.
- The process of creating a Data Model Swift file is explained, which involves creating a Card class to model each card and a Deck class to model all the cards.
- The tutorial explains how to add shuffle functionality to make the game playable, by creating a shuffledDeck variable and a repeat-while statement to add shuffled cards from a new deck.
- The tutorial also shows how to handle errors, such as initializing all the properties, which is done by creating an initializer.
Dive into iOS development with this comprehensive guide that covers topics such as connecting the UI to the View Controller, adding card and deck classes, and implementing shuffle functionality.
This exercise is excerpted from Noble Desktop’s past app development training materials and is compatible with iOS updates through 2021. To learn current skills in web development, check out our coding bootcamps in NYC and live online.
Topics Covered in This IOS Development Tutorial:
Connecting the UI to the View Controller, Modeling a Single Card by Adding a Card Class, Modeling All the Cards by Adding a Deck Class, Adding the Shuffle Functionality
Exercise Overview
In this exercise, we’ll finish up our UI by connecting each element to the code, so we can refer to these elements in our View Controller. Additionally, we’ll create the data model that will define properties and methods for both our deck and our cards.
Getting Started
Launch Xcode if it isn’t already open.
If you completed the previous exercise, Card War.xcodeproj should still be open. If you closed it, re-open it now.
-
We suggest you complete the previous exercise (5A) before doing this one. If you did not do the previous exercise, complete the following:
- Go to File > Open.
- Navigate to Desktop > Class Files > yourname-iOS App Dev 1 Class > Card War Ready for Data Model and double–click on Card War.xcodeproj.
Connecting the UI to the View Controller
We can refer to these elements in our app, let’s add outlet or action connections to link up the visuals with the View Controller.
In the Project navigator, make sure Main is selected.
-
Go to the top right and click the Adjust Editor Options icon
and choose the Assistant option.
ViewController.swift should appear to the right of the Storyboard.
If you need to hide some of Xcode’s interface to make room on-screen, feel free to go to the top right and click one or both the Hide or show the Navigator
and Utilities
buttons. Do not hide the Document Outline!
-
In the View Controller on the right, delete everything inside class ViewController’s curly braces so it is empty:
class ViewController: UIViewController { }
-
Let’s first link the card back button to the code. In the Storyboard, make sure the button under the word Deck is visible. Then hold the Control key (or the Right mouse button) and drag from the Deck button to the View Controller. Release once you see the blue connector line under this line of code:
class ViewController: UIViewController {
-
In the prompt that appears, set the following:
Name: deckButton (Remember to pay attention to uppercase vs. lowercase!) Type: UIButton Storage: Weak -
To apply the connection, click Connect (or press Return).
We’ve got our first outlet! We’ll eventually use this deckButton variable to change the image when the game ends and there are no more cards in our virtual deck.
We need to keep track of each player’s score so whenever a player wins a round, their score number increments up. So one at a time, we’ll need to drag from a zero. To make sure we don’t accidentally make a connection for the wrong label, go to the Storyboard and in the Document Outline nested under superView, select the top 0.
-
Hold Control (or the Right mouse button) and drag from the top 0 to the View Controller, releasing once you see the blue line under the following:
@IBOutlet weak var deckButton: UIButton!
In the prompt that appears, set Name to player1ScoreLabel and click Connect.
Rinse and repeat for the bottom 0. Once again, it will be easiest to drag from the Document Outline instead of the Editor. Release the mouse when the blue line is between the most recent line of code and the final curly brace.
-
In the prompt, set Name to player2ScoreLabel and press Return.
Whenever the user clicks either the deck button or restart button, our app will need to execute some logic. That means we’ll need to create an action for these two. (We’ll just create them now, and flesh them out in another exercise.)
-
In the Storyboard, hold the Control key (or the Right mouse button) and drag from the Deck button to the View Controller underneath this line of code:
@IBOutlet weak var player2ScoreLabel: UILabel!
-
In the prompt that appears, set the following:
Connection: Action (don’t forget this setting!) Name: drawCards Type: Any Click Connect (or press Return).
In the Storyboard, hold Control (or the Right mouse button) and drag from the Restart button to the View Controller. Release the mouse once the blue connector line is underneath the previous function, but within the ViewController class’ final curly bracket.
-
In the prompt that appears, create another action as follows:
Connection: Action Name: restartButton Type: Any Click Connect (or press Return).
-
Make sure your code looks like this (feel free to add any additional spacing, as we did to enhance readability):
class ViewController: UIViewController { @IBOutlet weak var deckButton: UIButton! @IBOutlet weak var player1ScoreLabel: UILabel! @IBOutlet weak var player2ScoreLabel: UILabel! @IBAction func drawCards(_ sender: Any) { } @IBAction func restartButton(_ sender: Any) { } }
We’re done with the UI, so let’s get to coding! We’ll start by modeling our data.
Creating the Data Model Swift File
In the Project navigator, select the Card War folder so that the file is added inside.
Go to File > New > File or use the shortcut Cmd–N.
Under iOS and Source, double–click on the Swift File template.
Next to Save As, type: Data Model.swift
Navigate to Desktop > Class Files > yourname-iOS App Dev 1 Class > Card War (or Card War Ready for Data Model) > Card War.
Click Create.
-
If you previously hid the Project navigator on the left, go to the top right and click the Hide or show the Navigator button
to make it reappear.
Notice that Data Model.swift was created. (If it is not currently selected, click on it to open it in the Editor.)
Also near the top right, click the Show the Standard editor icon
so the files are no longer displayed side-by-side.
-
We will model the properties and methods for our cards and deck in this new file. When you’re learning how to model data, it can be helpful to see a visual reference of the objects you will be creating a template for. To open an image in a new tab:
- Press Cmd–T to open a new tab.
- With the new tab active, go to the Project navigator and click on the Assets.xcassets folder.
- Click on Deck near the bottom.
- Select the 1x deck image in the right column and press Spacebar so we can take a closer look at the visual representation of all of our cards.
-
Take note of the unique properties of our cards (we’ll need to model these):
- Each card has a different image and has a different letter or number.
- Each row of cards is a different suit: Hearts, Diamonds, Clubs, and Spades.
- Within each row, the cards are displayed in order of their value (starting with the lowly 2 and ending with the omnipotent Ace).
Press Spacebar again once you’re done previewing the deck image.
Modeling a Single Card by Adding a Card Class
Near the top of Xcode, click on the Data Model.swift tab to switch to this file.
-
In order to display cards in our UI, we need to use Apple’s provided UIImage class. It’s part of UIKit, so we need to import that. Change the import line as shown:
import UIKit
-
We’ll need a Card class to model each card. (Remember that we use UpperCamelCase for class names.) Add the bold code shown below:
import UIKit class Card { }
Ignore the red error
for now. We’ll be adding the initializer to fix this shortly.
-
Let’s start adding properties to the Card class. Add an image property:
class Card { var image: UIImage }
-
The suit property will be a set of special characters that will represent each of the four suits. We’ll see how to add these characters shortly. For now, set the suit property to a String value as shown below:
var image: UIImage var suit: String }
-
Add a rank property that takes a String value as shown in bold:
var image: UIImage var suit: String var rank: String
This property represents the letter or number on the card. We are using the String data type because the value can be either a number between 2 and 10, or one of the letters: J, Q, K, or A.
-
Add one final property to the class as shown:
var image: UIImage var suit: String var rank: String var value: Int
The value property models what the card’s letter or number means in terms of its value. For instance, a 2 card will have the lowest value of 2, and an Ace will have the highest value—13. These are all whole numbers, so we’re using the integer type.
-
Now that we’ve created our four properties, we need to create an initializer. As shown below, add the following bold initializer that will take the three parameters for suit, rank, and value, and assign the properties for each:
var value: Int init(suit: String, rank: String, value: Int) { self.suit = suit self.rank = rank self.value = value } }
We are using the self instance to refer the variables to the parameters that are passed into the initializer.
Hmm, we still have a red error
. Click on it to see that we still haven’t initialized all the properties. But how do we initialize the image?
-
Take a look through the Assets.xcassets folder to see that each card has its own file. They are all named using the same format: the suit (using the emoji character that represents the suit’s symbol), followed by the rank (2–A).
NOTE: Emoji characters are accessed from the Edit menu when typing the filename.
-
Back in our Data Model code, initialize the image so the card loads its own image. When a card is given a suit and rank, it will create its own string interpolation and load a UIImage with the exact same name:
init(suit: String, rank: String, value: Int) { self.suit = suit self.rank = rank self.value = value image = UIImage(named: "\(suit)\(rank)")! }
The image does not need a self instance, as we did not specify it in the initializer.
Modeling All the Cards by Adding a Deck Class
-
To define the attributes and behaviors of the entire deck of 52 cards, let’s create another class. Above the Card class block of code, add the Deck class as shown:
import UIKit class Deck { } class Card {
-
Let’s create a deck variable that will contain an array of the Card class. Type the following (you will see a red error—we’ll make it disappear in the next step):
class Deck { private var deck: [Card] = { }() }
The deck array is initialized with a closure (indicated by the curly braces {}). Keep in mind that if a function does not take any parameters, we indicate this using empty parentheses (). We’ll explain why the deck variable is private later in this exercise.
-
We’ll generate the card deck to fill up the array over the next few steps, but for now, let’s simply get rid of the red error. Type the following bold code:
private var deck: [Card] = { var cards = [Card]() return cards }()
Upon initialization, we want the private deck variable to receive the contents of the cards array once it’s filled. For this reason, we want to return the cards variable.
-
To start filling up our deck with cards, we first need to know which suits to add. Let’s create an array containing the four suit characters. Set up the array as follows:
var cards = [Card]() var suits = ["", "", "", ""] return cards
We need to insert emoji characters into this array. Go to Edit > Emoji & Symbols.
-
The window that appears allows you to insert emoji or other special characters. It can be expanded or contracted, depending on your preference. You can only drag a character when using the expanded version, so let’s make sure we’re all seeing the same thing.
Look at the top of the window. If it doesn’t say Characters, go to the top right and click the
button to expand it.
At the top right of the window, click into the search field and type: suit
-
We want to use the four suit symbols beginning with the left spade selected in the screenshot below:
-
With Xcode still visible, drag in one of each of the four suit characters to fill the suits array as shown:
Close the window of special characters when done.
-
Next, we’ll add another array of all 13 different ranks. Type the following:
var ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
-
To generate all 52 of the cards, we’ll need to iterate through both of the arrays we just added using two for loops. (The second one will be nested inside the first.) First we need to iterate through the suits array. Add the beginning of its loop as follows:
var ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"] for suit in suits { } return cards
-
Every time we start with a new suit, we want to reset the card value to 1. Add the bold code shown below:
for suit in suits { var value = 1 }
-
Now that we have all of our suits present, we need to iterate through the ranks array. Let’s nest a new loop within the suits loop as follows:
var value = 1 for rank in ranks { cards.append(Card(suit: suit, rank: rank, value: value)) value += 1 } return cards
This loop is increasing the value by 1 each time. To generate the cards, we are calling the main initializer of the Card class, and giving each card a suit, rank, and value. The card will load its own image based on these, and append the generated card to the deck array, which will hold all the cards in order!
Adding the Shuffle Functionality
To make our game interesting (not to mention playable), we need to shuffle the deck so they’re not always in the same order! We need our Deck class to take the ordered deck we just modeled and use that to create a new, randomized deck.
-
At the top of the Deck class, add a variable called shuffledDeck:
class Deck { var shuffledDeck = [Card]() private var deck: [Card] = {
We made this variable public because it will be used by the ViewController (which you’ll see in the next exercise). We are setting it to an empty array.
-
We need to populate the empty array with cards that are shuffled in a random order. At the bottom of the Deck class, start creating a function that shuffles the cards:
return cards }() func shuffle() { }
-
Inside the function we’ll generate a new deck, which is a copy of the original deck:
func shuffle() { var newDeck = deck }
Earlier, we set the original deck variable to private. This ensures that only our newDeck is used to generate the random shuffledDeck.
-
Add the variable that will keep a count of the remaining cards:
func shuffle() { var newDeck = deck var remainingCards: UInt32 = 52 }
NOTE: We’ve set the variable to the UInt32 data type (an unsigned integer) as it’s required by the arc4random_uniform function we’ll use to pick random cards.
-
We need to reset the card deck each time, in case this isn’t the first time we shuffle the cards. Remove all cards in the shuffledDeck by adding the following code:
var remainingCards: UInt32 = 52 shuffledDeck.removeAll() }
-
Now that shuffledDeck is empty, we’ll use a repeat loop (similar to a while loop) to add the shuffled cards from newDeck, until no remaining cards are left. Add the following:
shuffledDeck.removeAll() repeat { } while remainingCards > 0
NOTE: A repeat-while statement allows a block of code to be executed as long as the condition remains true. The code we added says to repeat an action as long as the remainingCards variable is greater than 0 (meaning we aren’t done seeding our randomized deck with cards).
-
Add the following code to tell the loop to pick random cards from the new copy of the deck, and then add them into the shuffledDeck, one at a time:
repeat { let cardToPick = arc4random_uniform(remainingCards) shuffledDeck.append(newDeck.remove(at: Int(cardToPick))) remainingCards -= 1 } while remainingCards > 0
By reducing the number of remainingCards each time the randomized cardToPick has been removed from the newDeck and placed into the shuffledDeck, we eventually end up with 0 cards. Once that happens, our shuffledDeck is ready for action, and the repeat-while loop terminates.
-
Lastly, we need to add a shuffle initializer to the deck. Above the shuffle function, add the following bold code:
return cards }() init() { shuffle() } func shuffle() {
To sum up our data model, when we create an instance of the Deck class, it calls the shuffle method. The shuffle method picks random cards from the deck and generates the shuffledDeck array. Because of this, we don’t need to initialize any properties as shuffledDeck is implicitly initialized to the empty array of cards. The initializer we just added calls the shuffle method, so that shuffledDeck loads and is ready to work!
Save the file and keep Xcode open so that we can continue with this file in the next exercise.