Life & Technology

Handful lessons in different areas of technology and life in general.

Category Archives: iOS Development

GCNetworkKit: Download with progress via UIAlertView

For the past few months, I’ve been using this simple to use yet powerful HTTP set of classes called GCNetworkKit to communicate to your server with ease. For a brief description from the github page:

GCNetworkKit is a helpful set of classes for interacting with a http server. All classes make use of ARC. In order to use it you need to drag the folder into your xcode project. You also need to add the MobileCoreServices and the SystemConfiguration framework to your project.

And with this, I’ve come up with a simple example that demonstrate the ease of use of this library.

UIAlertView + UIProgressView

For this demo, we want to have a file download functionality that we need to display via UIAlertView with additional UIProgressView to view the progress and a UILabel to determine the total percentage completion. These two subviews will be attached to our UIAlertView and will be used during the file downloading. If you downloaded the app called CityMaps2Go, the way they present their maps download is exactly what we’ll be creating.

Our initial screen will look like this:

Next, we will hook our UIButton to trigger the display of UIAlertView on the screen and immediately start the process.

  1. -(IBAction)download:(id)sender
  2. {
  3.     NSURL *fileURL = [NSURL URLWithString:@”http://YOUR_URL_HERE/PATH/TO/FILE.pdf”%5D;
  4.     downloadAlertView = [[UIAlertView alloc] initWithTitle:@”Downloading…”
  5.                                                     message:@”             “
  6.                                                    delegate:self
  7.                                           cancelButtonTitle:@”Cancel”
  8.                                           otherButtonTitles:nil];
  9.     [progressView setProgress:0];
  10.     [downloadAlertView addSubview:progressView];
  11.     [downloadAlertView addSubview:progressLabel];
  12.     [progressView setProgressViewStyle: UIProgressViewStyleBar];
  13.     [downloadAlertView show];
  14.     [self downloadDataAtURL:fileURL];
  15. }

After adding the UIProgressView and UILabel on our UIAlertView, we will call our method that will actually perform the file downloading. Line 16 will start to call GCNetworkKit’s block-based methods as follows:

  1. – (void)downloadDataAtURL:(NSURL *)url {
  2.     __GC_weak ViewController *weakReference = self;
  3.     weakReference.progressLabel.text = @”Preparing …”;
  4.     [self performSelectorOnMainThread:@selector(updateStatusLabel:) withObject:@”> Status: Download Starting” waitUntilDone:NO];
  5.     GCNetworkDownloadRequest *request = [GCNetworkDownloadRequest requestWithURL:url];
  6.     request.autoDeleteTMPFile = YES;
  7.     request.loadWhileScrolling = YES;
  8.     request.continueInBackground = YES;
  9.     request.progressHandler = ^(CGFloat progress){
  10.         NSLog(@”Progress on downloading data: %f.”, progress);
  11.         [weakReference updateProgress:progress];
  12.     };
  13.     request.downloadCompletionHandler = ^(NSString *filePath){
  14.         self.requestHash = nil;
  15.         [self performSelectorOnMainThread:@selector(updateStatusLabel:) withObject:@”> Status: Download Completed.” waitUntilDone:NO];
  16.         [downloadAlertView dismissWithClickedButtonIndex:0 animated:YES];
  17.         NSLog(@”Downlaoded data and saved at: %@.”, filePath);
  18.         UIAlertView *success = [[UIAlertView alloc] initWithTitle:@”Download Status” message:@”File successfully downloaded!” delegate:nil cancelButtonTitle:@”Great!” otherButtonTitles: nil];
  19.         [success show];
  20.     };
  21.     self.requestHash = [[GCNetworkRequestQueue sharedQueue] addRequest:request];
  22. }

You see on Line 11, we don’t have to worry getting the current progress status of the file being downloaded, GCNetworkKit already has this functionality built-in. And on Line 15 is the completionHandler callback that we can use to process the file being downloaded or even display another UIAlertView that will popup the completion alert message.

And if we like to cancel the download process, we only need to call cancelRequestWithHash: method to immediately halt the current operation. This is possible because every request that is put in the queue automatically has a unique id in the form of hash.

  1. – (void) cancelDownload
  2. {
  3.     [[GCNetworkRequestQueue sharedQueue] cancelRequestWithHash:self.requestHash];
  4.     self.requestHash = nil;
  5. }

Using GCNetworkKit library, we achieved our task with minimal effort in coding and the result is exactly what we expected!

Screenshots for our demo project

The XCode project is compiled under ARC and available for download here.

Advertisements

NSString Category Function: isIn()

Usually when we’re trying to check if two strings are equal, we often used this code:

 NSString *isoView = @"Isometric View";
 if([isoView isEqualToString:@"Parallel View"]) // do something

Which is fine but if you’re testing against multiple values for equality, you might end up writing code like this:

 NSString *isoView = @"Isometric View";
 if([isoView isEqualToString:@"Parallel View"] || [isoView isEqualToString:@"Dynamic View"]
    || [isoView isEqualToString:@"Inverted View"] || [isoView isEqualToString:@"Range View"])
     // Do somethng here

And you’re code will contain bunch of ORs ( || ) just to test if your string matches to your qualifiers.

NSString+Additions
With this problem, I have created a simple NSString Category that basically solves this kind of situation and improves your code a little bit. I created a boolean function that accepts multiple string values terminated by nil that will be used for equality check of string.

Our interface header looks like this:

// NSString+Additions.h
#import <Foundation/Foundation.h>

@interface NSString (Additions)
- (BOOL) isIn: (NSString *) strings, ... NS_REQUIRES_NIL_TERMINATION;
@end

And the implementation code is a simple for .. loop to achieve our goal:

// NSString+Additions.m
#import "NSString+Additions.h"

@implementation NSString (Additions)

- (BOOL) isIn: (NSString *) strings, ...
{
    BOOL isFound = NO;

    va_list args;
    va_start(args, strings);
    for (NSString *arg = strings; arg != nil; arg = va_arg(args, NSString*))
    {
        if ([self caseInsensitiveCompare:arg] == NSOrderedSame) {
            isFound = YES;
            break;
        }
    }
    va_end(args);

    return isFound;
}
@end

If we’re going back to our sample snippets above, we would then write the conditional checking as follows:

NSString *isoView = @"Isometric View";
if ([isoView isIn:@"Parallel View", @"Dynamic View", @"Inverted View", @"Range View", nil])
   // Do Something

Simple and not cluttering with OR ( || ) statements!

The basic principle on the above Category is based on Objective C feature called Variable Argument Lists which is explained clearly by Matt on his blog post.

The category is easy to override to include checking against NSArray or NSDictionary.

Implementing dynamic UITableViewCell height on UITableView

If you’ve used Twitter iOS application, then most probably you will notice that content streams differ in sizes based on the length of each tweets. And if we are implementing such as this in simplest way of doing this, we could be using UITableView’s heightForRowAtIndexPath delegate method and get/set the size of each string on the array:

For demonstrating this behavior, we’ll be creating a simple View based project that needs an NSMutableArray and a UITableView:

@interface TableTestViewController : UIViewController<UITableViewDelegate, UITableViewDataSource> {
    NSMutableArray *_quotes;
    UITableView *_tableView;
}    
@end

And setting up our _tableView and adding some test data with varying length of characters:

- (void)viewDidLoad
{
    [super viewDidLoad];
    _quotes = [[NSMutableArray alloc] initWithObjects: 
               @"I do the very best I know how-the very best I can; and I mean to keep doing so until the end. If the end brings me out all right, what is said against me won't amount to anything. If the end brings me out wrong, ten angels swearing I was right would make no difference.", 
               @"Be systematically heroic in little unnecessary points, do every day or two something for no other reason than its difficulty, so that, when the hour of need draws nigh, it may find you not unnerved or untrained to stand the test.", 
               @"The trick of it, she told herself, is to be courageous and bold and make a difference. Not change the world exactly, just the bit around you. Go out there with your double-first, your passion and your new Smith Corona electric typewriter and work hard at ... something. Change lives through art maybe. Write beautifully. Cherish your friends, stay true to your principles, live passionately and fully well. Experience new things. Love and be loved if at all possible. Eat sensibly. Stuff like that.", 
               @"I know of no more encouraging fact than the unquestioned ability of a man to elevate his life by conscious endeavor.", nil];
    
    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460) style:UITableViewStylePlain];
    _tableView.dataSource = self;
    _tableView.delegate = self;        

    [self.view addSubview: _tableView];
}

Our _tableView is set up to be the same UIWindow height less the size of Status bar (480 – 20). And setting the current view implements its delegate. Then, add our _tableView as a subview to our main view.

Let’s implement _tableView’s delegate and dataSource:

#pragma mark - UITableViewDataSource methods

- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
    return [_quotes count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString* reuseIdentifier = @"Cell";
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
    
    if (nil == cell) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier] autorelease];
        cell.textLabel.font = [UIFont systemFontOfSize:14];
        cell.textLabel.numberOfLines = 0;
    }
    
    cell.textLabel.text = [_quotes objectAtIndex:indexPath.row];
    return cell;
}
@end

Code above is pretty standard code for Table-view based application. The interesting code to notice is the numberOfLines = 0, this tells us that cell will contain a multi-line string with unknown size. If we forgot to set this property to 0, the default value of 1 will be in effect causing our text to display and truncated in a single line.

#pragma mark - UITableViewDelegate methods

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    CGSize size = [[_quotes objectAtIndex:indexPath.row] 
                   sizeWithFont:[UIFont systemFontOfSize:14] 
                   constrainedToSize:CGSizeMake(300, CGFLOAT_MAX)];
    return size.height + 10;
}

In order to achieve our dynamic height nature of our cells, heightForRowAtIndexPath will do the trick. So for each cell iteration, it also gets the size of the string including its font size to the count. The constrainedToSize parameter is the maximum acceptable size of the string. We set the height to be the constant max value of CGFLOAT but you can set it manually to a higher number, say 5000 or 9000. After we get the size of the current string on the cell, we added 10 more to the height for readability purpose.

A sample screen below gives us the output of the codes above:

UITableView_DynamicHeight

Quotes on the sample are taken at Quotations Page.

iOS Series: Recreating ABS-CBN News iPhone Application

With this iOS blog series, I will try to recreate existing application found on the AppStore that I assume is not natively developed in Objective-C codes but rather using 3rd party frameworks that produces/generates binaries specific to platform.

My target iOS application, ABS-CBN News is an iPhone-only application which I think is built using Titanium and a good candidate for this series for its simplicity. And we will try to examine components of this app and build our own from the extracted features. And also, we could try adding features not currently implemented if possible.

With this exercise, we will be exploring and using some popular opensource libraries to achieve our goals and to also help other developers who’s just starting in iOS development familiarize themselves to these awesome libraries.

On its core, ABS-CBN News has these features:
* A horizontal tab based menu for news category. Clicking each category will refresh the main table and lists all news focusing on that category.
* A live twitter feeds for @abscbnnews and its anchors and reporters. A read-only type view of individual tweets and no options to comment/public reply or even retweet the message.
* A settings page which the user can select what to appear on his/her “My News” category. User can also set total number of news per category. Maximum is 20.

This iOS Series blog post will be splitted into 3 parts:
Part 1: Building the basic UI components and defining its use.
Part 2: Creating Data Models and Consuming current REST Web Services
Part 3: Enhancing current features and adding/suggesting some.

Stay tuned for the first part of this series.

Horizontal Menu with UITableView Tap Status Bar not working

HorizMenu_ScreenshotYesterday, I downloaded the ABS-CBN News iOS app to see news and articles and let myself update on what’s going on. The application looks good and it feels natural, very intuitive as they presented their menus as a horizontal tab placed on the header and then the content is being changed on the UITableView. Even the UIView Activity Indicator on the bottom is non-intrusive and very well implemented.

But one thing I noticed, when I scroll news up to the bottom and scroll back on top by clicking the status bar, nothing happen, no matter how I try or select different tab, still it won’t work. And upon investigating, the problem is being caused by two UIScrollViews on the main view which prevents the UITableView from responding to scrollToTop.

 

As per Apple documentation:

/*
 this is for the scroll to top gesture. by default, a single scroll visible scroll view with this flag set will get the call. if there is more than one visible with this
 flag set or the delegate method returns NO, the view isn't scrolled 
 */
@property(nonatomic) BOOL  scrollsToTop;          // default is YES. if set, special gesture will scroll to top of view after consulting delegate

To fix the issue, I made a sample iOS application which uses the excellent opensource MKHorizMenu and use his demo application to add a UITableView and a logic that will check and set the scrollsToTop property to false if the UIScrollView implementation is not UITableView as seen below:

- (void)viewDidLoad
{
    self.items = [NSArray arrayWithObjects:@"Headlines", @"UK", @"International", @"Politics", @"Weather", @"Travel", @"Radio", @"Hollywood", @"Sports", @"Others", nil];    
    [self.horizMenu reloadData];

    [super viewDidLoad];
    self.title = @"Tap Status";
    
    for (UIView* v in [self.view subviews])
    {
        if ([v isKindOfClass:[UIScrollView class]])
        {
            if ([v isKindOfClass:[MKHorizMenu class]]) {
                ((UIScrollView *)v).scrollsToTop = NO;
            }
        }
    }
}

Revised Demo application of MKHorizMenu is available for download here.

Hope this helps.