Dive deep into iOS development with this comprehensive tutorial, covering everything from declaring variables to defining the drawCards function, and learn how to create your own card game. This tutorial is ideal for those interested in app development, looking to expand their understanding of variables, constants, and class interaction.
Key Insights
- This tutorial provides an in-depth look at iOS development, focusing on creating a card game.
- In this tutorial, you'll learn how to declare variables, use the `didSet` property observer to respond to changes in a variable's value, and define the `drawCards` function.
- You'll also learn how to manage changes in the User Interface (UI) as the game progresses, use the View Controller to represent cards visually, and understand the interaction between the UI and the data model.
- The tutorial provides detailed instructions on writing code for variable and constant declaration, defining classes, and using methods in the View Controller.
- Understanding the interaction between View Controller code and the UI is essential for effective iOS development.
- By following this tutorial, you'll gain practical skills in iOS development that can be applied to real-world projects.
Learn the essentials of iOS Development in this comprehensive tutorial covering topics such as declaring variables, responding to changes in a variable’s value using the didSet property observer, and getting started with the drawingCards function.
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:
Declaring Variables, Responding to Changes in a Variable’s Value Using the DidSet Property Observer, Starting with the DrawingCards Function
Exercise Overview
In this exercise, we are going to move on from our data model to the View Controller code. We’ll focus on defining the variables and constants that the View Controller will use to visually represent the cards in our deck. You’ll learn how to write a variable that performs an animated transition every time its value changes. Along the way, you’ll see how View Controller code interacts with both the UI in our Storyboard, as well as the data model that serves as a template for our Card and Deck classes.
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 exercises (5A–5B) before doing this one. If you did not do the previous exercises, complete the following:
- Go to File > Open.
- Navigate to Desktop > Class Files > yourname-iOS App Dev 1 Class > Card War Ready for View Controller Variables and double–click on Card War.xcodeproj.
Declaring Variables
Now that we’ve modeled the attributes and actions associated with the data in our app, let’s move on to the View Controller code. Let’s start by defining some variables that will get used to manage the changes in our UI as the game progresses.
While writing code in one portion of your app, it can be helpful to reference the other parts of the app to see how everything interacts with each other. If you do not have Main.storyboard and Data Model.swift open in separate Editor tabs, open them now. (Press Cmd–T to open a new tab.)
Press Cmd–T to open a new tab, then in the Project navigator, click on ViewController.swift.
-
The first thing we’re going to add to the View Controller is an instance of the Deck class we defined in our model. Below the three @IBOutlets and before the two @IBAction functions, add the following variable that can hold our cards:
@IBOutlet weak var player2ScoreLabel: UILabel! var deck = Deck() @IBAction func drawCards(_ sender: Any) {
-
Every time a set of two cards is drawn (one for each player), the code will create two instances of Apple’s provided UIImageView class to display them. We can manage them here in the View Controller, add another variable:
var deck = Deck() var cardsImageViews = [UIImageView]() @IBAction func drawCards(_ sender: Any) {
This array gives us an easy way to store all the UIImageViews that get created over the course of the game, so they can be removed from the superView when the game is restarted. If we don’t remove them, the new game’s cards would overlap the cards from the old game. Our app’s memory would be overflowing with cards, and would crash!
-
Create a variable that will keep track of the number of times a set of two cards has been drawn:
var cardsImageViews = [UIImageView]() var drawNumber = 0 @IBAction func drawCards(_ sender: Any) {
Our deck has 52 cards that will be divided amongst two players, so the max value of drawNumber will be 26, half of 52. We’re setting its initial value to 0, so when the app starts up, the code knows that no cards have been drawn yet. Later on, we’ll write the code that increments the drawNumber each time a set of cards is drawn.
-
In a little while, we’ll write code that will generate a set of two new UIImageViews every time the card back button gets clicked. For now, we need to create a variable that manages the horizontal spacing between the cards. Write the following variable:
var drawNumber = 0 var cardLayoutDistance: CGFloat! @IBAction func drawCards(_ sender: Any) {
We’ll use this variable to make sure the cards each player draws get laid out with nice spacing between them. Its value will be computed as a factor.
-
The variable we just wrote is an implicitly unwrapped optional, because we’re going to initialize it when the view appears on-screen. Let’s do so now by writing the following viewDidAppear function:
var cardLayoutDistance: CGFloat! override func viewDidAppear(_ animated: Bool) { cardLayoutDistance = view.frame.width / 44 } @IBAction func drawCards(_ sender: Any) {
-
Let’s break that down:
When the view appears, the code will know the total width of the view (named superView in our Storyboard). We can then calculate the distance between the UIImageViews that will hold our cards.
To calculate the cardLayoutDistance, we’re dividing the width of the view by 44, because this factor produced the best spacing between cards when we tried out different factors. As you can see below in the image that was taken at the end of the game, we’ll be able to see a portion of all the cards that have been drawn, with a nice amount of margin on the right.
Responding to Changes in a Variable’s Value Using the DidSet Property Observer
-
Add two variables that keep track of the scores for both players:
var cardsImageViews = [UIImageView]() var player1Score = 0 var player2Score = 0 var drawNumber = 0
-
The initial score for both players will be zero, but we’ll need to update that score each time a player wins a round (by drawing a card with a higher value than the one their opponent drew). Add the following code to the player1Score variable:
var player1Score = 0 { didSet { } } var player2Score = 0 {
The didSet keyword is a property observer that keeps tabs on the variable’s value. When didSet detects that the variable has been updated, it will execute the code inside its set of curly brackets (which is currently empty).
-
Let’s tell the code what to do when it detects that player 1’s score has changed. Place the cursor inside the empty curly braces, type UIView.transition, and from the dialog, select the transition(with:… suggestion. You will have the following placeholder code:
var player1Score = 0 { didSet { UIView.transition(with: UIView, duration: TimeInterval, options: UIViewAnimationOptions, animations: (() -> Void)?, completion: ((Bool) -> Void)?) } }
The UIView class’s provided transition method code tells the view to perform an animated transition. We first need to tell it what to transition.
-
Let’s perform the transition on the player1ScoreLabel. With the UIView placeholder highlighted, type the bold code and press Tab to advance to the next placeholder:
didSet { UIView.transition(with: player1ScoreLabel, duration: TimeInterval, options: UIViewAnimationOptions, animations: (() -> Void)?, completion: ((Bool) -> Void)?) }
Remember that player1ScoreLabel is the numeric score that corresponds to player 1. This is the top 0 in Main.storyboard.
-
Let’s next tell the view how long the transition will last (in seconds), and which provided animation to use. Fill in the next two placeholders, pressing Tab each time:
didSet { UIView.transition(with: player1ScoreLabel, duration: 0.3, options: .transitionCurlUp, animations: (() -> Void)?, completion: ((Bool) -> Void)?) }
-
For the animations parameter, we need to specify that we want the text inside the player1ScoreLabel to display the new value of the player1Score variable. Add the following bold code and press Tab:
didSet { UIView.transition(with: player1ScoreLabel, duration: 0.3, options:.transitionCurlUp, animations: { self.player1ScoreLabel.text = "\(self.player1Score)" }, completion: ((Bool) -> Void)?) }
NOTE: We need the self keyword here because UIView’s transition method is running inside a closure, instead of the main thread (the other View Controller code). When we use a property defined here in the View Controller, we need to reference it using self because that’s the class we’re in.
-
Lastly, we need to specify what to do on completion—when the animation has run its course. We don’t need to do anything, so add the following code to finish filling up the placeholders:
didSet { UIView.transition(with: player1ScoreLabel, duration: 0.3, options:.transitionCurlUp, animations: { self.player1ScoreLabel.text = "\(self.player1Score)" }, completion: nil) }
-
Highlight the entire player1Score variable code and press Cmd–C to copy it:
var player1Score = 0 { didSet { UIView.transition(with: player1ScoreLabel, duration: 0.3, options:.transitionCurlUp, animations: { self.player1ScoreLabel.text = "\(self.player1Score)" }, completion: nil) } }
Replace the var player2Score = 0 code by highlighting that entire line of code and pasting a copy of the player1Score code (using Cmd–V).
-
In the pasted code, change the following:
var player1Score = 0 { didSet { UIView.transition(with: player1ScoreLabel, duration: 0.3, options:.transitionCurlUp, animations: { self.player1ScoreLabel.text = "\(self.player1Score)" }, completion: nil) } } var player2Score = 0 { didSet { UIView.transition(with: player2ScoreLabel, duration: 0.3, options:.transitionCurlUp, animations: { self.player2ScoreLabel.text = "\(self.player2Score)" }, completion: nil) } }
Starting with the DrawingCards Function
Because the game of War is all about drawing cards to see which player gets the one with the higher value, the bulk of our View Controller code will specify what happens when a player is drawing cards (by clicking the card back button).
-
We first need to create two more variables. Between the cardLayoutDistance and the viewDidAppear method, define the following variable with a boolean value:
var drawNumber = 0 var cardLayoutDistance: CGFloat! var drawingCards = false override func viewDidAppear(_ animated: Bool) { cardLayoutDistance = view.frame.width / 44 }
When the user clicks the card back button to draw a set of two cards (one per player), the app will flip the cards (using code we’ll write later) and change the score by performing the animated transition you previously wrote.
This will not happen instantaneously. (For instance, our animated transitions last 0.3 seconds.) To prevent an impatient user from being able to tap the button and draw a new set of cards while the app is busy flipping cards and changing the score, we’ll test whether or not this new drawingCards variable is true.
-
Next, add another variable that stores a boolean value:
var drawingCards = false var gameOver = false
As you probably guessed, this variable will test whether or not the game is finished. In a later exercise, we’ll use it to change the picture of the card back button to visually indicate the lack of cards in our deck:
-
Let’s start filling out the contents of the drawCards function associated with the card back button. Write this precautionary line of code to prevent the impatient user from being able to do anything with this button while the cards are being drawn:
@IBAction func drawCards(_ sender: Any) { if drawingCards { return } }
The return makes us immediately exit the function if cards are being drawn.
-
Add another if statement that checks whether the game is over, restarting the game and exiting the function if that condition evaluates as true:
@IBAction func drawCards(_ sender: Any) { if drawingCards { return } if gameOver { restartGame() gameOver = false return } }
If gameOver is false, we’ll redistribute the cards. This code will not start a new game—that’s what the restartGame() function will do.
-
To get rid of the pesky red error that appeared, define the restartGame function underneath all the other functions but before the ViewController class’s final curly bracket as shown (we’ll write code here in a later exercise):
@IBAction func restartButton(_ sender: Any) { } func restartGame() { } }
-
Now that we’re done with the precautions in our drawCards function, let’s get to the nitty gritty of what this function is actually doing:
if gameOver { restartGame() gameOver = false return } drawingCards = true drawNumber += 1
This function is going to make a double assignment. It’s first going to reset the boolean to say that now we’re drawing the cards. We increase the drawNumber, which originally was 0, by 1 every time we press the card back button. This is how we’ll keep track of the number of times we’re drawing cards.
-
Every time the user draws a set of two cards from our shuffled deck, we want to assign the two topmost cards to each player. To store this data, add the following two constants:
drawNumber += 1 let player1Card = deck.shuffledDeck.removeLast() let player2Card = deck.shuffledDeck.removeLast()
Remember that the Deck class in Data Model.swift has a variable called shuffledDeck. If you remember correctly, this variable is going to hold a copy of the original deck (the stuff in the private closure), but in random order, generated by the class’s shuffle method.
Here in the View Controller, we’re going into the original deck, then the shuffledDeck (one of deck’s properties), then removing the last card using Apple’s removeLast method, so we get the one from the top. We’re assigning it to a constant because the loop within the drawCards function is only going to be alive once. Every time we draw a new card, this constant is generated.
Save the file and keep Xcode open so that we can continue with this file in the next exercise.