How to UIWebView iOS

4 Comments

Here’s a simple walk through of how to display a web page in iOS.

Drag out a WebView onto your ViewController.

Create a property.

ViewController.h

@interface ViewController : UIViewController 
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end

Implement the UIWebViewDelegate

ViewController.h

@interface ViewController : UIViewController <UIWebViewDelegate>

Fix the autogenerated synthesize:

ViewController.m

@synthesize webView = _webView;

Make yourself the delegate and display your web page.

ViewController.m

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.webView.delegate = self;
        
    NSURL *url = [NSURL URLWithString:@"http://google.com"];
    NSURLRequest *requestURL = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:requestURL];
}

Hit Run.

Can optionally add the following delegate methods if you want to kick it up a notch.

ViewController.m

#pragma mark - Optional UIWebViewDelegate delegate methods
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    return YES;
}

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

Here’s the source.

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIWebViewDelegate>
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize webView = _webView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.webView.delegate = self;
    
    NSURL *url = [NSURL URLWithString:@"http://google.com"];
    NSURLRequest *requestURL = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:requestURL];
}

- (void)viewDidUnload
{
    [self setWebView:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

#pragma mark - Optional UIWebViewDelegate delegate methods
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    return YES;
}

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

@end

Mobile apps are more work than you think

14 Comments

Expectation management is a big part of software delivery. Unless you write software for a living, it’s hard to appreciate how much work goes into the little white box you see on the Google search screen, or the math and physics behind a game like Angry Birds.

Having just finished my first iPhone app, even I was surprise at the amount of work it took to take a few pictures and generate an email to send to a couple of friends.

Building on this post from Kent Nguyen, I thought it would be fun to share some of the behind the scenes magic that makes these simple apps work.

TakeMyStuff

The app itself is pretty simple. It’s called takemystuff (sorry not available in iStore yet) and it basically helps people give stuff away to friends.

Basically, you take pictures of stuff you’d like to give away.

Select the friends you’d like to tell.

And then it builds you an email combining the images and tagged with the friends you selected.

That’s it! Pretty simple eh? Here’s some of the stuff that’s going behind the scenes.

1. Taking pictures

To build a screen that takes pictures and then displays thumbnails in a table

you need to:

  1. Tag images as they are taken (to separate yours from those taken with the regular camera).
  2. Track which ones are selected/unselected, and
  3. Save that information something to be accessed later.

That’s the minium amount of work required to just make it work. Here are some of the wrinkles.

Image capturing is asynchronous

iOS saves images you take with the camera asynchronously. That means when you take a picture, it’s not instantly there for you to display. You have to wait.

Then when the worker thread is done, you are free to refresh the table on the screen.

Not a big deal, but it complicates things because you need to put refreshes in the appropriate places and it’s not always obvious where. It also requires a lot more manual testing (more on than later) and a fair bit of trial and error.

Table cells are pooled and reallocated dynamically

The engineers to Apple have gone through great lengths to make the iPhone fast. One way they do that is to dynamically allocate and reuse cells used for rows in tables.

For example you might have x100 photos ready for display. But when you bind them to a table, a much smaller number gets shown.

That means you need to create data structures outside of the table itself to track which photos are displayed and whether they are selected.

It’s not super hard but it’s non-trivial work.

And if you collect a lot of metadata for each picture you’ll either have to make use of something complex (like CoreData) or track and synchronize these data structures yourself (through things like Arrays and Dictionaries).

Locations services

Location services are what the iPhone uses to tell apps where they are. Foursquare, Weather, and Maps are obvious services that could use this kind information.

But cameras can use this information too. And while I didn’t require location services but your app might.

That means you need to check at runtime whether the service is turned on, and inform your users of what they should do if it isn’t.

Now let’s take a look at some of the things you need to deal with when compiling a list of friends.

2. Selecting friends

Selecting and adding friends on a simple table like this is a bit of work. You need to:

  • Pop open the address book and detect which friend was selected
  • Extract the friend data and handle all the edge cases of there not being a first or last name, or even an email address
  • Add logic to make sure choosing a friend on screen is synchronized with friends chosen in your data structure
  • Add logic to delete (right swipe) or remove a friend from your list (and again synchronize that with your data source)
  • Handle all the refreshes from your data structure changing to that on the screen

Then you need to popup and hide the appropriate alerts and error messages if your users have data you either didn’t expect or can’t handle.

Again, a fair bit of work for what looks like a dead simple screen.

Now let’s take a look at what it takes to bring it all together in an email.

3. Building the email

Here we grab the photos selected by the user from the first screen and combine them with the email addresses selected from the second. Here’s how that works:

  • First you need to walk the assets library and extract all the photos you tagged with your application (more asynchronous programming).
  • Then you need to grab and parse the email address of the friends.
  • Then you need to combine those and create the email.
  • If they choose not to send the email, you need to handle the ‘Cancel’ button being pressed and make sure you transition them back to their previous views (which you also need to track.

And then you need to handle the cases of where they didn’t select any photos or they didn’t select any friends.

Other stuff that needs to be done

Art work

This application doesn’t have a lot of of art but I still needed to create some icons for the tab bar and one for the application itself.

Deploying to the app store

This was (still is) probably the most painful part of the iOS development process. Trying to get your work on other peoples devices.

I won’t go into all the details but you need to jump through a lot of hoops just to test your app on another device.

  • You need to get their device ids.
  • You need to create certificates.
  • You need to create deployment profiles
  • You need to copy/email files around with complex instructions for how you testers are supposed to load you app.
  • And then after doing all that it still doesn’t work with little or no feedback as to why

It’s a tough, frustrating process it’s no wonder startups are forming just to help you deploy your iOS application.

If that’s not a wake up call to Apple that their deployment process is overly complicated I don’t know what is.

A lot more manual testing

With so much application code directed to the UI, mobile apps require a lot more manual labor intensive testing.

Yes there are frameworks and tools that can help but I have yet to see anyone favor these over repeatedly clicking through your app and making sure everything works.

And while you can unit test, it doesn’t deliver the payback we’ve grown accustomed to on server side frameworks like Rails and Java/.NET.

And we aren’t even doing anything complicated!

If the makers of Path and PlantsVsZombies were reading this they’d laugh. Complicated! You ain’t seen nothing yet! And they’d right.

This app doesn’t do any:

  • custom graphics
  • sound and audio
  • custom UI
  • push notification
  • in app purchases

All areas of hairiness and complexity and hairiness. Not to mention that there’s no backend to this app. It’s all local to the phone (which simplifies things greatly but limits what the app can do).

In summary

Writing great software isn’t cheap or easy. It takes a lot of hard work, dedication, and persistence.

And everything I’ve said here about mobile apps applies equally (though in different ways) to enterprise apps and backend systems (just imagine the complexity Google or the guys at DropBox regularly handle).

So the next time someone tells you that building that screen shouldn’t take more than a couple hours, smile, be patient, and understand where they are coming from. To our users much of what we do is magic. It looks easy and they expect it to work.

Explain to them some of the challenges. Be upfront and honest about the cost. And know that you are not alone. Managing expectations is a big part of being a software professional and setting expectations on software projects ain’t easy (though I know a book that can help).

Happy coding.

How to track last view on iPhone navigation

Leave a comment

I don’t know if there’s a better way to do this, but if someone cancelled some action in my app (say cancel sending email) I wanted to know where they came from so I could pop that view back up on the screen.

Do to that I created a variable for each view in my app:

Utils.h

#define CAMERA_VIEW 0
#define FRIEND_VIEW 1
#define LASTVIEW_FILE @"%@/viewFile"

And then whenever a view disappears I stored that last view information locally on disk (stored in an array):

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    
    // store last view
    NSArray* lastView = [NSArray arrayWithObject:[NSNumber numberWithInt:CAMERA_VIEW]];
    [lastView writeToDocumentDirectory:LASTVIEW_FILE];
}

Then when someone cancels some action (like the sending of an email) I simply read the last view from disk, and transition there:

-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
    [self dismissModalViewControllerAnimated:YES];
    [self transitionToLastView];
}   

- (void)transitionToLastView {
    
    NSArray* lastViewArray = [NSArray arrayWithContentsFromDefaultFile:LASTVIEW_FILE];
    NSNumber* lastViewNumber = [lastViewArray objectAtIndex:0];
    int lastViewIndex = [lastViewNumber intValue];
    
    UITabBarController *tabBarController = self.tabBarController;
    UIView * fromView = tabBarController.selectedViewController.view;
    UIView * toView = [[tabBarController.viewControllers objectAtIndex:lastViewIndex] view];
    
    [UIView transitionFromView:fromView
                        toView:toView 
                      duration:0.5 
                       options:UIViewAnimationOptionTransitionNone
                    completion:^(BOOL finished) {
                        if (finished) {
                            tabBarController.selectedIndex = lastViewIndex;
                        }
                    }];
}

I don’t know if there’s a better way to do this, but it works!

Note: writeToDocumentDirectory is a helper routine that stores the array at the default directory location on the iPhone.

-(void) writeToDocumentDirectory:(NSString *)filename {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *fullFileName = [NSString stringWithFormat:filename, documentsDirectory];
    [self writeToFile:fullFileName atomically:YES];
}

Test Automation (UIAutomation) example with XCode Instruments

14 Comments

UIAutomation is the library/tool Apple added in iOS SDK 4.0 to help with test automation on the iOS platform.

Scripts are written in JavaScript and executed through the Instruments Automation Instrument.

Here a quick walkthrough of how to record a script from your iPhone and play it back on your device.

Open Instruments

You open instruments through XCode -> Open Developer Tool -> Instruments.

Add Automation Instrument

There’s lots of instruments to choose from. The one we want is the ‘Automation’ instrument.

Add a script

Choose your target

Your target isn’t your device. It’s the device and your application.

Hit the record button

Record your script by hitting the ‘record’ button at the top or bottom of the tool and then click through your app.

Play it back

To see your script hit the funny looking Trace Log/Editor Log drop down and switch to scrip log view.

You should then see your recorded script.

You can play it back by clicking the ‘play’ button beside the record button at the bottom and you should see the same script played back against your phone. Output should look like this.

Save your script

This simple script doesn’t do too much, but it’s a start and if you want to save it and play it back later you can create a directory and story it in your app like this:

Much more

There’s a lot more we can do with UIAutomation. This is just a start and we’ve only scratched the surface.

But you can do a lot with this tool, and this is merely the first step.

Note: If your tests don’t run, make sure you aren’t connected and running your device from XCode. Can’t run both simultaneously so stop the XCode one then try running your tests again.

Links that help:

http://alexvollmer.com/posts/2010/07/03/working-with-uiautomation/

UIAutomation reference

Simple objective-c inheritance example

Leave a comment

Here a simple example of how inheritance works in objective-c (based on this older example).

1. Define the parent class.

Rectangle.h

#import <Foundation/NSObject.h>

@interface Rectangle: NSObject 

@property (nonatomic) int width;
@property (nonatomic) int height;

-(Rectangle*) initWithWidth: (int) w height: (int) h;
-(void) setWidth: (int) w height: (int) h;
-(void) print;

@end

Rectangle.m

#import "Rectangle.h"
#import <stdio.h>

@implementation Rectangle

@synthesize width = _width;
@synthesize height = _height;

-(Rectangle*) initWithWidth: (int) w height: (int) h {
    self = [super init];
    
    if ( self ) {
        [self setWidth: w height: h];
    }
    
    return self;
}

-(void) setWidth: (int) w height: (int) h {
    self.width = w;
    self.height = h;
}

-(void) print {
    printf( "width = %i, height = %i", self.width, self.height );
}

@end

2. Inherit from the parent.

Here we inherit our behavior from the parent with this line:

@interface Square: Rectangle


<strong>Square.h</strong>


#import "Rectangle.h"

@interface Square: Rectangle
-(Square*) initWithSize: (int) s;
-(void) setSize: (int) s;
-(int) size;
@end

And add our own customer behaviour:

Square.m

#import "Square.h"

@implementation Square
-(Square*) initWithSize: (int) s {
    self = [super init];
    
    if ( self ) {
        [self setSize: s];
    }
    
    return self;
}

-(void) setSize: (int) s {
    self.width = s;
    self.height = s;
}

-(int) size {
    return self.width;
}

@end

You can see here we inherit the Rectangle’s setWidth setHeight and print methods while providing our own setSize implementation.

3. Run it.

SpikeInheritanceTests.m

#import "SpikeInheritanceTests.h"
#import "Square.h"
#import "Rectangle.h"
#import <stdio.h>

@implementation SpikeInheritanceTests

- (void)testExample
{
    Rectangle *rec = [[Rectangle alloc] initWithWidth: 10 height: 20];
    Square *sq = [[Square alloc] initWithSize: 15];
    
    // print em
    printf( "Rectangle: " );
    [rec print];
    printf( "\n" );
    
    printf( "Square: " );
    [sq print];
    printf( "\n" );
    
    // update square
    [sq setWidth: 20];
    printf( "Square after change: " );
    [sq print];
    printf( "\n" );
}

@end

If all goes well output should look something like this:

Where is NSLog output for OCUnit tests?

4 Comments

If you’re writing out to NSLog() and can’t find the output don’t worry you are not alone.

- (void)testNSLog
{
    NSLog(@"\n\n=============");
    NSLog(@"Logging!");
    NSLog(@"\n\n=============");
    
    STAssertEquals(2, 3, nil);
}

That’s because all logging goes to the build logs which is in another window.

To view build log output click that comic book voice bubble looking thing in the upper right hand corner in XCode

Then click on the line of your latest unit test run.

Then way over on the right hand side of your unit test line you’ll see a stack of lines.

That’s your NSLog() output window.

Click on it and voila! You should see your output.

How to add unit tests to existing iOS project

4 Comments

After struggling with an illogical UI I finally figured out how to add unit tests to an existing project in XCode 4.2.

Open your project.

Goto File -> New -> New Target

Select iOS Cocoa Touch bundle.

Give it a name like ‘LogicTests’

Then click on your blue app area in your navigation.

Then click ‘Build Phases’ and expand the Target Dependency area. See how it’s blank?

That’s a problem. We need to add your application here so your unit test target is linked to your app.

So go ahead and click the ‘+’ sign and add your application.

Now you should be able to run your unit tests (by holding down the build icon until it turns to a test icon).

When you do you’ll see a configure app for testing window pop-up.

Just click ‘Edit Scheme…’ hit the plus sign (making sure the Test/Debug panel is highlighted) and add your unit test scheme as shown below.

Now you can hit that test button and all your unit tests should run.

There’s a gotcha

There’s one more gotcha you need to be aware of. As of XCode 4.2 when you add a new test class

You will get a screen that looks like this:

The defaults here are wrong. The MyApp shouldn’t be selected. We want the unit test target ‘Logic Tests’ to be checked.

If you accept these, when you go to run your tests you’ll get an error like:

<SenTestingKit/SenTestingKit.h> file not found.

This occured because the wizard default added your new test class to your main project and not your test target.

You can manually fix this by going into the build phase of your project and adding your test file to your test target and removing it from your main application target under the ‘Compile Sources’ tab.

Or you can just create the class from scratch and select your test project and not the application one.

Hope this helps. Happy testing!

How to setup OCMock XCode 4 iOS 5

4 Comments

This was harder than I would have liked. Here’s some notes on how to set it up.

Setup OCUnit on your existing project

These notes from Apple are pretty good about how to setup OCUnit on your XCode project.

The only difference was when I ran my unit tests (Product -> Test) I had to manually add my own unit test scheme.

Just hit the ‘Edit Scheme’ button, then hit the ‘+’ sign and click on your target unit tests and click the ‘Add button’.

You should now be able to run your tests against the simulator (not real phone) and see the failure.

Setup OCMock

Create a Libraries directory

Create a ‘Libraries’ directory in your project by right clicking on your project icon and selecting ‘Add Files to …’.

Click the ‘New folder’ button, type the name ‘Libraries’, and click ‘Add’ so your libraries directory is at the same level as your project and unit test target. When your done it should look something like this:

Note: Make sure you select your ‘Test’ target (grey) not the main ‘Project’ target blue. Else you will make all these lovely changes to the wrong config and nothing will work.

Copy in libOCMock.a

Download the libOCMock.a and stick it in the Libraries directory we just created.

Then add it to your project by dragging it into the Libraries directory we just added to your project.

When you do a window will pop up, make sure you select the ‘Copy items’ box at the top and click on your unit test target so it will be linked to OCMock.

Copy OCMock header files

Download the latest OCMock dmg file, located the OCMock directory inside containing the header files, and just like we did before, drag it into the Libraries directory.

Again select ‘Copy’ and click your unit test target and now your directory structure should look like this:

Configure Build Settings

We now need to tell our project how to find these header files we just added.

Click your blue project icon (click somewhere else and then click it again if it doesn’t immediately change) and click the ‘Build Settings’ tab for your project.

In the ‘Search Paths -> Library Search Paths’ section add $(SRCROOT)/Libraries in double quotes to Debug and Release sections. Once you do they will automatically convert to your local machine paths as shown below.

Note: As of XCode 4.3 the Library Search Paths may already be configured for you. The headers ones through aren’t.

And then do the exact same thing for the ‘Search Paths ->Header Search Path’ section (be sure to double click on it after and make sure both are set to ‘recursive’).

Note: As of XCode 4.3 you won’t see the word recursive here – just the box. Click it for both. If you don’t when you run your test you won’t be able to find the OCMock header files.

Next head down to the ‘Linking -> Other Link Flags’ section and add the ‘-all_load’ option.

If you don’t do this you’ll get a “Did you forget to add the -force_load linker flag?” error when you run your tests and you can read about why here.

Add a mock test case

We should now be good to go. Open you the default test case OCUnit created for you and add the following:

#import "UnitTests.h"
#import <OCMock/OCMock.h>

@implementation UnitTests

- (void)setUp
{
    [super setUp];
    
    // Set-up code here.
}

- (void)tearDown
{
    // Tear-down code here.
    
    [super tearDown];
}

- (void)testExample
{
    //STFail(@"Unit tests are not implemented yet in UnitTests");
}

- (void)testOCMockPass {
    id mock = [OCMockObject mockForClass:NSString.class];
    [[[mock stub] andReturn:@"mocktest"] lowercaseString];
    
    NSString *returnValue = [mock lowercaseString];
    STAssertEqualObjects(@"mocktest", returnValue, @"Should have returned the expected string.");
}

@end

If everything worked, you should be able to run your test now and see a nice failure in your log output window:

Links that help:

https://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/UnitTesting/02-Setting_Up_Unit_Tests_in_a_Project/setting_up.html#//apple_ref/doc/uid/TP40002143-CH3-SW1

http://www.raywenderlich.com/3716/unit-testing-in-xcode-4-quick-start-guide

https://developer.apple.com/library/mac/#qa/qa1490/_index.html

http://ocmock.org/

How to configure UITableView for multirow selection

Leave a comment

This is a little harder than it sounds.

UITableView reuses cells – so you can’t just detect with row was selected and then expect that cell to be selected later. That cell will be recycled and your index will be lost.

So what we do is create a separate data structure (say a mutable array) to track which rows were selected, and then turn rows on and off by toggling a check mark.

The data structure

In my app I need to track which photos are selected:

So I am going to create an NSMutableArray to do that:

ViewController.h

@property (nonatomic, strong) NSMutableArray *selectedPhotoTracker;

ViewController.m

@synthesize selectedPhotoTracker = _selectedPhotoTracker;

and I am going to initialize him once my photos have loaded:

ViewController.m

-(void) initializeSelectedPhotoTracker {
    NSLog(@"initializeSelectedPhotoTracker");
    NSMutableArray *tracker = [NSMutableArray array];
    for (int i = 0; i < [[self photos] count] ; i++) {
        [tracker addObject:[NSNumber numberWithBool:NO]];
    }
    
    self.selectedPhotoTracker = tracker;    
}


-(void) refresh {
    
    NSLog(@"refresh");
    
    NSMutableArray *collector = [[NSMutableArray alloc] initWithCapacity:0];
    ALAssetsLibrary *al = [Utils defaultAssetsLibrary];
    
    [al enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                      usingBlock:^(ALAssetsGroup *group, BOOL *stop) 
     {
         
         [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
          {
              if (asset) {
                  
                  // check for tag
                  NSDictionary *metadata = asset.defaultRepresentation.metadata;
                  NSDictionary *tiffDictionary = [metadata objectForKey:(NSString *)kCGImagePropertyTIFFDictionary];
                  NSString *takeMyStuffValue = [tiffDictionary objectForKey:(NSString *)kCGImagePropertyTIFFMake];

                  if ([takeMyStuffValue isEqualToString:TAKEMYSTUFF_METATAG]) {
                      [collector addObject:asset];
                  }
                  
              }  
          }];
         
         self.photos = collector;
         [self initializeSelectedPhotoTracker];
     }
                    failureBlock:^(NSError *error) { NSLog(@"Error refreshing photos.");}
     ];
}

Note: this looks more complicated that it really is but I included it to make that point that when you initialize this array matters.

First time I did this I tried putting :

         [self initializeSelectedPhotoTracker];

in the viewWillAppear view lifecycle method. It didn’t work because the photos (which are loaded in a separate thread in another block) hadn’t finished yet.

So if you are loading your data structure and nothing is there look out for that. Where you do your load and initialization matters.

Multi-row selection

This part is pretty straight forward. Apple doesn’t actually like you using blue highlighted row as an indicator of whether a row is selected (human interface guidelines).

They prefer you use check marks.

So with our datastructure in place we can just turn checkmarks on and off like this:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    int selectedRow = indexPath.row;

    NSDictionary *selectedPhoto = [[self selectedPhotoTracker] objectAtIndex:selectedRow];
    NSString *urlKey = [[selectedPhoto allKeys] objectAtIndex:0];
    
    // add checkmark
    UITableViewCell *thisCell = [tableView cellForRowAtIndexPath:indexPath];
    
    if (thisCell.accessoryType == UITableViewCellAccessoryNone) {
        thisCell.accessoryType = UITableViewCellAccessoryCheckmark;
        [selectedPhoto setValue:[NSNumber numberWithBool:YES] forKey:urlKey];        
    } 
    else 
    {
        thisCell.accessoryType = UITableViewCellAccessoryNone;
        [selectedPhoto setValue:[NSNumber numberWithBool:NO] forKey:urlKey];        
    }

    [[self selectedPhotoTracker] replaceObjectAtIndex:selectedRow withObject:selectedPhoto];
    [[self getSelectedImages] writeToDocumentDirectory:SELECTED_PHOTOS_FILE];
}

This method tracks that a row was selected and updates our model, and then it toggles the checkmark on or off respectively.

Links that help:
http://stackoverflow.com/questions/308081/is-it-possible-to-configure-a-uitableview-to-allow-multiple-selection

http://stackoverflow.com/questions/5370311/uitableview-didselectrowatindexpath-add-additional-checkmark-at-tap

How to programmatically transition between views in Tab Bar Controller

Leave a comment

Say you’ve got a uitabbarcontroller and you want to programatically switch from one to the other. Here’s how you do it:

- (IBAction)test:(id)sender {
    // switch back
    
    int controllerIndex = 1;
    
    UITabBarController *tabBarController = self.tabBarController;
    UIView * fromView = tabBarController.selectedViewController.view;
    UIView * toView = [[tabBarController.viewControllers objectAtIndex:controllerIndex] view];
    
    // Transition using a page curl.
    [UIView transitionFromView:fromView 
                        toView:toView 
                      duration:0.5 
                       options:(controllerIndex > tabBarController.selectedIndex ? UIViewAnimationOptionTransitionCurlUp : UIViewAnimationOptionTransitionCurlDown)
                    completion:^(BOOL finished) {
                        if (finished) {
                            tabBarController.selectedIndex = controllerIndex;
                        }
                    }];
}

The controller index is the view you want to transition to (0 based). So you just pick your index, get your from and to view, and then use the transitionFromView method to switch.

Note: You can’t do this from within a viewWillAppear or other lifecyle event. You may get away with it, but it only works once your view is fully loaded.

Helper link:
http://stackoverflow.com/questions/5161730/iphone-how-to-switch-tabs-with-an-animation

Older Entries

%d bloggers like this: