Apr 22

For a project I’m currently working on, I needed to show a ‘Load more’ cell when there were >25 results, this is not a basic 1-2-3 on how to fill tablecells with data, so below code is assuming you already have that and it’s left out

In my code I will fetch a maximum of 25 results (which can be less) and stick this into an array. If the array count is 25, we have to add an extra cell, to do this we increase the count by one:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        NSLog(@"Setting numberOfRowsInSection to %i",[self.localJsonArray count]);
        if ( [jsonArray count] < 25 ) {
                return [self.localJsonArray count];
        } else {
                return [self.localJsonArray count] + 1;
        }       
}

Next we populate the tablecells, we fill it with data from the index, and we look if we’re still working with data or are at the +1 we defined above:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        static NSString *CellIdentifier = @"Cell";
        ImageCell *cell = (ImageCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (indexPath.row != [localJsonArray count] ) { // As long as we haven’t reached the +1 yet in the count, we populate the cell like normal
                if (cell == nil) {
                        cell = [[[ImageCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
                }
                NSDictionary *itemAtIndex = (NSDictionary *)[self.localJsonArray objectAtIndex:indexPath.row];
                [cell setData:itemAtIndex];
        } // Ok, all done for filling the normal cells, next we probaply reach the +1 index, which doesn’t contain anything yet
        if ( [jsonArray count] == 25 ) { // Only call this if the array count is 25
                if(indexPath.row == [localJsonArray count] ) { // Here we check if we reached the end of the index, so the +1 row
                        if (cell == nil) {
                                cell = [[[ImageCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
                        }
                        // Reset previous content of the cell, I have these defined in a UITableCell subclass, change them where needed
                        cell.cellBackground.image = nil;
                        cell.titleLabel.text = nil;
                        // Here we create the ‘Load more’ cell
                        loadMore =[[UILabel alloc]initWithFrame: CGRectMake(0,0,362,73)];
                        loadMore.textColor = [UIColor blackColor];
                        loadMore.highlightedTextColor = [UIColor darkGrayColor];
                        loadMore.backgroundColor = [UIColor clearColor];
                        loadMore.font=[UIFont fontWithName:@"Verdana" size:20];
                        loadMore.textAlignment=UITextAlignmentCenter;
                        loadMore.font=[UIFont boldSystemFontOfSize:20];
                        loadMore.text=@"Load More..";
                        [cell addSubview:loadMore];
                }
        }
        return cell;
}

And voila, cell #26 is the ‘Load more’ cell, next we start detecting if it’s that cell that’s being selected:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        if ( [jsonArray count] == 25 ) { //  Only call the function if we have 25 results in the array
                if (indexPath.row == [localJsonArray count] ) {
                        NSLog(@"Load More requested"); // Add a function here to add more data to your array and reload the content
                } else {
                       NSLog(@"Normal cell selected"); // Add here your normal didSelectRowAtIndexPath code
               }
        } else {
                       NSLog(@"Normal cell selected with < 25 results"); //  Add here your normal didSelectRowAtIndexPath code
        }
}
 

Note that I’m working with 2 arrays here, jsonArray and localJsonArray, the jsonArray is what I fill with new results, i.e. 1-25 26-50, the localJsonArray is what’s being filled more and more, so on every ‘Load More’ selection, the localJsonArray will grow with +25 if there’s >= 25 items to load.

Tagged with:
Apr 12

In general try to avoid to set too many global variables, instead use local class variables instead to keep things as less complicated as possible.
But sometimes it can come in handy to set variables in your app that you can address from all your classes.

The easiest way to do this, is to use your AppDelegate class for this. You can just include your AppDelegate where needed and address the functions and variables that are in there.

First, define the variables in the AppDelegate’s .h and .m files:

#import <UIKit/UIKit.h>

@interface My_ApplicationAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
    UIWindow *window;
    NSInteger *myGlobalInteger;
 }
@property (nonatomic, assign) NSInteger *myGlobalInteger;

#import "My_ApplicationAppDelegate.h"
@implementation My_ApplicationAppDelegate
@synthesize window,myGlobalInteger;

To access these global variables from and to your AppDelegate class, first include your AppDelegate in the .m file of the class where you need it.

#import "My_ApplicationAppDelegate.h"

And then define the following in the function where you need to access the variable:

- (void)myFunction {
        My_ApplicationAppDelegate *appDelegate = (My_ApplicationAppDelegate *)[[UIApplication sharedApplication] delegate];
        appDelegate.myGlobalInteger = 1;
        NSLog(@"The integer value is %i",myGlobalInteger);
 }

And voila, you can access the myGlobalInteger value from everywhere where you allocate the My_ApplicationAppDelegate like shown above.

Tagged with:
Apr 06

Formatting a proper date string can be a bit tedious, I’ve tried to work with the time function available in C, but that didn’t work out very well due dates not getting converted properly to my locale (Dutch in my case).

After fiddeling quite a bit, it turned out that combining NSDateFormatter with NSLocale and NSDate would work the magic I needed for displaying a proper date in a UITableView.

NSDateFormatter *setDateLocale = [[NSDateFormatter alloc] init];
NSLocale *nl_NL = [[NSLocale alloc] initWithLocaleIdentifier:@"nl_NL"];
[setDateLocale setDateFormat:@"EEE, d MMM yyy"];
[setDateLocale setLocale:nl_NL];
NSDate *now = [[NSDate alloc] init];
NSString *FormattedYMD = [setDateLocale stringFromDate:now];
[nl_NL release];
[setDateLocale release];
[now release];

And voila, FormattedYMD now holds the current datestamp formatted as: ma, 6 apr 2010

Tagged with:
Apr 04

Somehow, I was always struggling with the proper certificates, permissions, names, icons, etc. when building an app for either my testers or the app store.

iPhonedevelopertips.com has a very nice distribution build cheat sheet online.
Whenever I setup a new project for Ah-Hoc distribution or for Appstore distribution, I go over several steps in this document, it saves a lot of headaches on why XCode is complaining on missing certificates, provisioning profiles or why your just built app just won’t install on that tester’s iPhone.

Tagged with:
Apr 02

There’s a few methods to change your company name that shows up in new source files you add to your project.

To do it on a system-wide basis so it applies to all projects, open a terminal window, and type the following line (one line, no enters):

defaults write com.apple.Xcode PBXCustomTemplateMacroDefinitions ‘{ORGANIZATIONNAME="YourCompanyNameHere";}’

To do it on a per project basis so it applies only to files in your current project, right-click your project and select ‘Get info’

Get project info

Now select the ‘general’ tab and change the company name to what you’d like in the project files to show up.
Change company name

Tagged with:
Mar 31

A nice feature of UIWebView is that you can make it transparent. This way you can set a nice static background in interface builder which blends with your app and doesn’t give the user a feeling they’re looking at a website. It’s the same technique I use in my KookJij app when watching recipes:

Everything under the blocked logo is web content in a UIWebView, behind that is a static background with watermark directly loaded from the app itself:

To get this effect, first define the UIWebView in the .h file

IBOutlet UIWebView *myWebView;

Next set the color of the UIWebView to a clearColor status in the viewDidLoad and load the content:

- (void)viewDidLoad {
        [myWebView setBackgroundColor:[UIColor clearColor]];
         NSURL *url = [NSURL URLWithString:@"http://url.to/your/content.htm"];
         NSURLRequest *request = [NSURLRequest requestWithURL:url];
         [myWebView loadRequest:request];
}

And the final trick, is to set the background of the actual web content you’re loading in the UIWebView to be transparent too. So make sure the HTML page you’re loading in the webview has the transparent tag set for the body:

<html><head></head><body  style="background-color: transparent">content goes here</body></html>

And there you have it, a transparent UIWebView with HTML content blending in perfectly with your app.

Tagged with:
Mar 30

Nov 4th 2010 note: This is still a valid and working method, but since iOS4 there’s an easier method to detect swipes using Apple’s UISwipeGestureRecognizer method

I needed proper swipe detection in a UITableViewCell, I’ve seen some online examples but in practice they responded fairly poorly.
I found a proper example in the iPhoneIncubator example which is freely available, and does a proper detection of a swipe through a NSNotificationCenter.

Personally I didn’t need the NSNotificationCenter, so I modified the code to fit into a UITableViewCell subclass.
First we define the horizontal swipe distance at which it gets registered as being an actual swipe, the allowed vertical scrolling margin for people with shaky fingers and some generic CGPoint math operations (these are a 1on1 copied from the original source, I take no credit for this)

#define SWIPE_DRAG_HORIZ_MIN 40
#define SWIPE_DRAG_VERT_MAX 40

#pragma mark -
#pragma mark Helper functions for generic math operations on CGPoints

CGFloat CGPointDot(CGPoint a,CGPoint b) {
        return a.x*b.x+a.y*b.y;
}
CGFloat CGPointLen(CGPoint a) {
        return sqrtf(a.x*a.x+a.y*a.y);
}
CGPoint CGPointSub(CGPoint a,CGPoint b) {
        CGPoint c = {a.x-b.x,a.y-b.y};
        return c;
}
CGFloat CGPointDist(CGPoint a,CGPoint b) {
        CGPoint c = CGPointSub(a,b);
        return CGPointLen(c);
}
CGPoint CGPointNorm(CGPoint a) {
        CGFloat m = sqrtf(a.x*a.x+a.y*a.y);
        CGPoint c;
        c.x = a.x/m;
        c.y = a.y/m;
        return c;
}

Then we begin the touchesBegan function, to detect and possibly override a touch in the tablecell

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
        NSArray *allTouches = [[event allTouches] allObjects];
        UITouch *touch = [[event allTouches] anyObject];
       
        if (touch.phase==UITouchPhaseBegan) {
                startTouchPosition1 = [touch locationInView:self];
                startTouchTime = touch.timestamp;

                if ([[event allTouches] count] > 1) {
                        startTouchPosition2 = [[allTouches objectAtIndex:1] locationInView:self];
                        previousTouchPosition1 = startTouchPosition1;
                        previousTouchPosition2 = startTouchPosition2;
                }
        }       
        [super touchesBegan:touches withEvent:event];
}

Next we do the actual detection of the swipe, and define which action to take, note that there’s a set of if statements in there which sets and checks various variables. These are simply integers I defined in the header file (booleans would be fine too). The localSwipeActive variable defines if the swipe is being performed, this will prevent a single swipe being registered as multiple swipes.

The cellSwiped is a variable I use to see which action to take (either invoke the setSwipeView or the resetSwipeView function) based on the cell’s swipe status.

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
        UITouch *touch = [[event allTouches] anyObject];
        CGPoint currentTouchPosition = [touch locationInView:self];
        NSLog(@"%d %f %d %f time: %g",fabsf(startTouchPosition1.x – currentTouchPosition.x) >= SWIPE_DRAG_HORIZ_MIN ? 1 : 0,
                 fabsf(startTouchPosition1.y – currentTouchPosition.y),
                 fabsf(startTouchPosition1.x – currentTouchPosition.x) > fabsf(startTouchPosition1.y – currentTouchPosition.y)  ? 1 : 0, touch.timestamp – startTouchTime, touch.timestamp – startTouchTime);
        if (fabsf(startTouchPosition1.x – currentTouchPosition.x) >= SWIPE_DRAG_HORIZ_MIN &&
                fabsf(startTouchPosition1.y – currentTouchPosition.y) <= SWIPE_DRAG_VERT_MAX &&
                fabsf(startTouchPosition1.x – currentTouchPosition.x) > fabsf(startTouchPosition1.y – currentTouchPosition.y) &&
                touch.timestamp – startTouchTime < .7 && self.localSwipeActive == 0
                ) {
                // It appears to be a swipe.
                if (startTouchPosition1.x < currentTouchPosition.x) {
                        NSLog(@"swipe right");
                        self.localSwipeActive = 1; // set the swipe status to active so this function doesn’t get called again within the same swipe
                        if (self.cellSwiped == 0) {
                                self.setSwipeView;
                                self.cellSwiped = 1;
                                } else {
                                        if (self.cellSwiped == 1) {
                                                self.resetSwipeView;
                                                self.cellSwiped = 0;
                                        }       
                                }
                        } else {
                                NSLog(@"swipe left");
                                self.localSwipeActive = 1;
                                if (self.cellSwiped == 0) {
                                        self.setSwipeView;
                                        self.cellSwiped = 1;
                                }  else {
                                        if (self.cellSwiped == 1) {
                                                self.resetSwipeView;
                                                self.cellSwiped = 0;
                                        }       
                                }
                        }
                }
        startTouchPosition1 = CGPointMake(-1, -1);
}

And at the end we define that the swipe is over:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
        self.localSwipeActive = 0; // Reset the swipeActive status now that we finished the swipe
        [super touchesEnded:touches withEvent:event];
}

There you have it, a proper swipe detection in a UITableViewCell subclass

Mar 30

Yesterday I ran into an issue where a UITableView would not display until some remote data was loaded into a local array, even though I was loading this data in a seperate thread.

- (void)viewDidAppear:(BOOL)animated {
        [self doInitialLoad];
}
- (void)doInitialLoad {
        [self showActivityViewer]; // shows a screen activity indicator overlay
        [self laadJSONTabel]; // loads the actual data into the tableview
        [self.recentTable reloadData]; // reloads the tableview so data shows up
        [self hideActivityViewer]; // hides the activity indicator overlay
}

It turns out that visual stuff gets queued to the end run of the loop, this resulted in a very sloppy interface which would react very slow to a change to another UIViewController due to remote data being downloaded first.

An easy fix for this, is using a performSelector on the function with a 0.0 delay, that will give it a zero delay but move loading the data to the next run-loop so all other stuff gets properly executed first. This results in an interface which feels a lot snappier and a proper UIActivityIndicatorView being displayed on screen while the data is loading:

- (void)viewWillAppear:(BOOL)animated {
        [self showActivityViewer];
}
- (void)viewDidAppear:(BOOL)animated {
        [self performSelector:@selector(doInitialLoad) withObject:NULL afterDelay:0.0];

}
- (void)doInitialLoad {
        [self laadJSONTabel];
        [self.recentTable reloadData];
        [self hideActivityViewer];
}

Tagged with:
preload preload preload