Friday, July 15, 2011

Using regular expressions on iOS

Recently I encountered a problem when trying to extract info from a webpage. My first option was to tidy the HTML and use DOM transversal to obtain what i needed. The problem was the overhead of doing that: first I needed to clean up the HTML code, then make the DOM tree and last but not least get the info. Of course I wouldn't do that myself, I was planning on using Element Parser but the amount of resources that was going to consume was a concern to me.

After analyzing other available options I thought about using Regular Expressions, I had worked with them before when I developed for Goby and this looked like a good opportunity to use them again.

Before iOS 4.0 there was no way of using regular expressions other than external libraries. Since 4.0 Apple introduced the NSRegularExpression class and with it a new way to use regular expressions in your iOS Apps.

For those of you not sure on what regular expressions are or if you just want to know a little bit more about them you can check http://www.regular-expressions.info/. That site contains almost everything you need to know about regexes.

Creating a regular expression

Let's suppose our test text is the following:
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Now, if we wanted to find all words that start with an "L" (case insensitive) we would use something like

(?si)((?<!\w)L\w+)


How do we do this on our app? In order to start working with regular expressions you will need to create an NSRegularExpression instance.

NSRegularExpression *regex = [NSRegularExpression 
               regularExpressionWithPattern:@"((?<!\\w)L\\w+)" 
               options:NSRegularExpressionCaseInsensitive|NSRegularExpressionDotMatchesLineSeparators 
               error:nil];

You will note a couple of things here:


  • \w was changed to \\w, that's because we have to escape \ keywords when compiling a pattern in iOS
  • No more (?si) at the beginning of the regex. Those options are added as the option parameter when creating the NSRegularExpression object

Once you have your NSRegularExpression instance you have 2 options


  • You can get the first match, which in our case would be "Lorem"
  • You can get an array of matches, which would give you every match in the text
Let's get the full array of matches


NSString *testString = @"Lorem ..."; // The full test string

NSArray *matches = [regex matchesInString:testString 
               options:0 range:NSMakeRange(0, [testString length])];
Now we have an array of NSTextCheckingResult objects and we can use a for loop to iterate it. Bear in mind that matching group indexes start at 1, since 0 is the whole text that match the regex.


for (NSTextCheckingResult *textMatch in matches) {
  NSRange *textMatchRange = [textMatch rangeAtIndex:1];
  NSString *theLWord = [testString substringWithRange:textMatchRange];

  // Do you processing here

}

Monday, May 23, 2011

Using custom Cells in your UITableView via Interface Builder

One of the first things I tried when developing iOS was making a custom row.
Why? you may ask, the fact that the default one only provided title, subtitle and an image wasn't enough.

The first serious app I developed for iOS was Traffic Status (or Estado del Transito in spanish); this app required a certain extra amount of info be displayed inside the cell so that the user could get quick glimpse of what the status of a certain avenue or subway line was.

I investigated several alternatives trying to figure out what to do here until I settled for a method that gets the view from a nib and iterates its children to find the correct subview.

The solution is as follows:

  • Try to reuse a cell previously stored by the TableView
  • If there is no such cell then call the customViewHelper
  • Load the top level objects from a given nib, in our case this nib will be named the same as the class we want to load into a View
  • Iterate through it's top level objects
  • When an object matches the subclass of the UITableViewCell we are looking for that object is returned
What we will obtain after this is the following:

Focus on the cells, how to do the header will come in the next Blog post


Let's go through this process step by step...

Create the UITableViewCell subclass in XCode

Go to XCode and create a new UITableViewCell subclass and call it "TransitStatusTableViewCell". This subclass will contain the following Outlets:

@property (nonatomic, retain) IBOutlet UILabel *firstLineLabel;
@property (nonatomic, retain) IBOutlet UILabel *secondLineLabel;
@property (nonatomic, retain) IBOutlet UILabel *snippetLabel;
@property (nonatomic, retain) IBOutlet UIImageView *backgroundImageView;



Make sure to release this properties when you dealloc your object and to synthesize each of them in your .m.

Now that you have your UITableViewCell Subclass you can create the corresponding .xib.
For this create a new Interface File in an Empty XIB. 
Create a UITableViewCell element from the library and tune it to your liking; but remember three important things
  1. The class of the UITableViewCell must be set to your class
  2. Set the reuse identifier or else your table view will be slow as hell
  3. The outlets are connected from the UITableViewCell to the elements, the File owner, unlike what happens in most cases, plays no part in creating custom views
You should end up with something like this





Create the CustomViewHelp Class in XCode


We are creating this Helper class to save us the work of iterating through subviews every time we want a custom view. This class one very simple to understand method and should be implemented like this


+ (id)customViewForClass:(Class)customClass {
    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(customClass) owner:nil options:nil];
    for ( id currentObject in topLevelObjects ) {
        if ( [currentObject isKindOfClass:customClass] ) {
            return currentObject;
        }
    }

    return nil;
}




Instantiating and filling the customCell


With both our custom cell and the Helper out of the way the only thing left to do is to actually plug this baby into a UITableViewController (or a UIViewController with the delegate methods).
With that in mind we will go straight to the tableView:cellForRowAtIndexPath: method and put our brand new cell to the test.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *cellIdentifier = NSStringFromClass([TransitStatusTableViewCell class]);

    TransitStatusTableViewCell *cell = (TransitStatusTableViewCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (cell == nil) {
        cell = [CustomViewHelper customViewForClass:[TransitStatusTableViewCell class]];
        cell.backgroundColor = [UIColor clearColor];
    }

    // This is to change the background image depending on if the cell is the last cell or any other
    // These two images should be created in the viewDidLoad method

    if (indexPath.row == [[dataArray objectAtIndex:indexPath.section] count] - 1) {
        cell.backgroundImageView.image = lastRowImage;
    } else {
        cell.backgroundImageView.image = anyRowImage;
    }

    // Fill your cell with whichever info you want
    // For example:
    // cell.firstLineLabel = @"Effective Mobility";

    return cell;
}





Conclusion


What you end up with is a really simple and reusable customCell just like the one you can see on the newest version of Estado del TrĂ¡nsito (right now it's pending approval by Apple).