Tuesday, September 29, 2009

 

little bits of context-free iPhone code

What it says on the label.

First, an example of how to handle Settings in an iPhone app. The way I do it is, I have a "Settings" class, with lots of class get-and-set methods, so at any time you can just call "[Settings getLanguage]"; then, I have a SettingsViewController, to change them. (Yes, you can register them to be changed in the iPhone's Settings app, but since you have to leave your app for that, this is annoying.)

Best of all, you don't have to use Core Data. Instead you can use the even simpler NSUserDefault class, like so:


@interface Settings : NSObject {

}
+(int)getListMax;
+(void) setListMax:(int)value;
+(NSString*)getLanguage;
+(void) setLanguage:(NSString*)language;
+(BOOL) doDownload;
+(void) setDoDownload:(BOOL)yesno;

@implementation Settings

//This method does all the work
+(id) getSettingFor:(NSString*)key withDefault:(id)defaultValue {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
id value = [defaults objectForKey:key];
if (value==nil) {
[defaults setObject:defaultValue forKey:key];
return defaultValue;
}
return value;
}

+(int) getListMax {
return [[Settings getSettingFor:@"listMax" withDefault:[NSNumber numberWithInt:500]] intValue];
}
+(void) setListMax:(int)value {
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:value] forKey:@"listMax"];
}

+(NSString*) getLanguage {
return [Settings getSettingFor:@"language" withDefault:@"en"];
}
+(void) setLanguage:(NSString*)language {
[[NSUserDefaults standardUserDefaults] setObject:language forKey:@"language"];
}

+(BOOL) doDownload {
return [[Settings getSettingFor:@"doDownload" withDefault:[NSNumber numberWithBool:YES]] boolValue];
}
+(void) setDoDownload:(BOOL)yesno {
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:yesno] forKey:@"doDownload"];
}

[...no dealloc, as we never create an instance]



Note that you easily lazy-initialize all your defaults in code, and then let the user overwrite them. How, you ask? Via the SettingsViewController. Which you could create using the Layout Manager; but I prefer to do it programmatically, with a TableViewController, as it looks slicker much easier to add a Setting that way. Also, it lets me show you an example of my TableViewCell pattern. And while we're at it, a UIActionSheet example too. Voila:



@implementation SettingsViewController

- (void)viewDidLoad {
[super viewDidLoad];
}

//Note that we save the settings as we leave the view
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[NSUserDefaults standardUserDefaults] synchronize];
}

#pragma mark -
#pragma mark TableView methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//# of settings
return 3;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row==0) { //list max
LabelTextFieldCell *cell = (LabelTextFieldCell *)[tableView dequeueReusableCellWithIdentifier:@"LabelTextField"];
if (cell == nil)
cell = [[[LabelTextFieldCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"LabelTextField"] autorelease];

cell.fieldName = @"listMax";
cell.fieldDisplayName.text = @"List Max";
cell.fieldValue.tag=indexPath.row;
cell.fieldValue.keyboardType = UIKeyboardTypeURL;
cell.fieldValue.autocapitalizationType = UITextAutocapitalizationTypeNone;
cell.fieldValue.delegate=self;
cell.fieldValue.text=[[NSNumber numberWithInt:[Settings getListMax]] stringValue];
}
else if (indexPath.row==1) { // do download
SwitchCell *cell = (SwitchCell *)[tableView dequeueReusableCellWithIdentifier:@"Switch"];
if (cell == nil)
cell = [[[SwitchCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Switch"] autorelease];

cell.fieldName = @"doDownload";
cell.fieldDisplayName.text = @"Download pages";
cell.mySwitch.tag=indexPath.row;
cell.mySwitch.on=[Settings doDownload];
cell.currentController = self; //we do this so we can respond to changes
}
else if (indexPath.row==3) { //language
ButtonCell *cell = (ButtonCell *)[tableView dequeueReusableCellWithIdentifier:@"Button"];
if (cell == nil)
cell = [[[ButtonCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Button"] autorelease];

cell.fieldName=@"language";
cell.fieldDisplayName.text=@"Language";
cell.button.tag=indexPath.row;
[cell.button setTitle:[Settings getLanguageName] forState:UIControlStateNormal];
[cell.button addTarget:self action:@selector(changeLanguage) forControlEvents:UIControlEventTouchUpInside];
}
else
return nil;
}

#pragma mark -
#pragma mark UITextFieldDelegate

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField.tag==0) {
//TODO: check that it's a valid int!
[Settings setListMax:[textField.text intValue]];
}
}

#pragma mark -
#pragma mark Switch

-(void) valueChanged:(UISwitch*)aSwitch {
if (aSwitch.tag==1) {
[Settings setDoDownload:aSwitch.on];
}
}

#pragma mark -
#pragma mark Button

-(void) changeLanguage {
UIActionSheet *action = [[UIActionSheet alloc]
initWithTitle:@"Select Language"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:nil];

for (NSString* language in [Util getValidLanguageNames]) {
[action addButtonWithTitle:language];
}

[action showInView:self.view];
}

#pragma mark -
#pragma mark UIActionSheetDelegate

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
[Settings setLanguage:[[Util getValidLanguageValues] objectAtIndex:buttonIndex]];

UIButton *button = (UIButton*) [self.tableView viewWithTag:6];
[button setTitle:[actionSheet buttonTitleAtIndex:buttonIndex] forState:UIControlStateNormal];

[actionSheet dismissWithClickedButtonIndex:buttonIndex animated:YES];
[actionSheet release];
}

[...dealloc etc...]



That's pretty straightforward; build a table view, populate it with table view cells, and respond to the actions in the cells. But how do the cells work? Well, first look at their (theoretically abstract) common parent superclass, ITRTableViewCell:



@interface ITRTableViewCell : UITableViewCell {
NSString *fieldName;
UILabel *fieldDisplayName;
UIViewController *currentController;
}

@property (nonatomic, retain) NSString *fieldName;
@property (nonatomic, retain) UILabel *fieldDisplayName;
@property (nonatomic, assign) UIViewController *currentController; //weak reference


@implementation ITRTableViewCell

@synthesize fieldName, fieldDisplayName, currentController;

-(id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
[super initWithFrame:frame reuseIdentifier:reuseIdentifier];

self.fieldDisplayName = [[UILabel alloc] init];
fieldDisplayName.textAlignment = UITextAlignmentLeft;
fieldDisplayName.adjustsFontSizeToFitWidth=YES;
fieldDisplayName.minimumFontSize=8;
fieldDisplayName.numberOfLines=2;
fieldDisplayName.font = [UIFont systemFontOfSize:14];

return self;
}

[...dealloc etc...]


which will make more sense when you look at its use in a subclass:


@interface LabelTextFieldCell : ITRTableViewCell {
UITextField *fieldValue;
}

@property (nonatomic, retain) UITextField *fieldValue;


@implementation LabelTextFieldCell

@synthesize fieldValue;

-(id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
[super initWithFrame:frame reuseIdentifier:reuseIdentifier];

[self.contentView addSubview:fieldDisplayName];

self.fieldValue = [[UITextField alloc] init];
fieldValue.clearsOnBeginEditing = NO;
fieldValue.enablesReturnKeyAutomatically = YES;
fieldValue.returnKeyType = UIReturnKeyNext;
fieldValue.autocapitalizationType = UITextAutocapitalizationTypeSentences;
fieldValue.autocorrectionType = UITextAutocorrectionTypeNo;
fieldValue.enablesReturnKeyAutomatically = YES;
fieldValue.backgroundColor = [self getBackgroundColor];
fieldValue.textColor = [self getTextColor];
fieldValue.textAlignment = UITextAlignmentLeft;
fieldValue.font = [UIFont systemFontOfSize:14];
fieldValue.borderStyle = UITextBorderStyleBezel;
[self.contentView addSubview:fieldValue];

return self;
}

-(void)layoutSubviews {
[super layoutSubviews];
CGRect contentRect = self.contentView.bounds;
CGFloat boundsX = contentRect.origin.x;
CGRect frame;

frame = CGRectMake(boundsX+10, 0, 140, 30);
fieldDisplayName.frame = frame;

frame = CGRectMake(boundsX+150, 0, 160, 30);
fieldValue.frame = frame;
}

[...dealloc...]


Ya see? The superclass defines the name on the left; the subclass defines the input on the right with which the user interacts. Here are the ButtonCell and SwitchCell implementations:


@implementation ButtonCell

@synthesize button;

-(id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
[super initWithFrame:frame reuseIdentifier:reuseIdentifier];

[self.contentView addSubview:fieldDisplayName];

self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.backgroundColor = [self getBackgroundColor];
button.titleLabel.textColor = [self getTextColor];
button.titleLabel.textAlignment = UITextAlignmentLeft;
button.titleLabel.font = [UIFont systemFontOfSize:14];
[self.contentView addSubview:button];

return self;
}

-(void)layoutSubviews {
[super layoutSubviews];
CGRect contentRect = self.contentView.bounds;
CGFloat boundsX = contentRect.origin.x;
CGRect frame;

frame = CGRectMake(boundsX+10, 0, 140, 30);
fieldDisplayName.frame = frame;

frame = CGRectMake(boundsX+150, 0, 160, 30);
button.frame = frame;
}

[...dealloc...]

@implementation SwitchCell

@synthesize mySwitch;

-(id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
[super initWithFrame:frame reuseIdentifier:reuseIdentifier];

[self.contentView addSubview:fieldDisplayName];

self.mySwitch = [[UISwitch alloc] init];
mySwitch.backgroundColor = [self getBackgroundColor];
[mySwitch addTarget:self action:@selector(switchAction:) forControlEvents:UIControlEventValueChanged];
[self.contentView addSubview:mySwitch];

return self;
}

-(void)layoutSubviews {
[super layoutSubviews];
CGRect contentRect = self.contentView.bounds;
CGFloat boundsX = contentRect.origin.x;
CGRect frame;

frame = CGRectMake(boundsX+10, 0, 140, 30);
fieldDisplayName.frame = frame;

frame = CGRectMake(boundsX+210, 0, 110, 30);
mySwitch.frame = frame;
}

-(void)switchAction:(UISwitch*) sender {
[currentController performSelector:@selector(valueChanged:) withObject: sender];
}

[...dealloc...]


Note the callbacker on the SwitchCell, not necessary on ButtonCell because we can set its target action when we create it.


There you go: a Settings architecture, and a programmatic TableViewController with a useful custom TableViewCell architecture and three examples of same. Use as you like.


And now, an actual algorithm I wrote. An algorithm! I know! Real programming! Hence my pride. The problem was, given a list of points that might have a few spurious outliers, get rid of those outliers and build a zoomed-in region that excludes them, to show in an MKMapView. My solution, in a class called Locator:


+(MKCoordinateRegion)getRegionForAnnotations:(NSArray*)annotations {

NSMutableArray *latitudes = [NSMutableArray arrayWithCapacity:[annotations count]];
NSMutableArray *longitudes = [NSMutableArray arrayWithCapacity:[annotations count]];

for (id entry in annotations) {
double myLat = [entry coordinate].latitude;
double myLong = [entry coordinate].longitude;
[latitudes addObject:[NSNumber numberWithDouble:myLat]];
[longitudes addObject:[NSNumber numberWithDouble:myLong]];
}

//OK, we've got the box that includes *all* the annotations
//Now we get rid of outliers aka bad address finds.
NSArray *sortedLatitudes = [latitudes sortedArrayUsingSelector:@selector(compare:)];
NSArray *sortedLongitudes = [longitudes sortedArrayUsingSelector:@selector(compare:)];

NSRange latLongest = [Locator getLongestContiguousRangeIn:sortedLatitudes];
NSRange lonLongest = [Locator getLongestContiguousRangeIn:sortedLongitudes];

NSNumber* minLat = [sortedLatitudes objectAtIndex:latLongest.location];
NSNumber* maxLat = [sortedLatitudes objectAtIndex:latLongest.location+latLongest.length-1];
NSNumber* minLon = [sortedLongitudes objectAtIndex:lonLongest.location];
NSNumber* maxLon = [sortedLongitudes objectAtIndex:lonLongest.location+lonLongest.length-1];
MKCoordinateRegion regionToShow = [Locator getRegionForMinLat:minLat minLong:minLon maxLat:maxLat maxLong:maxLon];
return regionToShow;
}

+(NSRange) getLongestContiguousRangeIn:(NSArray*)numbers {
NSRange longest; longest.location=0; longest.length=0;
NSRange range; range.location=0; range.length=0;
for (int i=0; i<[numbers count]; i++) {
double thisLat = [[numbers objectAtIndex:i] doubleValue];
double lastLat = i>0 ? [[numbers objectAtIndex:i-1] doubleValue] : thisLat;
if (thisLat-lastLat<=(double)1.0)
range.length++;
else {
range.location=i;
range.length=0;
}
if (range.length>longest.length)
longest=range;
}
return longest;
}

+(MKCoordinateRegion) getRegionForMinLat:(NSNumber*)minLat minLong:(NSNumber*)minLong maxLat:(NSNumber*)maxLat maxLong:(NSNumber*)maxLong {
CLLocationCoordinate2D regionCenter;
regionCenter.latitude=([minLat doubleValue]+[maxLat doubleValue])/2;
regionCenter.longitude=([minLong doubleValue]+[maxLong doubleValue])/2;

MKCoordinateSpan regionSpan;
regionSpan.latitudeDelta=[maxLat doubleValue]-[minLat doubleValue];
regionSpan.longitudeDelta=[maxLong doubleValue]-[minLong doubleValue];

MKCoordinateRegion region;
region.center=regionCenter;
region.span = regionSpan;
return region;
}


Quite elegant and efficient, if I do say so myself. It would make for an interesting interview question, too.

Labels: , , , , , , , , , , , , ,


Comments:
I really appreciate the shared information. Keep updating more informative articles. Stafford Traffic Lawyer
 
Thanks for providing these posts and t's pretty frickin hilarious. How they did not see that is beyond us best psychologist in islamabad.
 
Thanks for the post.
virginia prenuptial lawyer
 
I wanted to express my heartfelt gratitude for dedicating your time and expertise to create this enlightening blog post. Your meticulous research and fresh perspectives have undeniably broadened my horizons on this topic. I am truly grateful for the dedication you've exhibited in delivering such a valuable piece of content. Thank you for your outstanding contribution!
reckless driving lawyer Union County

 
This post is a treasure trove for aspiring iPhone developers, offering invaluable insights into context-free code. The clarity and conciseness make it a fantastic resource for those diving into iPhone development. The author's expertise shines through, making complex concepts more accessible. The inclusion of code snippets enhances the learning experience, and the overall presentation is commendable. Aspiring developers are sure to find this post a valuable companion in their journey to mastering iPhone programming. Great work!
divorce center new york ny





 

Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]