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 complete our user interface by establishing the critical connections between UI elements and code, enabling programmatic control over visual components in our View Controller. Beyond the UI work, we'll architect a robust data model that defines the properties and methods for both our deck and individual cards—a fundamental skill that applies to any iOS application requiring structured data management.
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 strongly recommend completing the previous exercise (5A) before proceeding with this one. If you did not complete the previous exercise, follow these steps:
- 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.
Pre-Exercise Requirements
Ensures proper UI foundation is established
Maintains continuity in development workflow
Confirms all necessary assets and files are present
Connecting the UI to the View Controller
Now that our interface is visually complete, we need to establish the programmatic bridge between what users see and what our code can control. We'll create outlet and action connections that link visual elements with our View Controller, enabling dynamic behavior in our app.
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, giving you a side-by-side view that's essential for creating connections.
If you need to maximize screen real estate, feel free to go to the top right and click one or both the Hide or show the Navigator
and Utilities
buttons. However, keep the Document Outline visible—it's crucial for precise element selection.In the View Controller on the right, delete everything inside class ViewController's curly braces so it appears as a clean slate:
class ViewController: UIViewController { }Let's establish our first connection with the deck button. In the Storyboard, ensure 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 when you see the blue connector line positioned just under this line of code:
class ViewController: UIViewController {In the connection prompt that appears, configure the following settings:
Name: deckButton (Pay careful attention to camelCase conventions!) Type: UIButton Storage: Weak To finalize the connection, click Connect (or press Return).
Excellent! We've established our first outlet connection. This deckButton variable will allow us to programmatically modify the button's appearance—for instance, changing its image when the game concludes and our virtual deck is exhausted.
Next, we'll connect the score tracking labels. These outlets are essential for updating player scores as rounds are won and lost. To ensure precision in our connections, navigate to the Storyboard and in the Document Outline nested under superView, select the top 0 label.
Hold Control (or the Right mouse button) and drag from the top 0 to the View Controller, releasing when you see the blue connection line positioned under this existing code:
@IBOutlet weak var deckButton: UIButton!In the connection prompt, set Name to player1ScoreLabel and click Connect.
Repeat this process for the bottom 0 label. Using the Document Outline ensures accuracy—release the mouse when the blue line appears between the most recent line of code and the final curly brace.
In the prompt, set Name to player2ScoreLabel and press Return.
Now we'll create action connections for user interactions. When players tap the deck button or restart button, our app needs to execute specific logic. We'll create these action methods now and implement their functionality in subsequent exercises.
In the Storyboard, hold the Control key (or the Right mouse button) and drag from the Deck button to the View Controller, positioning the release point underneath this line:
@IBOutlet weak var player2ScoreLabel: UILabel!In the connection prompt, configure these settings:
Connection: Action (This setting is crucial—don't miss it!) 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 when the blue connector line appears beneath the previous function but within the ViewController class's closing curly bracket.
In the connection prompt, create another action with these specifications:
Connection: Action Name: restartButton Type: Any Click Connect (or press Return).
Verify that your code matches this structure (feel free to add spacing for enhanced 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) { } }With our UI connections established, we're ready to dive into the core programming challenge: creating our data model. This is where we'll define the logical structure that represents our cards and deck in code.
UI Connection Process
Enable Assistant Editor
Access split view to see both Storyboard and ViewController.swift simultaneously for efficient connection workflow.
Create Outlet Connections
Control-drag from UI elements to code to establish references for deckButton and score labels.
Define Action Methods
Link user interactions to function calls for drawCards and restartButton functionality.
Use descriptive naming conventions for outlets and actions. Pay attention to case sensitivity and use weak references for outlets to prevent memory leaks.
Creating the Data Model Swift File
In the Project navigator, select the Card War folder to ensure our new file is properly organized within the project structure.
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, restore it by clicking the Hide or show the Navigator button
at the top right.Notice that Data Model.swift now appears in your project. If it's not currently active, click on it to open it in the Editor.
Near the top right, click the Show the Standard editor icon
to return to single-file view, giving us more space for coding.Before we begin modeling our data structures, let's examine the visual reference that will guide our implementation. To open the deck image in a new tab:
- Press Cmd–T to open a new tab.
- With the new tab active, navigate 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 to preview the complete visual representation of all our cards.
Study the unique properties that define our cards—these characteristics will directly translate into our code structure:
- Each card displays a distinct image and features a unique letter or number identifier.
- Each horizontal row represents a different suit: Hearts, Diamonds, Clubs, and Spades.
- Within each row, cards are arranged by ascending value, beginning with the humble 2 and culminating with the powerful Ace.
Press Spacebar again to close the preview once you've analyzed the card structure.
Separating data models into dedicated Swift files promotes code organization, reusability, and maintainability in larger iOS projects.
Modeling a Single Card by Adding a Card Class
Now we'll translate the visual card structure we just examined into a programmatic representation. This Card class will serve as the blueprint for every individual card in our deck, encapsulating both data and behavior.
Click on the Data Model.swift tab to switch to our new file.
To display card images in our interface, we'll need access to Apple's UIImage class, which is part of the UIKit framework. Modify the import statement as shown:
import UIKitNow we'll create our Card class to model individual card behavior. Note that we follow Swift's UpperCamelCase convention for class names. Add the following code:
import UIKit class Card { }You'll see a red error
temporarily—this will disappear once we add the required initializer.Let's define our card's properties, starting with the visual representation. Add an image property:
class Card { var image: UIImage }The suit property will store special Unicode characters representing each of the four card suits. We'll implement these characters shortly, but for now, let's define it as a String:
var image: UIImage var suit: String }Add a rank property that stores the card's face value as a String:
var image: UIImage var suit: String var rank: StringWe use String rather than Int because ranks can be either numeric (2-10) or alphabetic (J, Q, K, A). This flexibility is crucial for accurate card representation.
Finally, add a value property for game logic calculations:
var image: UIImage var suit: String var rank: String var value: IntThe value property translates card ranks into numeric values for comparison during gameplay—a 2 has value 2, while an Ace has value 14. These integer values enable our game logic to determine winning cards.
With our properties defined, we need to create an initializer that sets up new Card instances. Add this initialization method:
var value: Int init(suit: String, rank: String, value: Int) { self.suit = suit self.rank = rank self.value = value } }We use the self keyword to distinguish between instance properties and parameter names, ensuring proper assignment during initialization.
The persistent red error
indicates we haven't initialized the image property yet. Click on the error to see the specific issue.Examine the Assets.xcassets folder structure to understand our image naming convention. Each card image file follows a consistent pattern: the suit (represented by an emoji character) followed immediately by the rank (2–A).
NOTE: Emoji characters can be inserted using Edit > Emoji & Symbols when creating filenames.
Back in our Data Model code, let's initialize the image property using string interpolation to construct the correct filename:
init(suit: String, rank: String, value: Int) { self.suit = suit self.rank = rank self.value = value image = UIImage(named: "\(suit)\(rank)")! }This elegant solution automatically generates the correct image filename by concatenating the suit and rank. The force unwrap (!) is safe here because we control the image assets in our bundle.
Card Class Properties
Visual Representation
UIImage property stores the visual appearance loaded from Assets.xcassets using string interpolation naming.
Suit and Rank
String properties define the card's suit symbol and rank value for identification and display purposes.
Numerical Value
Integer property represents game logic value for comparison operations during card battles.
The Card class uses a custom initializer that automatically generates the image property through string interpolation, reducing manual setup requirements.
Modeling All the Cards by Adding a Deck Class
With individual cards defined, we now need a container class that manages all 52 cards as a cohesive unit. The Deck class will handle card generation, storage, and eventually shuffling—core functionality for any card game.
Above the existing Card class, let's create our Deck class that will manage the entire collection of cards:
import UIKit class Deck { } class Card {We'll create a deck property using a closure-based initialization pattern—a powerful Swift feature that allows complex setup during property declaration:
class Deck { private var deck: [Card] = { }() }The closure (indicated by {}) will contain our card generation logic, and the empty parentheses () immediately invoke it. The private modifier encapsulates our deck array, preventing external classes from directly manipulating our card collection.
Let's establish the foundation of our card generation logic. Add the following code to eliminate the current error:
private var deck: [Card] = { var cards = [Card]() return cards }()We create a local cards array that will be populated with Card instances, then return it to initialize our deck property. This pattern ensures our deck is ready for use immediately upon instantiation.
To populate our deck systematically, we need to define the four card suits. Create an array to hold the suit characters:
var cards = [Card]() var suits = ["", "", "", ""] return cardsNow we'll insert the actual emoji characters representing each suit. Access the character palette by going to Edit > Emoji & Symbols.
The character window can be displayed in compact or expanded mode. For easier character selection, ensure you're using the expanded view. If the window header doesn't display Characters, click the
button at the top right to expand it.In the search field at the top right of the Characters window, type: suit
Locate the four card suit symbols, beginning with the leftmost spade symbol shown in this screenshot:

With Xcode visible alongside the Characters window, drag each of the four suit symbols into your suits array:

Close the Characters window when finished.
Next, we'll define all possible card ranks. Add this array containing all 13 rank values:
var ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]To generate all 52 unique cards, we'll use nested loops to iterate through every suit-rank combination. Begin with the outer loop that cycles through suits:
var ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"] for suit in suits { } return cardsFor each suit, we need to reset the card value to 1, ensuring consistent value assignment across all suits:
for suit in suits { var value = 1 }Now we'll nest the ranks loop inside the suits loop, creating every possible card combination while incrementing values appropriately:
var value = 1 for rank in ranks { cards.append(Card(suit: suit, rank: rank, value: value)) value += 1 } return cardsThis nested loop structure systematically creates all 52 cards by combining each suit with each rank. The value increments from 1 to 13 for each suit, and each new Card instance automatically loads its corresponding image based on the suit-rank combination.
Deck Composition
Deck Generation Algorithm
Define Suit Arrays
Create arrays containing emoji suit symbols and rank strings from 2 through Ace for iteration.
Nested Loop Structure
Use nested for loops to iterate through each suit and rank combination, creating all 52 cards.
Value Assignment
Increment value counter for each rank within suit to maintain proper card hierarchy.
Adding the Shuffle Functionality
A predictable card order makes for a very boring game! We need to implement shuffling functionality that randomizes our ordered deck, ensuring each game session offers a fresh, unpredictable experience. Modern iOS development provides built-in methods that make this surprisingly straightforward.
The shuffle algorithm uses arc4random_uniform function with UInt32 data type to ensure cryptographically secure random card selection without bias.
Shuffle Algorithm Implementation
Initialize Variables
Create newDeck copy and set remainingCards counter to 52 for tracking shuffle progress.
Repeat-While Loop
Execute random card selection until all cards are transferred from newDeck to shuffledDeck array.
Card Transfer Process
Select random card index, append to shuffledDeck, remove from newDeck, and decrement counter.
Making the original deck private ensures only the shuffled version is accessible, preventing accidental manipulation of the ordered deck structure.