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