In this chapter we’re going to start with an existing, very old iOS project I wrote around 2009. It’s a very simple slot machine app that came out for the second-generation iPhone, the iPhone 3G. In fact, at that time there was no iOS. Apple called the operating system iPhone OS, even though it worked on the iPod Touch device as well. There was no iPad released yet.
Problem
You’re asked to add features to an existing Objective-C project that would be better served using the Swift language because of its modern features, or to simply update the project.
Solution
You need to go through each of the Objective-C files and make the conversion yourself in order to ensure that things work properly.
Let’s Work Through the Project
By the time this book is released I expect there will be at least a few Objective-C-to-Swift conversion programs. Today there is one called Swiftify that can be easily found on the Internet. It actually seemed to work for some simple code segments, and at the time of this writing it had a subscription-based pricing. That is, you need to pay to convert code of more than 10 KB in size. So, just for fun, I’ll show you what it can do.
Listing 14-1 shows a code snippet in Objective-C that implements the ubiquitous cellForRowAtIndexPath, which anyone who’s ever coded more than the simplest Hello, World app has written.
Listing 14-1. Objective-C Showing How to Fill a Table View
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = @"ItemID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
Listing 14-2 shows the result of the code in Listing 14-1 using the free version of the program.
Listing 14-2. Using the Code from Earlier
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
static var simpleTableIdentifier: String = "ItemID"
var cell: UITableViewCell =
tableView.dequeueReusableCellWithIdentifier(simpleTableIdentifier)
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyleDefault,
reuseIdentifier: simpleTableIdentifier)
}
cell.textLabel.text = tableData.objectAtIndex(indexPath.row)
return cell
}
I might be inclined to use this for segments of code here and there to see how well it works. For now, though, I’m going to work through our project by essentially taking it line by line or in sections where appropriate. Why, you ask? I’ve come up with at least three reasons. First, if I rely on a conversion program and just do quick inspections of the result, by the time I get to the point where all the conversions are completed, if the app doesn’t work, I’ll just have to go through it anyway to find the problem or problems. The truth is that I’m not doing much more than what this type of conversion program accomplishes, except that I’m looking at each line as I do the conversion. Because I wrote the original program, I should know every detail about the program, and they should be in the comments to make sure any details are not overlooked. Second, this app was written in a very early version of Objective-C, and the conversion tools may not be designed to work with a code base that old. Finally, the app is heavily graphics oriented. It’s not a consuming game with animation or stuff like that, it’s just that during this period of iPhone OS, the rudimentary graphics manipulations, such as stacking images to mask other graphics out, had to be done for even this simple game app. It was really hard back then! And yes, it was my very first “real” app, and there wasn’t much around in the way of technical support.
About the App
First, I want to give a brief description about the parts of the app itself.
Naming Conventions
There are three different names that we want to be aware of. First, the original app was called TownSlot and was written to work on the iPhone 3G. For purposes of this project, I converted that project to the latest version of Objective-C and Xcode and called it SlotMachine for simplicity and to avoid confusion. The Swift-converted version of the app is called TownSlot2, again to differentiate it from the original. To speed things along, we’ll use the same graphics files as were included in the original version; that is, we won’t be changing the graphics to reflect the new name; it will still show on the UI as just The Town Slot.
TownSlot = original iPhone OS app written for iPhone 3G in Objective-C
SlotMachine = Updated version in Objective-C that will be our starting point for the conversion
TownSlot2 = Converted Swift version, i.e., the result of this project.
Appearance
SlotMachine, our starting point, presents a single view to the user of a three-wheel Las Vegas–style slot machine, as shown in Figure 14-1. At the very top are three lights that blink after the player taps the Spin button until the “wheels” stop spinning. Each wheel is actually a long, narrow strip of images that repeats to help give the illusion of a wheel turning. The long strip contains four smaller, 9-element segments connected at the top and bottom to create a 36-element column. Figure 14-2 shows the long strip broken down into the four repeating segments. Note that the two center, 9-element strips are blurred to help give the illusion of the wheel spinning.
Figure 14-1. Our app project presents a simple, three-wheel slot machine to the user
Figure 14-2. Each “wheel” consists of a 4 x 9 (36) item strip of images. The center 18 items have been blurred to help the illusion of fast spinning
Below the wheels are three text fields that indicate, from left to right, the amount of credits owned by the user, the amount of the current bet, and the winnings paid on the last spin. If a player loses a turn, the amount of the bet is deducted from the credits. Similarly, if she wins, the bet amount is added to the total credits. The winning or losing criteria is set inside the logic and is written in Objective-C. It varies depending on what the final spin looks like. You don’t have to have three-of-a-kind necessarily to win back your bet.
Finally, at the bottom are three buttons. The Bet Max button provides the user an easy way to bet the max on a spin. The code is set to allow a maximum bet of 10 credits. The Bet+1 button adds one to the bet amount. If the current bet is 10 credits, tapping the Bet+1 button will roll over the bet to 1 credit. The Spin button starts the animation, essentially acting like the pull-arm of a traditional one-armed-bandit.
There are several sound animations. When the player presses any of the three buttons, a click is played. When the wheels are spinning, a Vegas-like little snippet of music plays. If the player loses, a sad sounding horn plays, but if she wins, a much happier bit of music is heard.
Architecture
The app consists of an AppDelegate and ViewController, each with both a header (.h) and implementation (.m) file. Because of the timeframe of when the app was initially created, storyboards are not used. Instead, the view is built programmatically in the viewDidLoad method of the ViewController.m file. The images are stacked on top of one another, with the topmost graphics being the “closest” visually to the player. At the bottom, furthest away, would be the wheels shown in Figure 14-2. Next would be the slot machine front-facing panel with any accoutrements (Figure 14-3).
Figure 14-3. The slot machine’s front panel graphics are placed atop the wheels. The holes in the panel allow the current position of the wheel to be seen by the player
Labels, buttons, and flashing lights are then placed on top of the front face to allow for player interaction.
The application delegate exists as generated during the initial creation of the project and is shown in Listing 14-3.
Listing 14-3. The App Delegate Initializes the View Controller to Make It Visible to the User
//
// AppDelegate.m
// SlotMachine
//
// Created by Molly Maskrey on 9/23/15.
// Copyright © 2015 Global Tek Labs. All rights reserved.
//
#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
@synthesize window;
@synthesize viewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
@end
The complete functionality of the app resides in the ViewController files and consists of two primary methods, viewDidLoad() and spin(). The viewDidLoad() method sets up everything for the app to function: the view hierarchy as we described earlier, the user defaults persistent storage, sounds, labels, size determination based on which device is being used, and a few others. The spin() function does all of the work when the user presses the Spin button, calling any number of subordinate methods.
To begin the conversions from Objective-C to Swift, the path I chose was to implement a top-down translation. This allowed the basics of the app to begin functioning very early in the conversion to Swift. As each method was translated to Swift, more and more operational functionality was added until eventually things worked exactly the same in both versions.
In Figure 14-4 you can see the first step of the conversion process, which shows a collapsed visualization of the ViewController.Swift file. We’ll discuss the initial project creation momentarily, but since this section is concerned with the project architecture, this hierarchy of methods represents a good overview. The viewDidLoad method does all the setup; we’ll show much more detail on this shortly. The didReceiveMemoryWarning method is a standard method included with any project creation to allow us to handle any cleanup when iOS may be running short of resources and thinking about shutting our app down. Because this is just a game, we’re not too concerned with what happens in this example. Here, we focus more on converting between languages.
Figure 14-4. All the methods making up the content of the ViewController implementation file
The prefersStatusHidden method tells iOS if we want the iPhone’s status bar to be seen at the time the app is loaded. Since this is a full-screen game, we do not, so we return true. The operating system essentially calls this method in each app and, depending on the response, either hides or shows the status bar.
The addToBet and addMaxToBet methods either increment the amount the user bets on the spin by one credit or set it to the default maximum of ten credits. The spin method is called when the player taps the Spin button, simulating a pull of the arm on the one-arm-bandit. As with the viewDidLoad method, we’ll also cover this in great detail shortly.
The next three routines—firstWheelReverse, secondWheelReverse, thirdWheelReverse—create a change of direction on the animation of each of the wheel image strips. Because we don’t really have wheels, and because the strips are of a finite length, the spinning is simulated by moving the strip using animation one way and then the other. This creates a longer wheel-spinning effect without needing an unnecessarily long image strip. The initial animation is started in the spin method with these three being called once the animation ends; that is, when the last image on each strip is reached. Along with blurring, this provides a fairly satisfactory appearance of a spinning wheel. In a similar category, the spinningHasStopped method performs everything to determine whether the player as won or lost after the last wheel has stopped moving. The lights stop flashing, the music terminates, either a happy win sound or a sad lose sound plays, and the score is updated.
Either at the beginning of a new game or when a player has lost everything, the resetGame method clears out all the variables and starts everything at the beginning with the initial set of credits. Whenever a player wins or loses, the updateLabels method adds or subtracts the proper values from the score and shows the value on the face of the slot machine. The values are derived from the calculateWinnings method, which evaluates the quantity of credits to be added depending on the values of the wheels. This is where you might change things up; for example, maybe you want the “bar” icon to be the default scoring value rather than the cherries as I’ve set it. It’s all up to you. If the calculateWinnings method determines the player is completely out of credits, then the youLost method gets called and the player can restart everything.
Six methods control the flashing of the red and green lights atop the image of the slot machine: setupGreenLightSequence, startGreenLightSequence, stopGreenLightSequence, setupRedLightSequence, startRedLightSequence, and stopRedLightSequence. Setup methods position the various colors depending on what size of device screen the player uses. Stop and start methods do exactly what you would expect.
The makeButtonClick method plays the audio file that simulates the clicking noise when the button is pressed. An ideal replacement might be to swap that audio file for one that sounds like an arm being pulled on an actual machine.
Finally, saveGameState and restoreUserSettings put and get critical information to persistent storage with NSUserDefaults.
These methods exist in both the Objective-C and Swift versions, though Figure 14-4 reflects the Swift file because of the ability in Xcode to easily collapse all the method implementations.
Objective-C Code
Because all of the functionality for this app resides in the ViewController files, we’ll only be looking at these in our analysis. Listing 14-4 shows the ViewController.h header file, while Listing 14-5 depicts the implementation.
Listing 14-4. Objective-C ViewController Header (.h) File
//
// ViewController.h
// SlotMachine
//
// Created by Molly Maskrey on 9/23/15.
// Copyright © 2015 Global Tek Labs. All rights reserved.
//
#import <UIKit/UIKit.h>
#include <AudioToolbox/AudioToolbox.h>
#import "AppDelegate.h"
#define numberOfIcons 9
#define kInitialCredits 100
@class SetupViewController;
@interface ViewController : UIViewController {
UIImageView *greenLightSequenceImageView;
UIImageView *redLightSequenceImageView;
SetupViewController *setupViewController;
BOOL allowSpin;
BOOL isSpinning;
BOOL gameOver;
UIView *contentView;
CGRect slotStripViewWheel1PosStart;
CGRect slotStripViewWheel1PosEnd;
CGRect slotStripViewWheel2PosStart;
CGRect slotStripViewWheel2PosEnd;
CGRect slotStripViewWheel3PosStart;
CGRect slotStripViewWheel3PosEnd;
CGRect slotStripViewWheel1PosComplete;
CGRect slotStripViewWheel2PosComplete;
CGRect slotStripViewWheel3PosComplete;
// These are the three buttons, two used for betting and one to start the spin
UIButton *spinButton;
UIButton *betButton;
UIButton *betMaxButton;
// These are the three numbers shown in red at about the center of the display
UILabel *creditsLabel;
UILabel *betLabel;
UILabel *winLabel;
// These three image views hold the slot icons on a long strip that we
// move underneath the main Slot machine frame to give a sense of spinning.
UIImageView *slotStripViewWheel1;
UIImageView *slotStripViewWheel2;
UIImageView *slotStripViewWheel3;
// These are used to hold the random values for each virtual wheel
// and the adjusted value of all three.
NSUInteger spin1;
NSUInteger spin2;
NSUInteger spin3;
NSUInteger spinValue;
// properties that hold the credit, bet, and winnings values
Int winThisSpin;
int thisBet;
int totalCredits;
// URL reference and sound object IDs for spinning, button click, winning, and losing
CFURLRef spinFileURLRef;
SystemSoundID spinSoundObject;
CFURLRef clickFileURLRef;
SystemSoundID clickSoundObject;
CFURLRef winFileURLRef;
SystemSoundID winSoundObject;
CFURLRef loseFileURLRef;
SystemSoundID loseSoundObject;
Float stoppingPoints[9];
}
@property (nonatomic,retain) UIImageView *greenLightSequenceImageView;
@property (nonatomic,retain) UIImageView *redLightSequenceImageView;
@property (nonatomic,retain) SetupViewController *setupViewController;
@property (nonatomic, retain) UILabel *creditsLabel;
@property (nonatomic, retain) UILabel *betLabel;
@property (nonatomic, retain) UILabel *winLabel;
@property (nonatomic,retain) UIButton *spinButton;
@property (nonatomic,retain) UIButton *betButton;
@property (nonatomic,retain) UIButton *betMaxButton;
@property (nonatomic) BOOL allowSpin;
@property (nonatomic) BOOL gameOver;
@property (nonatomic) BOOL isSpinning;
@property (readwrite) CFURLRef spinFileURLRef;
@property (readonly) SystemSoundID spinSoundObject;
@property (readwrite) CFURLRef clickFileURLRef;
@property (readonly) SystemSoundID clickSoundObject;
@property (readwrite) CFURLRef winFileURLRef;
@property (readonly) SystemSoundID winSoundObject;
@property (readwrite) CFURLRef loseFileURLRef;
@property (readonly) SystemSoundID loseSoundObject;
@property (nonatomic) int winThisSpin;
@property (nonatomic) int thisBet;
@property (nonatomic) int totalCredits;
@property (nonatomic, retain) UIView *contentView;
@property (nonatomic) CGRect slotStripViewWheel1PosStart;
@property (nonatomic) CGRect slotStripViewWheel1PosEnd;
@property (nonatomic) CGRect slotStripViewWheel2PosStart;
@property (nonatomic) CGRect slotStripViewWheel2PosEnd;
@property (nonatomic) CGRect slotStripViewWheel3PosStart;
@property (nonatomic) CGRect slotStripViewWheel3PosEnd;
@property (nonatomic) CGRect slotStripViewWheel1PosComplete;
@property (nonatomic) CGRect slotStripViewWheel2PosComplete;
@property (nonatomic) CGRect slotStripViewWheel3PosComplete;
@property (nonatomic, retain) UIImageView *slotStripViewWheel1;
@property (nonatomic, retain) UIImageView *slotStripViewWheel2;
@property (nonatomic, retain) UIImageView *slotStripViewWheel3;
@property (nonatomic, retain) UIImageView *topMostView;
typedef enum {
kiPhone4S,
kiPhone5,
kiPhone6,
kiPhone6Plus
} iPhoneType;
@property (nonatomic) iPhoneType iphoneType;
-(void)spin;
-(void)makeButtonClick;
-(void)saveGameState;
-(void)restoreUserSettings;
-(int)calculateWinnings;
-(void)updateLabels;
-(void)youLost;
-(void)resetGame;
// Animations of the lights on top of the machine
-(void)setupGreenLightSequence;
-(void)startGreenLightAnimation;
-(void)stopGreenLightAnimation;
-(void)setupRedLightSequence;
-(void)startRedLightAnimation;
-(void)stopRedLightAnimation;
@end
Listing 14-5. Objective-C ViewController Implementation (.m) file
//
// ViewController.m
// SlotMachine
//
// Created by Molly Maskrey on 9/23/15.
// Copyright © 2015 Global Tek Labs. All rights reserved.
//
#import <AudioToolbox/AudioToolbox.h>
#import "ViewController.h"
@implementation ViewController
@synthesize setupViewController;
@synthesize greenLightSequenceImageView;
@synthesize redLightSequenceImageView;
@synthesize gameOver;
@synthesize allowSpin;
@synthesize isSpinning;
@synthesize spinButton;
@synthesize betButton;
@synthesize betMaxButton;
@synthesize winThisSpin;
@synthesize thisBet;
@synthesize totalCredits;
@synthesize creditsLabel;
@synthesize betLabel;
@synthesize winLabel;
@synthesize contentView;
@synthesize slotStripViewWheel1PosStart;
@synthesize slotStripViewWheel1PosEnd;
@synthesize slotStripViewWheel2PosStart;
@synthesize slotStripViewWheel2PosEnd;
@synthesize slotStripViewWheel3PosStart;
@synthesize slotStripViewWheel3PosEnd;
@synthesize slotStripViewWheel1PosComplete;
@synthesize slotStripViewWheel2PosComplete;
@synthesize slotStripViewWheel3PosComplete;
@synthesize slotStripViewWheel1;
@synthesize slotStripViewWheel2;
@synthesize slotStripViewWheel3;
@synthesize topMostView;
@synthesize spinFileURLRef;
@synthesize spinSoundObject;
@synthesize clickFileURLRef;
@synthesize clickSoundObject;
@synthesize winFileURLRef;
@synthesize winSoundObject;
@synthesize loseFileURLRef;
@synthesize loseSoundObject;
@synthesize iphoneType;
//NSNotificationCenter messages
NSString * const userResetGame = @"resetGame";
// delta value used to move over the wheels
float shiftOverValue = 0.0;
// By setting this the return value of this method to YES, the
// UIViewController will hide the small status bar at the top
// allowing more usable space for the slot graphics.
-(BOOL)prefersStatusBarHidden{
return YES;
}
// Used to set the amount of credits that we bet on the next spin
-(void)addToBet
{
if (thisBet < totalCredits) {
if (self.thisBet < 10)
{
self.thisBet++; // bump bet
} else
self.thisBet = 1;
[self updateLabels];
self.allowSpin = YES;
}else { // can't bet more than what you have left
NSLog(@"Can't bet more than you have left");
self.thisBet = 0;
[self updateLabels];
self.allowSpin = NO;
}
}
// Default to the max bet, which is 10 credits
-(void)addMaxToBet
{
if (totalCredits == 0) return; // can't bet
if (totalCredits < 10) {
self.thisBet = totalCredits;
}
else {
self.thisBet = 10;
}
[self updateLabels];
}
//
// The primary method called when the device loads the view.
// Here, we set up pretty much everything to begin playing the game.
// NSLog statements are used to show information to the console periodiclly
// as things happen (as the program runs) to let us know what's going on.
//
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib
NSLog(@"viewDidLoad");
isSpinning = NO; // initially not spinning;
stoppingPoints[0] = 95.0;
stoppingPoints[1] = 35.0;
stoppingPoints[2] = -25.0;
stoppingPoints[3] = -85.0;
stoppingPoints[4] = -145.0;
stoppingPoints[5] = -210.0;
stoppingPoints[6] = -270.0;
stoppingPoints[7] = -330.0;
stoppingPoints[8] = -395.0;
//SETUP NOTIFICATION CENTER
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(resetGame) name:userResetGame object:nil];
NSLog(@"Registered with notification center");
// *** Create the MAIN WINDOW
CGSize appSize = [UIScreen mainScreen].bounds.size;
CGRect appRect = CGRectMake(0.0, 0.0, appSize.width, appSize.height);
NSLog(@"screen size: Width: %f, Height: %f",appSize.width,appSize.height);
//
// Determine iPhone type (4,5,6,6P) from screen size so we can
// us that to correctly position
if ((appSize.width == 320.0) && (appSize.height == 480.0)) {
iphoneType = kiPhone4S;
NSLog(@"iPhone4S");
} else if ((appSize.width == 320.0) && (appSize.height == 568.0)) {
iphoneType = kiPhone5;
NSLog(@"iPhone5");
} else if ((appSize.width == 375.0) && (appSize.height == 667.0)) {
iphoneType = kiPhone6;
NSLog(@"iPhone6");
} else if ((appSize.width == 414.0) && (appSize.height == 736.0)) {
iphoneType = kiPhone6Plus;
NSLog(@"iPhone6 Plus");
}
contentView = [[UIView alloc] initWithFrame:appRect];
contentView.backgroundColor = [UIColor blackColor];
[self.view addSubview:contentView];
// Pick Slot Face Image based on screen size
switch (iphoneType) {
case kiPhone4S:
topMostView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f,0.0f,320.0f,480.0f)];
[topMostView setImage:[UIImage imageNamed:@"SlotFaceiPhoneBasic.png"]];
break;
case kiPhone5:
topMostView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f,0.0f,320.0f,568.0f)];
[topMostView setImage:[UIImage imageNamed:@"SlotFaceiPhone5.png"]];
break;
case kiPhone6:
topMostView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f,0.0f,375.0f,667.0f)];
[topMostView setImage:[UIImage imageNamed:@"SlotFaceiPhone6.png"]];
break;
case kiPhone6Plus:
topMostView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f,0.0f,414.0f,736.0f)];
[topMostView setImage:[UIImage imageNamed:@"SlotFaceiPhone6Plus.png"]];
break;
default:
break;
}
// See if the user has played before and pull up last wheel positions
NSMutableArray *userData;
userData = [[NSUserDefaults standardUserDefaults] objectForKey:@"gameState"];
// Slide the wheels over to the right (value) depending on screen size
switch (iphoneType) {
case kiPhone4S:
case kiPhone5:
break;
case kiPhone6:
shiftOverValue = 30.0;
break;
case kiPhone6Plus:
shiftOverValue = 50.0;
break;
default:
break;
}
if ([userData count] == 6) // if data is present, then the game state was saved previously
{
slotStripViewWheel1PosStart = CGRectMake(33.0f + shiftOverValue, stoppingPoints[[[userData objectAtIndex:0] intValue]], 90.0f, 2900.0f);
slotStripViewWheel2PosStart = CGRectMake(116.0f + shiftOverValue, stoppingPoints[[[userData objectAtIndex:1] intValue]], 90.0f, 2900.0f);
slotStripViewWheel3PosStart = CGRectMake(199.0f + shiftOverValue, stoppingPoints[[[userData objectAtIndex:2] intValue]], 90.0f, 2900.0f);
self.winThisSpin = [[userData objectAtIndex:3] intValue];
self.thisBet = [[userData objectAtIndex:4] intValue];
self.totalCredits = [[userData objectAtIndex:5] intValue];
} else { // if not any data, then restart game state
NSLog(@"initializing game - no data was stored");
slotStripViewWheel1PosStart = CGRectMake(33.0f + shiftOverValue, 95.0f, 90.0f, 2900.0f);
slotStripViewWheel2PosStart = CGRectMake(116.0f + shiftOverValue, 95.0f, 90.0f, 2900.0f);
slotStripViewWheel3PosStart = CGRectMake(199.0f + shiftOverValue, 95.0f, 90.0f, 2900.0f);
[self resetGame];
}
// set up the slot wheel positions that are not saved...i.e., the end position where we reverse the wheel
// to make it look like a long spin
slotStripViewWheel1PosEnd = CGRectMake(33.0f + shiftOverValue, -2600.0f, 90.0f, 2900.0f);
slotStripViewWheel2PosEnd = CGRectMake(116.0f + shiftOverValue, -2600.0f, 90.0f, 2900.0f);
slotStripViewWheel3PosEnd = CGRectMake(199.0f + shiftOverValue, -2600.0f, 90.0f, 2900.0f);
slotStripViewWheel1 = [[UIImageView alloc] initWithFrame:slotStripViewWheel1PosStart];
[slotStripViewWheel1 setImage:[UIImage imageNamed:@"SlotStripLong.png"]];
slotStripViewWheel2 = [[UIImageView alloc] initWithFrame:slotStripViewWheel2PosStart];
[slotStripViewWheel2 setImage:[UIImage imageNamed:@"SlotStripLong.png"]];
slotStripViewWheel3 = [[UIImageView alloc] initWithFrame:slotStripViewWheel3PosStart];
[slotStripViewWheel3 setImage:[UIImage imageNamed:@"SlotStripLong.png"]];
// SET UP SCORING LABELS
// CREDITS
creditsLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 75.0f, 20.0f)];
self.creditsLabel.textAlignment = NSTextAlignmentRight;
self.creditsLabel.backgroundColor = [UIColor blackColor];
self.creditsLabel.textColor = [UIColor redColor];
self.creditsLabel.font = [UIFont boldSystemFontOfSize:20];
NSString *totString = [[NSString alloc] initWithFormat:@"%d",totalCredits];
// THIS BET
betLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 25.0f, 20.0f)];
self.betLabel.textAlignment = NSTextAlignmentRight;
self.betLabel.backgroundColor = [UIColor blackColor];
self.betLabel.textColor = [UIColor redColor];
self.betLabel.font = [UIFont boldSystemFontOfSize:20];
NSString *betString = [[NSString alloc] initWithFormat:@"%d",thisBet];
// THIS SPIN'S WIN VALUE
winLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 35.0f, 20.0f)];
self.winLabel.textAlignment = NSTextAlignmentRight;
self.winLabel.backgroundColor = [UIColor blackColor];
self.winLabel.textColor = [UIColor redColor];
self.winLabel.font = [UIFont boldSystemFontOfSize:20];
NSString *winString = [[NSString alloc] initWithFormat:@"%d",winThisSpin];
// SET UP BUTTONS
// SPIN BUTTON
spinButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 65.0f, 65.0f)];
[spinButton setBackgroundImage:[UIImage imageNamed:@"spinButton.png"] forState:UIControlStateNormal];
[spinButton setBackgroundImage:[UIImage imageNamed:@"spinButtonPressed.png"] forState:UIControlStateHighlighted];
[spinButton addTarget:self action:@selector(spin) forControlEvents:UIControlEventTouchUpInside];
[spinButton addTarget:self action:@selector(makeButtonClick) forControlEvents:UIControlEventTouchDown];
//BET BUTTON
betButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 65.0f, 65.0f)];
[betButton setBackgroundImage:[UIImage imageNamed:@"betButton.png"] forState:UIControlStateNormal];
[betButton addTarget:self action:@selector(addToBet) forControlEvents:UIControlEventTouchUpInside];
[betButton addTarget:self action:@selector(makeButtonClick) forControlEvents:UIControlEventTouchDown];
//BET MAX BUTTON
betMaxButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 65.0f, 65.0f)];
[betMaxButton setBackgroundImage:[UIImage imageNamed:@"betMaxButton.png"] forState:UIControlStateNormal];
[betMaxButton addTarget:self action:@selector(addMaxToBet) forControlEvents:UIControlEventTouchUpInside];
[betMaxButton addTarget:self action:@selector(makeButtonClick) forControlEvents:UIControlEventTouchDown];
// Pick based on screen size
switch (iphoneType) {
case kiPhone4S:
case kiPhone5:
[creditsLabel setCenter:CGPointMake(93.0f,213.0f)];
[betLabel setCenter:CGPointMake(160.0f,213.0f)];
[winLabel setCenter:CGPointMake(220.0f,213.0f)];
[spinButton setCenter:CGPointMake(260.0f,300.0f)];
[betButton setCenter:CGPointMake(150.0f,300.0f)];
[betMaxButton setCenter:CGPointMake(65.0f,300.0f)];
break;
case kiPhone6:
[creditsLabel setCenter:CGPointMake(120.0f,216.0f)];
[betLabel setCenter:CGPointMake(190.0f,216.0f)];
[winLabel setCenter:CGPointMake(255.0f,216.0f)];
[spinButton setCenter:CGPointMake(290.0f,302.0f)];
[betButton setCenter:CGPointMake(190.0f,302.0f)];
[betMaxButton setCenter:CGPointMake(100.0f,302.0f)];
break;
case kiPhone6Plus:
[creditsLabel setCenter:CGPointMake(140.0f,212.0f)];
[betLabel setCenter:CGPointMake(212.0f,212.0f)];
[winLabel setCenter:CGPointMake(280.0f,212.0f)];
[spinButton setCenter:CGPointMake(320.0f,300.0f)];
[betButton setCenter:CGPointMake(220.0f,300.0f)];
[betMaxButton setCenter:CGPointMake(120.0f,300.0f)];
break;
default:
break;
}
self.creditsLabel.text = totString;
self.betLabel.text = betString;
self.winLabel.text = winString;
[contentView addSubview:slotStripViewWheel1];
[contentView addSubview:slotStripViewWheel2];
[contentView addSubview:slotStripViewWheel3];
[contentView addSubview:topMostView];
[contentView addSubview:spinButton];
[contentView addSubview:betButton];
[contentView addSubview:betMaxButton];
[contentView addSubview:creditsLabel];
[contentView addSubview:betLabel];
[contentView addSubview:winLabel];
// restore user setting
// [self restoreUserSettings]; // things like spin, score, etc
// SET UP SOUNDS
CFBundleRef mainBundle;
mainBundle = CFBundleGetMainBundle ();
// Get the URL to the sound file to play
spinFileURLRef = CFBundleCopyResourceURL (
mainBundle,
CFSTR ("spinSound1"),
CFSTR ("wav"),
NULL
);
clickFileURLRef = CFBundleCopyResourceURL (
mainBundle,
CFSTR ("click1"),
CFSTR ("wav"),
NULL
);
winFileURLRef = CFBundleCopyResourceURL (
mainBundle,
CFSTR ("win"),
CFSTR ("wav"),
NULL
);
loseFileURLRef = CFBundleCopyResourceURL (
mainBundle,
CFSTR ("youLose"),
CFSTR ("wav"),
NULL
);
// Create a system sound object representing the sound file
AudioServicesCreateSystemSoundID (
spinFileURLRef,
&spinSoundObject
);
AudioServicesCreateSystemSoundID (
clickFileURLRef,
&clickSoundObject
);
AudioServicesCreateSystemSoundID (
winFileURLRef,
&winSoundObject
);
AudioServicesCreateSystemSoundID (
loseFileURLRef,
&loseSoundObject
);
//SETUP LIGHTS
[self setupGreenLightSequence];
[self setupRedLightSequence];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// GAME PLAY METHODS
// Spin, of course, does the most of the work when the player clicks on the 'spin' button.
// in the viewDidLoad method above, you can see that when we create the spin button, we set
// the "selector" to 'spin,' which is this function. This is the example of event-driven programming;
// when the spin button event occurs, iOS (the operating system) calls this function to be executed.
-(void)spin
{
// start flashing the red and green lights at the top of
// the slot machine image on the device.
[self startGreenLightAnimation];
[self startRedLightAnimation];
// If we're spinning, disable the buttons so the player can't cause
// problems much like a real slot machine
isSpinning = YES;
spinButton.enabled = NO;
betButton.enabled = NO;
betMaxButton.enabled = NO;
// THE THREE SPINS - generate a random place to stop on our simulated 'wheel'
spin1 = arc4random() % numberOfIcons; // large number modulo the # of icons
spin2 = arc4random() % numberOfIcons; // large number modulo the # of icons
spin3 = arc4random() % numberOfIcons; // large number modulo the # of icons
// Create a single number that tells us what the spin is
// using a decimal scheme...one wheel is the hundreds position, one the tens, and
// the right-most is the ones position.
spinValue = (spin1 * 100) + (spin2 * 10) + spin3;
NSLog(@"The three wheel spins are: %lu, %lu, %lu",(unsigned long)spin1,(unsigned long)spin2,(unsigned long)spin3);
NSLog(@"Spin Value = %lu", (unsigned long)spinValue);
slotStripViewWheel1PosComplete = CGRectMake(33.0f + shiftOverValue, stoppingPoints[spin1], 90.0f, 2900.0f);
slotStripViewWheel2PosComplete = CGRectMake(116.0f + shiftOverValue, stoppingPoints[spin2], 90.0f, 2900.0f);
slotStripViewWheel3PosComplete = CGRectMake(199.0f + shiftOverValue, stoppingPoints[spin3], 90.0f, 2900.0f);
// These three chunks of code set up the animation of each of the three 'wheels'
// essentially, all were doing is moving the strips of fruit images up and down
// to give the appearance of the three wheels spinning.
//
[UIView beginAnimations:@"wheel1" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(firstWheelReverse:)];
[UIView setAnimationCurve: UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:2.0];
[slotStripViewWheel1 setFrame:slotStripViewWheel1PosEnd];
[UIView commitAnimations];
[UIView beginAnimations:@"wheel2" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(secondWheelReverse:)];
[UIView setAnimationCurve: UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:2.0];
[slotStripViewWheel2 setFrame:slotStripViewWheel2PosEnd];
[UIView commitAnimations];
[UIView beginAnimations:@"wheel3" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(thirdWheelReverse:)];
[UIView setAnimationCurve: UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:2.0];
[slotStripViewWheel3 setFrame:slotStripViewWheel3PosEnd];
[UIView commitAnimations];
// SOUNDS
AudioServicesPlaySystemSound (self.spinSoundObject);
} // end SPIN method
//
// Because we are using finite-length strips of images to simulate a continuous
// 'wheel' to get that sense of spinning, when we reach the end of a strip, we
// just reverse it and move it the other way, hoping the details of what we're doing
// aren't visible on the screen to the player.
- (void)firstWheelReverse:(NSString *)animationID {
[UIView beginAnimations:@"reverseWheel1" context:nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:1.0];
[slotStripViewWheel1 setFrame:slotStripViewWheel1PosComplete];
[UIView commitAnimations];
}
- (void)secondWheelReverse:(NSString *)animationID {
[UIView beginAnimations:@"reverseWheel2" context:nil];
[UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:1.4];
[slotStripViewWheel2 setFrame:slotStripViewWheel2PosComplete];
[UIView commitAnimations];
}
- (void)thirdWheelReverse:(NSString *)animationID { // Assume third wheel is the last to stop
NSLog(@"Spinning Has Stopped");
[UIView beginAnimations:@"reverseWheel3" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(spinningHasStopped:)];
[UIView setAnimationCurve: UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:1.8];
[slotStripViewWheel3 setFrame:slotStripViewWheel3PosComplete];
[UIView commitAnimations];
}
//
// When the animation has completed, this method executes.
// We enable the buttons again so the player can continue,
// play sounds, stop flashing the lights, etc.
//
-(void)spinningHasStopped:(NSString *)animationID
{
int winMultiplier;
NSLog(@"spinningHasStopped CALLED");
isSpinning = NO;
spinButton.enabled = YES;
betButton.enabled = YES;
betMaxButton.enabled = YES;
//STOP LIGHTS
[self stopGreenLightAnimation];
[self stopRedLightAnimation];
// CHECK FOR WIN
winMultiplier = [self calculateWinnings];
// Lose
if (winMultiplier == 0) {
self.totalCredits -= self.thisBet;
AudioServicesPlaySystemSound (self.loseSoundObject);
} else { // Win
self.totalCredits += (self.thisBet * winMultiplier);
AudioServicesPlaySystemSound (self.winSoundObject);
}
[self updateLabels];
// save state
[self saveGameState ];
}
-(void)resetGame
{
NSLog(@"RESET GAME");
[self makeButtonClick];
self.winThisSpin = 0;
self.thisBet = 1;
self.totalCredits = kInitialCredits;
self.allowSpin = YES;
self.gameOver = NO;
[self updateLabels];
// save state - in case user exits immediately after a reset either from alert or info panel
[self saveGameState];
}
//
// This method posts the values for bet, total credits, and win amount to the
// display on the slot machine front panel image.
//
-(void)updateLabels;
{
//TOTAL
NSString *totString = [[NSString alloc] initWithFormat:@"%d", totalCredits];
[creditsLabel setText:totString];
//BET
NSString *betString = [[NSString alloc] initWithFormat:@"%d", thisBet];
[betLabel setText:betString];
//WIN AMMOUNT
NSString *winString = [[NSString alloc] initWithFormat:@"%d", winThisSpin];
[winLabel setText:winString];
}
//
// Here is where you can change how you want to pay out to the
// player depending on the spin
//
-(int)calculateWinnings
{
int winMultiplier;
// Any single cherry
if ((spin1 == 2) && (spin2 != 2) && (spin3 != 2)) return 1;
if ((spin1 != 2) && (spin2 == 2) && (spin3 != 2)) return 1;
if ((spin1 != 2) && (spin2 != 2) && (spin3 == 2)) return 1;
// Any DOUBLE cherry
if ((spin1 == 2) && (spin2 == 2) && (spin3 != 2)) return 3;
if ((spin1 != 2) && (spin2 == 2) && (spin3 == 2)) return 3;
if ((spin1 == 2) && (spin2 != 2) && (spin3 == 2)) return 3;
// Three CHERRIES
if ((spin1 == 2) && (spin2 == 2) && (spin3 == 2)) return 150;
switch (spinValue) {
case 000:
winMultiplier = 100; // 3 Bars
break;
case 888:
winMultiplier = 100; // 3 sevens
break;
case 111:
case 222:
case 333:
case 444:
case 555:
case 666:
case 777:
winMultiplier = 3; // 3 anything else --> 3X bet
break;
default:
winMultiplier = 0; // anything else --> lose
break;
}
return winMultiplier;
}
// Pop up an alert to let user reset the game
-(void) youLost
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"You Lose" message:@"Lost it all huh? Way to go champ!" preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:alert animated:YES completion:nil];
}
// LIGHT ANIMATIONS
-(void)setupGreenLightSequence
{
UIImage* img1;
UIImage* img2;
UIImage* img3;
UIImage* img4;
UIImage* img5;
greenLightSequenceImageView = [[UIImageView alloc] init];
if (iphoneType == kiPhone6Plus) {
img1 = [UIImage imageNamed:@"100greenTop6P.png"];
img2 = [UIImage imageNamed:@"110greenTop6P.png"];
img3 = [UIImage imageNamed:@"111greenTop6P.png"];
img4 = [UIImage imageNamed:@"011greenTop6P.png"];
} else { //smaller screen size
img1 = [UIImage imageNamed:@"100greenTop.png"];
img2 = [UIImage imageNamed:@"110greenTop.png"];
img3 = [UIImage imageNamed:@"111greenTop.png"];
img4 = [UIImage imageNamed:@"011greenTop.png"];
}
NSArray *images = [NSArray arrayWithObjects:img1, img2,img3,img4,img5, nil];
[greenLightSequenceImageView setAnimationImages:images];
[greenLightSequenceImageView setAnimationRepeatCount:0];
[greenLightSequenceImageView setAnimationDuration:0.5];
switch (iphoneType) {
case kiPhone4S:
greenLightSequenceImageView.frame = CGRectMake(71,1, 200, 20);
break;
case kiPhone5:
greenLightSequenceImageView.frame = CGRectMake(71,1, 200, 20);
break;
case kiPhone6:
greenLightSequenceImageView.frame = CGRectMake(100,1, 200, 20);
break;
case kiPhone6Plus:
greenLightSequenceImageView.frame = CGRectMake(114,1, 200, 20);
break;
default:
break;
}
}
-(void)startGreenLightAnimation
{
[greenLightSequenceImageView startAnimating];
[self.view addSubview:greenLightSequenceImageView];
}
-(void)stopGreenLightAnimation
{
[greenLightSequenceImageView stopAnimating];
[greenLightSequenceImageView removeFromSuperview];
}
-(void)setupRedLightSequence
{
UIImage* img1;
UIImage* img2;
UIImage* img3;
UIImage* img4;
UIImage* img5;
redLightSequenceImageView = [[UIImageView alloc] init];
if (iphoneType == kiPhone6Plus) {
img1 = [UIImage imageNamed:@"001redBottom6P.png"];
img2 = [UIImage imageNamed:@"011redBottom6P.png"];
img3 = [UIImage imageNamed:@"111redBottom6P.png"];
img4 = [UIImage imageNamed:@"110redBottom6P.png"];
img5 = [UIImage imageNamed:@"100redBottom6P.png"];
} else { //smaller screen size
img1 = [UIImage imageNamed:@"001redBottom.png"];
img2 = [UIImage imageNamed:@"011redBottom.png"];
img3 = [UIImage imageNamed:@"111redBottom.png"];
img4 = [UIImage imageNamed:@"110redBottom.png"];
img5 = [UIImage imageNamed:@"100redBottom.png"];
}
NSArray *images = [NSArray arrayWithObjects:img1, img2,img3,img4,img5, nil];
[redLightSequenceImageView setAnimationImages:images];
[redLightSequenceImageView setAnimationRepeatCount:0];
[redLightSequenceImageView setAnimationDuration:0.5];
switch (iphoneType) {
case kiPhone4S:
redLightSequenceImageView.frame = CGRectMake(71,5, 200, 15);
break;
case kiPhone5:
redLightSequenceImageView.frame = CGRectMake(71,5, 200, 15);
break;
case kiPhone6:
redLightSequenceImageView.frame = CGRectMake(100,5, 200, 15);
break;
case kiPhone6Plus:
redLightSequenceImageView.frame = CGRectMake(114,5, 200, 15);
break;
default:
break;
}
}
-(void)startRedLightAnimation
{
NSLog(@"Start Animating RED");
[redLightSequenceImageView startAnimating];
[self.view addSubview:redLightSequenceImageView];
}
-(void)stopRedLightAnimation
{
[redLightSequenceImageView stopAnimating];
[redLightSequenceImageView removeFromSuperview];
}
// SOUND ANIMATIONS
-(void)makeButtonClick
{
AudioServicesPlaySystemSound (self.clickSoundObject);
}
// PERSISTANCE - this saves the player's game state.
// Because we greatly simplified this game for newer versions of
// iOS, we don't actually do that much here. We're more concerned
// in this exercise about the process of converting, so while we
// do care about game state items such as score and last spin, we
// are not concerned about switch settings for whether to play sounds
// or not.
//
-(void)saveGameState
{
NSLog(@"Calling Save Game State");
NSMutableArray *userData = [[NSMutableArray alloc] init];
[userData addObject:[NSNumber numberWithInt:(int)spin1]];
[userData addObject:[NSNumber numberWithInt:(int)spin2]];
[userData addObject:[NSNumber numberWithInt:(int)spin3]];
[userData addObject:[NSNumber numberWithInt:self.winThisSpin]];
[userData addObject:[NSNumber numberWithInt:self.thisBet]];
[userData addObject:[NSNumber numberWithInt:self.totalCredits]];
[[NSUserDefaults standardUserDefaults] setObject: userData forKey:@"gameState"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
-(void)restoreUserSettings
{
NSLog(@"Called restore user settings");
// CHECK USER SETTINGS
}
@end
We won’t be going through the conversion line by line, as that would take up far too much space, and, frankly, it would be pretty boring. Instead, I want to walk through a few examples of where we convert Objective-C to Swift. Simple assignments, conditionals, operations and so forth work nearly the same across the languages. Other specific features of Swift as it differs from Objective-C are well known, such as not needing semicolons, or that all potential conditions in a switch need to be explicitly handled.
I’ll focus instead on the issues you’re likely to come up against that you probably didn’t think about. Also, we’ll cover some of the differences in how you use certain frameworks when porting our slot machine app.
Project Setup
Problem
You’re given the Objective-C project, but you can’t just convert it as it is. You want to use it as a reference and make sure things work as expected.
Solution
We want to set up a completely new project and work with them side by side until everything functions exactly as we would expect. First, create a new iOS single view application project, as shown in Figure 14-5.
Figure 14-5. Create a single view project to begin the conversion
I chose to call this project townslot2 (Figure 14-6) in order to differentiate it from the original app name, but at the same time keep it similar. If we decide to publish it in the App Store we’ll have a usable name, since Apple’s database would recognize the original townslot app and reject our reuse of the name. Note that I’m not including any Unit or UI Testing in order to focus on just the conversion process. While we could use Core Data for persistent storage, that would be a bit of overkill for our needs of simply storing six values; it would also deviate from the original app’s design. Finally, as seen in Figure 14-7, don’t select “Create a Git repository,” as we will address source control in a different section of this book.
Figure 14-6. Create a Swift project without core data or tests in order to keep things simple
Figure 14-7. We won’t be worried about source code control in this example
That gets us to where we need to be, with a new, blank Swift project in which we can place our newly converted Swift code.
Problem
After creating the project, our build settings show that we have no project team assigned.
Solution
In the project navigator on the left side of Xcode , select the top-level folder, i.e., the name of the project, which in our case is townslot2. To the left select “General” and look for the Identity section. If you see beside Team anything other than “None” (Figure 14-8), verify that it is either your individual team, which should show as your name, or a team that you created for your company info. If it shows the word “None,” use the drop-down menu to select the desired team. If you don’t see any options, you may want to review Chapter 3 on how to set up Xcode for this operation.
Figure 14-8. Unless you’ve previously set up your certificates, app IDs, devices, provisioning, and teams, you may see no team selected under Identity in project settings
Problem
You need to set up your code-signing identities.
Solution
From where we just were, select “Build Settings” and look for the Code Signing section . You’ll probably see something that looks like Figure 14-9 and shows the generic term “iOS Developer,” which usually represents the team and works for localized testing. However, once you’re ready to distribute to beta testers you’ll need to set this to a specific identity, as shown in Figure 14-10.
Figure 14-9. In the Code Signing section of Build Settings, make sure you select a valid identity for debug builds. It’s also a good idea to verify that Release is also set properly
Figure 14-10. Set up specific identities when building for beta testing or App Store release
Problem
Xcode shows you have no provisioning profiles set up, as seen in Figure 14-11.
Figure 14-11. After setting up identities for what you need to accomplish, you may find that Xcode now gives you a missing provisioning profile error
Solution
Sometimes simply pressing the Fix Issue button will handle the problem . This usually works if you have a single Apple developer account, as Xcode can easily figure out what needs to happen. There are two situations in which Xcode sometimes won’t be able to correct things. The first situation is if you haven’t yet set up any profiles in the developer portal, although at the time you’re reading this, that feature may have already been added to Xcode. If that is the situation you’re facing and Xcode doesn’t do it for you, refer to Chapter 3 and set up your provisioning profiles with the steps I’ve outlined there.
The second situation wherein Xcode may not automatically handle things for you is when you have a company account or multiple accounts, either individual, company, or mixed. In this situation, what I usually do is to force the issue, or rather, force the fix to the issue. Simply put, with multiple developer IDs, signing credentials, and so on, Xcode can get a little confused. So, if I’ve created a provisioning profile for my app, I download it from the portal to my computer and then drag the downloaded file icon right on top of the Xcode app icon. This is the way we used to do things a couple years ago, and as of the time of writing, it still works consistently. You’ll likely only need this if you have a more confusing developer account setup, but it’s a good trick to know when you have to use it.
Conversions
Problem
You start converting your program to Swift from Objective-C and you immediately see the error shown in Figure 14-12. The error “Class ViewController has no initializers” means that there are variables that have either not been initialized or should be treated as optionals.
Figure 14-12. No Initializer error
Solution
In Objective-C we declared our properties in one place, the header (.h) file; synthesized the accessors at the top of the implementation (.m) file; and usually allocated and initialized them later in the implementation. In Swift we just call them variables using the var keyword, but, unless we declare it as an optional, it has to be initialized when declared. This is part of the safety features built in to the language that, while annoying at first, will save us time down the road, preventing a crash when our backs are against the wall. So, as shown in Figure 14-13, we initialize the variables when they are declared, and the problem goes away.
Figure 14-13. By initializing the variables when created, our class error goes away
We obviously can’t go through everything line by line, so I want to cover a few functions so you get the basic idea before we move on. We’ll start by looking at some of the simpler supporting functions.
Problem
When converting the function to save the state of the game into persistent storage, you get a lot of errors, as shown in Figure 14-14. The error “NSMutableArray is not implicitly convertible to [AnyObject]” tells us that there is no automatic conversion between the types from Objective-C to Swift.
Figure 14-14. Use of AnyObject causing conversion errors
Solution
Although it worked in earlier versions, Swift now does not convert between NSArray/NSMutableArray and Swift’s native array type. While we could cast this to make it work in much the same manner, a better approach would be to explicitly set the values we want to save into the standard user defaults, because there are only six items that we need to track. Simply create a constant defaults object. Since we’re not actually changing the object, but only calling the methods on that object, we can use the safer, Swift let statement as shown in Figure 14-15. Note that we also change to the Swift print to let us know we’re in this function as well. Then, all we need do is use the setInteger method to save each of our six items with an explicit key for each.
Figure 14-15. Rather than directly converting from Objective-C to Swift, in many cases it’s easier to change the way the code functions. Here, we’ve added explicit keys to store each item individually, making the code easier to read
Then, it becomes a simple matter of making similar explicit calls to get and restore the defaults when needed, as shown in Figure 14-16. However, you do have to be careful here. Since it’s possible to call the restoreUserSettings function before any items have been saved, if you just try to use a value, it could be nil, which would cause the app to crash. So, what I’ve done is to create a check to see if the first value we want to return, spin1, is nil or not. If it is NOT nil, then we know we’ve saved our values and can reasonably expect to be able to retrieve and use the remaining five items successfully. If the value is nil, then we haven’t yet saved any defaults and want to start by initializing the game. It would actually be even safer—and you should do this in a true production application—to check each and every value before attempting to access it. As always, there are many ways to execute the same functionality, and each case would be slightly different in how it should be addressed.
Figure 14-16. Make sure to check that a value is present before attempting to use it
Problem
After converting your Objective-C to Swift, the app crashes on an iPhone 4S or iPhone 5, but works fine otherwise. The crash occurs as soon as you hit Spin in the area shown in Figure 14-17. Because arc4random returns a 32-bit unsigned integer, on a 32-bit device like the iPhone 4S and iPhone 5, if the returned value is large enough, it could overflow and cause the app to crash. The “EXEC_BAD_INSTRUCTION” error is an indication that the calls used on Objective-C likely don’t match what we need to implement here in the Swift code.
Figure 14-17. Although this run crashed at spin2, it may occur at any of these three statements
Solution
By using arc4random_uniform and passing an upper bound to the possible return value (Figure 14-18), you prevent the operation from overflowing. This works fine on all four devices of interest in this app: iPhone 4S, iPhone 5, iPhone 6, and iPhone 6 Plus.
Figure 14-18. Use arc4random_uniform and pass in an upper limit to prevent overflows on 32-bit devices such as iPhone 4S and iPhone 5
Problem
You work through the rest of the conversions, but once you load the app onto a real device, no app icon is displayed, as in Figure 14-19.
Figure 14-19. Once you install the app onto a device, only the default app icon is displayed
Solution
To make the icon display, we need to associate the image files we intend to use with the app in the AppIcon set. If you look in the project at the AppIcon, you see no images, as shown in Figure 14-20.
Figure 14-20. Because we created a new project, we haven’t yet moved over any icon images, so the AppIcon xcassets will be empty
If we look back at the Objective-C project’s AppIcon xcassets set, you see that all the images we need to use are properly associated with the app (Figure 14-21).
Figure 14-21. The Objective-C version of the project shows the proper AppIcon images
If you right-click on any of the images seen in Figure 14-21, you can select “Show In Finder” to see where the actual image files are located (Figure 14-22). Then simply copy them to the same relative place, the AppIcon.appiconset sub-folder of the Assets.appiconset folder in the Swift version and you’re almost there. You will need to go back into the Xcode project and move the icons in the AppIcon set to the proper position, but they should be in the proper order already. If not, verify the size as shown in the Xcode window. Rebuild the app and load it onto your device. You may discover that you need to do a clean project first, but this has not been necessary in the most recent version of Xcode. You should then see the icon properly displayed on the home screen, as in Figure 14-23.
Figure 14-22. The appropriate image files most likely will be found in a sub-directory of the original project. Simply copy them to the same relative place in the new project hierarchy, then move them to the proper place in Xcode
Figure 14-23. Finally, clean (if necessary) and rebuild the project, loading it to your device, and the icon should be properly displayed
Swift Code
As I mentioned earlier, we could cover every detail of every conversion issue in this project. But, even with this simple app, that would likely take much more time and space than either of us have to devote at this stage of our journey. Listing 14-6 shows our final conversion of the Objective-C project.
Listing 14-6. ViewController.swift File
//
// ViewController.swift
// townslot2
//
// Created by Molly Maskrey on 11/10/15.
// Copyright © 2015 Global Tek Labs. All rights reserved.
//
import UIKit
import AudioToolbox
let userResetGame: String = "resetGame"
let kInitialCredits : Int = 100
class ViewController: UIViewController {
//
// PROPERTIES from OBJ-c to Swift
//
var thisBet : Int = 0
var totalCredits : Int = 0
var allowSpin: Bool = true
var isSpinning: Bool = false
var gameOver: Bool = false
var stoppingPoints : [Double] = [95.0,35.0,-25.0,-85.0,-145.0,-210.0,-270.0,-330.0,-395.0]
enum iPhoneType {
case knotSelectedYet
case kiPhone4S
case kiPhone5
case kiPhone6
case kiPhone6Plus
}
var iphoneType : iPhoneType = .knotSelectedYet
var topMostView : UIView?
var shiftOverValue = 0.0
var slotStripViewWheel1PosStart: CGRect?
var slotStripViewWheel1PosEnd: CGRect?
var slotStripViewWheel2PosStart: CGRect?
var slotStripViewWheel2PosEnd: CGRect?
var slotStripViewWheel3PosStart: CGRect?
var slotStripViewWheel3PosEnd: CGRect?
var slotStripViewWheel1PosComplete: CGRect?
var slotStripViewWheel2PosComplete: CGRect?
var slotStripViewWheel3PosComplete: CGRect?
var winThisSpin : Int = 0
var slotStripViewWheel1 : UIImageView?
var slotStripViewWheel2 : UIImageView?
var slotStripViewWheel3 : UIImageView?
var greenLightSequenceImageView : UIImageView = UIImageView()
var redLightSequenceImageView : UIImageView = UIImageView()
var creditsLabel : UILabel?
var betLabel : UILabel?
var winLabel : UILabel?
let spinButton = UIButton(frame: CGRectMake(0.0, 0.0, 65.0, 65.0))
let betButton = UIButton(frame: CGRectMake(0.0, 0.0, 65.0, 65.0))
let betMaxButton = UIButton(frame: CGRectMake(0.0, 0.0, 65.0, 65.0))
var spinFileURLRef: CFURLRef?
var spinSoundObject: SystemSoundID = 0
var clickFileURLRef: CFURLRef?
var clickSoundObject: SystemSoundID = 0
var winFileURLRef: CFURLRef?
var winSoundObject: SystemSoundID = 0
var loseFileURLRef: CFURLRef?
var loseSoundObject: SystemSoundID = 0
var spin1: Int = 0
var spin2: Int = 0
var spin3: Int = 0
var spinValue : Int = 0
let numberOfIcons : Int = 9
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("viewDidLoad")
isSpinning = false // initially not spinning - NOTE this is not needed
// because it was set as an initializer...
let nc: NSNotificationCenter = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: "resetGame", name: userResetGame, object: nil)
print("Registered with notification center")
let appSize: CGSize = UIScreen.mainScreen().bounds.size
let appRect: CGRect = CGRectMake(0.0, 0.0, appSize.width, appSize.height)
print("screen size: Width: (appRect.width), Height: (appRect.height)")
let contentView = UIView(frame: appRect)
contentView.backgroundColor = UIColor.blackColor()
self.view.addSubview(contentView)
//
// Determine iPhone type (4,5,6,6P) from screen size so we can
// us that to correctly position
if (appSize.width == 320.0) && (appSize.height == 480.0) {
iphoneType = .kiPhone4S
print("iPhone4S")
}
else {
if (appSize.width == 320.0) && (appSize.height == 568.0) {
iphoneType = .kiPhone5
print("iPhone5")
}
else {
if (appSize.width == 375.0) && (appSize.height == 667.0) {
iphoneType = .kiPhone6
print("iPhone6")
}
else {
if (appSize.width == 414.0) && (appSize.height == 736.0) {
iphoneType = .kiPhone6Plus
print("iPhone6 Plus")
}
}
}
}
switch iphoneType {
case .kiPhone4S:
topMostView = UIImageView(frame: CGRectMake(0.0, 0.0, 320.0, 480.0))
topMostView?.backgroundColor = UIColor(patternImage: UIImage(named: "SlotFaceiPhoneBasic.png")!)
print("iPhone 4S")
case .kiPhone5:
topMostView = UIImageView(frame: CGRectMake(0.0, 0.0, 320.0, 568.0))
topMostView?.backgroundColor = UIColor(patternImage: UIImage(named: "SlotFaceiPhone5.png")!)
print("iPhone 5")
case .kiPhone6:
topMostView = UIImageView(frame: CGRectMake(0.0, 0.0, 375.0, 667.0))
topMostView?.backgroundColor = UIColor(patternImage: UIImage(named: "SlotFaceiPhone6.png")!)
print("iPhone 6")
case .kiPhone6Plus:
topMostView = UIImageView(frame: CGRectMake(0.0, 0.0, 414.0, 736.0))
topMostView?.backgroundColor = UIColor(patternImage: UIImage(named: "SlotFaceiPhone6Plus.png")!)
print("iPhone 6 Plus")
default:
print("entered iphoneType set topMostView DEFAULT case")
}
// Slide the wheels over to the right (value) depending on screen size
switch iphoneType {
case .kiPhone4S:
shiftOverValue = 0.0
case .kiPhone5:
shiftOverValue = 0.0
case .kiPhone6:
shiftOverValue = 30.0
case .kiPhone6Plus:
shiftOverValue = 50.0
default:
break
}
// SET UP SCORING LABELS
creditsLabel = UILabel(frame: CGRectMake(0.0, 0.0, 75.0, 20.0))
self.creditsLabel!.textAlignment = .Right
self.creditsLabel!.backgroundColor = UIColor.blackColor()
self.creditsLabel!.textColor = UIColor.redColor()
self.creditsLabel!.font = UIFont.boldSystemFontOfSize(20)
let totString: String = String(format: "%d", totalCredits)
self.creditsLabel!.text = totString;
betLabel = UILabel(frame: CGRectMake(0.0, 0.0, 25.0, 20.0))
self.betLabel!.textAlignment = .Right
self.betLabel!.backgroundColor = UIColor.blackColor()
self.betLabel!.textColor = UIColor.redColor()
self.betLabel!.font = UIFont.boldSystemFontOfSize(20)
let betString: String = String(format: "%2d", totalCredits)
self.betLabel!.text = betString;
winLabel = UILabel(frame: CGRectMake(0.0, 0.0, 35.0, 20.0))
self.winLabel!.textAlignment = .Right
self.winLabel!.backgroundColor = UIColor.blackColor()
self.winLabel!.textColor = UIColor.redColor()
self.winLabel!.font = UIFont.boldSystemFontOfSize(20)
let winString: String = String(format: "%d", totalCredits)
self.winLabel!.text = winString;
restoreUserSettings()
slotStripViewWheel1PosEnd = CGRectMake(33.0 + CGFloat(shiftOverValue), -2600.0, 90.0, 2900.0);
slotStripViewWheel2PosEnd = CGRectMake(116.0 + CGFloat(shiftOverValue), -2600.0, 90.0, 2900.0);
slotStripViewWheel3PosEnd = CGRectMake(199.0 + CGFloat(shiftOverValue), -2600.0, 90.0, 2900.0);
slotStripViewWheel1 = UIImageView(frame: slotStripViewWheel1PosStart!)
slotStripViewWheel1?.image = UIImage(named: "SlotStripLong.png")
slotStripViewWheel2 = UIImageView(frame: slotStripViewWheel2PosStart!)
slotStripViewWheel2?.image = UIImage(named: "SlotStripLong.png")
slotStripViewWheel3 = UIImageView(frame: slotStripViewWheel3PosStart!)
slotStripViewWheel3?.image = UIImage(named: "SlotStripLong.png")
spinButton.setImage(UIImage(named: "spinButton.png"), forState: .Normal)
spinButton.setImage(UIImage(named: "spinButtonPressed.png"), forState: .Highlighted)
spinButton.addTarget(self, action: "spin", forControlEvents: .TouchUpInside)
spinButton.addTarget(self, action: "makeButtonClick", forControlEvents: .TouchUpInside)
betButton.setImage(UIImage(named: "betButton.png"), forState: .Normal)
betButton.addTarget(self, action: "addToBet", forControlEvents: .TouchUpInside)
betButton.addTarget(self, action: "makeButtonClick", forControlEvents: .TouchUpInside)
betMaxButton.setImage(UIImage(named: "betMaxButton.png"), forState: .Normal)
betMaxButton.addTarget(self, action: "addMaxToBet", forControlEvents: .TouchUpInside)
betMaxButton.addTarget(self, action: "makeButtonClick", forControlEvents: .TouchUpInside)
switch iphoneType {
case .kiPhone4S, .kiPhone5:
creditsLabel!.center = CGPointMake(93.0, 213.0)
betLabel!.center = CGPointMake(160.0, 213.0)
winLabel!.center = CGPointMake(220.0, 213.0)
spinButton.center = CGPointMake(260.0, 300.0)
betButton.center = CGPointMake(150.0, 300.0)
betMaxButton.center = CGPointMake(65.0, 300.0)
case .kiPhone6:
creditsLabel!.center = CGPointMake(120.0, 216.0)
betLabel!.center = CGPointMake(190.0, 216.0)
winLabel!.center = CGPointMake(255.0, 216.0)
spinButton.center = CGPointMake(290.0, 302.0)
betButton.center = CGPointMake(190.0, 302.0)
betMaxButton.center = CGPointMake(100.0, 302.0)
case .kiPhone6Plus:
creditsLabel!.center = CGPointMake(140.0, 212.0)
betLabel!.center = CGPointMake(212.0, 212.0)
winLabel!.center = CGPointMake(280.0, 212.0)
spinButton.center = CGPointMake(320.0, 300.0)
betButton.center = CGPointMake(220.0, 300.0)
betMaxButton.center = CGPointMake(120.0, 300.0)
default:
break
}
contentView.addSubview(slotStripViewWheel1!)
contentView.addSubview(slotStripViewWheel2!)
contentView.addSubview(slotStripViewWheel3!)
contentView.addSubview(topMostView!)
// Note Order of buttons and labels ON TOP of TOPMOST VIEW
contentView.addSubview(creditsLabel!)
contentView.addSubview(betLabel!)
contentView.addSubview(winLabel!)
contentView.addSubview(spinButton)
contentView.addSubview(betButton)
contentView.addSubview(betMaxButton)
// SET UP SOUNDS
var mainBundle: CFBundleRef
mainBundle = CFBundleGetMainBundle()
// Get the URL to the sound file to play
spinFileURLRef = CFBundleCopyResourceURL(mainBundle, "spinSound1" as CFString , "wav" as CFString , nil)
AudioServicesCreateSystemSoundID(spinFileURLRef!, &spinSoundObject)
clickFileURLRef = CFBundleCopyResourceURL(mainBundle, "click1" as CFString , "wav" as CFString , nil)
AudioServicesCreateSystemSoundID(clickFileURLRef!, &clickSoundObject)
winFileURLRef = CFBundleCopyResourceURL(mainBundle, "win" as CFString , "wav" as CFString , nil)
AudioServicesCreateSystemSoundID(winFileURLRef!, &winSoundObject)
loseFileURLRef = CFBundleCopyResourceURL(mainBundle, "youLose" as CFString , "wav" as CFString , nil)
AudioServicesCreateSystemSoundID(loseFileURLRef!, &loseSoundObject)
setupGreenLightSequence()
setupRedLightSequence()
updateLabels()
} // END VIEW_DID_LOAD *******
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//
// PORTED OBJ-C METHODS to FUNCS
//
override func prefersStatusBarHidden() -> Bool {
return true
}
func addToBet() -> () { // Null return OPTIONAL
if thisBet < totalCredits {
if self.thisBet < 10 {
self.thisBet++
}
else {
self.thisBet = 1
}
self.updateLabels()
self.allowSpin = true
}
else {
print("Can't bet more than you have left")
self.thisBet = 0
self.updateLabels()
self.allowSpin = false
}
}
func addMaxToBet() {
if totalCredits == 0 {
return
}
if totalCredits < 10 {
self.thisBet = totalCredits
}
else {
self.thisBet = 10
}
self.updateLabels()
}
//
// SPIN FUNCTION - This is where most of the activity takes place
//
func spin() {
print("SPIN called")
// start flashing the red and green lights at the top of
// the slot machine image on the device.
startGreenLightAnimation()
startRedLightAnimation()
// If we're spinning, disable the buttons so the player can't cause
// problems, much like a real slot machine
isSpinning = true
spinButton.enabled = false
betButton.enabled = false
betMaxButton.enabled = false
// THE THREE SPINS - generate a random place to stop on our simulated 'wheel'
// spin1 = Int(arc4random()) % numberOfIcons // large number modulo the # of icons
// spin2 = Int(arc4random()) % numberOfIcons // large number modulo the # of icons
// spin3 = Int(arc4random()) % numberOfIcons // large number modulo the # of icons
spin1 = Int(arc4random_uniform(10000)) % numberOfIcons
spin2 = Int(arc4random_uniform(10000)) % numberOfIcons
spin3 = Int(arc4random_uniform(10000)) % numberOfIcons
// Create a single number that tells us what the spin is
// using a decimal scheme. One wheel is the hundreds position, one the tens, and
// the right-most is the ones position.
spinValue = (spin1 * 100) + (spin2 * 10) + spin3;
print("The three wheel spins are: (spin1) , (spin2), (spin3) ")
print("SpinValue = (spinValue)")
slotStripViewWheel1PosComplete = CGRectMake(33.0 + CGFloat(shiftOverValue), CGFloat(stoppingPoints[Int(spin1)]), 90.0, 2900.0)
slotStripViewWheel2PosComplete = CGRectMake(116.0 + CGFloat(shiftOverValue), CGFloat(stoppingPoints[Int(spin2)]), 90.0, 2900.0)
slotStripViewWheel3PosComplete = CGRectMake(199.0 + CGFloat(shiftOverValue), CGFloat(stoppingPoints[Int(spin3)]), 90.0, 2900.0)
// These three chunks of code set up the animation of each of the three 'wheels.'
// Essentially, all we’re doing is moving the strips of fruit images up and down
// to give the appearance of the three wheels spinning.
//
UIView.beginAnimations("wheel1", context: nil)
UIView.setAnimationDelegate(self)
UIView.setAnimationDidStopSelector("firstWheelReverse:")
UIView.setAnimationCurve(.EaseIn)
UIView.setAnimationDuration(2.0)
slotStripViewWheel1!.frame = slotStripViewWheel1PosEnd!
UIView.commitAnimations()
UIView.beginAnimations("wheel2", context: nil)
UIView.setAnimationDelegate(self)
UIView.setAnimationDidStopSelector("secondWheelReverse:")
UIView.setAnimationCurve(.EaseIn)
UIView.setAnimationDuration(2.0)
slotStripViewWheel2!.frame = slotStripViewWheel2PosEnd!
UIView.commitAnimations()
UIView.beginAnimations("wheel3", context: nil)
UIView.setAnimationDelegate(self)
UIView.setAnimationDidStopSelector("thirdWheelReverse:")
UIView.setAnimationCurve(.EaseIn)
UIView.setAnimationDuration(2.0)
slotStripViewWheel3!.frame = slotStripViewWheel3PosEnd!
UIView.commitAnimations()
// SOUNDS
AudioServicesPlaySystemSound(spinSoundObject)
}
func firstWheelReverse(animationID: String) {
UIView.beginAnimations("reverseWheel1", context: nil)
UIView.setAnimationCurve(.EaseOut)
UIView.setAnimationDuration(1.0)
slotStripViewWheel1!.frame = slotStripViewWheel1PosComplete!
UIView.commitAnimations()
}
func secondWheelReverse(animationID: String) {
UIView.beginAnimations("reverseWheel2", context: nil)
UIView.setAnimationCurve(.EaseOut)
UIView.setAnimationDuration(1.4)
slotStripViewWheel2!.frame = slotStripViewWheel2PosComplete!
UIView.commitAnimations()
}
func thirdWheelReverse(animationID: String) {
UIView.beginAnimations("reverseWheel3", context: nil)
UIView.setAnimationDelegate(self)
UIView.setAnimationDidStopSelector("spinningHasStopped:")
UIView.setAnimationCurve(.EaseOut)
UIView.setAnimationDuration(1.8)
slotStripViewWheel3!.frame = slotStripViewWheel3PosComplete!
UIView.commitAnimations()
}
func spinningHasStopped(animationID: String) {
print("Spinning Has Stopped")
var allCreditsGone: Bool = false
var winMultiplier: Int = 0
isSpinning = false
spinButton.enabled = true
betButton.enabled = true
betMaxButton.enabled = true
//STOP LIGHTS
stopGreenLightAnimation()
stopRedLightAnimation()
winMultiplier = calculateWinnings()
// Lose
if winMultiplier == 0 {
self.totalCredits -= self.thisBet
if self.totalCredits <= 0 {
allCreditsGone = true
}
AudioServicesPlaySystemSound(self.loseSoundObject)
}
else {
// Win
self.totalCredits += (self.thisBet * winMultiplier)
AudioServicesPlaySystemSound(self.winSoundObject)
}
updateLabels()
if allCreditsGone {
youLost()
}
saveGameState()
}
func resetGame() {
print("ResetGame")
makeButtonClick()
winThisSpin = 0
thisBet = 1
totalCredits = kInitialCredits
allowSpin = true
gameOver = false
updateLabels()
saveGameState()
}
func updateLabels() {
// TOTAL
let totString: String = String(format: "%d", totalCredits)
creditsLabel!.text = totString
//BET
let betString: String = String(format: "%d", thisBet)
betLabel!.text = betString
//WIN AMMOUNT
let winString: String = String(format: "%d", winThisSpin)
winLabel!.text = winString
}
func calculateWinnings() -> Int {
var winMultiplier: Int = 1
// Any single cherry
if (spin1 == 2) && (spin2 != 2) && (spin3 != 2) {
return 1
}
if (spin1 != 2) && (spin2 == 2) && (spin3 != 2) {
return 1
}
if (spin1 != 2) && (spin2 != 2) && (spin3 == 2) {
return 1
}
// Any DOUBLE cherry
if (spin1 == 2) && (spin2 == 2) && (spin3 != 2) {
return 3
}
if (spin1 != 2) && (spin2 == 2) && (spin3 == 2) {
return 3
}
if (spin1 == 2) && (spin2 != 2) && (spin3 == 2) {
return 3
}
// Three CHERRIES
if (spin1 == 2) && (spin2 == 2) && (spin3 == 2) {
return 150
}
switch spinValue {
case 000:
winMultiplier = 100
// 3 Bars
case 888:
winMultiplier = 100
// 3 sevens
case 111, 222, 333, 444, 555, 666, 777:
winMultiplier = 3 // 3 anything else --> 3X bet
default:
winMultiplier = 0 // anything else --> lose
}
return winMultiplier
}
func youLost() {
let alertController = UIAlertController(title: "Lost it All", message: "APress OK to play again.", preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction!) in
self.resetGame()
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
}
func setupGreenLightSequence() {
var img1: UIImage
var img2: UIImage
var img3: UIImage
var img4: UIImage
if iphoneType == .kiPhone6Plus {
img1 = UIImage(named: "100greenTop6P.png")!
img2 = UIImage(named: "110greenTop6P.png")!
img3 = UIImage(named: "111greenTop6P.png")!
img4 = UIImage(named: "011greenTop6P.png")!
}
else {
//smaller screen size
img1 = UIImage(named: "100greenTop.png")!
img2 = UIImage(named: "110greenTop.png")!
img3 = UIImage(named: "111greenTop.png")!
img4 = UIImage(named: "011greenTop.png")!
}
var images: [UIImage] = []
images.append(img1)
images.append(img2)
images.append(img3)
images.append(img4)
greenLightSequenceImageView.animationImages = images
greenLightSequenceImageView.animationRepeatCount = 0
greenLightSequenceImageView.animationDuration = 0.5
switch iphoneType {
case .kiPhone4S:
greenLightSequenceImageView.frame = CGRectMake(71, 1, 200, 20)
case .kiPhone5:
greenLightSequenceImageView.frame = CGRectMake(71, 1, 200, 20)
case .kiPhone6:
greenLightSequenceImageView.frame = CGRectMake(100, 1, 200, 20)
case .kiPhone6Plus:
greenLightSequenceImageView.frame = CGRectMake(114, 1, 200, 20)
default: break
}
}
func startGreenLightAnimation() {
greenLightSequenceImageView.startAnimating()
view.addSubview(greenLightSequenceImageView)
}
func stopGreenLightAnimation() {
greenLightSequenceImageView.stopAnimating()
greenLightSequenceImageView.removeFromSuperview()
}
func setupRedLightSequence() {
var img1: UIImage
var img2: UIImage
var img3: UIImage
var img4: UIImage
var img5: UIImage
if iphoneType == .kiPhone6Plus {
img1 = UIImage(named: "001redBottom6P.png")!
img2 = UIImage(named: "011redBottom6P.png")!
img3 = UIImage(named: "111redBottom6P.png")!
img4 = UIImage(named: "110redBottom6P.png")!
img5 = UIImage(named: "100redBottom6P.png")!
}
else {
//smaller screen size
img1 = UIImage(named: "001redBottom.png")!
img2 = UIImage(named: "011redBottom.png")!
img3 = UIImage(named: "111redBottom.png")!
img4 = UIImage(named: "110redBottom.png")!
img5 = UIImage(named: "100redBottom.png")!
}
var images: [UIImage] = []
images.append(img1)
images.append(img2)
images.append(img3)
images.append(img4)
images.append(img5)
redLightSequenceImageView.animationImages = images
redLightSequenceImageView.animationRepeatCount = 0
redLightSequenceImageView.animationDuration = 0.5
switch iphoneType {
case .kiPhone4S:
redLightSequenceImageView.frame = CGRectMake(71, 1, 200, 20)
case .kiPhone5:
redLightSequenceImageView.frame = CGRectMake(71, 1, 200, 20)
case .kiPhone6:
redLightSequenceImageView.frame = CGRectMake(100, 1, 200, 20)
case .kiPhone6Plus:
redLightSequenceImageView.frame = CGRectMake(114, 1, 200, 20)
default: break
}
}
func startRedLightAnimation() {
redLightSequenceImageView.startAnimating()
view.addSubview(redLightSequenceImageView)
}
func stopRedLightAnimation() {
redLightSequenceImageView.stopAnimating()
redLightSequenceImageView.removeFromSuperview()
}
func makeButtonClick() {
AudioServicesPlaySystemSound(clickSoundObject)
}
func saveGameState() {
print("Calling Save Game State")
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(spin1, forKey: "spin1")
defaults.setInteger(spin2, forKey: "spin2")
defaults.setInteger(spin3, forKey: "spin3")
defaults.setInteger(winThisSpin, forKey: "winthisspin")
defaults.setInteger(thisBet, forKey: "thisbet")
defaults.setInteger(totalCredits, forKey: "totalcredits")
defaults.synchronize()
}
func restoreUserSettings() {
let defaults = NSUserDefaults.standardUserDefaults()
// Determine if values have been previously saved and, if so,
// load them in. Otherwise, initialize the game.
if (defaults.objectForKey("spin1") != nil) {
spin1 = defaults.objectForKey("spin1") as! Int
slotStripViewWheel1PosStart = CGRectMake(33.0 + CGFloat(shiftOverValue), CGFloat(spin1), 90.0, 2900.0)
slotStripViewWheel2PosStart = CGRectMake(116.0 + CGFloat(shiftOverValue), CGFloat(defaults.objectForKey("spin2") as! Int), 90.0, 2900.0)
sslotStripViewWheel3PosStart = CGRectMake(199.0 + CGFloat(shiftOverValue), CGFloat(defaults.objectForKey("spin3") as! Int), 90.0, 2900.0)
winThisSpin = defaults.objectForKey("winthisspin") as! Int
thisBet = defaults.objectForKey("thisbet") as! Int
totalCredits = defaults.objectForKey("totalcredits") as! Int
} else {
print("initializing game - no data was stored")
slotStripViewWheel1PosStart = CGRectMake(33.0 + CGFloat(shiftOverValue), 95.0, 90.0, 2900.0)
slotStripViewWheel2PosStart = CGRectMake(116.0 + CGFloat(shiftOverValue), 95.0, 90.0, 2900.0)
slotStripViewWheel3PosStart = CGRectMake(199.0 + CGFloat(shiftOverValue), 95.0, 90.0, 2900.0)
self.resetGame()
}
}
//
// END VIEW CONTROLLER CLASS
//
}
Summary
In this chapter we have addressed the basics of what it would be like to convert from an existing Objective-C program to Swift. As a new employee at an iOS development organization, it’s quite likely that you could be given these kind of assignments to prove your worth to the organization.
By the time of publication, there will likely exist several methods of conversion between existing Objective-C code and Swift to make your life easier. Most likely your company will have standards in place to address these, along with guidelines you’ll be required to follow.
As the Swift compiler and Xcode progress and new features are added to the language, some of the syntax requirements may cause warnings or errors, especially with tricky conversions from much older projects. The best answer is to research the literature, message boards, and Apple documentation to stay on top of things.