Tag Archives: objectivec

Extra space/row in UIPopoverController content?

If you’re using UISplitViewController in your iPad/Universal apps, you probably implemented the UISplitViewControllerDelegate to add a UIBarButtonItem to the detail view controller’s toolbar to display a popover. The popover might have some extra space before the first row after you rotate the simulator or the iPad from landscape to portrait, took me a few hours to figure this out, it is because the navigation bar’s translucent property is set to YES, set it to NO before you add the button and you won’t see the extra space. Also, if you instantiate the nav bar in your NIB, leave its style asdefault in Interface Builder, otherwise the popover will be messed up on load when you launch the app in portrait orientation. Set them in code instead.

- (void)viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
	
	self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
	self.navigationController.navigationBar.translucent = YES;
}

#pragma mark -
#pragma mark UISplitViewControllerDelegate methods

- (void)splitViewController:(UISplitViewController *)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)pc {
	self.navigationController.navigationBar.translucent = NO; //if I don't do this we get extra space in popover
	barButtonItem.title = @"Some Title";
	
	// Keep references to the popover controller and the popover button, and tell the detail view controller to show the button.
	self.popoverController = pc;
	self.rootPopoverButtonItem = barButtonItem;
	UIViewController <SubstitutableDetailViewController> *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
	[detailViewController showRootPopoverButtonItem:rootPopoverButtonItem];
}

- (void)splitViewController:(UISplitViewController *)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
	//setting it back to black translucent
	self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
	self.navigationController.navigationBar.translucent = YES;
	
	// Nil out references to the popover controller and the popover button, and tell the detail view controller to hide the button.
	UIViewController <SubstitutableDetailViewController> *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
	[detailViewController invalidateRootPopoverButtonItem:rootPopoverButtonItem];
	self.popoverController = nil;
	self.rootPopoverButtonItem = nil;
}

Speeding up Core Data-based UITableViewController

It is pretty common for an iPhone/iPad app to make an API call to a server, get the JSON response data back, parse that data, and display it in a table view. The usual way to do this looks like this:

- (void)apiCall {
	NSString *urlString = [[NSString alloc] initWithFormat:@"%@/some_models/some_action.json", apiEndpoint];
	NSURL *url = [[NSURL alloc] initWithString:urlString];
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
	[request setHTTPMethod:@"GET"];
	[NSURLConnection connectionWithRequest:request delegate:self];
	[url release];
	[urlString release];
}

This fires off the API call asynchronously, and then you implement some delegate methods like this:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
	responseData = [[NSMutableData alloc] initWithCapacity:[response expectedContentLength]+100];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
	[responseData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
	NSString *jsonString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
	[self dictionaryToCoreData:[jsonString JSONValue]]; //this parses the JSON data and persists into Core Data
	[jsonString release];
	[responseData release];
}

This approach works fine, but when you run it on the real devices, you might notice that the table view locks up when you go between the navigation flow. Basically the table view ignores user inputs until everything above is finished. This is because everything is performed on the main thread and it locks up the UI. The asynchronous NSURLConnection method used above doesn’t like to be used in a background thread, and there is really no reason to do things asynchronously if you’re working in the background, conveniently, there is a +sendSynchronousRequest method that waits until we get the response and data in NSURLConnection. To perform the above API call in a background thread, the code is actually much simpler:

- (void)apiCall {
	[self performSelectorInBackground:@selector(backgroundApiCall) withObject:nil];
}

- (void)backgorundApiCall {
	@synchronized(self) {
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool

		NSString *urlString = [[NSString alloc] initWithFormat:@"%@/some_models/some_action.json", apiEndpoint];
		NSURL *url = [[NSURL alloc] initWithString:urlString];
		NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
		[request setHTTPMethod:@"GET"];
		
		NSURLResponse *resp;
		NSError *error;
		NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&resp error:&error];
		// you should probably do some error handling here
		NSString *jsonString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
		[self dictionaryToCoreData:[jsonString JSONValue]];
		[jsonString release];		
		[url release];
		[urlString release];
		[pool release];
	}
}

Now, when the table view is first loaded, it displays the stale data in Core Data, the API call is fired off in the background, and when we get data back from the call the table view is updated with the new data. Stale data is better than locked up UI.

Notice the very first time you run the app the table view will be empty until the background thread finishes, if this bothers you, you can preload the database with sample data. In our app we can’t really do this as the data is unique to the user, but it might make sense for you to ship your apps with pre-loaded data.