Tag Archives: ios

This Old App: Hooking up the Model

A few months ago, Apple removed my app Interval RunCalc from the App Store for being too old and not updated. In this series of articles I’ll document what I did on rebuilding and improving the app. In the last installment, I hooked up my picker view interface to my app, and built the first round of user interfaces, along the way finding I’ve painted myself into a corner a few times and made several really silly mistakes.  In this installment, I hook up the model so the app actually calculates something – and get a pleasant surprise.

Hook up the Model

Up to now I’ve been setting the initial data to the picker as a literal. For example in the time entry

@IBAction func timerEntry(_ sender: UIButton) {

    if runStats.locked != .time{

      let vc = storyboard?.instantiateViewController(withIdentifier:>"InputController") as! PickerViewController

      vc.delegate = self

      vc.units = .hoursMinutesSecond

      vc.value = 4283.0// in seconds 3600 + 600 + 60 + 20 + 3 = 1 hr 11min 23sec

      vc.modalTransitionStyle = .crossDissolve

      present(vc, animated: true, completion:nil)

    }

  }

I set the picker to always get a 1:11:23 time. I’m going to change this to the RunStats model.

Before I do this, I notice an error I missed up to now. I have timerEntry instead of timeEntry. It’s a little thing, but it affects documentation, so I’ll fix it. Since this is an action, I unconnected the action, changed the spelling, cleaned with Command-Shift-K and then re-connect it.

Once I do that, I change the value to the runStats value for time, based on the model I created a few installments ago.

vc.units = .hoursMinutesSecond

vc.value = runStats.time.seconds()

I’ll do this next for distance, which is a bit more complicated. Each of my stats in the model has a value, which has a getter for the correct units(Kilometer or mile), and a enum for the correct units for display. The picker does too, but the enum is a different type, and the value is a generic Double, getting all the unit information from the enum. I create a switch statement to convert from the model to the picker.

      switch runStats.distance.displayUnits{

      case .kilometers:

        vc.units = .kilometers

        vc.value = runStats.distance.kilometers()

      case .miles:

        vc.units = .miles

        vc.value = runStats.distance.miles()

      }

For pace I should have the same thing, but there a wrinkle. The picker requires a Double for all measurements. In the model I have a string for minutesSecondsPerMile. The picker is looking for a double of seconds per mile, which I don’t have in PaceStat.  In the PaceStat class, I defined that string like this:

  public func minutesSecondsPerMile() -> String{

    let secondsPerMile = 1609.344 / paceStat

    let minutes = Int(secondsPerMile) / 60

    let seconds = Int(secondsPerMile) % 60

    return String(format:"%02i:%02i",minutes,seconds)

  }

Since seconds per mile was a necessary calculation to create the string, this is an easy fix. I can break that method into two methods:

func secondsPerMile()->Double{

    return 1609.344 / paceStat

  }

   

func minutesSecondsPerMile() -> String{

    let minutes = Int(secondsPerMile()) / 60

    let seconds = Int(secondsPerMile()) % 60

    return String(format:"%02i:%02i",minutes,seconds)

  }

Then add to the paceEntry action in my view controller this switch to convert units and values for the picker.

switch runStats.pace.displayUnits{

case .kilometersPerHour:

  vc.units = .kilometersPerHour

  vc.value = runStats.pace.kilometersPerHour()

case .milesPerHour:

  vc.units = .milesPerHour

  vc.value = runStats.pace.milesPerHour()

case .minutesSecondsPerMile:

  vc.units = .minutesSecondsPerMile

  vc.value = runStats.pace.secondsPerMile()

}

The Locked Value

Next I’ll add calculations for the locked value. This was the big surprise. I’ll add this to the RunStats model:

func recalculate() {

    switch locked{

    case .distance:

      self.distance = distance(pace: pace, time: time)

    case .pace:

      self.pace = pace(time: time, distance: distance)

    case .time:

      self.time = time(pace: pace, distance: distance)

    }

  }

 

There are times when one over-analyzes a situation, and leads to a complex solution. I did that in version 1.0 with the calculations above. Because I planned out the model so carefully for V 2.0, this ends up incredibly easy to deal with. In the V1.0 model, I was basing things on what changed, not what was locked. Locked is my solution, not just a locked variable. It the difference between “Pace just changed! What do I do?” to “Solve for time given pace and distance” The second is a lot simpler to think about and code.

Initialization

I’ll make sure I have some starting data in view did load

override func viewDidLoad() {

  super.viewDidLoad()

  runStats.pace = PaceStat(minutes: 10, seconds: 00)

  runStats.pace.displayUnits = .minutesSecondsPerMile

  runStats.distance = DistanceStat(miles: 26.2)

  runStats.distance.displayUnits = .miles

  runStats.locked = .time

  runStats.time = runStats.time(pace: runStats.pace, distance: runStats.distance)

  updateDisplay()

}

I’m ready to build and run. I’m pleasantly surprised that the first run works perfectly. The initial value loads from the model.

This is my ultimate goal for a marathon: a 4:22 finish. That’s way in the future – I’ve never run more than a half marathon. I try my last 5K run to find pace, changing the distance units, and solving for pace.

Yeah, I got a lot of training (and weight loss) ahead of me. My last long run used a slower combination of run/walk so it looks like this:

I have a ten Mile race coming up based on those paces, I can calculate My time between 2:24:50 and 2:16:50.

All of these match data calculated by other apps, so it looks like the app is working. For a change, I don’t have a lot of work to do in a development stage. I just plugged things in and they worked. I know it is supposed to work when I plan things out carefully, but it still amazes me when it does. I’ve done too much debugging to believe something actually works right the first time. In the next installment, I’ll start adding another level of complexity and that might change. I’ll start adding the interval table. First part of that phase is making a custom table view cell.

This Old App: 2013 Code, Meet Xcode 8

A few months ago my app Interval RunCalc was removed from the App Store for being too old and not updated. In this series of articles I’ll document what I did on rebuilding and improving the app. In our first article I analyzed the changes necessary. In the second, I built the model in a Swift playground. Now it is time to place that model into the old app, and make a temporary view and controller to test it.

Open the App in Xcode

I made a copy of the original app development files, then opened them in Xcode. The issue navigator started complaining right away:

Clicking on the issue, I find several updates Xcode wanted to make.

I select Perform Changes resolving the issues. I find in the target settings for the project, there’s more issues: provisioning profiles for debug and release.

>

Above the issue, I’ll find a checkbox Automatically Manage Signing. I check that and enable the automatic signing.

I find provisioning one of the most irritating parts of working with Xcode. Anything to get me away from it is a good thing.

Of course that’s not the end of the issues. I get another error message.

If I manage singing automatically, I have to pick the team. I only have me so I pick myself from the drop down. The target is set up, and Xcode is happy again.

Configuring the New Version

This is a major version update for the app. I want to change the version number to version 2.0. Originally I wanted the app to be called runner’s calc, but that name was taken. I settled on the mouthful Interval RunCalc. That’s too long for an icon, I want to change the Display Name to a shorter name, Run Calc to show better on an icon.

The next change I’ll make is the deployment info. Currently, the deployment is iOS 8.0 and only on iPhone in portrait.

Which iOS version to set to is a good question. In most cases, you want to deploy a version or two back for compatibility. Here’s where getting user data comes in handy. I’ll use iTunes Connect’s analytics, and look at the analytics for 2016.

Two active devices per month means there’s almost no one but me using this. Usage data strongly hints me that the biggest user is me. The large peaks occur at races I either ran or was a spectator (marathons are my 2018 goal). While a second user might be at the same races, I find it unlikely.

I’m looking at a sample that’s less than a third of total apps out there. Still, upping the version to 10.3 is not tragic since it looks like nobody but me uses this. I’ll bump it up all the way for the revision. I’m also going to make this a Universal app instead of a iPhone app. My new settings look like this:

Later, I’ll set the iPad and iPhone interface to the same initial storyboard.

The Old Structure

Opening the project navigator, You can see how I put together this app:

You’ll see the root files are the RunnersCalc files. The top level contains the initial view controller and the help controller. There was a help controller, though no one knows how to get to it — I unfortunately hid it too well. If you got to it, it was just written instructions, and very badly written. It’s another thing to go on the list of changes.

The folders under this root level has one folder for the model, and three folders for the view controllers: Intervals, Input controls and Splits.

For those from a completely Swift background, those .h and .m files are Objective-C files. The .h files contain a header which indicates what properties and methods are sharable to the rest of the app. Your code is in the .m files.

Opening up the Model folder and clicking the PCPace.h file, I find the declarations for the model, the properties of the model were:

//properties of the object

@property NSTimeInterval pace; // seconds per mile

@property float speed; // miles per hour

@property float distance; //miles

@property NSTimeInterval time; //seconds of the run or split

@property NSTimeInterval splitPace; //seconds per mile or km

@property int splitCount; //total number of splits

@property int splitNumber; //current split -- necessary?

@property int fullSplitCount; //number of splits for the full mile/kilometer

@property NSTimeInterval elapsedTime; //elapsed time in a split

@property float totalDistance; //totalDistance to this point

enum PCVariable {

  kcPCSpeed,kcPCDistance,kcPCPace,kcPCTime

};

@property enum PCVariable finding;


Add the Playground Code to the App

I’m not too worried about any of this code, since I’m about to replace it all. I open up the RunStats playground so I can copy my code. I have the code stored in four files:

In the App project, I’ll make four new files in the model folder. I’ll select the Model Folder first to keep the group correct as I add files. I’ll hit Command-N to make a new file. There’s several ways I can make these files, since I’m cutting and pasting everything into the file. I’ll select Swift File for the template, though I could have just as easily picked a Cocoa Touch Class subclassing NSObject.

I’ll be asked to save with a file name. I’ll call this one RunStats.

On the bottom, I’ll make sure the target is correct and the Group is Model

We click Create, and get a new Message:

I usually create the bridging header. As the message says the bridging header opens up the classes, methods and properties in the Objective-C code to the the newer Swift code if you want to use it. In this application I won’t use it. However, it is not a lot of overhead. I’m planning to scrap all the Objective-C, but I never know in an intermediate step if I’ll need it, so I’ll create it anyway.

I’ll repeat this three more times for the classes DistanceStat, PaceStat and TimeStat. I have two classes in the RunStats playground page. I’ll add one more for RunStatsIntervals, so every class has its own file.

I’ll organize this a little better. I’ll select my Objective-C files PCPace.h, PCPace.m, and the bridging header. I’ll right click and make New Group From Selection, naming the group Objective-C. I’ll do the same for the Swift code, selecting it all and making a Swift group for it.

I’ll select RunStats, and see this:

//

// RunStats.swift

// RunnersCalc

//

// Created by Steven Lipton on 4/6/17.

// Copyright © 2017 Steven Lipton. All rights reserved.

//



import Foundation


I’ll delete the import foundation, and then copy and paste my RunStats class from the playground to the App, leaving the intervals class and the test data in the playground. I’ll make sure I copy the import UIKit when I do. It will of course have lots of errors for the moment. We have the three classes it refers to yet to add. I’ll do the same copy and paste to the other four files, adding the classes and import UIKit in each case.

Once I’m done with this there are seven issues to solve. When transferring a playground, you’ll probably get these errors.

If you click on the first error, it will show you this in the editor:

The time.seconds() and the distance.meters() lines Xcode wants to assign to something. I used them to get a quicklook for debugging the playground. All the errors are like this. The simple solution is delete that code. The fatal error is the same thing. it’s on the line

 self.pace

It was to get a quicklook for the object in the playground. I deleted it to clear the error.

A New View and Controller

The new model is in the app, and I’d like to test it. Right now, this code is ignored by the app. What I’ll do is set up a new storyboard to check the model. In the Storyboards group, I have a blank iPad storyboard and the Main_iPhone.storyboard file, which has the current storyboard for the app:

This is a big mess. This is one of those times when doing an update project like this you just shake your head and wonder what you were thinking back then. With a little investigation, you’ll find some of those answers. I iterated this model with a inadequate model to start, and I needed a lot of UI to cover up my mistakes. The major reason this app never had an iPad version was I’d have to replicate this storyboard on an iPad.

The new app had a completly new Storyboard. I’m using auto layout to need one storyboard for all devices. I Press Command-N, selecting a Storyboard object and name it Main, making sure my Group is Storyboards.

The storyboard I get is completely blank. I’ll drag a view controller onto the storyboard

I’ll change the attribute to give this a name and set this view controller to Is Initial view controller.

I’ll need a ViewController file for this scene. I’ll press Command-N again, click a Cocoa Touch Class for the template and make the Class RootViewController, subclassing UIViewController and Swift as the language.

When I save the file I’ll save it to the RunnersCalc root folder, where the root and help view controllers are. Going back to the main storyboard, I’ll set the Custom Class to RootViewController. Now I’m ready to add some controls.

This first layout is only for testing purposes. No auto layout just yet, just drag and drop. I’ll add a label for a result and a button to calculate the result. I’ll use text fields for input, so I’ll need room for the keyboard.  I’ll set the button at the center of the iPhone screen, giving the keyboard I hope plenty of room, and the result at the top. I’ll make the label read Result and the button Calculate

I Drop two text fields between the label and the button, which I’ll call variable 1 and 2. That will be for decimal values. For time values I’ll add three textfields under those two text fields. The layout looks like this, using placeholder text for titles.

Next, I’ll wire up my view controller. I’ll open the assistant editor. All but the button are outlets, So I’ll do those first, control dragging into the view controller.

@IBOutlet weak var result: UILabel!

  @IBOutlet weak var variable1: UITextField!

  @IBOutlet weak var variable2: UITextField!

  @IBOutlet weak var hours: UITextField!

  @IBOutlet weak var minutes: UITextField!

  @IBOutlet weak var seconds: UITextField!

I’m keeping this very generic for a reason. This is a test bed. I’ll use these same fields to check different parts of the model. I’ll calculate the values in code in the calculate button, which I now wire up as an action.

@IBAction func calculate(_ sender: UIButton) { 

  }

There’s a function I’ll need in the view controller. It will take a TextField and convert the text in it to a Double. I can close up the storyboard and assistant editor, and then add the function to RootViewController

  func double(textField:UITextField!)->Double{

    guard let textField = textField 

       else {return 0.0} //uwrap textfield

    guard let value = Double(textField.text!) 

       else {return 0.0} //find double

    return value

  }

This will give me a value of 0 for anything that isn’t a true Double, and return a Double if it is. There’s three optionals, and I deal with all the unwrapping here giving me a value or a zero. This is not something I’ll need in the finished code. The input system (which we cover next time) will restrict the user to only valid values.

With the structure of the test bed done, I’ll add the model to the code.

 let runStats = RunStats()

I’ll test the function the IBAction calculate. I’ll start with a simple test to make sure everything works, converting kilometers to miles.

@IBAction func calculate(_ sender: UIButton) {

    let distance = double(textField: variable1)

    runStats.distance.distance(kilometers: distance)

    let resultText = String(format: "%02.2d", runStats.distance.miles())

    result.text = resultText

  }


I use the double function to turn the variable1 text field into a double, then add it to the model with a kilometer measure. I change the distance to miles in a string, placing the result in result.

I’m almost ready to run this code, but there’s one more thing I need to do: Change the initial storyboard. In the General tab of the Target Properties, you’ll find this:

When run, the app goes to the old Main_iPHone storyboard, not the new Main. I also restricted the layout to portrait in the old version, and I’ll use all orientations for the new version. I set both the iPad and iPhone to the same storyboard Main:

Setting my simulator to an iPhone 7 I run the app. Two things happen. I get 44 warnings, but the app runs. I try putting in a value. I find I didn’t leave enough room for the keyboard

I’ll stop the app and move the calculate button up. I try running again. I’ll type in 5 when the calculator appears, and get rewarded with a value of…Zero instead of 3.11.

I first check to see if the model is working right with a few print statements

@IBAction func calculate(_ sender: UIButton) {

    let distance = double(textField: variable1)

    print("distance in km \(distance)")

    runStats.distance.distance(kilometers: distance)

    print("distance in km \(runStats.distance.kilometers())")

    print("distance in km \(runStats.distance.miles())")

    let resultText = String(format: "%02.2d", runStats.distance.miles())

    result.text = resultText

  }

When run, I get output on the console:

distance in km 5.0

distance in km 5.0

distance in km 3.10685596118667

The model is working fine. My bug is a silly one of setting the format wrong in the string. I change the format to this:

let resultText = String(format: "%6.2f", runStats.distance.miles())

When I run again, the test works.

I go back to the storyboard. That alphanumeric keyboard is annoying. For all the TextFields, I set the keyboard type to decimal pad.

I’m ready to try another test. Let’s take distance in miles and a speed in miles per hour and calculate time. I’ll convert distance and speed to Doubles, then set them to the runstats model. I’ll try this code.

@IBAction func calculate(_ sender: UIButton) {

    let distance = double(textField: variable1)

    let speed = double(textField: variable2)

    runStats.distance.distance(miles: distance)

    let pace = PaceStat(milesPerHour: speed)

    runStats.time(changedBy: pace)

    result.text = runStats.time.hoursMinutesSeconds()

  }  

I try a simple case where I run five miles at five miles an hour, And yes, I get the expected one hour.

I try a function with a time variable using the three textfields on the bottom. I’ll compute the pace in minutes per mile for a given distance.

@IBAction func calculate(_ sender: UIButton) {

    runStats.distance = DistanceStat(miles: double(textField: variable1))

    let seconds = Int(double(textField: self.seconds))

    let minutes = Int(double(textField: self.minutes))

    let hours = Int(double(textField: self.hours))

    let time = TimeStat(hours: hours, minutes: minutes, seconds: seconds)

    runStats.pace(changedBy: time)

    result.text = runStats.pace.minutesSecondsPerMile()

  }

I build and run this. A simple test is a one hour run of five miles, which should give us a twelve minute mile.

And my target time for my first marathon of five hours 45 minutes ( yeah, I’m a bit slow)

As expected, I get the 13:10 pace. I’d do a lot of tests like this to make sure the components work with the model.

A Model Change

During the testing, I get the feel for using the code in the model. There’s one thing I don’t like. Consider these two statements

runStats.distance.distance(miles: 5.0)

print(runStats.distance.miles())

I use different identifiers for the setter and getter. I like this better:

runStats.distance.miles(5.0)

print(runStats.distance.miles())

It is easer to read and shorter to type. It is not a huge change in the model code, and it works better. I can change the setters to this:

//set distance from miles

  public func meters(_ meters:Double){

    distanceStat = meters

  }

  public func miles(_ miles:Double){

    distanceStat = miles * 1609.344

  }

  public func kilometers(_ kilometers:Double){

    distanceStat = kilometers * 1000.0

  }


I change the resulting code using these functions in the rest of the model. I’ll do the same for the other stat types. The current code only requires changes to the stat types DistanceStat, PaceStat and TimeStat. Pace and time add one wrinkle in two and three parameter setters. The first parameter becomes the name of the setter, and the rest are parameters like this:

public func hours(_ hours:Int,minutes:Int,seconds:Int) {

    timeStat = Double(seconds + minutes * 60 + hours * 3600)

  }

I didn’t do it intially, but some uses of these functions in initializers will need to specify self to work properly.

public init(minutes:Int,seconds:Int){

    self.hours(0, minutes: minutes, seconds: seconds)

  }

Running the code as written I get the same results, and no problems.

The 44 Warnings 

There’s one last thing to address: those 44 warnings. Closing the folders they are in, I find they fall into the following categories:

Those warnings are why Apple wants me to change this code, and threw me out of the app store to do it. This is all old stuff that needs updating. Sometimes living in the Swift world, I forget the Objective-C is improving over time too. The deprecations for example are all the same issue: there’s no more UIActionSheets in iOS.  Many of the others are number conversions which changed in newer versions of iOS in Objective-C

I have choices here: I can fix this code and remove the errors, I can ignore the errors for now, or I can delete the code now. While the errors are annoying, they are not affecting the code. I go for the last one. I will delete all of the Objective-C code before shipping, replacing it with the new Swift code. It doesn’t need to be there, but I use it as documentation for the old version until I don’t need it anymore.

So I got the new model into the old code. I started replacing components, and got the old code to actually function in the latest Xcode. My next steps are to get the views and controllers working properly. I”ll start that process not at the root, but by replacing those text views with UIPickers in the next installment.

I’m a LinkedIn Learning author, with five courses up in the LinkedInLearning library.  My latest is a trilogy for iOS notifications: Learning iOS NotificationsiOS App Development: Notifications, and watchOS App Development: Notifications If you a re interested in Notifications, I’d Check them out. 

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.
    }


}


Basic Tap, Pinch and Rotate Gestures

Many controls such as table views, map views, scroll views and buttons use gestures. You as a developer might want to use a gesture for your own purposes outside of these controls. The gesture recognizer classes can do that. The standard ones such a as tap, pinch and rotate are rather easy to set up, either by storyboard or by code. In this tutorial, I’ll show you how to set them up in code.

Set Up the Project

Make a new  single view project called TapPinchDemo in Swift with a Universal device.  Go to the storyboard. Add a label to the upper left of the storyboard. I used auto layout to pin the label 10up, 10 left and 10 right. Change the label’s font to Title.

Open the assistant editor.  Control drag from the label to the code and make an outlet named statusLabel.  Close the assistant editor and go to the ViewController.swift code. Add a constant at the beginning of the ViewController class for a color.

 let background = UIColor(red: 1.0, green: 0.98, blue: 0.96, alpha: 1.0)

In viewDidLoad add this color as the background.

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = background
}

Tap Gestures

There are three parts to a gesture: Configuring a gesture recognizer object, making an action for the object, and adding it to the view.  For the first gesture, you’ll make a tap gesture.  In viewDidLoad add the following line to make a tap gesture:

 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAction(sender:)))

There’s two parameters in the constructor. Both indicate the location of an action that will be called when the gesture occurs. the first give the class, and the second the selector. selectors are functions called by the parameters of another function. While really a Objective-C thing, Swift had a compiler tag #selector() to indicate a selector. In the tap gesture tapAction(selector:) is the function called.

You’ll notice an error. Once you use a #selector, you must implement the function.  Add the function tapAction
above viewDidLoad

func tapAction(sender:UITapGestureRecognizer){
}

We’ll come back in a minute to finish filling out this code. Go back to viewDidLoad and configure tapGesture.

tapGesture.numberOfTapsRequired = 1
tapGesture.numberOfTouchesRequired = 1

This sets the gesture recognizer to one tap with one finger. Add the gesture recognizer to the view using the addGestureRecognizer method.

view.addGestureRecognizer(tapGesture)

Go back up to the function tapAction. Gestures have states. You must check the state before you do anything. Specific states are necessary for specific gestures to work. For a tap, the state must be .ended. Add this code to the tap action selector:

if sender.state == .ended{
    statusLabel.text = "Tapped"
    view.backgroundColor = background
}

Build and run. Tap the screen and the label reads tapped.

2016-11-28_05-51-41

Pinch Gesture

The pinch gesture is a movement of two fingers moving towards or away from eacth other. Pinch gestures have no properties you need to set. Adding them is even easier than a tap. Just call the UIPinchGestureRecognizer constructor and add the gesture recognizer to the view. Add this to viewDidLoad.

 let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(sender:)))
        view.addGestureRecognizer(pinchGesture)

The action specified is a bit more complex than the tap. You have three states you might want to look at: .began, .changed, and .ended. Most often your concern is the changed state which occurs when a user moves his or her fingers. But you might find uses for beginning and ending a pinch. Add the following method to the ViewController code.

func pinchAction(sender:UIPinchGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Pinch Began"
            view.backgroundColor = UIColor.yellow
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"Pinch scale: %1.3f",sender.scale)
        }
        if sender.state == .ended{
            statusLabel.text = "Pinch Ended"
            view.backgroundColor = background
        }
    }

Pinches are examples of continuous gestures. These are gestures which will have more than one state to watch for. This changes the background when you have started the pinch and restores when you finish the pinch. When you change the value, it shows the scale value for the gesture. Scale is a value that shows the change between the user’s first pinch and the movement of the user’s fingers. Scale is the value you use in your applications. Build and run. Try pinching the screen. In the simulator, hold down the option key and drag on the trackpad or mouse.

Rotation Gesture

The last gesture we’ll discuss is rotation. Rotations set up just like pinches, except they use a UIRotationGestureRecognizer constructor. Add this to viewDidLoad:

let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotateAction(sender:)))
view.addGestureRecognizer(rotateGesture)

And add this function for the action. Like pinch, rotate is a continuous gesture, so I’ll set it up similar to  pinchAction.

func rotateAction(sender:UIRotationGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Rotate Began"
            view.backgroundColor = UIColor.cyan
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"rotation: %1.3f",sender.rotation)
        }
        if sender.state == .ended{
            statusLabel.text = "Rotate Ended"
            view.backgroundColor = background
        }
    }

The code will turn the background cyan when the user starts a rotate action. The display will give the rotation angle in radians using the property of a rotator gesture rotation. When the rotation ends with  the user removing fingers, the background returns to the default background color. Build and run. To simulate a rotation click down on the simulator using the mouse and then press Option(it’s annoyingly tricky). Move the cursor and you will have a rotation gesture.

This is only the beginning to using gestures, but should give you a good foundation for other gestures. While I most often use code to add gestures, you can also add then by dragging them onto the storyboard. Once added, you find them in the document outline, where you can control drag them to the code like any other object and make an action. Gestures my also conflict with each other, so try to keep the number in a single view to a minimum.

The Whole Code

//
//  ViewController.swift
//  tapSwipeDemo
//
//  Created by Steven Lipton on 11/25/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var statusLabel: UILabel!
    let background = UIColor(red: 1.0, green: 0.98, blue: 0.96, alpha: 1.0)
    func tapAction(sender:UITapGestureRecognizer){
        
        if sender.state == .ended{
            statusLabel.text = "Tapped"
            view.backgroundColor = background
        }
    }
    func pinchAction(sender:UIPinchGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Pinch Began"
            view.backgroundColor = UIColor.yellow
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"Pinch scale: %1.3f",sender.scale)
        }
        if sender.state == .ended{
            statusLabel.text = "Pinch Ended"
            view.backgroundColor = background
        }
    }
    
    func rotateAction(sender:UIRotationGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Rotate Began"
            view.backgroundColor = UIColor.cyan
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"rotation: %1.3f",sender.rotation)
        }
        if sender.state == .ended{
            statusLabel.text = "Rotate Ended"
            view.backgroundColor = background
        }
    }

    func swipeAction(sender:UISwipeGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Swipe!"
        }
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = background
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAction(sender:)))
        tapGesture.numberOfTapsRequired = 1
        tapGesture.numberOfTouchesRequired = 1
        view.addGestureRecognizer(tapGesture)
        
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(sender:)))
        view.addGestureRecognizer(pinchGesture)
        
        let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotateAction(sender:)))
        view.addGestureRecognizer(rotateGesture)
        
    }
    

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


}

WWDC2016: Swift 3.0 and the Playgrounds for iOS

Last week the WWDC2016 left all of us with a lot to think about. I want to talk about some of the high points. I say some, because that’s a lot of stuff to absorb, and I didn’t get through enough to talk reasonably about all of them. The SiriKit session and all the WatchOS and tvOS sessions I didn’t have time to see yet.For this, I’m going to focus on Swift 3.0, and Playgrounds for iPad.

Swift 3.0

I have never heard such an awkward silence as when Apple did their best to try to hide an extremely painful change for most developers: Swift 3.0 changes. In the What’s New in Swift session, it was clear they know this was going to be a problem, and did their best to explain. It’s still going to be a pain. I heard over Twitter one developer that tried compiling their Swift 2.x app on their new Xcode 8 had two hundred and thirty syntax errors– after automatic conversion. While there are hundreds of changes in Swift 3.0 here’s the ones most likely to give everyone a headache:

Deprecation of ++ and —

This was one of those we’ve been warned about by the latest versions of Xcode. If you want to add or subtract 1 from a value the now ancient C operators ++ and -- are no more in Swift. Instead you need to use += or -=. For example, if you have

var x = 0
x++

You need to use

var x = 0
x += 1

C-Style for loops

The classic C syntax loop

for var int = 0 ; int >= 10; int++{...}  

is gone. Now you must use some of the for-in style like

for int in 0..>10{...}

to do the same.

First Parameters

Current Swift syntax let you declare a function

func myFunction(x:Int,y:Int){...}

and call that function with

myFunction(3,y:5),

You now have to call that function with

myFunction(x:3,y:5)

There is some good news. Using the underscore like this

func myFunction(_ x:Int,y:Int){...}

will let you call the function correctly. Otherwise you need to change all your function calls to conform to the new standard. While the reason for this is to make converting between Objective-c and Swift easier, it’s going to be a pain for developers transitioning code between Swift2.x and Swift 3.x.

Swift API Style Guide

There’s more here than meets the eye. Swift 3.0 has a style guide of how Apple and everyone else names functions and properties. I seriously suggesting watching Session 403 Swift API Design Guidelines, and check out the resources there. The short of it is to make your code read like a sentence. Functions and methods should be written as verbs, and properties and arguments as nouns.

NS is Gone

Many classes with the NS prefix in Swift are no more. Not the class, but the NS. NSDate, NSDateFormatter or NSTimer in Swift 3.0 is just Date, DateFormatter and Timer. It was after announcing that you could hear a very awkward silence. Your code that looked like this:

let date = NSDate()
let dateFormatter = NSDateFormatter()

Now looks like this

let date = Date()
let dateFormatter = DateFormatter()

I admit it’s a lot cleaner than previously, but still will be a lot of changes.

 

Extensions

If you haven’t worked with extensions in swift, this may be a good time to start. Extensions power many of the new API’s. Extensions do exactly what they say: instead of subclassing a class you add on more properties and methods to an existing class.

These existing classes include all API classes, where they become the most useful. In another lesson I discuss subclassing UITabBarController with a property to share models between tabs for example. Instead of subclassing, you could use an extension

extension UITabBarController{
	var mySharedModel = SharedModel()
}

and never touch or subclass UITabBarController. Many of the new APIs work by building extensions to them .

If you have the chance do catch Session 404 Getting Started in Swift on video. Even for a beginner’s session I learned a few things I didn’t know and it will sharpen your Swift skills.

Swift Playgrounds for iOS

I’ve wanted this for a while and passionately. It took a lot of effort for everyone in the Starbucks I was watching the Keynote to not look at me like a complete fool screaming and dancing. I wanted to — I didn’t. Apple introduced Swift playgrounds for the iPad, and it is far, far more than I could ever imagine. If you combine MIT’s Scratch language with Cut the rope in a Swift Playground, you’d start getting close. Add in complete access to the iPad’s UI and devices and you’d be even closer.

The Swift playground is directly targeted at the Education market. At first glance, it’s just a kids toy. Like Scratch, you can drag and drop code into the playground using a series of icons. The demo at The WWDC keynote had a series of lessons using both 3D and 2D animation for learning Swift. That series of lessons plays like many puzzle games where you have a character, in this case named Byte, perform some collection task, in this case collecting gems. The environment is rendered beautifully in animated 3D.

2016-06-19_11-06-37

The later demo for the Platforms State of the Union went into writing code you can import directly into Xcode. By adding import UIKit to your playground, you have all the UIKit API’s available to your playground — and all the devices. The demo included creating a new control for a color picker.

The playground is included in the iOS 10 beta. I downloaded it for my iPad Pro. Once you open it, you can download the Learn to Code Playground. I at first thought this playground was going to be a lame very basic tutorial. It’s actually one of the most addicting puzzle games I’ve played in a while. I planned to try just one level (using commands) and ended up completing the functions and loops levels as well. The levels go through most of the basic language, leaving more advanced topics to a second set of activities which at the time of their writing have yet to be released.

In these lessons you are really programming, not just filling out forms. I added a few advanced features to one lesson just to see how it would react and it just worked just fine.

If you want to get serious, you can open a blank playground. By importing UIKit, you have all the functionality of an app in Xcode. If you want to quickly play around with a single view application, you can easily do that in a few lines of code.

>import UIKit
import PlaygroundSupport

class ViewController:UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
}

let viewController = ViewController()
PlaygroundPage.current.liveView = viewController
PlaygroundPage.current.needsIndefiniteExecution = true

This give you full access to a view controller. The PlaygroundSupport library had several classes which give you the playgrounds as a simulator. As you’ll se it the code above with only four lines of code. Of course, there’s no storyboard, so you’ll have to be more programmatic about your views. I wrote some quick code to make a table view that looked pretty good and completely interactive.

2016-06-19_11-05-54

Playgrounds support markdown in your code, so you can write full tutorials like Apple did. Using a special file structure, a few plists and some of your own code, you too can write stuff for playgrounds.

Overall I’m very impressed with Playgrounds. It’s a great mobile tool for trying out new code without the overhead of Xcode. I went into WWDC2016 the least optimistic I have ever been with one, I’m far more optimistic now. While there’s a lot of work in migration, the end result will be far cleaner, understandable applications in Swift 3.0.

Swift Swift: How to Make a Drop Shadow in User Interfaces

2015-05-18_07-50-06

Flat User interfaces are a lie. They are not completely flat. One effect continues to be used with them to show depth: Shadows. In flat interfaces it can look like there are some views floating above others by the use of shadows. In this lesson we’ll learn to make shadows using the CALayer found in UIViews, and use them to make some special effects on UIButtons.

Make a new single-view project named SwiftShadowDemo with a Swift as the Language and Universal device. Go to the storyboard.  Drag a button on to the storyboard. Give it a Blue color for a background and white lettering.  Title it Shadow.

2015-05-15_07-28-32

With the button selected, Control-Drag Up from the button and to the left, until your cursor is on the white of the background scene. Release the button. You will get a menu. Shift select the items  Center Horizontally to Container, Center vertically to Container, Equal Widths and Equal Heights like this:

2015-05-15_07-31-29

You’ve probably not set the button in the exact center of the scene.  Go to the the size inspector and find the constraints. Select Edit on the  Align X to Shadow constraint. Change the constant to 0 .

2015-05-15_07-34-46

Do the same for the Align Center Y to Shadow. When done both constraints should read like this

2015-05-15_07-35-28

Now edit the Equal Width to Superview. Change the multiplier to 3 or 1:3 depending which gives you a smaller rectangle.

 2015-05-15_07-36-09

Do the same to Equal Height to Superview, with a multiplier of 3 or 1:3.  Next in the resolver menu, Update the frames

2015-05-15_07-37-16

This should give you a centered proportionally sized button:

2015-05-15_07-37-51

Open the assistant editor. Control-drag from the button to the code. Make an outlet named shadowButton.


 @IBOutlet weak var shadowButton: UIButton!

 

Control drag again from the button to the code. Make a action named shadowButton.

@IBAction func shadowButton(sender: AnyObject) {
}

Making Shadows on the CALayer

All UIViews have a special property layer of type CALayer. This is a Core Animation layer. As it name implies various animation effects to your views happen here.  The layer property is great for many quick special effects, some built-in. Drop shadows are one of those built-in effects.  Add the following to viewDidLoad.

shadowButton.layer.shadowOpacity = 0.7

The property shadowOpacity defaults to 0.  The shadow is always there, but hidden. To show a shadow, change the opacity. Build and run. We have a shadow:

2015-05-17_06-42-06

This shadow’s light source is from the lower right. Most times it’s best to have a light source from the top and left, so the shadow shows on the lower right. We change that by the  property of CALayer. Add this to viewDidLoad:

shadowButton.layer.shadowOffset = CGSize(width: 10.0, height: 10.0)

Build and run and you get this:

2015-05-17_06-52-19

We have  a nice bold shadow.  Usually shadows are subtle. Change the shadowOffset to this:

shadowButton.layer.shadowOffset = CGSize(width: 3.0, height: 2.0)

Build and run. The shadow is more subtle:

2015-05-17_12-18-32

We can also change the radius of the shadow. This affects how blurry the shadow appears.  Add this to viewDidLoad

shadowButton.layer.shadowRadius = 5.0

Build and run:

2015-05-17_12-25-10

We have a more blurred shadow.  We can also change the color of the shadow.  The default color is black. Let’s change the color to yellow.  Add this:

shadowButton.layer.shadowColor = UIColor.yellowColor().CGColor

Build and run. Rhe shadow now look like more of a glow.

2015-05-18_05-36-45

Glows are more radiant from the center. For a good glow effect change the code to the following:

shadowButton.layer.shadowOpacity = 0.7
shadowButton.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
shadowButton.layer.shadowRadius = 15.0
shadowButton.layer.shadowColor = UIColor.yellowColor().CGColor
view.backgroundColor = UIColor.grayColor()

To better see the glow we made the background gray to contrast against the glow.  Build and run:

2015-05-18_06-11-03

We get a glow behind the button.

While this is nice for a static shadow, glows and shadows make for dynamic effects on a button.  To the shadow button action, add the following code:

 @IBAction func shadowButton(sender: AnyObject) {
     shadowButton.layer.shadowOpacity = 0.7
     shadowButton.layer.shadowOffset = CGSize(width: 3.0, height: 2.0)
     shadowButton.layer.shadowRadius = 5.0
     shadowButton.layer.shadowColor = UIColor.blackColor().CGColor
}

This will make our simple drop shadow when we release the button. When we press the button, we will give one of two shadow effects, depending on the selection on a segmented control.  Add this outlet and action to your code:

 @IBOutlet weak var touchDownType: UISegmentedControl!
@IBAction func shadowButtonTouchDown (sender: AnyObject) {
// code to change the button on pressing
    if touchDownType.selectedSegmentIndex == 0{ //make the button glow
         shadowButton.layer.shadowOpacity = 0.5
         shadowButton.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
         shadowButton.layer.shadowColor = UIColor.yellowColor().CGColor
         shadowButton.layer.shadowRadius = 20.0
    } else { //make the button depress by having a shadow in the upper left
         shadowButton.layer.shadowOpacity = 0.9
         shadowButton.layer.shadowOffset = CGSize(width: -3.0, height: -2.0)
         shadowButton.layer.shadowColor = UIColor.blackColor().CGColor
         shadowButton.layer.shadowRadius = 2.0
    }
}

Go to the storyboard. Add a segmented control above the button. Control drag directly down from the segmented control to the button. In the auto layout menu that appears, Shift-select Vertical Spacing, Center X,  and Equal Widths

2015-05-18_06-28-31

As we did earlier with the button, we will change some constraints. With the segmented control selected, go to the size inspector.  Change the Bottom Space to Shadow  constraint to 20. Change Align Center X to Shadow to 0. Your constraints should read like this:

2015-05-19_05-33-50

Change the first segment title to Glow. Change the second segment title to Depression. Select the button. In the size inspector, change the Proportional width to Superview’s Multiplier to 1.5 if set it at 3. Set the multiplier to  1:1.5 if set at 1:3.

2015-05-18_06-38-29

Your scene should look like this:

2015-05-18_06-39-41

Open the assistant editor. From the circle next to the touchDownType outlet, drag to the storyboard to connect it to the segment.

2015-05-18_06-55-09

For events other than a touch up for a button we need to explicitly set the event. For our new action, we need to set an event for touching down on the button. Right click the button in the storyboard. In the menu that appears, find Touch Down. Drag from the circle to the right up to anywhere in the shadowButtonTouchDown  method’s code in the assistant editor. When the code highlights, release the button.

2015-05-18_06-59-05

We have wired our storyboard up. Now to set our default. Change viewDidLoad to this:

override func viewDidLoad() {
super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    shadowButton.layer.shadowOpacity = 0.5
    shadowButton.layer.shadowOffset = CGSize(width: 3.0, height: 2.0)
    shadowButton.layer.shadowRadius = 5.0
    shadowButton.layer.shadowColor = UIColor.blackColor().CGColor
    shadowButton.setTitleColor(UIColor.yellowColor(), forState: .Highlighted) //need to show button title
    view.backgroundColor = UIColor(red: 0.25, green: 0.2, blue: 0.6, alpha: 1.0)
}

We set up our basic shadow, like we did in the touch up event. We also added two lines of code to makes things a bit more visible. First we changed the color of the button’s title. If we leave it as the white from the storyboard, it will disappear. Changing the color to yellow fixes this. Secondly, we gave a contrasting color to both our glow and shadow so they are visible.

Build and run. Rotate the device in the simulator with Command-Right arrow. You should see this:

   2015-05-18_07-21-47

Press the button, and the button should glow:

2015-05-18_07-22-14

Switch to Depression

2015-05-18_07-23-04

Press the button and the button seems to press down:

2015-05-18_07-23-28

This is some of the basics of drop shadows. You can do this to any UIView based control.  For example you can do this to the segment and a label, which we’ll leave to the reader to set up to produce the image below:


//a shadow on the segmented control and on a label
//label layout and setup is left to the reader.
let offset = CGSize(width: 3.0, height: 2.0) //keep offsets consistent -- usually make this a constant.
touchDownType.layer.shadowOpacity = 0.7
touchDownType.layer.shadowOffset =  offset
shadowLabel.layer.shadowOffset = offset
shadowLabel.layer.shadowOpacity = 0.9
shadowLabel.layer.shadowRadius = 7.0

2015-05-18_07-50-06

The Whole Code

//
//  ViewController.swift
//  SwiftShadowDemo
//
//  Created by Steven Lipton on 5/15/15.
//  Copyright (c) 2015 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
//MAARK: - Outlets
    @IBOutlet weak var shadowLabel: UILabel!
    @IBOutlet weak var shadowButton: UIButton!
    @IBOutlet weak var touchDownType: UISegmentedControl!
//MARK: - Actions
    @IBAction func shadowButtonTouchDown (sender: AnyObject) { //When button is pressed down
        if touchDownType.selectedSegmentIndex == 0{
            shadowButton.layer.shadowOpacity = 0.5
            shadowButton.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
            shadowButton.layer.shadowColor = UIColor.yellowColor().CGColor
            shadowButton.layer.shadowRadius = 20.0
        } else {
            shadowButton.layer.shadowOpacity = 0.9
            shadowButton.layer.shadowOffset = CGSize(width: -3.0, height: -2.0)
            shadowButton.layer.shadowColor = UIColor.blackColor().CGColor
            shadowButton.layer.shadowRadius = 2.0
        }
    }

    @IBAction func shadowButton(sender: AnyObject) { //When button released
        shadowButton.layer.shadowOpacity = 0.5
        shadowButton.layer.shadowOffset = CGSize(width: 3.0, height: 2.0)
        shadowButton.layer.shadowRadius = 5.0
        shadowButton.layer.shadowColor = UIColor.blackColor().CGColor
    }
//MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        shadowButton.layer.shadowOpacity = 0.5
        shadowButton.layer.shadowOffset = CGSize(width: 3.0, height: 2.0)
        shadowButton.layer.shadowRadius = 5.0
        shadowButton.layer.shadowColor = UIColor.blackColor().CGColor
        shadowButton.setTitleColor(UIColor.yellowColor(), forState: .Highlighted) //need to show button title
        view.backgroundColor = UIColor(red: 0.25, green: 0.2, blue: 0.6, alpha: 1.0)

        //extra code not in post - a shadow on the segmented control and on a label
        let offset = CGSize(width: 3.0, height: 2.0) //keep offsets consistent -- usually make this a constant.

        touchDownType.layer.shadowOpacity = 0.7
        touchDownType.layer.shadowOffset =  offset
        shadowLabel.layer.shadowOffset = offset
        shadowLabel.layer.shadowOpacity = 0.9
        shadowLabel.layer.shadowRadius = 7.0

    }

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

}