Well, a miracle happened on Thursday, April 25th and I managed to score a ticket for WWDC 2013. I had prepared for the announcement like any other Apple and iOS obsessed nerd would. I wrote several applications whose sole purpose in life was to alert me when the event was announced. Apps written for my Mac, my iPhone and web based apps who would quietly check on that special WWDC page every minute of every day since Feb of this year. In the end, all that work, those countless hours of coding in Obj-C, VB.Net and PHP were wasted with the announcement on Tuesday that tickets would be available that following Thursday. While this was much more fair than the old way of making tickets available on announcement day, it also took away all the special advantage I had. Oh well, at least I was one of the first to be notified of the announcement
In any event, I reformulated, hatched new and complex plans to avenge those lost hours and get my ticket anyway. In other words, I prayed and got as prepared as I could for Thursday 12PM Central Time. When that time came, I blazed through the screens of the ticket purchase, my mind having already gone through the scenario hundreds of times. My hands were robotic and instinctive on the mouse and keyboard. Me, the keyboard, mouse and web-site were fused together in a beautiful ballet of keyboard and mouse clicks. Less than 90 seconds later I finished the maze of the iTunes purchase process. That 90 seconds was a blur, but the final screen was anyone but. Congrats to my fellow developers who also made it through with success and my condolences to those that didn’t make it.
WWDC Bound!
LR TechFest Keynote PDF
If you are looking for the keynote PDF from the LR TechFest, link is below. The event was great! Please leave any comments or suggestions for improvements. Looking forward to next year.
iOS-Enterprise-Development-Keynote
NWA TechFest 2012
NWA Tech Fest 2012 Session 2 Intro to iOS Development materials HERE
(www.irockios.com/nwatechfest2012.zip).
This file contains the keynote and example project. Feel free to contact me with any questions.
DIY SearchBar without SearchDisplayController
I guess the first question would be “why???”. Using a TableViewController with a SearchDisplayController along with the SearchBar eliminates some of the work needed to get the search working and also takes care of some of the delegate work (scrolling, tableviewdatasource, etc). Well, I find it useful to always show the SearchBar sometimes. If using indexes is not a good fit for the particular TableView, then if you use the standard SearchDisplayController you have given the user no easy way to jump to the top of the TableView once the user scrolls the SearchBar off the View. In this case, resizing the TableView down some and adding a SearchBar does a better job of keeping the SearchBar where the user can access it no matter the scrolling position of the TableView.
So the first step is to create a ViewController (not a TableViewController), and add a TableView to it. Next make sure to set the ViewController to be the DataSource and Delegate for the TableView. When you add the TableView, it will want to take the entire View, let it. Next resize the TableView down 44 pixels to give the proper amount of room for the SearchBar. Now add the SearchBar and check the Shows Cancel Button in the inspector. Next, be sure to set the ViewController to also be the delegate for the SearchBar.
Now for the fun part. Here we are assuming that you are using a MutableArray for the datasource of the TableView. If you are using a FetchedResultsController (CoreData), then this won’t apply to you. I will be putting an example of how to do this using the FRC later on.
First some caveats:
My mutable array for my datasource is called tableViewData, the mutable array that builds that array (the array that gets built from the JSON response of my http request) is called originalTableViewData.
Now first thing is to declare the properties and methods we will need (in the Interface file):
@property(nonatomic, strong) NSMutableArray *tableViewData; @property(nonatomic, strong) NSMutableArray *originalTableViewData; @property(nonatomic, strong) NSMutableArray *searchArray; @property(nonatomic, strong) NSMutableDictionary *units; @property(nonatomic, weak) IBOutlet UITableView myTableView; @property(nonatomic, weak) IBOutlet UISearchBar *mySearchBar;
Then in the Implementation file (.m), make sure to synthesize the setters/getters
@synthesize originalTableViewData; @synthesize tableViewData; @synthesize searchArray; @synthesize myTableView; @synthesize mySearchBar; @synthesize units;
Now that the boring stuff is out of the way, we can implement the methods needed. First, the preparation methods:
#pragma mark - SearchBar Delegate Methods
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
[self filterContentForSearchText:searchText];
}
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
searchBar.text = @"";
tableViewData = [originalTableViewData mutableCopy];
[self.myTableView reloadData];
[searchBar resignFirstResponder];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
}
Next, we need to implement the method that will do the filtering for us:
- (void)filterContentForSearchText:(NSString *)searchText {
if (searchText && searchText.length) {
[tableViewData removeAllObjects];
for (NSDictionary *dictionary in originalTableViewData)
{
for (NSString *thisKey in [dictionary allKeys]) {
if ([thisKey isEqualToString:@"SearchKey1"] ||
[thisKey isEqualToString:@"SearchKey2"] ) {
if ([[dictionary valueForKey:thisKey] rangeOfString:searchText
options:NSCaseInsensitiveSearch].location != NSNotFound) {
[tableViewData addObject:dictionary];
} // for (NSString *thisKey in allKeys)
} // if ([thisKey isEqualToString:@"SearchKey1"] || ...
} // for (NSString *thisKey in [dictionary allKeys])
} // for (NSDictionary *dictionary in originalTableViewData)
[self.myTableView reloadData];
} // if (query && query.length)
}
That method will rebuild our working Mutable Array and then reload the tableview. This works great but when the user starts scrolling the tableview, there will be a problem (the keyboard will be in the way). To fix this, implement these methods:
First capture the delegate method when the user starts scrolling the tableview:
#pragma mark - ScrollView (UITableView) delegate methods
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[mySearchBar resignFirstResponder];
[self performSelector:@selector(enableCancelButton:) withObject:self.mySearchBar afterDelay:0.0];
}
And then re-enable the cancel button:
// Used to re-enabled the cancel button when a user starts scrolling
- (void)enableCancelButton:(UISearchBar *)aSearchBar {
for (id subview in [aSearchBar subviews]) {
if ([subview isKindOfClass:[UIButton class]]) {
[subview setEnabled:TRUE];
}
}
}
That’s it, using this process is very fast even with large data sets (I have had great results with as many as 7000 objects in the data arrays.
LJ Wilson doing a session on beginning iOS development at NWA Tech Fest 2012
I am scheduled do do a session in the Mobile Development track at the NWA Tech Fest 2012 on August 24th. The session is called Intro to iOS Development. I plan to cover lots of ground and keep the session oriented toward what is important, leaving out the extra stuff that doesn’t need to be soaked up in the beginning.
There are lots of tracks and lots of sessions, please pass the word along and make plans to attend. It seems to be very well structured and has lots of quality people speaking there. A group of us from Arkansas Children’s Hospital is going up early Friday the 24th. and coming back the same day. Should be a great opportunity to gets lots of knowledge and collaboration for only the cost of travel.
Using Blocks to Load Web Content
Blocks are all the rage today. They greatly simplify code and eliminate many callbacks and delegate methods from having to be coded. The lines of code advantage is big, but the performance and efficiency advantages really make this a great choice.
To illustrate, I will show what I used to use to pull web content (in this case, an image stored on a public web server) down to my application against what this same process requires when using a class method and blocks.
Before:
In the Interface file, set up the properties needed:
@property (weak, nonatomic) IBOutlet UIImageView *myImageView; @property(nonatomic,retain) NSMutableData *incomingImageData;
And then in the Implmentation file, synthesize these and setup the methods needed to pull the image from the web server:
@synthesize myImageView;
@synthesize incomingImageData;
#pragma mark - My Methods
-(void)reloadImage {
NSString *urlString = @"http://mywebserver/myimage.jpg";
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = @"Loading Image...";
self.incomingImageData = [[NSMutableData alloc] init];
NSURLRequest *request = [[NSURLRequest alloc]
initWithURL: [NSURL URLWithString:urlString]
cachePolicy: NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval: 10
];
NSURLConnection *connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self
startImmediately:YES];
}
And then the delegate methods needed for NSURLConnection:
#pragma mark - NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.incomingImageData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.incomingImageData = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
// Set appIcon and clear temporary data/image
if (!incomingImageData) {
NSString *errorImage =
[[NSBundle mainBundle] pathForResource:@"ErrorImage" ofType:@"png"];
NSData *errorImageData = [NSData dataWithContentsOfFile:errorImage];
UIImage *image = [[UIImage alloc] initWithData:errorImageData];
myImageView.image = image;
}
else {
UIImage *image = [[UIImage alloc] initWithData:self.incomingImageData];
myImageView.image = image;
}
self.incomingImageData = nil;
}
What all this does is load the image asynchronously so the UI thread isn’t blocked. It works and works pretty fast.
Doing this same thing in a block (contained in a class method I wrote) requires this:
Interface file:
@property (weak, nonatomic) IBOutlet UIImageView *myImageView;
Implementation file:
@synthesize myImageView;
#pragma mark - My Methods
-(void)reloadImage {
NSString *urlString = @"http://mywebserver/myimage.jpg";
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = @"Loading Image...";
[WebImageOperations processImageDataWithURLString:urlString andBlock:^(NSData *imageData) {
if (self.view.window) {
UIImage *image = [UIImage imageWithData:imageData];
myImageView.image = image;
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
}];
}
That’s it, no delegate methods needed and much less code written. In case you wanted to see it, here is the class method I wrote to take in an NSString (url) and a block and process the image using Grand Central Dispatch:
+ (void)processImageDataWithURLString:(NSString *)urlString andBlock:(void (^)(NSData *imageData))processImage
{
NSURL *url = [NSURL URLWithString:urlString];
dispatch_queue_t callerQueue = dispatch_get_current_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("com.myapplication.processimagedataqueue", NULL);
dispatch_async(downloadQueue, ^{
NSData * imageData = [NSData dataWithContentsOfURL:url];
dispatch_async(callerQueue, ^{
processImage(imageData);
});
});
dispatch_release(downloadQueue);
}
There ya go, much less code and faster! I did the benchmarking on two different phones. One (an iPhone 4S) running the old way of doing it (NSURLConnection) and the other (an iPhone 3GS) running the new way of doing it (blocks and GCD). I had both phones using 3G (no WiFi) so the time difference could be seen better. The new way (3GS) outperformed the old way 8 times out of 10. Both methods were kicked off at the same time and the difference was visible. This wasn’t even a fair comparison as the 4S is much faster at processing and GPU output than the 3GS, but the 3GS won 80% of the time.
January Meeting Date Change
The meeting in January has been moved from Jan 21st to Jan 14th. Topics will be posted closer to the meeting date.
December Meeting Reminder
Reminder, next meeting is a week from this Saturday (Dec 17th). Unless other arrangements have been made, meeting will be at LJ Wilson’s house.
Topics so far:
- Using an NSMutableArray for the DataSource of a UITableView and setting the Section names properly.
- Setting up filtering on a UITableView using a fixed position UISearchBar
As always, if you want to do a tutorial or demo, just bring your code with ya.
If you need directions, send him a message via the Facebook page.
FIXME done right
So, if you are like me, you tend to stub out incomplete methods or add in comments to indicate that something needs to be done in the future. I have done this using a specific and consistent pattern since I started programming. My style has always been something like this:
// FIXME make sure to finish this method out
-(void)someMethod {
}
This works fine as I have made it a consistent habit of global searching my project for FIXME before deploying. Xcode makes this soooo much easier and much more reliable with the #warning flag. So now, at least with Objective-C, my style is now this:
#warning FIXME Make sure to finish this method out
-(void)someMethod {
}
This way the compiler warns me EVERY time I build and things which used to be forgotten until right before deployment are brought to light every build.
I have also taken this a step further and changed my code snippet for NSLog to this:
#warning FIXME REMOVE NSLog(@"Log %@",someObj);
That way, I get warnings for all my NSLog statements, so I can take them out of production code.
In App Email Made Easy
While you can’t make a phone call without leaving an application, you can integrate email into your application. Apple has made this super easy with the class . First thing to do is to include the MessageUI.framework in your application. After doing this, add the references in your class.h file:
#import <MessageUI/MessageUI.h> #import <MessageUI/MFMailComposeViewController.h>
Then add your class as a delegate for the MFMailComposeViewController:
@interface MyViewController : UIViewController <MFMailComposeViewControllerDelegate>
Then just add the call to the mail class when you want to instantiate the email dialog (it is modal). Notice that the first thing you need to do is check to make sure the device can send an email, then create an instance, set ourself as a delegate and call it:
// Send email using MailComposer
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mailViewController = [[MFMailComposeViewController alloc] init];
mailViewController.mailComposeDelegate = self; // This is critical
[mailViewController setToRecipients:[NSArray arrayWithObject:self.emailLabel.text]];
[mailViewController setSubject:@""];
[mailViewController setMessageBody:@"" isHTML:NO];
[self presentModalViewController:mailViewController animated:YES];
}
This will show the modal email dialog along with the options (subject, body) you populate. The last thing to do is to add the delegate method for the MFMailComposeViewController:
#pragma mark - MailCompose Delegate Methods
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
[self dismissModalViewControllerAnimated:YES];
}
That’s it, easy and efficient in application email.
