Creating new views with interface builder

The easiest way of creating a custom view is to use interface builder to drag and drop the contents. This is typically done with a UITableView and a prototype table cell.

Creating a table view controller

Drag in the table view controller from the object library onto the main storyboard, and drag and drop from the tab bar controller to the newly created table view controller to create a relation segue called view controllers. (Segues are covered in more detail in the Storyboards, Segues, and Scenes section in Chapter 4, Storyboard Applications with Swift and iOS)

By default, the table view controller will have dynamic property content—that is, it will be able to display a variable number of rows. This is defined in the Table View section of the Attributes Inspector.

Creating a table view controller

Note

There is an option for tables to have static content; a fixed number of rows in the table. This is sometimes useful when creating scrollable content that can be partitioned into slices, even if it doesn't look like a table. Most of the elements in the iOS settings are represented as a fixed-size table view.

At the top of the table view are one or more prototype cells. These are used to define the look and feel of the table items. By default, a UITableViewCell is used, which has a label and an image, but a prototype cell can be used to add more data to the entries.

Creating a table view controller

The prototype cell can be used to provide additional information or views. For example, two labels can be dragged into the view; one label can be centered at the top and can be displayed in the headline font, while the second can be left aligned.

Drag two labels from the object library into the prototype cell. Arrange them using Auto Layout appropriately.

To change a label's font, select the label in the editor and go to the Attributes Inspector. In the Label section, click on the font chooser icon and select Headline or Subhead, as appropriate.

Creating a table view controller

When finished, the prototype cell will look like:

Creating a table view controller

When the application is run, an empty table will be seen. This is because the table doesn't have any items displayed at the moment. The next section shows how to add data to a table so that it binds and displays items to the prototype cell.

Showing data in the table

A UITableView acquires data from a UITableViewDataSource. The UITableViewController class already implements the UITableViewDataSource protocol, so only a small number of methods are required to provide data for the table.

Tip

Because UITableView was originally implemented in Objective-C, the methods defined in the protocol take a tableView. As a result, all of the UITableViewDataSource delegate methods in Swift end up being called tableView with different arguments.

Create a new SampleTable class that extends UITableViewController. Implement the class as follows:

import UIKit
class SampleTable: UITableViewController {
  var items = [
    ("First", "A first item"),
    ("Second", "A second item"),
  ]
  required init(coder:NSCoder) {
    super.init(coder:coder)
  }
  override func tableView(tableView: UITableView,
    numberOfRowsInSection section:Int) -> Int {
    return items.count
  }
  override func tableView(tableView: UITableView,
    cellForRowAtIndexPath indexPath: NSIndexPath)
     -> UITableViewCell {
    let cell = tableView.
     dequeueReusableCellWithIdentifier("prototypeCell")
     as UITableViewCell
    // configure labels
    return cell
  }
}

Once the data source methods are implemented, the labels need to be configured to display the data from the array. There are three things that need to be done: the prototype cell must be acquired from the xib file; the labels need to be extracted; and finally the table view controller needs to be associated with the custom SampleTable class.

Firstly, the cellForRowAtIndex function needs an identifier for reusable cells. The identifier is set on the prototype cell in the main storyboard. To set it, select the prototype cell and go to the Attributes Inspector. Enter prototypeCell in the Identifier of the Table View Cell section:

Showing data in the table

The identifier is used in the dequeueReusableCellWithIdentifier method of the tableView. When a xib is used to load the cell, the return value will either reuse a cell that has gone off-screen earlier or a new cell will be instantiated from xib.

Each label can be given a non-zero integer tag so that the label can be extracted from the prototype cell using the viewWithTag method:

let titleLabel = cell.viewWithTag(1) as UILabel
let subtitleLabel = cell.viewWithTag(2) as UILabel

To assign tags to the views, select the heading label, navigate to the Attributes Inspector, and change the Tag to 1. Do the same thing for the subheading label with the tag 2.

Showing data in the table

Now the text values for the row can be set:

let (title,subtitle) = items[indexPath.row]
titleLabel.text = title
subtitleLabel.text = subtitle

Finally, the SampleTable needs to be associated with the table view controller. Click on the table, go to the Identity Inspector, and enter SampleTable in the Custom Class section.

When the application is run, the following view will be shown:

Showing data in the table

Tip

To hide the status bar, add or change Status bar is initially hidden to YES and View controller-based status bar appearance to NO in the Info.plist file.

Defining a view in a XIB file

It is possible to create a view using interface builder, save it as a xib file, and then instantiate it on demand. This is what happens under the covers with the UITableView—there is a method registerNib:forCellReuseIdentifier: which takes an xib file and an identifier (which corresponds to prototypeCell in the previous example).

Create a new interface file CounterView.xib to represent the view, by navigating to File | New | File | iOS | User Interface | View. When opened, it will show as an empty view with no content and in a 600 x 600 square. To change the size to something that is a little more reasonable, go to the Attributes Inspector and change the size from Inferred to Freeform. At the same time, change the Status Bar, Top Bar, and Bottom Bar to None. Then, change to the Size Inspector and modify the view's Frame Rectangle to 300 x 50.

Defining a view in a XIB file

This should resize the view so that it is displayed as 300 x 50 instead of the previous 600 x 600, and the status bar and other bars should not be seen. Now, add a Stepper from the object library by dragging it to the left-hand side of the view and dragging a Label to the right. Adjust the size and add the missing constraints so that the view looks similar to the following screenshot:

Defining a view in a XIB file

Wiring a custom view class

Create a new CounterView class that extends UIView, define an @IBOutlet for the label and an @IBAction change method that takes a sender. Wire the stepper's valueChanged event to the change method and connect the label outlet. Implement the change function such that the label text is changed when the stepper is picked:

import UIKit
class CounterView: UIView {
  @IBOutlet weak var label:UILabel!
  @IBAction func change(sender:AnyObject) {
    let count = (sender as UIStepper).value
    label.text = "Count is (count)"
  }
}

The CounterView will be added to the table header of the SampleTable. Each UITableViewController has a reference to its associated UITableView, and each UITableView has an optional headerView (and footerView) that is used for the table as a whole.

Note

The UITableView also has a sectionHeader and a sectionFooter, which are used to separate different sections of the table. A table can have multiple sections—for example, one section per month—and a separate header and footer can be used per section.

To create a CounterView, the xib file must be loaded. This is done by instantiating a UINib with a nibName and a bundle. The most appropriate place to do this is in the viewDidLoad method of the SampleTable class:

class SampleTable: UITableViewController {
  override func viewDidLoad() {
    let xib = UINib(nibName:"CounterView", bundle:nil)
    // continued

Once the xib is loaded, the view must be created. The instantiateWithOwner method allows the object(s) in the xib to be deserialized.

Note

It is possible to store multiple objects in a xib file (for example, to define a separate view that is suitable for a small display device versus a big display device) but in general a xib file only contains one view.

The owner is passed to the view so that any connections can be wired up to the File's Owner in the interface. This is typically either self or nil if there are no connections.

    // continued from before
    let objects = xib.instantiateWithOwner(self, options:nil)
    // continued

This returns an array of AnyObject instances, so casting the first element to a UIView is a common step.

Tip

It is possible to use objects[0], but this will cause a failure if the array is empty. Instead, use objects.first to get an optional value containing the first element.

Using the as? cast, it is possible to convert the optional value to a more specific type, and from that perform the assignment to the tableHeaderView:

    // continued from before
    let counter = objects.first as? UIView
    tableView.tableHeaderView = counter
  }

When the application is run in the simulator, the header is seen at the top of the table:

Wiring a custom view class

One of the advantages of having a xib for representing the user interface is that it can be reused in many places with a single definition. For example, it is possible to use the same xib to instantiate another view for the footer of the table:

tableView.tableFooterView = 
  xib.instantiateWithOwner(self,options:nil).first as? UIView

When the application is run now, counters are created at the top and bottom of the table:

Wiring a custom view class

Dealing with intrinsic size

When a view is added into a view that is being managed with Auto Layout, its intrinsic content size is used. Unfortunately, views defined in interface builder have no way of setting their intrinsic size programmatically or of specifying it in interface builder. The Size Inspector allows this value to be changed, but as Xcode notes, this has no effect at runtime:

Dealing with intrinsic size

If a custom class is associated with the view, then an appropriate intrinsic size can be defined. Add a method to CounterView that overrides the intrinsicContentSize method and returns a CGSize, and to allow for some xib customization, return the maximum of the label's intrinsic size and a value such as (300,50):

override func intrinsicContentSize() -> CGSize {
  let height = max(50,label.intrinsicContentSize().height)
  let width = max(300,label.intrinsicContentSize().width)
  return CGSize(width: width, height: height)
}

Now when the CounterView is added into a view that is managed by Auto Layout, it will have an appropriate initial size, although it can grow larger.

Note

The size should take into account the size of the various views contained inside as well as any font sizes or themes, which might change the view. Using the label's intrinsicSize to calculate a maximum is a good idea.

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

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