© Molly K. Maskrey 2016

Molly K. Maskrey, App Development Recipes for iOS and watchOS, 10.1007/978-1-4842-1820-4_14

14. Swift Conversion Project

Molly K. Maskrey

(1)Parker, Colorado, USA

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.

A371701_1_En_14_Fig1_HTML.jpg
Figure 14-1. Our app project presents a simple, three-wheel slot machine to the user
A371701_1_En_14_Fig2_HTML.gif
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).

A371701_1_En_14_Fig3_HTML.jpg
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.

A371701_1_En_14_Fig4_HTML.jpg
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.

A371701_1_En_14_Fig5_HTML.jpg
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.

A371701_1_En_14_Fig6_HTML.jpg
Figure 14-6. Create a Swift project without core data or tests in order to keep things simple
A371701_1_En_14_Fig7_HTML.jpg
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.

A371701_1_En_14_Fig8_HTML.jpg
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.

A371701_1_En_14_Fig9_HTML.jpg
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
A371701_1_En_14_Fig10_HTML.jpg
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.

A371701_1_En_14_Fig11_HTML.jpg
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.

A371701_1_En_14_Fig12_HTML.jpg
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.

A371701_1_En_14_Fig13_HTML.jpg
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.

A371701_1_En_14_Fig14_HTML.jpg
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.

A371701_1_En_14_Fig15_HTML.jpg
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.

A371701_1_En_14_Fig16_HTML.jpg
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.

A371701_1_En_14_Fig17_HTML.jpg
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.

A371701_1_En_14_Fig18_HTML.jpg
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.

A371701_1_En_14_Fig19_HTML.jpg
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.

A371701_1_En_14_Fig20_HTML.jpg
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).

A371701_1_En_14_Fig21_HTML.jpg
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.

A371701_1_En_14_Fig22_HTML.jpg
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
A371701_1_En_14_Fig23_HTML.jpg
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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.239.76.211