Tag Archives: iphone

Introducing Core Motion: Make A Pedometer

Apple packed a lot of sensors into the small packages that are iOS devices. You as a developer can detect movement in three dimensions, acceleration, rotations, and impact and use those in your applications. All of this brought to your application in the CoreMotion framework.

CoreMotion has many uses. Game developers use it to make a phone into a gaming device where the phone’s motion controls the game play. Fitness developers use core motion to measure types of movements common to a sport on both the iPhone and Apple Watch. Using the sensors and Core motion could measure a swing of a baseball bat or golf club for example.  Basic movements can also let other application execute a function. The Apple watch wakes up to a raise of the wrist for example.

Core motion is a complex  framework to use, not only for coding reasons. As it deals with motion, a developer might need to know more about that motion before developing an application. The framework has some low-level objects for working directly with the sensors.  There are, however, some motions that are so universal, core motion combines several sensors to get more bottom-line data. One good example of this kind of motion is putting one foot in front of the other. For walking and running motions CoreMotion has the CMPedometer class. The class gives you direct data about the number of steps,   To give you a taste of some of the issues involved in developing for Core motion, let’s make a pedometer from the CMPedometer class. You’ll need to hook up a phone to Xcode since the simulator does not work with Core Motion.

Setting Up the Project

Create a new single-view project named CoreMotionPedometer. Use Swift as a language and a iPhone for a device.  Save the project.

When the applications loads, change the Display Name to CMPedometer

2017-02-10_06-03-10

This will give us a smaller name for the Icon. In this application you may need to test off of Xcode, and this will help you find your app better.

Core motion in general and the pedometer more specifically uses several motions that Apple considers private data. On a pedometer, you’re also using location data, so privacy is an even bigger issue. Developers need to add a key NSMotionUsageDescription to the info.plist to ask permission of the user. You can add the key in XML like this:

<key>NSMotionUsageDescription</key>
<string>This is a step counter, and we&apos;d like to track motion. </string>

Most people will just go to the info.plist and add a entry to the dictionary.
2017-02-10_06-11-09

In the drop down, select Privacy – Motion Usage Descriptor
2017-02-10_06-10-45

This creates a key with a String value. The string will be the body of an alert to accept the privacy settings for the pedometer. Add This is a step counter, and we’d like to track motion.
2017-02-10_06-17-55

Designing the Storyboard

Go to the main storyboard. Add Five labels and a button. Place the button towards the bottom of the scene and the labels towards the top. Make the labels white, the button a green background and the scene background to black like this:

2017-02-10_06-26-20

Layout the button on the bottom of the phone a quarter of the height of the device. Change the tile color for the Start button to White, and the title font to System Black 28pt. Using the auto layout pin tool pinMenuButton, turn off the Constrain to Margins  and set the left to 0, bottom to 0 and right to 0.

2017-02-10_06-31-35

Add the three constraints. Control drag from the green of the button up to the black of the background.

2017-02-10_06-39-09

When you release the mouse button, select Equal Heights in the menu that appears

2017-02-10_06-37-13

The start button will fill the screen.  Go to the size inspector. Find the Equal Height to Superview constraint, and click the Edit button there.

2017-02-10_06-41-07

In the edit dialog that appears change the Multiplier to 1:4.

2017-02-10_06-45-07

You’ll have a nice big button on the bottom.

2017-02-10_06-48-00

I like having a big start/stop button. If you are doing a workout, it may be difficult to hit a small button, and this helps the user hit the button. It’s on the bottom in the thumb zone so the user can hit it one handed.

Select the Pedometer Demo label. In the attribute inspector, set the font to 22pt System heavy.  Using the auto layout pin tool pinMenuButton, turn  Using the auto layout pin tool pinMenuButton, turn on the Constrain to Margins  and set the Top to 20,  Left to 0 and Right to 0. Add the constraints.

Select the other four labels.  Set the font to System Heavy 17pt, Lines to 2 and Line Break to Word Wrap

2017-02-10_06-53-00

You’ll be using two lines of text in this demo, one for a metric measurement and one for an Imperial measurement used in most sporting events.  You’ll place them on two lines in the same label.

With all four labels selected, select the Stackview tool  stack view button from the autolayout toolbar in the lower right. The labels will embed in a vertical stack view.  Change the stack view to these attributes:

2017-02-10_07-02-31

Use the pin tool to pin all four sides of the stack view 10 points. Update the constraints.  You’ll have a storyboard like this one.

2017-02-10_07-04-04

Close the attributes inspector and open the assistant editor. Control drag from the labels and make the following outlets, with the statusTitle outlet to the Pedometer Demo label and the rest to their respective labels :

@IBOutlet weak var statusTitle: UILabel!
@IBOutlet weak var stepsLabel: UILabel!
@IBOutlet weak var avgPaceLabel: UILabel!
@IBOutlet weak var paceLabel: UILabel!
@IBOutlet weak var distanceLabel: UILabel!

Now add at the action. Control-drag from the button and add an action

@IBAction func startStopButton(_ sender: UIButton) {
}

Some Preliminary Coding

Before we code the pedometer, I’m going to add a few properties and functions that will help us later. Close the assistant editor and go to the ViewController.swift file.

A very critical line goes just under the import UIKit line, where we import core motion.

import CoreMotion

Then add these properties and constants.

    
    let stopColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
    let startColor = UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0)
    // values for the pedometer data
    var numberOfSteps:Int! = nil
    var distance:Double! = nil
    var averagePace:Double! = nil
    var pace:Double! = nil

The constants will change the color of the stat stop button. You’ll store values in these for the properties of the pedometer object. Of course you need a pedometer object. Add this after these properties:

var pedometer = CMPedometer()

Pedometers don’t compute elapsed time. You might want that too in your pedometer. Add these two more properties to use a timer. :

    // timers
    var timer = Timer()
    let timerInterval = 1.0
    var timeElapsed:TimeInterval = 0.0

As you’ll find out, there’s more to this timer than just recording time.

You’ll need some more  methods to handle some unit conversions you’ll do later, so add these:

   //MARK: - Display and time format functions
    
    // convert seconds to hh:mm:ss as a string
    func timeIntervalFormat(interval:TimeInterval)-> String{
        var seconds = Int(interval + 0.5) //round up seconds
        let hours = seconds / 3600
        let minutes = (seconds / 60) % 60
        seconds = seconds % 60
        return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }
    // convert a pace in meters per second to a string with
    // the metric m/s and the Imperial minutes per mile
    func paceString(title:String,pace:Double) -> String{
        var minPerMile = 0.0
        let factor = 26.8224 //conversion factor
        if pace != 0 {
            minPerMile = factor / pace
        }
        let minutes = Int(minPerMile)
        let seconds = Int(minPerMile * 60) % 60
        return String(format: "%@: %02.2f m/s \n\t\t %02i:%02i min/mi",title,pace,minutes,seconds)
    }
    
func computedAvgPace()-> Double {
    if let distance = self.distance{
        pace = distance / timeElapsed
        return pace
    } else {
        return 0.0
    }
}

func miles(meters:Double)-> Double{
        let mile = 0.000621371192
        return meters * mile
    }

The pedometer works in Metric, returning meters and meters per second. If you want other units such as a distance in miles, or a pace in minute per mile, you’ll need these to calculate the values. I convert times to strings in hh:mm:ss here too.

Coding the Pedometer

With all these functions in place, your’e ready to code the pedometer. You’ll use the startStopButton‘s action to toggle between starting and stopping the pedometer. Add this to the startStopButton code

if sender.titleLabel?.text == "Start"{
    //Start the pedometer
   //Toggle the UI to on state
   statusTitle.text = "Pedometer On"
   sender.setTitle("Stop", for: .normal)
   sender.backgroundColor = stopColor
} else {
   //Stop the pedometer
   //Toggle the UI to off state
   statusTitle.text = "Pedometer Off: "
   sender.backgroundColor = startColor
   sender.setTitle("Start", for: .normal)
}

You have button that toggles and appears correctly on the user interface. Below the Start the pedometer comment add this:

pedometer = CMPedometer()
pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
    if let pedData = pedometerData{
            self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
    } else {
            self.stepsLabel.text = "Steps: Not Available"
    }
})

The first line of this code clears the pedometer by creating a new instance in the pedometer property. The startUpdates:FromDate:withHandler: method starts sending updates from the pedometer. When an update occurs the handler within the closure executes. It checks of the pedometer is nil. If nil, the pedometer is not available. There are several other ways of checking this, but this is the most direct. If there is a value, the pedometer property number of steps is sent to the stepsLabel‘s text property.

To stop a pedometer, add the following under Stop the pedometer comment.

pedometer.stopUpdates()

Run the Application

You are ready to run our first iteration. However, core motion only works on a real device, not the simulator. Plug your phone into a device, and select the device in the run schemes. I’ll assume you know how to do this and how to set up your device for testing. Run the application.
You’ll get a screen like this:

052fa7ca-aa18-46fd-9127-06023afccf80

Press Start. You’ll get a screen asking for permission. Tap OK

112f8982-77fb-40af-b2ef-35842bc3e2f4

Pick up your phone. Move your arms back and forth for 30 seconds like you are jogging. This is one of the motions that the pedometer uses to count steps. Press STOP.

0a9e7fae-7abc-4372-b9b4-311ac9d13e9f

While you see a few steps on the pedometer, you also see a lot of errors in the console Most of them you can ignore — they are errors related to internal caches you can’t touch. The very last error on the other hand is very important:
This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.

What’s happening here? Remember the code for updating is a closure, and runs on a separate thread from the main thread where UI updates happen. The closure tries to access the main thread when it really shouldn’t, because its timing is not the same as the main thread. This can crash the app. While this specifically mentions auto layout, I’d suggest never directly changing outlets from the closure to avoid any bad behavior.

Using Timer Loops

Instead of setting the labels in the closure, you set a property. Change this:

self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"

to this:

self.numberOfSteps = Int(pedData.numberOfSteps)

The pedometer numberOfSteps property is of type NSNumber. You must covert it to an Int to use in the ViewController numberOfSteps property.
You might think you can use a property observer to change the display. For example change the numberOfSteps property to this:

// values for the pedometer data
var numberOfSteps:Int! = nil{
    didSet{
        stepsLabel.text = "Steps:\(numberOfSteps)"
    }
}

When the property changes, the label changes. You can run this code and do a simulated jog with your device. IF you do, you get that error message again. In code this is still updating the pedometer in the handler thread. You need a thread that has no problem updating to the view. That’s a timer.

Comment out the property observer:

// values for the pedometer data
var numberOfSteps:Int! = nil
/*{  //this does not work. 
    didSet{
        stepsLabel.text = "Steps:\(numberOfSteps)"
    }
}*/

We earlier declared some timer properties. You’ll use that to set up a timer with these functions:

    //MARK: - timer functions
        func startTimer(){
        if timer.isValid { timer.invalidate() }
        timer = Timer.scheduledTimer(timeInterval: timerInterval,target: self,selector: #selector(timerAction(timer:)) ,userInfo: nil,repeats: true)
    }
    
    func stopTimer(){
        timer.invalidate()
        displayPedometerData()
    }
    
    func timerAction(timer:Timer){
        displayPedometerData()
    }

I discuss timers in more detail here. Basically startTimer starts a timer with a 1 second interval that repeats. I’m being course here, you can set the interval to a finer resolution if you wish. Every second it calls the function timerAction from the selector. In timerAction I call a function displayPedometerData I’ve yet to define which will display my pedometer data. The stopTimer function shuts down the timer and updates the display one last time. Add the startTimer and stopTimer functions to the button action so it starts and stop the timer when the pedometer starts and stops.

@IBAction func startStopButton(_ sender: UIButton) {
        if sender.titleLabel?.text == "Start"{
            //Start the pedometer
            pedometer = CMPedometer()
            startTimer() //start the timer
            pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
                if let pedData = pedometerData{
                    self.numberOfSteps = Int(pedData.numberOfSteps)
                    //self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
                } else {
                    self.stepsLabel.text = "Steps: Not Available"
                }
            })
            //Toggle the UI to on state
            statusTitle.text = "Pedometer On"
            sender.setTitle("Stop", for: .normal)
            sender.backgroundColor = stopColor
        } else {
            //Stop the pedometer
            pedometer.stopUpdates()
            stopTimer() // stop the timer
            //Toggle the UI to off state
            statusTitle.text = "Pedometer Off: "
            sender.backgroundColor = startColor
            sender.setTitle("Start", for: .normal)
        }
    }

Create a new function to update the view. For now we’ll update just the steps again.

func displayPedometerData(){  
    //Number of steps
    if let numberOfSteps = self.numberOfSteps{
        stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
     }
}

I did two more things than I did with the property observer. I used an optional chain to unwrap numberOfSteps, and then used as String initializer to format the string.
If you run the application and do your little in-place run, you’ll notice two differences: the step count updates faster than before and the error message disappears. We only have that CoreLocation cache warning on the console. The timer thread indirectly updated the display separating the pedometer thread from the main thread.

Adding Elapsed Time

One advantage to a timer loop is we have a timer. Usually I use a higher resolution timer(0.1 seconds for example), but for this lesson I’ll leave it at a one second interval.

I can change the displayPedometerData function to this:

func displayPedometerData(){
    //Time Elapsed
    timeElapsed += self.timerInterval
    statusTitle.text = "On: " + timeIntervalFormat(interval: timeElapsed)
        //Number of steps
        if let numberOfSteps = self.numberOfSteps{
            stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
        }
    }

I increment the pedometer with a property timeElapsed I created earlier. It keeps a count of the number of seconds elapsed since I started the pedometer. I display it using one of the formatting functions we added earlier that displays the time as hh:mm:ss.

To keep this time after you stop the timer, append the timeIntervalFormat function to the status title label

statusTitle.text = "Pedometer Off: " + timeIntervalFormat(interval: timeElapsed)

Build and run. Start the pedometer. You’ll get both a timer and step count now.

9872d97b-a238-4012-8597-a74b3732a053

Stop the pedometer.

Adding Other Pedometer Properties

There’s several other properties of pedometers. I selected three more to show on our pedometer: current pace, Average pace and distance. Why you are getting that core location cache message makes sense now: the Pedometer checks your location repeatedly using CoreLocation. You have no control over that which is why I said to ignore the warning message. With that location data, the pedometer computes distance, and from the steps, distance, and it’s own timer pace and average pace.

All of these properties are optional. If the hardware or property is unavailable or nonexistent, the property returns nil. However if you can’t get pace from a pedometer, you can compute the average pace from the distance and time. I made a function earlier computedAvgPace that will compute an average pace or leave it as 0 if core location is not available.

To implement the other properties, change the startUpdates closure to add the pedometer data to the viewController’s properties:

  pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
                if let pedData = pedometerData{
                    self.numberOfSteps = Int(pedData.numberOfSteps)
                    //self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
                    if let distance = pedData.distance{
                        self.distance = Double(distance)
                    }
                    if let averageActivePace = pedData.averageActivePace {
                        self.averagePace = Double(averageActivePace)
                    }
                    if let currentPace = pedData.currentPace {
                        self.pace = Double(currentPace)
                    }
                } else {
                    self.numberOfSteps = nil
                }
            })

Each pedometer property, if a number, is converted from NSNumber! to a Double for use in the classes, like we did for the integer numberOfSteps.

In the displayPedometerData function, change it to this to include the other properties:

 func displayPedometerData(){
        timeElapsed += 1.0
        statusTitle.text = "On: " + timeIntervalFormat(interval: timeElapsed)
        //Number of steps
        if let numberOfSteps = self.numberOfSteps{
            stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
        }
        
        //distance
        if let distance = self.distance{
            distanceLabel.text = String(format:"Distance: %02.02f meters,\n %02.02f mi",distance,miles(meters: distance))
        } else {
            distanceLabel.text = "Distance: N/A"
        }
        
        //average pace
        if let averagePace = self.averagePace{
            avgPaceLabel.text = paceString(title: "Avg Pace", pace: averagePace)
        } else {
            avgPaceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        } 
        
        //pace
        if let pace = self.pace {
            paceLabel.text = paceString(title: "Pace:", pace: pace)
        } else {
            paceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        }
    }

For each of these properties we optionally chain the property. A nil vaule shows there was no reading for some reason. For the two pace strings, if we do not get a pace, I calculate the pace from the distance. I use the paceString function defined in the conversion functions to make a string of both meters per second and minutes per mile.

Run again, and start the pedometer. Make the running motion and the device will begin to display data.

6bbd7e99-1d47-4fb3-a8f1-cd886ebfc705

The Main Points for Core Motion

Core motion has a lot more to it, but this introduction give you some of the basics all core motion methods use. THis one is high level, the lower level, closer to the sensors require more tweaking than this virtually automatic method. However there are several points you should remember with Core Motion:

  • Add the entry to the info.plist for security permissions
  • Include the Core Motion LIbrary
  • Check for availability of the device and the functions by looking for nil on properties.
  • Don’t directly update outlets from a core motion closure
  • Do indirectly update outlets from a timer loop.

The Whole Code

You’ll find all but the info.plist entry in this code. if you run this, make sure to include that. There is a download file  coremotionpedometer with the completed project, including some app icons if you want to test off of Xcode. Go ahead, run a mile. It’s good for you.

ViewController.swift

//
//  ViewController.swift
//  CoreMotionPedometer
//
//  Created by Steven Lipton on 2/10/17.
//  Copyright © 2017 Steven Lipton. All rights reserved.
//

import UIKit
import CoreMotion

class ViewController: UIViewController {

//MARK: - Properties and Constants
    let stopColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
    let startColor = UIColor(red: 0.0, green: 0.75, blue: 0.0, alpha: 1.0)
    // values for the pedometer data
    var numberOfSteps:Int! = nil
    /*{ //this does not work. for demo purposes only.
        didSet{
            stepsLabel.text = "Steps:\(numberOfSteps)"
        }
    }*/
    var distance:Double! = nil
    var averagePace:Double! = nil
    var pace:Double! = nil
    
    //the pedometer
    var pedometer = CMPedometer()
    
    // timers
    var timer = Timer()
    var timerInterval = 1.0
    var timeElapsed:TimeInterval = 1.0
    

    
//MARK: - Outlets
    
    @IBOutlet weak var statusTitle: UILabel!
    @IBOutlet weak var stepsLabel: UILabel!
    @IBOutlet weak var avgPaceLabel: UILabel!
    @IBOutlet weak var paceLabel: UILabel!
    @IBOutlet weak var distanceLabel: UILabel!
    
    @IBAction func startStopButton(_ sender: UIButton) {
        if sender.titleLabel?.text == "Start"{
            //Start the pedometer
            pedometer = CMPedometer()
            startTimer()
            pedometer.startUpdates(from: Date(), withHandler: { (pedometerData, error) in
                if let pedData = pedometerData{
                    self.numberOfSteps = Int(pedData.numberOfSteps)
                    //self.stepsLabel.text = "Steps:\(pedData.numberOfSteps)"
                    if let distance = pedData.distance{
                        self.distance = Double(distance)
                    }
                    if let averageActivePace = pedData.averageActivePace {
                        self.averagePace = Double(averageActivePace)
                    }
                    if let currentPace = pedData.currentPace {
                        self.pace = Double(currentPace)
                    }
                } else {
                    self.numberOfSteps = nil
                }
            })
            //Toggle the UI to on state
            statusTitle.text = "Pedometer On"
            sender.setTitle("Stop", for: .normal)
            sender.backgroundColor = stopColor
        } else {
            //Stop the pedometer
            pedometer.stopUpdates()
            stopTimer()
            //Toggle the UI to off state
            statusTitle.text = "Pedometer Off: " + timeIntervalFormat(interval: timeElapsed)
            sender.backgroundColor = startColor
            sender.setTitle("Start", for: .normal)
        }
    }
    //MARK: - timer functions
    func startTimer(){
        if timer.isValid { timer.invalidate() }
        timer = Timer.scheduledTimer(timeInterval: timerInterval,target: self,selector: #selector(timerAction(timer:)) ,userInfo: nil,repeats: true)
    }
    
    func stopTimer(){
        timer.invalidate()
        displayPedometerData()
    }
    
    func timerAction(timer:Timer){
        displayPedometerData()
    }
    // display the updated data
    func displayPedometerData(){
        timeElapsed += 1.0
        statusTitle.text = "On: " + timeIntervalFormat(interval: timeElapsed)
        //Number of steps
        if let numberOfSteps = self.numberOfSteps{
            stepsLabel.text = String(format:"Steps: %i",numberOfSteps)
        }
        
        //distance
        if let distance = self.distance{
            distanceLabel.text = String(format:"Distance: %02.02f meters,\n %02.02f mi",distance,miles(meters: distance))
        } else {
            distanceLabel.text = "Distance: N/A"
        }
        
        //average pace
        if let averagePace = self.averagePace{
            avgPaceLabel.text = paceString(title: "Avg Pace", pace: averagePace)
        } else {
            avgPaceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        }
        
        //pace
        if let pace = self.pace {
            print(pace)
            paceLabel.text = paceString(title: "Pace:", pace: pace)
        } else {
            paceLabel.text = "Pace: N/A "
            paceLabel.text =  paceString(title: "Avg Comp Pace", pace: computedAvgPace())
        }
    }
    
    //MARK: - Display and time format functions
    
    // convert seconds to hh:mm:ss as a string
    func timeIntervalFormat(interval:TimeInterval)-> String{
        var seconds = Int(interval + 0.5) //round up seconds
        let hours = seconds / 3600
        let minutes = (seconds / 60) % 60
        seconds = seconds % 60
        return String(format:"%02i:%02i:%02i",hours,minutes,seconds)
    }
    // convert a pace in meters per second to a string with
    // the metric m/s and the Imperial minutes per mile
    func paceString(title:String,pace:Double) -> String{
        var minPerMile = 0.0
        let factor = 26.8224 //conversion factor
        if pace != 0 {
            minPerMile = factor / pace
        }
        let minutes = Int(minPerMile)
        let seconds = Int(minPerMile * 60) % 60
        return String(format: "%@: %02.2f m/s \n\t\t %02i:%02i min/mi",title,pace,minutes,seconds)
    }
    
    func computedAvgPace()-> Double {
        if let distance = self.distance{
            pace = distance / timeElapsed
            return pace
        } else {
            return 0.0
        }
    }
    
    func miles(meters:Double)-> Double{
        let mile = 0.000621371192
        return meters * mile
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}


Swift Swift: Using Auto Layout with Size Classes

Last week we looked at the basics of auto layout, this week, we learn

  • Diagonal Control Drag
  • Using the layout error  panel for misplaced layouts
  • Introduce Size Classes
  • Make a different layout for iPhone in portrait with Size Classes

Transcript

Hello, I’m Steve from MakeAppPie.com. In this video we are going to learn some more about Auto Layout and how to use Xcode’s new size classes feature. We’ll start with the completed project from the last video, so you might want to check that out first:

In the last project we made a universal app like this:

Screenshot 2014-11-12 06.24.40

It works the same for iPhone and iPad. It also works the same for each device in both landscape and portrait. Size classes let us customize certain devices or orientations for a different layout than this generic layout.

If you click the width and height size class you will get a popup with a bunch of squares on it. This allows you to set the size class you want to work with.

Size classes Keynote photos.002

There are three sizes for size classes: compact, any, and regular. Any is the generic size that works with anything. Our current layout, as we can see on the bottom the story board is width:any height:any. This layout is for all cases.

Size classes Keynote photos.003

iPhones and the iPod touch are the compact size in both landscape and portrait, with one exception.

Size classes Keynote photos.004

For both width and height, iPad is the regular size. The width in landscape is regular for an iPhone 6+.

Screenshot 2014-11-14 10.16.41

In our application, we will change the location of the three buttons from above and below the text file to left side of the text field for the compact iPhones in landscape, and leave them the way they are in portrait. For the iPad and iPhone 6, we’ll move all three buttons to the top of the screen in our next video.

Screenshot 2014-11-12 05.58.45

In the storyboard, click the width:any height:any button on the bottom of the storyboard. Move the square to any width, compact height for a iPhone in landscape.

Set up Xcode with the preview mode to have the phones in landscape so we can check them as we go through the steps. if you have the file browser open, you may want to close it to give yourself more room. Keep the document outline open.

To start let’s move the text view to the new location. Select the text view in the document outline. Select clear constraints in the constraints resolver. Drag the left handle so the view is less wide than the cheese button. We want to have a relationship to the pizza label, not the cheese button, so we can’t use the pin menu. Control drag from the text view to the label and release. select Vertical Spacing.

Screenshot 2014-11-14 10.18.12

Since we will be moving the done button, we want a relationship with the bottom of the container, not the button. We also want a relationship with the right margin. We can do both at once with a diagonal drag. Control-drag from the text view to the bottom right corner. Xcode give us both trailing and bottom constraints. Shift click the Bottom space to container margin and Trailing space to container margin, then press return. Xcode adds both constraints.

Screenshot 2014-11-14 10.21.28

As the warning indicates, we still have one more constraint to go, but we will get to that in a minute. Clear the constraints on the Cheese button. Move it under the pepperoni button.

Select pin and pin the cheese to the pepperoni 10 points away and the left margin 0 points away. Update the frames to the new constraints.

As you can see in the preview window, the buttons are now too small. Let’s fix the height first. Control drag from Cheese to Pepperoni and shift-select equal widths and equal heights. Remember pepperoni has an explicit width of 64, so that will make both 64 points high.

We get several layout error, but we can see in the preview that they are now the right width. Note on the preview that the text view has disappeared. That’s because we still didn’t set its width, which we’ll do as an equal width to the buttons, and have a space between them of 10 points.

Control drag from the pepperoni button to the text view. Shift select Horizontal spacing and equal widths, then press return. We get more errors. Click on the horizontal spacing constraint we just made, and in the inspector change the constant to 10 and press tab.

The preview shows we have the correct horizontal spacing but the storyboard shows errors. You’ll notice a small error indicator in the upper right corner of the inspector. If you click it, you will see your errors. All are misplaced views.

Select cheese on the storyboard, and click update frames for the selection. That fixes that.

You can also resolve misplaced views in the error panel. Select the warning icon for pepperoni and a pop up lets you update the frame. Select Update Frame and click Fix Misplacement. Now pepperoni is correct.

Screenshot 2014-11-14 10.23.16

Go back to the document outline and select the text view. Scroll to the constraints and change the top space to 10 points and the bottom space to 20. Select Text View in the outline and in the resolver Update selected frames

We need to lay out the done button next, which is now under the text view.  In the outline, select the Done button. Clear its constraints.

In the done button. we won’t bleed off the edge this time. We will instead align it with the other buttons. Pin the done button to the left margin with 0 and the bottom to 20. Update the frame.

You can control drag from the outline too, for cases where you can’t see you frame. Control drag from the done button in the outline to the text view. select horizontal spacing. Now in the inspector change trailing space to text view to 10.

Lets set the size of the button. Control drag from the done button to the pepperoni button. Select equal widths and equal heights. We get a constraint error which is just a misplaced view. Click the icon and fix the misplacement.

We’ve completed the re-arrangement for the iPhone and iPod touch. In the preview, rotate the device, and you will see that it only arranges itself like this in landscape.

Go over to the iPad, and it ignores this.

The iPhone 6 plus is not happy though. This is okay for now, because in out next lesson, we are going to make a different layout for regular width any height layouts.

Size classes Keynote photos.007

 

Swift Swift: Using Color and UIColor in Swift Part 1: RGB

Not so Cheap Sunglasses. Watercolor. 2008

[Updated to Swift 2.0/iOS9.0 9/18/15 SJL – errors corrected 11/20/15]
I’m color blind, and that makes me a very good person to teach you about color and UIColor.  I’ve painted for many years and my color blindness does not detract but actually helps me pick colors.  These two examples, one a water-color of mine from 2008 and another I did with the Crayon Style App for iPhone were each done in less than six colors. I want to give you the secrets of good colors that I learned the hard way.

PIllow. Crayon Style App 2014. from a photo reference.
PIllow. Crayon Style App 2014. from a photo reference.

I originally thought I would show you how to use color in one blog post, but it turned into two. In this post I’ll show you how to work with colors, how to use the RGB colors and how to apply them to controls in Swift with UIColor. In the next post I’ll show you how to make colorful, well designed displays with HSB.

The RGB  and RGBA Colors

There are several ways to  select colors. For what most people think of as color, there are two important color identifying systems: RGB and HSB. The most useful to most programmers is the RGB or Red-Green-Blue color system.   As far back as the time of the Apple ][, RGB colors are easy to communicate as bytes. They are also the standard color space for CSS, so any CSS web color scheme with a little work will translate into an app UI’s color scheme.

RGB traditionally has been a value between 0 and 255, and CSS uses this numbering pattern in one of two ways:

strong { color: #ff0000 }           /* #rrggbb */
strong { color: rgb(255,0,0) }      

Each of the color components can be expressed separately as a value between 0 and 255, with 255 being the full color and 0 being no color.  The first number is red, the second green and the third blue. The code line above is for the color red, since it is all red and no other color.  We can express red as a hexadecimal number #ff0000.  In hexadecimal 00 is 0 and ff is 255 in decimal.  We have three two digit numbers stuffed into one number. Xcode for the UIColor class decided to use a CGFloat between 1 and 0 instead of an integer between 0 and 255 .  The code to make a red color would be:

let myRedColor = UIColor(
    red:1.0,
    green:0.0,
    blue:0.0,
    alpha:1.0)

Notice for a UIColor there is a fourth attribute: A for alpha. Alpha levels are the amount of transparency of a color, or how much of the color underneath we can see. For our purposes in this post we will stick with solid colors, an alpha of 1.0. Feel free to experiment with translucency and transparency on your own.

System Colors

There is a set of often used system colors which have their own methods.  For example, instead of the assignment above,  we can type for red:

let myRedColor = UIColor.redColor()

With the same result. There are fourteen colors and a special color clearColor which is an alpha of 0 .

The Apple System Colors with a color wheel of RGB primary and secondaries.
The Apple System Colors with a color wheel of RGB primary and secondaries. Click on image for a better view.

In the illustration I organized the colors in a color wheel for the primary and secondary colors. A primary color is one we base all other colors. This for RGB  is not surprisingly red, green and blue. There are colors we can make by equally mixing two of the primaries. These are known as secondaries, and for RGB, those are Cyan, Magenta and Yellow, which also make up another color system CMYK which we’ll mention more in the next post.  Beneath them we have five different shades of gray.  Beneath that, three colors that don’t fit in a RGB scheme easily: Orange, Purple, and Brown.

Labels and Buttons: A Color Demo Application.

Let’s make a demonstration project. Open a new project in Xcode with a single view named SwiftColorDemo

Set up your storyboard to look something like this, with a button in the upper left and a label in the upper right:

2015-09-18_07-45-56

I set this up in autolayout. You can of course just drag and drop the controls, and adjust accordingly. For those who want to set up in auto layout, I pinned the button to the top and left margin of the view and the label to the top and right margin of the view. I made the label and button of equal widths and equal heights. Then I pinned the label and button 20 points horizontally from each other. I made the button one third of the height of the view by selecting equal heights between the superview and button, and then making the button have a multiplier of 1:3. I aligned the Label and Button labels with a pin to the view below them and aligned them to the leading edge.  For the sliders. I pinned then horizontally to the left and right margin. I took the top slider and aligned it to the vertical center, then pinned the other teo slider to it vertically. The segmented control I pinned to the bottom,left and right margins. If you don’t understand any of that, you might find my book Practical Autolayout very useful, though for this example you don’t need it.

Open the assistant editor. Make an outlet for the label by control-dragging from the label to the code in the assistant editor to make an outlet named mysampleColorLabel.

@IBOutlet weak var mySampleColorLabel: UILabel!

Getting to the code, in ViewController.swift, Add this to viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
//Set the label's color with the System colors
    mySampleColorLabel.backgroundColor = UIColor.blackColor() 
    mySampleColorLabel.textColor = UIColor.whiteColor()
 }

Here we set the label’s text color to white and background to black using system colors. Let’s set the background of the button to gray using a more explicit reference to red green and blue. Add this under the text color for the label:

//Set the button's back ground with a RGB specfied color
//integer and a float both work -- this is gray
mySampleColorButton.backgroundColor = UIColor(
    red: 0.5,
    green: 128/255.0,
    blue: 0.5,
     alpha: 1.0)

The parameters in the UIColor initializer are a CGFloat between 1 and 0. We can take a number and divide it by 255.0 to get a fraction that is between these two numbers, as we did with the green, since 128/255.0 equals 0.5. When you have a palette figured out in integer RGB values this is the way to specify values.
We have a background for a button but setting the text color is a little  different. Buttons are controls which have states. If we set the color by the textColor property of titleLabel, the system will ignore it. Add the following to your viewDidLoad code:

//the right way to set a color on a button title
mySampleColorButton.setTitleColor(
    UIColor.whiteColor(),
    forState: UIControlState.Normal)

When setting the titleColor of a button, use the method setTitleColor and for most cases set it for a state of UIControlState.Normal

Let’s set the background property of the view. It may help to have a light, neutral color to help us see the bolder colors we will play with. The color represented by #fde8d7 is a peach color and works nicely for our purposes. You’ll find many colors and color palettes described in hex numbers on websites like colourlovers.com or colourpod.com which feature pre-made palettes. But often such sites will  often use the hexadecimal system to describe the color. Swift fortunately can represent integers as hex values. Take the first two digits of the hex number as red, the next two as green, and the last two as blue. Write them with 0x  prefixed to the number Red for #fde8d7 would be 0xfd for example. Using this we can make our background peach like this:

//set the background color to #fde8d7
view.backgroundColor = UIColor(
    red: 0xfd/255,
    green: 0xe8/255,
    blue: 0xd7/255,
    alpha: 1.0)

Build and run. We have color!

Screenshot 2014-10-02 07.12.46

Making a Simple Color Picker

Lets hook up the sliders and the segment control to change the colors of the button and label.

We will need outlets. From the storyboard to the assistant editor, control drag from the button, label,sliders and segmented control to make more outlets:

@IBOutlet weak var mySampleColorButton: UIButton!
@IBOutlet weak var redSlider: UISlider!
@IBOutlet weak var greenSlider: UISlider!
@IBOutlet weak var blueSlider: UISlider!
@IBOutlet weak var backgroundTextSegment: UISegmentedControl!

Now control drag from the sliders to make three actions, one action for each slider, then add this code to those actions:

@IBAction func redSliderChanged(sender: UISlider) {
    displayColors()
}
@IBAction func greenSliderChanged(sender: UISlider) {
    displayColors()
}
@IBAction func blueSliderChanged(sender: UISlider) {
    displayColors()
}

The three sliders call a method displayColor. Add this to your code:

 //MARK: - instance methods
func displayColors(){
let red = CGFloat(redSlider.value)
let blue = CGFloat(blueSlider.value)
let green = CGFloat(greenSlider.value)
let color = UIColor(
    red: red,
    green: green,
    blue: blue,
    alpha: 1.0)
if backgroundTextSegment.selectedSegmentIndex == 0 { 
    mySampleColorButton.backgroundColor = color
    mySampleColorLabel.backgroundColor = color
} else {
    mySampleColorButton.setTitleColor(
        color,
        forState: .Normal)
    mySampleColorLabel.textColor = color
}
mySampleColorLabel.text = String(
    format: &quot;%i,%i,%i&quot;,
    Int(red * 255),
    Int(green * 255),
    Int(blue * 255))
let myTitleText = String(
    format: &quot;%6.2f,%6.2f,%6.2f&quot;,
    Float(red),
    Float(green),
    Float(blue))
mySampleColorButton.setTitle(
    myTitleText,
    forState: .Normal)
}

The first three lines of the function take the values from the three sliders and make them into a color. Sliders return Float and UIColor works in CGFloat. These are not the same type, Swift is very cranky about type so we need to convert red, green and blue with a CGFloat initializer before we do anything else. Next, we checks which segment the user selected. If it is the first segment, the color gets applies to backgrounds. If not, the text color changes. The final lies of code set the text on the label and button to show what color the sliders selected.

You can build and run this code. Now play with the sliders and the control to make pretty colors.

Screenshot 2014-10-02 07.14.20

Adding Color to the Slider Thumbs

It would be nice to add some color to the sliders. It would be even better if the tint color reflected the value of that color. The thumbTintColor property of UISlider does let us do that, but there is a catch: you need to initialize the tint with a image. Add the following code to the viewDidLoad method to initialize the view.

//initialize sliders for tints with an image
let sliderImage = UIImage(named: &quot;color knob&quot;)
redSlider.setThumbImage(
    sliderImage,
    forState:UIControlState.Normal)
greenSlider.setThumbImage(
    sliderImage,
    forState:UIControlState.Normal)
blueSlider.setThumbImage(
    sliderImage,
    forState:UIControlState.Normal)

This of course needs an image. I made the following one, which you can download by right clicking on it and save the image as color knob.

color knob

In the project navigator, open the Assets.xcassets file. Drag the image file you saved from finder into the list of images.

Now change the code for the sliders to this:

@IBAction func redSliderChanged(sender: UISlider) {
    let red = CGFloat(sender.value)
    sender.thumbTintColor = UIColor(
        red: red,
        green: 0.0,
        blue: 0.0,
        alpha: 1.0)
    displayColors()
}

@IBAction func greenSliderChanged(sender: UISlider) {
    let green = CGFloat(sender.value)
    sender.thumbTintColor = UIColor(
        red: 0.0,
        green: green,
        blue: 0.0,
        alpha: 1.0)
    displayColors()
 }
@IBAction func blueSliderChanged(sender: UISlider) {
     let blue = CGFloat(sender.value)
     sender.thumbTintColor = UIColor(
         red: 0.0,
         green: 0.0,
         blue: blue,
         alpha: 1.0)
     displayColors()
}

We get the slider’s value, and convert it to a CGFloat. We take that value and change the tint value of the slider for that color only, leaving the other two colors at 0.
Build and run and you start with the button image. Move the buttons and the color changes.

colorslider1

The image is any image if you never show it at all. Many people use the app icon. If you don’t want to show the image at all, you can change the color immediately after setting the image like this:

//give the sliders the initial color
redSlider.thumbTintColor = UIColor(
     red: 0.5,
     green: 0.0, 
     blue: 0.0, 
     alpha: 1.0)
greenSlider.thumbTintColor = UIColor(
     red: 0.0,
     green: 0.5,
     blue: 0.0,
     alpha: 1.0)
blueSlider.thumbTintColor = UIColor(
     red: 0.0,
     green: 0.0,
     blue: 0.5,
     alpha: 1.0)

Build and run. The image is gone
Screenshot 2014-10-02 07.18.38

Reading a UIColor to RGB values

There are times we need to get back an RGB value from a UIColor. For example our application has a bit of a bug that requires it. When we change from background to text, we lose the color data we had and the screen goes blank. The sliders do not reflect the current color data. When we switch from Background to text or vice versa, it would be good to change the sliders to reflect that change. Control-drag from the storyboard to the assistant editor to make an action for the segmented control like this:

 @IBAction func backgroundTextSegment(sender: UISegmentedControl) {
    }

There is a UIColor method getRed, however it is a bit tricky to use. Add the following code to this new method:

 @IBAction func backgroundTextSegment(sender: UISegmentedControl) {
// Create variables
    var r:CGFloat = 0
    var g:CGFloat = 0
    var b:CGFloat = 0
    var a:CGFloat = 0
    var myColor = UIColor()
//select the color you want to read from segment data
    if  sender.selectedSegmentIndex == 0{
        myColor = mySampleColorLabel.backgroundColor!
    } else {
        myColor = mySampleColorLabel.textColor
    }
//read the color and if it exists, set the sliders
    if myColor.getRed(&amp;r, green: &amp;g, blue: &amp;b, alpha: &amp;a){
        redSlider.setValue(Float(r), animated: true)
        greenSlider.setValue(Float(g), animated: true)
        blueSlider.setValue(Float(b), animated: true)
    }
}

The method getRed returns a Bool that it successfully found a color. It then does something odd: it places the colors in the parameters for the method, but as the well-named, totally evil type UnsafeMutablePointer<CGFloat>. We don’t want to mess with these — trust me on this. However there is an easy way to avoid them. In front of our variable name we have an & character, which tells the compiler to share and load the parameter value back into the variable. If the method finds a color, we set the sliders with animation, converting the CGFloats into Float values.

Build and run, switch between background and text, and the slider thumbs move from one side of the slider to the next, since it reads colors from the black and white label.

There is a Lot More to Cover

When I originally started this post I thought it would be a quick one. It turns out there are three parts I wanted to cover. This post covered much of UIColor using RGB and assigning color to controls. I need two more posts to cover UIColor with gray, alpha values and UIColor using HSB, which will include color matching, making rainbow buttons and making a workable palette. I will continue color in the next post, and I’ll discuss gray and gray tone palettes and alpha values in the newsletter. If you have not signed up for the newsletter, subscribe here where I will weekly include some cool stuff I don’t post on the blog.

The Whole Code

//
//  ViewController.swift
//  SwiftColorPlay
//
//  Created by Steven Lipton on 9/28/14.
//  Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var mySampleColorButton: UIButton!
    @IBOutlet weak var mySampleColorLabel: UILabel!
    @IBOutlet weak var redSlider: UISlider!
    @IBOutlet weak var greenSlider: UISlider!
    @IBOutlet weak var blueSlider: UISlider!
    @IBOutlet weak var backgroundTextSegment: UISegmentedControl!

    let sliderImage = UIImage(named: &quot;color knob&quot;)

        //MARK: - Actions

    @IBAction func backgroundTextSegment(sender: UISegmentedControl) {

        var r:CGFloat = 0
        var g:CGFloat = 0
        var b:CGFloat = 0
        var a:CGFloat = 0
        var myColor = UIColor()
        if  sender.selectedSegmentIndex == 0{
            myColor = mySampleColorLabel.backgroundColor!
        } else {
            myColor = mySampleColorLabel.textColor
        }
        if myColor.getRed(&amp;r, green: &amp;g, blue: &amp;b, alpha: &amp;a){
            redSlider.setValue(Float(r), animated: true)
            greenSlider.setValue(Float(g), animated: true)
            blueSlider.setValue(Float(b), animated: true)
        }

    }

    @IBAction func redSliderChanged(sender: UISlider) {
        let red = CGFloat(sender.value)
        sender.thumbTintColor = UIColor(
            red: red,
            green: 0.0,
            blue: 0.0,
            alpha: 1.0)
        displayColors()
    }

    @IBAction func greenSliderChanged(sender: UISlider) {
        let green = CGFloat(sender.value)
        sender.thumbTintColor = UIColor(
            red: 0.0,
            green: green,
            blue: 0.0, 
            alpha: 1.0)
        displayColors()

    }
    @IBAction func blueSliderChanged(sender: UISlider) {
        let blue = CGFloat(sender.value)
        sender.thumbTintColor = UIColor(
            red: 0.0,
            green: 0.0,
            blue: blue,
            alpha: 1.0)
        displayColors()

    }

    //MARK: - instance methods
    func displayColors(){
        //create a color from the sliders.
    let red = CGFloat(redSlider.value)
    let blue = CGFloat(blueSlider.value)
    let green = CGFloat(greenSlider.value)
    let color = UIColor(
        red: red,
        green: green,
        blue: blue,
        alpha: 1.0)
        //apply the color to the background or text
    if backgroundTextSegment.selectedSegmentIndex == 0 {
        mySampleColorButton.backgroundColor = color
        mySampleColorLabel.backgroundColor = color
    } else {
         mySampleColorButton.setTitleColor(
             color,
             forState: .Normal)
         mySampleColorLabel.textColor = color
        }
    mySampleColorLabel.text = String(
         format: &quot;%i,%i,%i&quot;,
         Int(red * 255),
         Int(green * 255),
         Int(blue * 255))
    let myTitleText = String(
         format: &quot;%6.2f,%6.2f,%6.2f&quot;,
         Float(red),
         Float(green),
         Float(blue))
    mySampleColorButton.setTitle(
         myTitleText,
         forState: .Normal)
    }

    //MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()

        //Set the label's color with the System colors
        mySampleColorLabel.backgroundColor = UIColor.blackColor()
        mySampleColorLabel.textColor = UIColor.whiteColor()

        //Set the button's back ground with a RGB specfied color
        // a hex number, and decimal and a 1-0 all work -- this is gray
        mySampleColorButton.backgroundColor = UIColor(red: 0x80/255, green: 128/255.0, blue: 0.5, alpha: 1.0)

        //This is syntactically correct but does nothing
        mySampleColorButton.titleLabel?.textColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)

        //the right way to set a color on a button title
        mySampleColorButton.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
        mySampleColorButton.setTitleColor(UIColor.blackColor(), forState: UIControlState.Disabled)

        //set the background color to #fde8d7
        view.backgroundColor = UIColor(red: 0xfd/255, green: 0xe8/255, blue: 0xd7/255, alpha: 1.0)

        //initialize sliders for tints with an image
        let sliderImage = UIImage(named: &quot;color knob&quot;)
        redSlider.setThumbImage(sliderImage, forState:UIControlState.Normal)
        greenSlider.setThumbImage(sliderImage, forState:UIControlState.Normal)
        blueSlider.setThumbImage(sliderImage, forState:UIControlState.Normal)

    }

}

SlippyFlippy 1.1: Adding a Fading-in and out Label with Background in SpriteKit

icon2_120Today we have a bug to deal with in SlippyFlippyPenguin. There are a two reports of when a user selects the hard skill level  the game  immediately goes to game over. I cannot find anything that is different in the code to account for this except user error. I think the user error is users not realizing how fast they have to take controls in the hard mode. The penguin drops to the bottom, and ends the game before they can react. I’ve had more reports from a lot other users that is a user problem.  It is the speed that flippy drops after pressing the button that gets them. The  game is not clear about Slippy touching the sides or about what you are to do — and all of that  happens fast.

So here is what I’m going to do.

  • Add directions as messages that appear and disappear.
  • Add a countdown timer before starting

I’ll  implement this in two stages. Lets’ start with placing a message on the screen. What we need to do is flash a string on the screen, then make it disappear.

-(void)flashMessage:(NSString *)message atPosition:(CGPoint)position duration:(NSTimeInterval)duration{
//a method to make a sprite for a flash message at a certain position on the screen
//to be used for instructions

//make a label that is invisible
     SKLabelNode *flashLabel = [SKLabelNode labelNodeWithFontNamed:@"MarkerFelt-Wide"];
     flashLabel.position = position;
     flashLabel.fontSize = 17;
     flashLabel.fontColor = [SKColor blackColor];
     flashLabel.text = message;
     flashLabel.alpha =0;
     flashLabel.zPosition = 100;
     [self addChild:flashLabel];
//make an animation sequence to flash in and out the label
     SKAction *flashAction = [SKAction sequence:@[
           [SKAction fadeInWithDuration:duration/3.0],
           [SKAction waitForDuration:duration],
           [SKAction fadeOutWithDuration:duration/3.0]
]];
// run the sequence then delete the label
[flashLabel runAction:flashAction completion:^{[flashLabel removeFromParent];}];

}

Pretty straightforward code, and the comments make clear each of our steps:

  • Make a label node
  • Set its properties, and make it invisible
  • Make an action that fades in the label, shows it, then fades it out
  • Run the action, then remove the sprite.

Couple of items to note here: it’s important to set the z-height to 100, so the messages are not obscured by anything in the game. A new method for me  was runAction:completion: which here immediately removes the node when the animation is done. I think this will be a good practice, and will be a modification to what I actually did in the SlippyFlippy challenge when I write about it shortly.

We implement that method in the touchesBegan: code when we select our difficulty level;

else if (CGRectContainsPoint(hardRect, location)){
                //   NSLog(@" Hard TOUCH!!");
                [self flashMessage:@"Avoid the sides" atPosition:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame)*0.1) duration:3];
                [self flashMessage:@"Avoid the sides" atPosition:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame)*0.8) duration:3];
                [self flashMessage:@"Tap the screen to move Penguin" atPosition:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame)*0.65) duration:1];
                [myLabel removeFromParent];
                [[self childNodeWithName:@"easy button"] removeFromParent];
                [[self childNodeWithName:@"hard button"] removeFromParent];
               
Notice I keep everything a relative position, a percentage of the full screen. This helps with the screen size fragmentation problem, and keeps everything proportional. 

We end up with this: iOS Simulator Screen shot Apr 1, 2014, 7.39.46 PM

This is good, but it needs a background to stand out more. So I made two more nodes: a SKNode as a container, and a SKSpriteNode as a background. I’ll put the label and the Sprite into the node and then animate the node. Note I had to set the label’s verticalAlignmentMode to center from its default baseline alignment. Otherwise our positioning is on the baseline of the text, and fails to align with the background.

 
-(void)flashMessage:(NSString *)message atPosition:(CGPoint)position duration:(NSTimeInterval)duration{
//a method to make a sprite for a flash message at a certain position on the screen
//to be used for instructions

//make a label
     SKLabelNode *flashLabel = [SKLabelNode labelNodeWithFontNamed:@"MarkerFelt-Wide"];
     flashLabel.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
     flashLabel.fontSize = 17;
     flashLabel.fontColor = [SKColor whiteColor];
     flashLabel.text = message;


//make a background based on the size of the label
     CGSize mySize = flashLabel.frame.size;
     mySize.height *=1.5;
     mySize.width *= 1.2;
     SKSpriteNode *background = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:mySize];

//make a container node
     SKNode *flashLabelNode = [SKNode node];
     flashLabelNode.position = position;
     flashLabelNode.zPosition = 100;
     flashLabelNode.alpha = 0;
     flashLabelNode.name = @"flashLabelNode";
//add the node tree together
     [flashLabelNode addChild:background];
     [flashLabelNode addChild:flashLabel];
     [self addChild:flashLabelNode];

//make an animation sequence to flash in and out the label
     SKAction *flashAction = [SKAction sequence:@[
          [SKAction fadeInWithDuration:duration/3.0],
          [SKAction waitForDuration:duration],
          [SKAction fadeOutWithDuration:duration/3.0]

     ]];
// run the sequence then delete the label
     [flashLabelNode runAction:flashAction completion:^{[flashLabelNode removeFromParent];}];

}

So our steps here are:

  • Make the SKLabelNode
  •  Make a background slightly bigger than the label’s size with a SKSpriteNode.
  • Make a SKNode as a container, then add the label and sprite to the node.
  • Make the action, though this time directed at the SKNode instead of the SKLabelNode
  • Perform the action, then remove the node.

iOS Simulator Screen shot Apr 1, 2014, 7.38.04 PMThis works but it leaves a bit of a problem. I don’t get rid of the labels when I need to. In a game over situation, the labels are still there, but I don’t want them there. I need to remove the labels completely at a game over state.

Lets remove all flash labels in one purge using the enumerateChildNodesWIthname:usingBlock: method

-(void)removeAllFlashMessages{

     [self enumerateChildNodesWithName:@"flashLabelNode" usingBlock:^(SKNode *node, BOOL *stop) {
          [node removeAllActions];
          [node removeFromParent];
     }];
}

iOS Simulator Screen shot Apr 1, 2014, 7.38.13 PMThat works great. I might want to identify the labels so I can get rid of them at events, but I’ll do that later.

Next time,  we will do the countdown timer.

SlippyFlippy 1.1: Making a High and Low Score Indicator

icon2_120This is the first of my improvements to the SlippyFlippy penguin game.  While this may not make sense since I have not posted all the game code yet, it might help those trying to come up with some of these solutions for their own games. While playing against  some of my friends on our different devices, we found we need a different score for the hard mode and easy mode. In the hard mode, the game ends if the penguin contacts the top or bottom walls.  In the easy mode the penguin bounces off the top and bottom walls.

There is a bit of a real estate problem about putting two scores on the screen.  The solution is to fade in one high score, then fade it out, then fade the other one in, then fade that one out. This repeats forever.

My current code for changing the label looks like this:

highScoreLabel.text = [NSString stringWithFormat:@"High Score: %li",(long)highScore];

It’s just setting the labels text.  This shows up at setup and game over. Since it shows up in two places, I’ll make a method for the animation. Before I do, I need to find and store the high score. I look at the game over code in update:

if (highScore < score ){
highScore = score;
highScoreLabel.text = [NSString stringWithFormat:@"High Score: %li",(long)highScore];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:highScore forKey:@"highScore"];
[defaults synchronize];

}

If we have a high score, we do two things. We first display the new high score, and secondly we save the new high score to NSUserDefaults.  I introduced an new NSInteger called highScoreHard. With this addition, there are two situations instead of one where we need to process a high score, so the if has to reflect both:

if ((highScore < score && isEasyMode) || (highScoreHard < score && !isEasyMode)){

The BOOL isEasyMode is set when we select the mode we want to use. If we are in easy mode and there is a new high score, or if we are in hard mode and we have a new high score,  we need to post a new high score. We can now use isEasyMode to post the score to the right place:

if (isEasyMode)
highScore = score;
else
highScoreHard = score;

Our last change to the game over code is to add another key entry to the NSUserDefaults entries under defaults.

[defaults setInteger:highScoreHard forKey:@"highScoreHard"];

If we stub our animation method in makeHighScoreAnimate  for the moment, this snippet of code changes to:

if ((highScore < score && isEasyMode) || (highScoreHard < score && !isEasyMode)){
if (isEasyMode)
highScore = score;
else
highScoreHard = score;

[self makeHighScoreAnimate];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:highScore forKey:@"highScore"];
[defaults setInteger:highScoreHard forKey:@"highScoreHard"];
[defaults synchronize];

}

We now need to read the high scores when we start the app in initwithSize:.  We have another entry in defaults with a new key. That segment of code in initwithSize: looks like this now:

//grab the last high score from persistent memory
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
highScore = [defaults integerForKey:@"highScore"];
highScoreHard = [defaults integerForKey:@"highScoreHard"];
[self makeHighScoreAnimate];

We have one more thing to do and that is make the animation code.

-(void)makeHighScoreAnimate{
//remove the old high score animation, if there
[highScoreLabel removeActionForKey:@"highScoreAnimate"];

//makes strings for the two scores
NSString *highScoreLabelText = [NSString stringWithFormat:@"Easy High: %li",(long)highScore];
NSString *highScoreLabelHardText = [NSString stringWithFormat:@"Hard High: %li",(long)highScoreHard];
//Create the animation sequence and run it forever.
SKAction *highScoreAnimateSequence = [SKAction sequence:@[
[SKAction runBlock:^{highScoreLabel.text = highScoreLabelText;}],
[SKAction fadeInWithDuration:1.00],
[SKAction waitForDuration:3.0],
[SKAction fadeOutWithDuration:1.0],
[SKAction runBlock:^{highScoreLabel.text = highScoreLabelHardText;}],
[SKAction fadeInWithDuration:1.00],
[SKAction waitForDuration:3.0],
[SKAction fadeOutWithDuration:1.0],
]];
SKAction *highScoreAnimate = [SKAction repeatActionForever:highScoreAnimateSequence];

//put the action in the label
[highScoreLabel runAction:highScoreAnimate withKey:@"highScoreAnimate"];
}

We remove the old high score first. If there is no SKaction there, it will do nothing.  To make the coding easier to read,  we make two strings highScoreLabelText and highScoreLabelHardText to hold the two high scores and titles.  We make an SKAction sequence, which is an array of SKActions. If you have something other than an SKAction that you want to do, you can add it in a runBlock: method, as we did with changing the titles.  I then take the sequence and repeat it forever.

I run the SKAction to the highScoreLabel and give it a key so I can remove it when I need to change it.  Now when I build and run, the two high scores fade in and out.

The Slippy Flippy Penguin Challenge: A Tutorial on Sprite Kit

I stuck my foot in my mouth.

I’m now chewing vigorously and that is why you are reading this.
Photo Mar 19, 9 52 47 AMFor those who never heard of Flappy Bird, it was the game sensation nobody expected. It was a game that was very simple: a little bird moves up if you tap the screen, and falls due to gravity if you don’t. The game play is also simple: fly between two pipes without touching them and get a point. If you touch the pies or the ground, the game ends. While that seems easy, game play is very difficult.
The programming world was outraged when this jumped into #1 slot in iTunes and the developer began making $50,000 a day, far more than much slicker games with intensely realistic graphics. Then suddenly, the author pulled it from the stores. There was controversy. Not only was it simple, the graphics used  went out of style before the Gameboy. How did such a cheap game added to the store almost a year ago suddenly have such a meteoric rise? Apple’s App Store and Google Play hunkered down for an invasion of clones to the now defunct Flappy Bird, (which since I first wrote this is being resurrected ) and the author now says he pulled it because it was so addictive.
I was explaining the Flappy Bird controversy to a Friend. I claimed I could, actually anyone could, write Flappy Bird in a week. My friend dared me to try, and so I had ended up with a week-long challenge — write a Flappy Bird clone in a week then get it into the App store, with a variation or two.
Thus Slippy Flippy Penguin was born. If it gets into the App Store is another thing.
I didn’t want to write a clone – I prefer originality, like the game I’ve been working months to finish and is still under development. Besides Apple throwing it into a garbage heap in a second with the rest of the cheap knockoffs of the originall, I want to teach how to write games and how to program. I’m an educator at heart. Apple also last year made game programming a lot easier by introducing Sprite Kit to Xcode, the suite of programs used to write apps for iPhone and iPad. So a bigger challenge looms — can anyone write not just a clone but any game similar within a week’s time? If you have a Mac and can load Xcode, you are certainly welcome to follow along and write your own game. If you don’t, you may learn something too about a lot of what goes on behind the touchscreen of your iPhone.
The game itself is simple, which is what makes it such a great educational tool, and I’m not the only one doing this.

So Let’s see how to do this one part at a time.