An Introduction to
iOS Development

Embedded Systems
Victor Poupet

A Word About iOS

iOS

Version History

Fragmentation

Distribution

The only officially accepted way to add apps to an iOS terminal is to use Apple's App Store

About this Lecture

iOS development is a vast topic

Mobile Development

Mobile Terminals

Architecture

Processor


Storage


Other hardware

iOS vs. Android

Developper


Programming language


Development environment


Development kit

Developper


Programming language


Development environment


Development kit

Cross-Compilation

Constraints

Limited resources


Handle urgent events

Other Constraints

User interface Fiability Security Speed

Workflow

Mobile development is built around the user interface (screen):




→ Writing the first app in XCode : Greeting

A Short Introduction
to Swift

Swift

General Syntax

let a = 10
var b: Int = 12

b += a
let primes = [2, 3, 5, 7, 11, 13]

func isPrime(n: Int) -> Bool {
  for x in 2 ..< n {
    if n % x == 0 {
      return false
    }
  }
  return true
}
var x: Int
x = 12

if isPrime(x) {
  println("\(x) is prime.")
} else {
  println("\(x) is not prime.")
}

Optionals

var a: Int? = 12
var b = 10

b += a  // error, a could be nil
if a != nil {
  b += a!  // a is unwrapped
}

a = nil
b = nil  // error

if let aValue = a {
  // conditional unwrapping
  b += aValue
}

Swift introduces “optional” versions of all types

Object Programming

In a “classical” program

In object-oriented programming

Classes and Instances

class Person {
  var name: String // a property
  var age: Int

  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }

  func greet() {  // a method
    print("Hello, \(name)!")
  }
  func increaseAge(years: Int) {
    age += years
  }

  func ageDifferenceWith(other: Person) -> Int {
    return age - other.age
  }
}

Inheritance

class Student: Person {
  var school: String?
  var year: Int?

  init(name: String, age: Int, school: String, year: Int){
    self.school = school
    self.year = year
    super.init(name: name, age: age)
  }

  override init(name: String, age: Int) {
    super.init(name: name, age: age)
  }

  override func greet() {
    print("Hi, student \(name)")
  }

  func classDifferenceWith(other: Student) -> Int? {
    if let year = year, oYear = other.year {
      return year - oYear
    }
    return nil
  }
}

Creating Objects

To create an object

These steps are performed by the init function


Objects are removed from memory automatically when nothing points to them (see later section)

Illustration

Back to the Greeting app.

Graphical User Interface

Model View Controller

Design pattern, for structuring an application in 3 parts

Model View Controller

Model

class Player {
  var cards: [Card]
  var game: Game
  var points: Int
  var isTurnPlayer: Bool

  init(...) {
    ...
  }

  func playCard(card: Card) {
    ...
  }

  func endTurn() {
    ...
  }

  func draw(numberOfCards: Int) {
    ...
  }

  ...
}

In iOS, the model is represented by the data object classes

View

Cocoa Touch (iOS UI Framework) defines a class UIView


Note : the elements added to the Storyboard are subclasses of UIView

UIView

To create a user interface

What is a view ?

  • A rectangle
    • in which it is possible to draw
    • that reacts to events
    • contained in a window (UIWindow)

View hierarchy

  • each view is placed in a superview
  • a view can have any number of subviews
  • lower level views are drawn on top of higher level
    • subviews of a same view are drawn one after the other
    • views can have transparent elements

Position

let labelFrame = CGRect(x: 20, y: 50, width: 300, height: 50)
let label = UILabel(frame: labelFrame)
label.text = "Hello"
view.addSubview(label)

label.center = CGPoint(x: 170, y: 200)
view is positioned relatively to its parent view :

UIView Hierarchy

var superview: UIView?
var subviews: [UIView]
var window: UIWindow?

func addSubview(UIView)
func bringSubviewToFront(UIView)
func sendSubviewToBack(UIView)
func removeFromSuperview()
func insertSubview(UIView, at: Int)
func insertSubview(UIView, aboveSubview: UIView)
func insertSubview(UIView, belowSubview: UIView)
func exchangeSubview(at: Int, withSubviewAt: Int)
func isDescendant(of: UIView) -> Bool

Example : Dominion

GameView
  LogView
    UITextView
  StacksView
    KingdomCardsView
      StackView (x10)
        UIImage (background)
        UILabel (cost)
        UILabel (remaining)
    TreasureCardsView
      StackView (x3)
        UIImage (background)
        UILabel (cost)
        UILabel (remaining)
    VictoryCardsView
      StackView (x4)
        UIImage (background)
        UILabel (cost)
        UILabel (remaining)
  PlayerView
    ChatView
      UITextInput
      UITextView
    ButtonsView
      UILabel (instruction)
      UIButton (x3)
    HandView
      CardView (x3)
        UIImage

About the Views

Cartesian coordinates system

Create a View

Create a class that inherits from UIView


See example : Multiplication Tables

UIViewController

Classes Hierarchy

View Controller Cycle

Important methods :

Kinds of View Controllers

Content view controllers display content


examples : table view controller, most user-made view controllers

Container view controllers arrange content of other view controllers


examples : navigation controller, tab bar controller, split view controller

Nested Content VC

let subVC = SomeViewController()

addChild(subVC)
subVC.didMove(toParent: self)
view.addSubview(subVC.view)

Most of the time one content view controller is sufficient

It is possible to nest content view controllers when necessary

Navigation Controller

Tab Bar Controller

Split View Controller

Create a Controller

var view: UIView!
var isViewLoaded: Bool

func loadView()
func viewDidLoad()

func viewWillAppear(Bool)
func viewDidAppear(Bool)
func viewWillDisappear(Bool)
func viewDidDisappear(Bool)
func viewWillLayoutSubviews()
func viewDidLayoutSubviews()
func updateViewConstraints()
...

Initialization

The UIViewController class defines two possible initializers


Warning : The view is not loaded at the initialization of the controller

Initialization

Example

A simple example : Quiz

Slightly more complex example : Tabs (UITabBarController)

Switching Controllers

let vc = MyViewController()
UIApplication.shared.keyWindow!.
  rootViewController = vc
let vc = MyViewController()
present(vc, animated: true, completion: nil)
dismiss(animated: true, completion: nil)

Example : SwitchController

// create a navigation controller and its
// root controller
let vc = ViewController()
let nc = UINavigationController(rootViewController: vc)
window.rootViewController = nc
// create a new controller an push it on top
let vc2 = ViewController()
navigationController?.pushViewController(vc2, animated: true)
// pop the top controller (thus recovering
// the previous one)
navigationController?.
  popViewController(animated: true)

Example : Navigation Controller

There are many ways to switch from a view controller to another one (and change the displayed view)


Controller transitions can be defined in the Interface Builder (storyboards)

Responding to Events

Touch and Motion

There are 4 kinds of basic events to interact with the UI :

The class of objects that are prepared to handle such events is UIResponder

The Responder Chain

When an event is detected it is sent to a specific responder

  • for a touch, the view where it happened
  • for another event, the first responder

The event goes through the responder chain until a responder handles it

  • from a view to its super view
  • from a top view to its controller
  • from a controller to the super view of its view
  • from the main controller to the window, and the application

Hit Test

Returns where a touch occurred


Only the point where the touch started counts

UIEvent

var timestamp: NSTimeInterval
var type: UIEventType
var subtype: UIEventSubtype
func allTouches() -> Set<UITouch>?
func touches(for: UIView) -> Set<UITouch>?
func touches(for: UIWindow) -> Set<UITouch>?

UIEvent is the class of objects created when an event occurs

UIEventSubtype

enum UIEventSubtype : Int {
  case none
  case motionShake
  case remoteControlPlay
  case remoteControlPause
  case remoteControlStop
  case remoteControlTogglePlayPause
  case remoteControlNextTrack
  case remoteControlPreviousTrack
  case remoteControlBeginSeekingBackward
  case remoteControlEndSeekingBackward
  case remoteControlBeginSeekingForward
  case remoteControlEndSeekingForward
}

Shake

UIKit detects 3 events associated with a shake


The motion for a shake is UIEventSubtypeMotionShake

Touch

There are 4 methods to handle touch events


The first parameter of each method contains the set of UITouch objects that are relevant to the method (began, moved, ended or cancelled)

Example

Touch events in Bezier

Gesture Recognizers

Some specific gestures can be recognized automatically :


The class UIGestureRecognizer (and its subclasses) handle such events

Gesture Recognizers

let tapRecognizer = UITapGestureRecognizer()
tapRecognizer.numberOfTapsRequired = 2
tapRecognizer.addTarget(self,
  action: #selector(handleTap))
tapRecognizer.delegate = self
view.addGestureRecognizer(tapRecognizer)

Warning : the view must have enabled the user interaction


view.userInteractionEnabled = true

To recognize a gesture :

Example

Double tap recognizer in Bezier

Focus on Table Views

Objective

A table view is the standard way to display a list of elements :

Examples

Everything in a Table

Representation of data


Used by almost all apps


All of these elements are fully customizable views

MVC Structure

Table views are a prime example of MVC


The view controller implements functions to act as

Data Source

// methods in UITableViewDataSource
tableView(_:cellForRowAt:) (*)
numberOfSections(in:)
tableView(_:numberOfRowsInSection:) (*)
sectionIndexTitles(for:)
tableView(_:sectionForSectionIndexTitle:at:)
tableView(_:titleForHeaderInSection:)
tableView(_:titleForFooterInSection:)

(*) required methods

The data source must be able to answer questions such as :

Making a Cell

Cell Management

Problem :


Solution :

Tight memory management


Access to cells should always use this system

Reuse Cells

func tableView(_: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier(
    "normalCell", forIndexPath: indexPath)
  cell.textLabel?.text = "new label"
  return cell
}

Modification of a Table

tableView(_:commit:forRowAt:)
tableView(_:canEditRowAt:)
tableView(_:canMoveRowAt:)
tableView(_:moveRowAt:to:)

The user interacts with the table view


User interaction calls functions from the UITableViewDataSource protocol


Controller must make changes to data

Delegate

The table view delegate helps manage other features of the table view


Example : TableViews

Memory Management

ARC

Swift uses Automatic Reference Counting (ARC) to manage objects in memory


Most of the time, ARC works fine automatically

Reference Cycle

class Person {
  var apartment: Apartment?
  ...
}
class Apartment {
  var tenant: Person?
  ...
}

var john = Person()
var apt = Apartment()
john.apartment = apt
apt.tenant = john
john = nil
apt = nil
// neither object is deinitialized

Problem : sometimes two (or more) objects refer to each other and are not removed from memory

Reference Cycle

john = Person(...)
apt = Apartment(...)
 
 
 
 
john = Person(...)
apt = Apartment(...)
john.apartment = apt
apt.tenant = john
 
 
john = Person(...)
apt = Apartment(...)
john.apartment = apt
apt.tenant = john
john = nil
apt = nil

Strong, Weak and Unowned

class A {
  var x: Int          // strong
  weak var y: Int?    // weak
  unowned var z: Int  // unowned
  ...
}

To break reference cycles, we use weak or unowned references

Breaking the Cycle

john = Person(...)                  class Apartment {
apt = Apartment(...)                  weak var tenant: Person?
john.apartment = apt                  ...
apt.tenant = john                   }
 
 
john = Person(...)                  class Apartment {
apt = Apartment(...)                  weak var tenant: Person?
john.apartment = apt                  ...
apt.tenant = john                   }
apt = nil
 
john = Person(...)                  class Apartment {
apt = Apartment(...)                  weak var tenant: Person?
john.apartment = apt                  ...
apt.tenant = john                   }
apt = nil
john = nil

Illustration

Automatic Reference Counting in a Playground

Other Interesting
Things

Device Motion

The iDevices contain motion detectors that can measure


CoreMotion framework

Main class is CMMotionManager

Device Motion

To react to motion

CoreMotion is independent from UIKit


data can be

Cannot be tested on simulator

Geolocation

iOS devices can track geographic location


CoreLocation framework

use CLLocationManager class

Geolocation

Similar to motion events


See CLLocationManagerDelegate protocol for possible interaction with the manager

Using location services requires user authorization


Notification Center

Idea : broadcast information between elements across your app


Part of the Foundation framework

Use NotificationCenter class and related elements

Notification Center

Default notification center : NotificationCenter.default


Many notifications already defined by API

Notification has


Notification Center (Example)

// declare a new notification name
extension Notification.Name {
  static let specialEvent = Notification.Name("Special Event")
}

// post a new notification
NotificationCenter.default.post(
  name: .specialEvent,
  object: self,
  userInfo: nil)

// register to a given notification
NotificationCenter.default.addObserver(
  self,
  selector: #selector(specialAction(notification:)),
  name: .specialEvent,
  object: nil)

// setup a handler
func specialAction(notification: NSNotification) {
  println("A special event occurred!")
}

Grand Central Dispatch

Sometimes, some tasks should be executed asynchronously (in parallel)


Tasks are organized in queues


Part of the Foundation framework

The whole set of functions is denoted as Grand Central Dispatch (GCD)

Grand Central Dispatch

To dispatch an asynchronous task

Queues can be

  • serial : tasks are run one after the other
  • concurrent : tasks execute in parallel

Some queues are already defined

  • main queue (serial) : created when app starts. Only queue that should modify the UI
  • global (concurrent) queues :
    • userInteractive
    • userInitiated
    • default
    • utility
    • background
    • unspecified

Grand Central Dispatch (Example)

// create a queue
let queue = DispatchQueue(label: "uniqueID")
let queue = DispatchQueue(label: "uniqueID", qos: .userInitiated)
// use existing
let bgQueue = DispatchQueue.global(qos: .global)
let mainQueue = DispatchQueue.main

// dispatch to queue
queue.async {
  // some code
}

// background task with UI action
bgQueue.async {
  // do some complicated task in the background
  DispatchQueue.main.async {
    // do a simple task on the UI when the previous task
    // finishes
  }
}

Persisting Data

There are many options available to store persistent data on an iDevice

UserDefaults

To read or write values :

Associate values to keys (Strings)

Usually used for storing default values or user preferences


Can store


Very simple to use, but limited

NSUserDefaults (Example)

// get a reference to the UserDefaults
let defaults = UserDefaults.standard

// set the value for key "userNameKey"
defaults.set("Guybrush Threepwood", forKey: "userNameKey")

// get the value for key "userNameKey"
let name = defaults.string(forKey: "userNameKey")
println(name!)

CoreData vs. NSCoding

CoreData NSCoding
Entity Modeling Yes No
Querying Yes No
Speed Fast Slow
Serialization Format SQLite, XML, or NSData NSData
Migrations Automatic Manual
Undo Manager Automatic Manual
CoreData NSCoding
Persists State Yes Yes
Pain in the Ass Yes No

NSCoding

The NSCoding protocol requires two methods


Classes NSKeyedArchiver and NSKeyedUnarchiver perform the conversions


works if all archived properties also conform to NSCoding

NSCoding

// define a class and describe how to archive/unarchive it
class Person: NSObject, NSCoding {
  let name: String
  let age: Int
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
  required init?(coder decoder: NSCoder) {
    self.name = decoder.decodeObject(forKey: "name") as! String
    self.age = decoder.decodeInteger(forKey: "age")
  }

  func encode(with coder: NSCoder) {
    coder.encode(name, forKey: "name")
    coder.encode(age, forKey: "age")
  }
}
var people = [Person]()
people.append(Person(name: "Sherlock Holmes", age: 25))
people.append(Person(name: "Mycroft Holmes", age: 32))

// encode objects and save data
let encodedData = NSKeyedArchiver.archivedData(withRootObject: people)
UserDefaults.standard.set(encodedData, forKey: "people")

// retrieve data and decode into objects
let data = UserDefaults.standard.data(forKey: "people")!
let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: data) as! [Person]

NSCoding Example

Saving the cards list in the TableViews app.

2D Graphics

There are two main options for managing and animating 2D objects


You could also redo everything by hand on UIView by redefining drawRect...

3D Graphics

Possible options for 3D graphics and animations :

Bibliography