Search
Follow
Recent Comments

Entries in Interface Builder (3)

Thursday
Nov242011

Creating Gesture Recognizers with Interface Builder

Whilst I was playing around with Split View Controllers in my last post I missed one small trick to eliminate some code. I also overlooked a bug that I need to correct.

The example made use of gesture recognizers to allow the master view to be swiped on and off screen. However I created and configured these objects in code rather than with Interface Builder. In this post I will show how easy it is to create gesture recognizers with IB and eliminate some code.

A quick recap

Just by way of a recap the example app I used in the last post made use of three gesture recognizers. These were created in the viewDidLoad method of the DetailViewController and added to the detail view. The code that was used to create the three objects is shown below:

   UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] 
                                           initWithTarget:self 
                                                   action:@selector(handleSwipeLeft)];
   swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
   [self.view addGestureRecognizer:swipeLeft];
   [swipeLeft release];

 

   UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] 
                                            initWithTarget:self 
                                                    action:@selector(handleSwipeRight)];
   swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
   [self.view addGestureRecognizer:swipeRight];
   [swipeRight release];

 

   UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] 
                                   initWithTarget:self 
                                           action:@selector(handleTap)];
   [self.view addGestureRecognizer:tap];
   [tap release];

 

Each gesture recognizer is allocated and initialised with its appropriate target and action, there is some gesture specific configuration such as setting the direction of the swipe or the number of taps we expect and then finally the gesture is added to the view.

Gesture Recognizers in Interface Builder

Interface Builder contains a full set of gesture recognizers in its objects library. So instead of creating these objects manually with code we can just drag the required gestures onto our view in the UYLDetailViewController XIB.

Before I do that though I need to correct an error in the example I showed last time. The gesture recognizers were assigned to the top level UIView which includes the toolbar and the toolbar button. This meant that any attempt to tap on the toolbar button was intercepted by the gesture recognizer preventing the button action from being called.

To correct this error I have introduced a subview that covers the entire user interface with the exception of the toolbar. The gesture recognizers can then be assigned to this subview leaving taps on the toolbar button to be handled by the button. For this example we need one Tap Gesture Recognizer object and two Swipe Gesture Recognizer objects. Dragging these objects onto the subview leaves us with a XIB file looks as follows:

It is the second view in this list that the gestures are assigned to. If you find your gestures are not working check the Referencing Outlet Collections connection in Interface Builder to ensure they are connected to the correct view:

With the gestures added, configuration becomes easier as the inspector shows you the gesture specific configuration options. So the tap gesture allows you set to the number of taps and touches you want to recognize:

The swipe gesture allows you set the direction of the swipe (left, right, up, down) and the number of touches:

The final step is to set the target-action for each gesture. Using IB you can create the target-action by control-clicking on the object and then dragging into the implementation file. IB will then prompt you for the name of the action to insert:

Since in this case we already have the action methods defined we will take a slightly different approach. We will first fix the methods to add IBAction hints for Xcode and then using the same control-click method drag each recognizer object to its method. As you mouse over the action it highlights to show the connection:

With the gestures configured and connected there is nothing more to do other than clean up the now redundant code from the view controller. A quick compile and run should confirm that the gestures are recognised and function as before.

The updated Xcode project can be found here or in my CodeExamples github repository.

Monday
Mar282011

Interface Builder Outlet Collections

I posted a while back about the UINib class which was a minor but useful addition to iOS 4.0. Today I want to mention another iOS 4.0 addition that sneaked in under the radar  - Interface Builder Outlet Collections.

Interface Builder Outlets

Interface Builder Outlets are nothing new. The IBOutlet identifier added to a variable declaration in your class header file provides a hint to Interface Builder. The outlet representing an instance variable in your class can then be visually connected to the UI object in Interface Builder.

@property (nonatomic, retain) IBOutlet UIWindow *window;

@property (nonatomic, retain) IBOutlet NibCollectionViewController *viewController;


Interface Builder Outlet Collections

IB Outlet Collections extend the concept of an IB Outlet to allow one outlet to be associated with a collection of IB objects. To illustrate why this might be useful consider the following example of a user interface containing a number of switches and a reset button (I don’t expect to win any prizes for UI design here but stick with me):

The UISwitch elements can all be accessed individually but when the reset button is used the switches are all reset to the off position. Using just IBOutlets you could create an instance variable for each switch and connect each one up in turn. The action that is invoked when the reset button is used would then access each switch in turn and set the switch to off.

IB Outlet Collections provide an easier solution. Instead of defining instance variables for each UISwitch we use an NSArray to represent the collection of switches:

  @interface NibCollectionViewController : UIViewController {
    NSArray *switchCollection;
  }

  @property (nonatomic, retain) IBOutletCollection(UISwitch) NSArray *switchCollection;

  - (IBAction)resetAction:(id)sender;
  - (IBAction)switchAction:(id)sender;
  @end

Instead of using the IBOutlet macro we use IBOutletCollection which takes an optional parameter to specify the class name of the objects in the collection. When specified IB requires all objects to be of that class. So in this case we can specify UISwitch since the collection only contains switches. We will look at a second example later when the objects in the collection are not all of the same type.

With the macro defined we can the connect up the outlets in Interface Builder. Looking at the connections for one of the UISwitch items, instead of making the connection from “Referencing Outlets” it is made from “Referencing Outlet Collections”.

A connection is made from each switch to the Outlet Collection so that it ends up as follows:

The method resetAction is connected to reset button and looks as follows:

  - (IBAction)resetAction:(id)sender {
    NSNumber *no = [NSNumber numberWithBool:NO];
    [switchCollection setValue:no forKey:@"on"];
  }

 

The method resetAction makes use of Key Value Coding (KVC) to set a value for the “on” property for each item in the switchCollection array. Note that since the “on” property of a UISwitch is actually a BOOL we need to wrap the “NO” value in an NSNumber. The implementation of setValue:forKey takes care of extracting the BOOL value from the NSNumber object.

This method works well but it misses one small refinement by setting the “on” property directly we lose the ability to animate the motion of the switches as they reset. To animate the reset action we need to send the message setOn:animated to each of the switches in the array. So an alternative implementation for resetAction with animation could be as follows:

  - (IBAction)resetAction:(id)sender {
    for (UISwitch *swItem in switchCollection) {
      [swItem setOn:NO animated:YES];
    }
  }

Collections of Different Objects

The example so far works with a single object type UISwitch in the collection but the use of Outlet Collections is not limited to a single object type. As I mentioned previously the class name parameter is optional for the IBOutletCollection. If you omit it (or specify id) you can include any type of UI element you want. So for example, consider a situation where I want to be able to enable or disable a mix of UI elements in one operation.

The property statement now becomes as follows:

@property (nonatomic, retain) IBOutletCollection(id) NSArray *elementCollection;


Since all of the UI elements in the collection have a property named “enabled” we can again make use of Key Value Coding to disable all of the elements:

NSNumber *no = [NSNumber numberWithBool:NO];

[elementCollection setValue:no forKey:@”enabled”];

 

Wrapping Up

As with the UINib class the use of IBOutletCollections can be considered a minor addition to the ever expanding set of iOS frameworks. Like UINib I think it is well worthwhile having it in your toolbox for those occasions when you need to perform the same operation on multiple UI elements.

You can find the Xcode project used for this post here.

Monday
Feb282011

Speeding up table view cell loading with UINib

It seems a long time ago that Apple released IOS 4.0 but I am still coming across things I missed in that release. One new class that was introduced is UINib which brings to iOS some functionality that has long been available via the NSNib class on Mac OS X.

The purpose of UINib on iOS (and NSNib on Mac OS X) is to speed up the loading of frequently used NIB files. It does that by caching the NIB file objects in memory the first time the NIB is loaded from disk. Subsequent attempts to load the NIB file then come from the memory copy avoiding the slower disk access. Apple claims that this provides a 2x speed boost when loading NIB files.

The most obvious place to use UINib is in UITableViewControllers where the table view cell is being loaded from a NIB file each time a new cell is created. The nice thing about UINib is that you can get the performance improvement without having to make a huge number of code changes. To illustrate we will first recap the standard mechanism for loading a table view cell from a NIB and then how to modify the code to take advantage of UINib.

Loading a table view cell from a NIB file

The Table View Programming Guide describes in detail the technique for loading a custom table view cell from a Nib file. I will skip some of the detail but the basic requirement is to create a Nib file that contains a single UITableViewCell that we will use to populate each row in our table. Starting from an Xcode template I created a sample app with two tab bar items each containing a table view controller. I will use the traditional Nib loading technique in the first controller and UINib in the second so that we can easily compare the two approaches.

To get started I will create a basic table view cell that contains three label subviews as illustrated below:

This cell is in its own Nib file named LabelCell.xib, the UITableViewCell has an identifier set to “LabelCell” in Interface Builder and each of the UILabel views has a unique tag so that we can retrieve them later (again refer to the Table View Programming Guide for step by step instruction on this approach).

There is nothing special about the first table view controller. The most interesting method is the one that creates the cell for each row in the table and is shown below:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  static NSString *CellIdentifier = @"LabelCell";
  NSUInteger row = [indexPath row];

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
    [[NSBundle mainBundle] loadNibNamed:@"LabelCell" owner:self options:nil];
    cell = self.labelCell;
    self.labelCell = nil;
  }
 
  UILabel *label1 = (UILabel *)[cell viewWithTag:TCTAG_LABEL1];
  label1.text = [NSString stringWithFormat:@"Item %d", row+1];
  UILabel *label2 = (UILabel *)[cell viewWithTag:TCTAG_LABEL2];
  label2.text = [NSString stringWithFormat:@"Item %d", row+1];
  UILabel *label3 = (UILabel *)[cell viewWithTag:TCTAG_LABEL3];
  label3.text = [NSString stringWithFormat:@"Item %d", row+1];
 
  return cell;
}

This is fairly standard stuff for a table view controller. If we do not have a cell queued that we can reuse we create a new one by loading it from the LabelCell Nib file. The labelCell outlet is wired up in interface builder to the UITableViewCell so after loading the Nib file it is set to the newly allocated object. After saving the value that the method will return the outlet is set to nil. The label subviews are located by searching for the tag set in Interface Builder.

The user interface looks as follows when run:

When the view is first loaded the cellForRowAtIndexPath:indexPath method is called for each visible row in the table and a new UITableViewCell is created. Each time a new cell is created the LabelCell Nib file is read from disk. Given the dimensions of the iPhone screen in portrait mode this will result in 10 cells being created meaning the Nib file is read 10 times. Note that when the table view is scrolled cells starts to disappear off the top or bottom of the screen and can be reused. This means that one or maybe two more cells will be allocated at most.

Using UINib

The second table view controller will make use of the UINib optimisation to load the LabelCell Nib once and then use the in-memory cached copy each time we need to create a new cell object. To store the cached copy of the cell object we need an instance variable stored in the table view controller. The property for this instance variable is as follows:

@property (nonatomic, retain) id labelCellNib;


The instance variable has its getter/setter methods (we will override the getter shortly) synthesized in the table view controller with the following statement:

@synthesize labelCellNib=_labelCellNib;

 Since we specified the retain attribute in the property statement we need to be sure to release the object in the dealloc method:

- (void)dealloc {
  [_labelCellNib release];
  [super dealloc];
}

Note that the type for this property is set to “id”, this is necessary since the UINib class is only available from iOS 4.0 and later. This means that we need to check the class exists before we use if we want to create an application that will run on 3.x versions of iOS. The runtime checks that we need to perform to ensure this backward compatibility are more complicated than usual unfortunately.

A Word of Warning

The release notes for IOS 4.0 contain the following warning:

iOS 4 includes a new UINib class to support rapidly unarchiving nib files. While this class is new to iOS SDK 4, it was present but private, in previous releases. Special care needs to be taken when deploying code that uses the UINib class and also runs on iOS releases prior to version 4. Specifically, you cannot determine the availability of the class solely using theNSClassFromString function, because that check returns a private class on iOS 3.x and earlier. Instead, after getting theUINib class using NSClassFromString, you must also use the respondsToSelector: method of the returned class to see if it responds to the nibWithNibName:bundle: method. If it responds to that method, you can use the class.

So it is not enough to check if the class UINib exists using the NSClassFromString function since the class does actually exist on prior versions. So in addition to check that the class exists we also need to check it responds to the nibWithNibName:bundle: method. To do that we will define a getter method for the instance variable labelCellNib which includes the runtime checks:

- (id)labelCellNib {
 
  if (!_labelCellNib) {
    Class cls = NSClassFromString(@"UINib");
    if ([cls respondsToSelector:@selector(nibWithNibName:bundle:)]) {
      _labelCellNib = [[cls nibWithNibName:@"LabelCell" bundle:[NSBundle mainBundle]] retain];
    }
  }
  return _labelCellNib;
}

 

So the first time we attempt to access the labelCellNib property we will invoke the getter method which if the ivar is zero performs the necessary runtime checks. Only after we are sure we have a valid UINib object the nibWithNibName:bundle: method is used to load the Nib file from disk and store it in the ivar. Note that we need to retain the loaded Nib file since we want it to stick around whilst the view controller is loaded. Subsequent accesses will just return the stored value directly.

One last thing we should do is define a viewDidUnload method that releases the cached Nib objects since the view has been unloaded from memory:

- (void)viewDidUnload {
  [super viewDidUnload];
  self.labelCellNib = nil;
}

 

To make use of the cached UITableViewCell we need a slight modification to the cellForRowAtIndexPath method as follows (some duplicate code omitted for clarity):

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  static NSString *CellIdentifier = @"LabelCell";
  NSUInteger row = [indexPath row];
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
 
    if ([self labelCellNib]) {
      [[self labelCellNib] instantiateWithOwner:self options:nil];
    } else {
      [[NSBundle mainBundle] loadNibNamed:@"LabelCell" owner:self options:nil];
    }
    cell = self.labelCell;
    self.labelCell = nil;
  }
 
  ...
  return cell;
}

 

This time when we need to create a new UITableViewCell we first check if the labelCellNib ivar is set. On iOS 4.0 and later this will, via the getter method we saw previously, cause the Nib to be read from disk the first time we access it. All subsequent accesses will return us the cached version of the Nib objects. Either way the instantiateWithOwner:options: method is used to create a working copy of the Nib avoiding the need to read from disk. If we are running on iOS 3.x the code falls back to the original Nib loading code for each cell.

Is it Worth the Effort?

Using the UINib class in this example means that we avoid reading the LabelCell Nib file 10 times each time the view is loaded. This makes the view appear faster when it is loaded but I have to say it is pretty hard to see a speed improvement in practise when testing on an actual device.

Since the actual speed improvement is very limited in this example it is certainly worth asking if it is worth the effort? To be honest I am not totally convinced, especially as modern iOS devices have greatly improved performance compared to earlier generation devices. However, if you compare the two table view controllers you will see that the extra effort required is pretty low so it seems to me to be worthwhile whenever you are loading custom cells from a Nib file.

You can find the Xcode project used for this post here.