Your First Swift 2 Project

After the release of Xcode 6 in 2014, it has been possible to build Swift applications for iOS and OS X and submit them to the App Store for publication. This article will present both a single view application and a master-detail application, and use these to explain the concepts behind iOS applications, as well as introduce classes in Swift.

In this article, we will present the following topics:

  • How iOS applications are structured
  • Single-view iOS applications
  • Creating classes in Swift
  • Protocols and enums in Swift
  • Using XCTest to test Swift code
  • Master-detail iOS applications
  • The AppDelegate and ViewController classes

(For more resources related to this topic, see here.)

Understanding iOS applications

An iOS application is a compiled executable along with a set of supporting files in a bundle. The application bundle is packaged into an archive file to be installed onto a device or upload to the App Store.

Xcode can be used to run iOS applications in a simulator, as well as testing them on a local device. Submitting an application to the App Store requires a developer signing key, which is included as part of the Apple Developer Program at https://developer.apple.com.

Most iOS applications to date have been written in Objective-C, a crossover between C and Smalltalk. With the advent of Swift, it is likely that many developers will move at least parts of their applications to Swift for performance and maintenance reasons.

Although Objective-C is likely to be around for a while, it is clear that Swift is the future of iOS development and probably OS X as well. Applications contain a number of different types of files, which are used both at compile time and also at runtime. These files include the following:

  • The Info.plist file, which contains information about which languages the application is localized for, what the identity of the application is, and the configuration requirements, such as the supported interface types (iPad, iPhone, and Universal), and orientations (Portrait, Upside Down, Landscape Left, and Landscape Right)
  • Zero or more interface builder files with a .xib extension, which contain user interface screens (which supersedes the previous .nib files)
  • Zero or more image asset files with a .xcassets extension, which store groups of related icons at different sizes, such as the application icon or graphics for display on screen (which supersedes the previous .icns files)
  • Zero or more storyboard files with a .storyboard extension, which are used to coordinate between different screens in an application
  • One or more .swift files that contain application code

Creating a single-view iOS application

A single-view iOS application is one where the application is presented in a single screen, without any transitions or other views. This section will show how to create an application that uses a single view without storyboards.

When Xcode starts, it displays a welcome message that includes the ability to create a new project. This welcome message can be redisplayed at any time by navigating to Window | Welcome to Xcode or by pressing Command + Shift + 1.

Using the welcome dialog's Create a new Xcode project option, or navigating to File | New | Project..., or by pressing Command + Shift + N, create a new iOS project with Single View Application as the template, as shown in the following screenshot:

When the Next button is pressed, the new project dialog will ask for more details. The product name here is SingleView with appropriate values for Organization Name and Identifier. Ensure that the language selected is Swift and the device type is Universal:

The Organization Identifier is a reverse domain name representation of the organization, and the Bundle Identifier is the concatenation of the Organization Identifier with the Product Name. Publishing to the App Store requires that the Organization Identifier be owned by the publisher and is managed in the online developer center at https://developer.apple.com/membercenter/.

When Next is pressed, Xcode will ask where to save the project and whether a repository should be created. The selected location will be used to create the product directory, and an option to create a Git repository will be offered.

In 2014, Git became the most widely used version control system, surpassing all other distributed and centralized version-control systems. It would be foolish not to create a Git repository when creating a new Xcode project.

When Create is pressed, Xcode will create the project, set up template files, and then initialize the Git repository locally or on a shared server.

Press the triangular play button at the top-left of Xcode to launch the simulator:

If everything has been set up correctly, the simulator will start with a white screen and the time and battery shown at the top of the screen:

Removing the storyboard

The default template for a single-view application includes a storyboard. This creates the view for the first (only) screen and performs some additional setup behind the scenes. To understand what happens, the storyboard will be removed and replaced with code instead.

Most applications are built with one or more storyboards.

The storyboard can be deleted by going to the project navigator, finding the Main.storyboard file, and pressing the Delete key or selecting Delete from the context-sensitive menu. When the confirmation dialog is shown, select the Move to Trash option to ensure that the file is deleted rather than just being removed from the list of files that Xcode knows about.

To see the project navigator, press Command + 1 or navigate to View | Navigators | Show Project Navigator.

Once the Main.storyboard file has been deleted, it needs to be removed from Info.plist, to prevent iOS from trying to open it at startup. Open the Info.plist file under the Supporting Files folder of SingleView. A set of key-value pairs will be displayed; clicking on the Main storyboard file base name row will present the (+) and (-) options. Clicking on the delete icon (-) will remove the line:

Now, when the application is started, a black screen will be displayed.

There are multiple Info.plist files that are created by Xcode's template; one file is used for the real application, while the other files are used for the test applications that get built when running tests.

Setting up the view controller

The view controller is responsible for setting up the view when it is activated. Typically, this is done through either the storyboard or the interface file. As these have been removed, the window and the view controller need to be instantiated manually.

When iOS applications start, application:didFinishLaunchingWithOptions: is called on the corresponding UIApplicationDelegate. The optional window variable is initialized automatically when it is loaded from an interface file or a storyboard, but it needs to be explicitly initialized if the user interface is being implemented in code.

Implement the application:didFinishLaunchingWithOptions: method in the AppDelegate class as follows:

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  func application(application: UIApplication,

   didFinishLaunchingWithOptions launchOptions:

   [NSObject:AnyObject]?) -> Bool {

    window = UIWindow()

    window?.rootViewController = ViewController()

    window?.makeKeyAndVisible()

    return true

  }

}

To open a class by name, press Command + Shift + O and type in the class name. Alternatively, navigate to File | Open Quickly...

The final step is to create the view's content, which is typically done in the viewDidLoad method of the ViewController class. As an example user interface, a UILabel will be created and added to the view. Each view controller has an associated view property, and child views can be added with the addSubview method. To make the view stand out, the background of the view will be changed to black and the text color will be changed to white:

class ViewController: UIViewController {

  override func viewDidLoad() {

    super.viewDidLoad()

    view.backgroundColor = UIColor.blackColor()

    let label = UILabel(frame:view.bounds)

    label.textColor = UIColor.whiteColor()

    label.textAlignment = .Center

    label.text = "Welcome to Swift"

 

    view.addSubview(label)

  }

}

This creates a label, which is sized to the full size of the screen, with a white text color and a centered text alignment. When run, this displays Welcome to Swift on the screen.

Typically, views will be implemented in their own class rather than being in-lined into the view controller. This allows the views to be reused in other controllers.

When the screen is rotated, the label will be rotated off screen. Logic would need to be added in a real application to handle rotation changes in the view controller, such as willRotateToInterfaceOrientation, and to appropriately add rotations to the views using the transform property of the view. Usually, an interface builder file or storyboard would be used so that this is handled automatically.

Swift classes, protocols, and enums

Almost all Swift applications will be object oriented. Classes, such as Process from the CoreFoundation framework, and UIColor and UIImage from the UIKit framework, were used to demonstrate how classes can be used in applications. This section describes how to create classes, protocols, and enums in Swift.

Classes in Swift

A class is created in Swift using the class keyword, and braces are used to enclose the class body. The body can contain variables called properties, as well as functions called methods, which are collectively referred to as members. Instance members are unique to each instance, while static members are shared between all instances of that class.

Classes are typically defined in a file named for the class; so a GitHubRepository class would typically be defined in a GitHubRepository.swift file. A new Swift file can be created by navigating to File | New | File… and selecting the Swift File option under iOS. Ensure that it is added to the Tests and UITests targets as well. Once created, implement the class as follows:

class GitHubRepository {

  var id:UInt64 = 0

  var name:String = ""

  func detailsURL() -> String {

    return "https://api.github.com/repositories/\(id)"

  }

}

This class can be instantiated and used as follows:

let repo = GitHubRepository()

repo.id = 1

repo.name = "Grit"

repo.detailsURL() // returns https://api.github.com/repositories/1

It is possible to create static members, which are the same for all instances of a class. In the GitHubRepository class, the api URL is likely to remain the same for all invocations, so it can be refactored into a static property:

class GitHubRepository {

  // does not work in Swift 1.0 or 1.1

  static let api = "https://api.github.com"

  …

  class func detailsURL(id:String) -> String {

    return "\(api)/repositories/\(id)"

  }

}

Now, if the api URL needs to be changed (for example, to support mock testing or to support an in-house GitHub Enterprise server), there is a single place to change it. Before Swift 2, a class variables are not yet supported error message may be displayed.

To use static variables in Swift prior to version 2, a different approach must be used. It is possible to define computed properties, which are not stored but are calculated on demand. These have a getter (also known as an accessor) and optionally a setter (also known as a mutator). The previous example can be rewritten as follows:

class GitHubRepository {

  class var api:String {

    get {

      return "https://api.github.com"

    }

  }

  func detailsURL() -> String {

    return "\(GitHubRepository.api)/repositories/\(id)"

  }

}

Although this is logically a read-only constant (there is no associated set block), it is not possible to define the let constants with accessors.

To refer to a class variable, use the type name—which in this case is GitHubRepository. When the GitHubRepository.api expression is evaluated, the body of the getter is called.

Subclasses and testing in Swift

A simple Swift class with no explicit parent is known as a base class. However, classes in Swift frequently inherit from another class by specifying a superclass after the class name. The syntax for this is class SubClass:SuperClass{...}.

Tests in Swift are written using the XCTest framework, which is included by default in Xcode templates. This allows an application to have tests written and then executed in place to confirm that no bugs have been introduced.

XCTest replaces the previous testing framework OCUnit.

The XCTest framework has a base class called XCTestCase that all tests inherit from. Methods beginning with test (and that take no arguments) in the test case class are invoked automatically when the tests are run. Test code can indicate success or failure by calling the XCTAssert* functions, such as XCTAssertEquals and XCTAssertGreaterThan.

Tests for the GitHubRepository class conventionally exist in a corresponding GitHubRepositoryTest class, which will be a subclass of XCTestCase. Create a new Swift file by navigating to File | New | File... and choosing a Swift File under the Source category for iOS. Ensure that the Tests and UITests targets are selected but the application target is not. It can be implemented as follows:

import XCTest

class GitHubRepositoryTest: XCTestCase {

  func testRepository() {

    let repo = GitHubRepository()

    repo.id = 1

    repo.name = "Grit"

    XCTAssertEqual(

      repo.detailsURL(),

      "https://api.github.com/repositories/1",

      "Repository details"

    )

  }

}

Make sure that the GitHubRepositoryTest class is added to the test targets. If not added when the file is created, it can be done by selecting the file and pressing Command + Option + 1 to show the File Inspector. The checkbox next to the test target should be selected. Tests should never be added to the main target. The GitHubRepository class should be added to both test targets:

When the tests are run by pressing Command + U or by navigating to Product | Test, the results of the test will be displayed. Changing either the implementation or the expected test result will demonstrate whether the test is being executed correctly.

Always check whether a failing test causes the build to fail; this will confirm that the test is actually being run. For example, in the GitHubRepositoryTest class, modify the URL to remove https from the front and check whether a test failure is shown. There is nothing more useless than a correctly implemented test that never runs.

Protocols in Swift

A protocol is similar to an interface in other languages; it is a named type that has method signatures but no method implementations. Classes can implement zero or more protocols; when they do, they are said to adopt or conform to the protocol. A protocol may have a number of methods that are either required (the default) or optional (marked with the optional keyword).

Optional protocol methods are only supported when the protocol is marked with the @objc attribute. This declares that the class will be backed by an NSObject class for interoperability with Objective-C. Pure Swift protocols cannot have optional methods.

The syntax to define a protocol looks similar to the following:

protocol GitHubDetails {

  func detailsURL() -> String

  // protocol needs @objc if using optional protocols

  // optional doNotNeedToImplement()

}

Protocols cannot have functions with default arguments. Protocols can be used with the struct, class, and enum types unless the @objc class attribute is used; in which case, they can only be used against Objective-C classes or enums.

Classes conform to protocols by listing the protocol names after the class name, similar to a superclass.

When a class has both a superclass and one or more protocols, the superclass must be listed first.

class GitHubRepository: GitHubDetails {

  func detailsURL() -> String {

    // implementation as before

  }

}

The GitHubDetails protocol can be used as a type in the same places as an existing Swift type, such as a variable type, method return type, or argument type.

Protocols are widely used in Swift to allow callbacks from frameworks that would, otherwise, not know about specific callback handlers. If a superclass was required instead, then a single class cannot be used to implement multiple callbacks. Common protocols include UIApplicationDelegate, Printable, and Comparable.

Enums in Swift

The final concept to understand in Swift is enumeration, or enum for short. An enum is a closed set of values, such as North, East, South, and West, or Up, and Down.

An enumeration is defined using the enum keyword, followed by a type name, and a block, which contains the case keywords followed by comma-separated values as follows:

enum Suit {

  case Clubs, Diamonds, Hearts // many on one line

  case Spades // or each on separate lines

}

Unlike C, enumerated values do not have a specific type by default, so they cannot generally be converted to and from an integer value. Enumerations can be defined with raw values that allow conversion to and from integer values. Enum values are assigned to variables using the type name and the enum name:

var suit:Suit = Suit.Clubs

However, if the type of the expression is known, then the type prefix does not need to be explicitly specified; the following form is much more common in Swift code:

var suit:Suit = .Clubs

Raw values

For the enum values that have specific meanings, it is possible to extend the enum from a different type, such as Int. These are known as raw values:

enum Rank: Int {

  case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten

  case Jack, Queen, King, Ace

}

A raw value enum can be converted to and from its raw value with the rawValue property and the failable initializer Rank(rawValue:) as follows:

Rank.Two.rawValue == 2

Rank(rawValue:14)! == .Ace

The failable initializer returns an optional enum value, because the equivalent Rank may not exist. The expression Rank(rawValue:0) will return nil, for example.

Associated values

Enums can also have associated values, such as a value or case class in other languages. For example, a combination of a Suit and a Rank can be combined to form a Card:

enum Card {

  case Face(Rank, Suit)

  case Joker

}

Instances can be created by passing values into an enum initializer:

var aceOfSpades: Card = .Face(.Ace,.Spades)

var twoOfHearts: Card = .Face(.Two,.Hearts)

var theJoker: Card = .Joker

The associated values of an enum instance cannot be extracted (as they can with properties of a struct), but the enum value can be accessed by pattern matching in a switch statement:

var card = aceOfSpades // or theJoker or twoOfHearts ...

switch card {

  case .Face(let rank, let suit):

    print("Got a face card \(rank) of \(suit)");

  case .Joker:

    print("Got the joker card")

}

The Swift compiler will require that the switch statement be exhaustive. As the enum only contains these two types, no default block is needed. If another enum value is added to Card in the future, the compiler will report an error in this switch statement.

Creating a master-detail iOS application

Having covered how classes, protocols, and enums are defined in Swift, a more complex master-detail application can be created. A master-detail application is a specific type of iOS application that initially presents a master table view, and when an individual element is selected, a secondary details view will show more information about the selected item.

Using the Create a new Xcode project option from the welcome screen, or by navigating to File | New | Project… or by pressing Command + Shift + N, create a new project and select Master-Detail Application from the iOS Application category:

In the subsequent dialog, enter appropriate values for the project, such as the name (MasterDetail), the organization identifier (typically based on the reverse DNS name), ensure that the Language dropdown reads Swift and that it is targeted for Universal devices:

When the project is created, an Xcode window will open containing all the files that are created by the wizard itself, including the MasterDetail.app and MasterDetailTests.xctest products. The MasterDetail.app is a bundle that is executed by the simulator or a connected device, while the MasterDetailTests.xctest and MasterDetailsUITests.xctest products are used to execute unit tests for the application's code.

The application can be launched by pressing the triangular play button on the top-left corner of Xcode or by pressing Command + R, which will run the application against the currently selected target.

After a brief compile and build cycle, the iOS Simulator will open with a master page that contains an empty table, as shown in the following screenshot:

The default MasterDetail application can be used to add items to the list by clicking on the add (+) button on the top-right corner of the screen. This will add a new timestamped entry to the list.

When this item is clicked, the screen will switch to the details view, which, in this case, presents the time in the center of the screen:

This kind of master-detail application is common in iOS applications for displaying a top-level list (such as a shopping list, a set of contacts, to-do notes, and so on) while allowing the user to tap to see the details.

There are three main classes in the master-detail application:

  • The AppDelegate class is defined in the AppDelegate.swift file, and it is responsible for starting the application and set up the initial state
  • The MasterViewController class is defined in the MasterViewController.swift file, and it is used to manage the first (master) screen's content and interactions
  • The DetailViewController class is defined in the DetailViewController.swift file, and it is used to manage the second (detail) screen's content

In order to understand what the classes do in more detail, the next three sections will present each of them in turn.

The code that is generated in this section was created from Xcode 7.0, so the templates might differ slightly if using a different version of Xcode. An exact copy of the corresponding code can be acquired from the Packt website or from this book's GitHub repository at https://github.com/alblue/com.packtpub.swift.essentials/.

The AppDelegate class

The AppDelegate class is the main entry point to the application. When a set of Swift source files are compiled, if the main.swift file exists, it is used as the entry point for the application by running that code. However, to simplify setting up an application for iOS, a @UIApplicationMain special attribute exists that will both synthesize the main method and set up the associated class as the application delegate.

The AppDelegate class for iOS extends the UIResponder class, which is the parent of all the UI content on iOS. It also adopts two protocols, UIApplicationDelegate, and UISplitViewControllerDelegate, which are used to provide callbacks when certain events occur:

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate,

   UISplitViewControllerDelegate {

  var window: UIWindow?

  ...

}

On OS X, the AppDelegate class will be a subclass of NSApplication and will adopt the NSApplicationDelegate protocol.

The synthesized main function calls the UIApplicationMain method that reads the Info.plist file. If the UILaunchStoryboardName key exists and points to a suitable file (the LaunchScreen.xib interface file in this case), it will be shown as a splash screen before doing any further work. After the rest of the application has loaded, if the UIMainStoryboardFile key exists and points to a suitable file (the Main.storyboard file in this case), the storyboard is launched and the initial view controller is shown.

The storyboard has references to the MasterViewController and DetailViewController classes. The window variable is assigned to the storyboard's window.

The application:didFinishLaunchingWithOptions is called once the application has started. It is passed with a reference to the UIApplication instance and a dictionary of options that notifies how the application has been started:

func application(

 application: UIApplication,

 didFinishLaunchingWithOptions launchOptions:

  [NSObject: AnyObject]?) -> Bool {

  // Override point for customization after application launch.

  ...

}

In the sample MasterDetail application, the application:didFinishLaunchingWithOptions method acquires a reference to the splitViewController from the explicitly unwrapped optional window, and the AppDelegate is set as its delegate:

let splitViewController =

 self.window!.rootViewController as! UISplitViewController

splitViewController.delegate = self

The … as! UISplitViewController syntax performs a type cast so that the generic rootViewController can be assigned to the more specific type; in this case, UISplitViewController. An alternative version as? provides a runtime checked cast, and it returns an optional value that either contains the value with the correctly casted type or nil otherwise. The difference with as! is a runtime error will occur if the item is not of the correct type.

Finally, a navigationController is acquired from the splitViewController, which stores an array of viewControllers. This allows the DetailView to display a button on the left-hand side to expand the details view if necessary:

let navigationController = splitViewController.viewController

 [splitViewController.viewControllers.count-1]

 as! UINavigationController

navigationController.topViewController

 .navigationItem.leftBarButtonItem =

 splitViewController.displayModeButtonItem()

The only difference this makes is when running on a wide-screen device, such as an iPhone 6 Plus or an iPad, where the views are displayed side-by-side in landscape mode. This is a new feature in iOS 8 applications.

Otherwise, when the device is in portrait mode, it will be rendered as a standard back button:

The method concludes with return true to let the OS know that the application has opened successfully.

The MasterViewController class

The MasterViewController class is responsible for coordinating the data that is shown on the first screen (when the device is in portrait orientation) or the left-half of the screen (when a large device is in landscape orientation). This is rendered with a UITableView, and data is coordinated through the parent UITableViewController class:

class MasterViewController: UITableViewController {

  var detailViewcontroller: DetailViewController? = nil

  var objects = [AnyObject]()

  override func viewDidLoad() {…}

  func insertNewObject(sender: AnyObject) {…}

  …

}

The viewDidLoad method is used to set up or initialize the view after it has loaded. In this case, a UIBarButtonItem is created so that the user can add new entries to the table. The UIBarButtonItem takes a @selector in Objective-C, and in Swift is treated as a string literal convertible (so that "insertNewObject:" will result in a call to the insertNewObject method). Once created, the button is added to the navigation on the right-hand side, using the standard .Add type which will be rendered as a + sign on the screen:

override func viewDidLoad() {

  super.viewDidLoad()

  self.navigationItem.leftBarButtonItem = self.editButtonItem()

  let addButton = UIBarButtonItem(

    barButtonSystemItem: .Add, target: self,

    action: "insertNewObject:")

  self.navigationItem.rightBarButtonItem = addButton

  if let split = self.splitViewController {

    let controllers = split.viewControllers

    self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController

}

The objects are NSDate values, and are stored inside the class as an Array of AnyObject elements. The insertNewObject method is called when the + button is pressed, and it creates a new NSDate instance which is then inserted into the array. The sender event is passed as an argument of the AnyObject type, which will be a reference to the UIBarButtonItem (although it is not needed or used here):

func insertNewObject(sender: AnyObject) {

  objects.insertObject(NSDate.date(), atIndex: 0)

  let indexPath = NSIndexPath(forRow: 0, inSection: 0)

  self.tableView.insertRowsAtIndexPaths(

   [indexPath], withRowAnimation: .Automatic)

}

The UIBarButtonItem class was created before blocks were available on iOS devices, so it uses the older Objective-C @selector mechanism. A future release of iOS may provide an alternative that takes a block, in which case Swift functions can be passed instead.

The parent class contains a reference to the tableView, which is automatically created by the storyboard. When an item is inserted, the tableView is notified that a new object is available. Standard UITableViewController methods are used to access the data from the array:

override func numberOfSectionsInTableView(

 tableView: UITableView) -> Int {

  return 1

}

override func tableView(tableView: UITableView,

 numberOfRowsInSection section: Int) -> Int {

  return objects.count

}

override func tableView(tableView: UITableView,

 cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{

  let cell = tableView.dequeueReusableCellWithIdentifier(

   "Cell", forIndexPath: indexPath)

  let object = objects[indexPath.row] as! NSDate

  cell.textLabel!.text = object.description

  return cell

}

override func tableView(tableView: UITableView,

 canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {

  return true

}

The numberOfSectionsInTableView function returns 1 in this case, but a tableView can have multiple sections; for example, to permit a contacts application having a different section for A, B, C through Z. The numberOfRowsInSection method returns the number of elements in each section; in this case, as there is only one section, the number of objects in the array.

The reason why each method is called tableView and takes a tableView argument is a result of the Objective-C heritage of UIKit. The Objective-C convention combined the method name as the first named argument, so the original method was [delegate tableView:UITableView, numberOfRowsInSection:NSInteger]. As a result, the name of the first argument is reused as the name of the method in Swift.

The cellForRowAtIndexPath method is expected to return UITableViewCell for an object. In this case, a cell is acquired from the tableView using the dequeueReusableCellWithIdentifier method (which caches cells as they go off screen to save object instantiation), and then the textLabel is populated with the object's description (which is a String representation of the object; in this case, the date).

This is enough to display elements in the table, but in order to permit editing (or just removal, as in the sample application), there are some additional protocol methods that are required:

override func tableView(tableView: UITableView,

 canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {

  return true

}

override func tableView(tableView: UITableView,

 commitEditingStyle editingStyle: UITableViewCellEditingStyle,

 forRowAtIndexPath indexPath: NSIndexPath) {

  if editingStyle == .Delete {

    objects.removeObjectAtIndex(indexPath.row)

    tableView.deleteRowsAtIndexPaths([indexPath],

     withRowAnimation: .Fade)

  }

}

The canEditRowAtIndexPath method returns true if the row is editable; if all the rows can be edited, then this will return true for all the values.

The commitEditingStyle method takes a table, a path, and a style, which is an enumeration that indicates which operation occurred. In this case, UITableViewCellEditingStyle.Delete is passed in order to delete the item from both the underlying object array and also from the tableView. (The enumeration can be abbreviated to .Delete because the type of editingStyle is known to be UITableViewCellEditingStyle.)

The DetailViewController class

The detail view is shown when an element is selected in the MasterViewController. The transition is managed by the storyboard controller; the views are connected with a segue (pronounced seg-way; the product of the same name based it on the word segue which is derived from the Italian word for follows).

To pass the selected item between controllers, a property exists in the DetailViewController class called detailItem. When the value is changed, additional code is run, which is implemented in a didSet property notification:

class DetailViewController: UIViewController {

  var detailItem: AnyObject? {

    didSet {

      self.configureView()

    }

  }

  …

}

When DetailViewController has the detailItem set, the configureView method will be invoked. The didSet body is run after the value has been changed, but before the setter returns to the caller. This is triggered by the segue in the MasterViewController:

class MasterViewController: UIViewController {

  …

  override func prepareForSegue(

   segue: UIStoryboardSegue, sender: AnyObject?) {

    super.prepareForSegue(segue, sender: sender)

    if segue.identifier == "showDetail" {

      if let indexPath =

       self.tableView.indexPathForSelectedRow() {

        let object = objects[indexPath.row] as! NSDate

        let controller = (segue.destinationViewController

         as! UINavigationController)

         .topViewController as! DetailViewController

        controller.detailItem = object

        controller.navigationItem.leftBarButtonItem =

         self.splitViewController?.displayModeButtonItem()

        controller.navigationItem.leftItemsSupplementBackButton =

         true

      }

    }

  }

}

The prepareForSegue method is called when the user selects an item in the table. In this case, it grabs the selected row index from the table and uses this to acquire the selected date object. The navigation controller hierarchy is searched to acquire the DetailViewController, and once this has been obtained, the selected value is set with controller.detailItem = object, which triggers the update.

The label is ultimately displayed in the DetailViewController through the configureView method, which stamps the description of the object onto the label in the center:

class DetailViewController {

  ...

  @IBOutlet weak var detailDescriptionLabel: UILabel!

  function configureView() {

    if let detail: AnyObject = self.detailItem {

      if let label = self.detailDescriptionLabel {

        label.text = detail.description

      }

    }

  }

}

The configureView method is called both when the detailItem is changed and when the view is loaded for the first time. If the detailItem has not been set, then this has no effect.

The implementation introduces some new concepts, which are worth highlighting:

  • The @IBOutlet attribute indicates that the property will be exposed in interface builder and can be wired up to the object instance.
  • The weak attribute indicates that the property will not store a strong reference to the object; in other words, the detail view will not own the object but merely reference it. Generally, all @IBOutlet references should be declared as weak to avoid cyclic dependency references.
  • The type is defined as UILabel! which is an implicitly unwrapped optional. When accessed, it performs an explicit unwrapping of the optional value; otherwise the @IBOutlet will be wired up as a UILabel? optional type. Implicitly unwrapped optional types are used when the variable is known to never be nil at runtime, which is usually the case for the @IBOutlet references. Generally, all @IBOutlet references should be implicitly unwrapped optionals.

Summary

In this article we saw two sample iOS applications; one in which the UI was created programmatically, and another in which the UI was loaded from a storyboard. Together with an overview of classes, protocols, and enums, and an explanation of how iOS applications start, this article gives a springboard to understand the Xcode templates that are frequently used to start new projects.

To learn more about Swift 2, you can refer the following books published by Packt Publishing (https://www.packtpub.com/):

Resources for Article:

 


Further resources on this subject:


You've been reading an excerpt of:

Swift Essentials - Second Edition

Explore Title