20 November 2008

Resistance is Useless

 I've been working may way through the UIKit components learning how they work and how to use them. Got a bit carried away with the UIPickerView and ended up building an entire app.

There's two tricks I've developed. The first is a simple means of getting the picker dials to wrap (by default the work like a list - once you reace the end you then have to scroll all the way back up to the beginning again). A number of the native apps, such as the alarm (but interestingly, not the timer) feature wrapping dials, and I thought it seemed like a good idea. The second trick was highlighting the selected row of each dial to make it more prominent and the overall look less like a kids toy.

Second one first. I have a class ResistorBand which all four band types inherit. The band objects are called upon by the controller (which acts as a delegate for the UIPickerView) to supply the views for their corresponding dial. I've subclassed UIView and overwritten  - (void)drawRect:(CGRect)rect which actually paints each square. If the view is not the selected view for that row, it has a white rectangle with a 0.6 alpha drawn over it. This has the effect of slightly muting the non-selected colours.

Getting the dials to wrap up being simple in the end, thanks to a suggestion from a friend of mine [thanks Matt]. 
To start, we need to tell the picker there's three times the number of rows than there really are:

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
    return [[self bandForIndex:component] viewCount]* 3;
}

Next, when the picker asks for a particular view, we need to map the row it's asking for, with the actual views, ie just row mod viewCount:

return [views objectAtIndex:position % [self viewCount]];

Finally, when the user selects a row, as well as our normal logic, we need to make sure the picker row selected is always in the range [band viewCount] and [band viewCount]*2-1 (inclusive). This means that whenever the picker draws the selectd view, there will always be others above and below it (we'll never reach the end of the list):

- (void)pickerView:(UIPickerView *)pickerView
      didSelectRow:(NSInteger)row
       inComponent:(NSInteger)component {

    ResistorBand *band= [self bandForIndex:component];
    [band selectPosition:row % [band viewCount]];

    NSInteger updatedRow= row;

    if (row< [band viewCount]) {
        updatedRow= row+ [band viewCount];
    } else if (row>= 2* [band viewCount]) {
        updatedRow= row- [band viewCount];
    }

    [pickerView selectRow:updatedRow
               inComponnt:component
                 animated:NO];

    [self updateDisplay];
}

Hope this makes sense.

2 comments:

  1. There is no such method like updateDisplay!

    ReplyDelete
  2. > There is no such method like updateDisplay!

    updateDisplay is a method I've written that recalculates everything and updates the top section of the display with the updated values

    ReplyDelete

Please note, comments are moderated which means they won't show up until I OK them. This seems to be the most effective way of stopping comment spam.