iPhone app development tutorial – add an intro view to your tab bar app

This tutorial was originally published in April 2011

This tutorial is on adding an intro view to your app. In this instance we’ll be adding it to a tab bar app. This is not the use of a splash view. A splash view is one which is shown each time an app loads and is only shown for a few short second, going away without any user interaction. Our intro view will be one that is shown at a specific time, mostly when the app is loaded for the first time. The view will state visible until the user takes some action to dismiss the view. We will be showing our view modally and using a boolean to determine whether to show the view when the app is loaded. In addition we will be storing the boolean in a plist and loading it’s value to determine whether to show our intro view.

So now that we’ve laid out what we want to do, let’s get started.

Simulator Shot

1.) Let’s start by creating a tab app and naming it IntroViewTutorial. I don’t really like to use the Storyboard, but definitely use Automatic Reference Counting.

Create Tabbed App

Name The App IntroViewController

2.) The first thing we’ll do is add the plist and create a Boolean. Select New > New File > Resource > Property List, and name it IntroViewData.plist

Create a new Property List

3.) After creating the plist, open it up and add a Boolean value. You can do this by either right clicking in the file and selecting Add Row.

Add Row to plist

Or with the plist selected,

Select plist

Go to the Editor menu and select Add Item.

Add Item to plist

Create the item with a Key of showIntroView, make it a Boolean, and give it an initial value of YES.

Create Boolean in plist

Save the plist.

4.) Next we’re going to change the FirstViewController.xib. Deleted the labels and add two buttons.

Change First View Controller

5.) Open FirstViewController.m and we’ll implement the plist methods. We’re going to add two IBActions that we’ll call from the buttons. These methods save a value in plist. I could have combined these into one method, but decided to keep it simple for now.

- (IBAction)showIntroView
{
    // get paths from root directory
    NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
    // get documents path
    NSString *documentsPath = [paths objectAtIndex:0];
    // get the path to our Data/plist file
    NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"IntroViewData.plist"];
    
    Boolean noBool = YES;
    
    // create dictionary with values in UITextFields
    NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: [NSNumber numberWithBool:noBool], nil] forKeys:[NSArray arrayWithObjects: @"showIntroView", nil]];
    
    NSString *error = nil;
    // create NSData from dictionary
    NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
    
    // check is plistData exists
    if(plistData)
    {
        // write plistData to our Data.plist file
        [plistData writeToFile:plistPath atomically:YES];
    }
    else
    {
        NSLog(@"Error in saveToPlist, IntroViewData does not exist: %@", error);
    }
}

- (IBAction)dontShowIntroView
{
    // get paths from root directory
    NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
    // get documents path
    NSString *documentsPath = [paths objectAtIndex:0];
    // get the path to our Data/plist file
    NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"IntroViewData.plist"];
    
    Boolean yesBool = NO;
    
    // create dictionary with values in UITextFields
    NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: [NSNumber numberWithBool:yesBool], nil] forKeys:[NSArray arrayWithObjects: @"showIntroView", nil]];
    
    NSString *error = nil;
    // create NSData from dictionary
    NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
    
    // check is plistData exists
    if(plistData)
    {
        // write plistData to our Data.plist file
        [plistData writeToFile:plistPath atomically:YES];
    }
    else
    {
        NSLog(@"Error in saveToPlist, IntroViewData does not exist: %@", error);
    }
}

After creating the IBActions, hook them up to the buttons in the view.
Hook up IBActions to button

6.) Now that we are saving a value into the plist let’s access that value in the app delegate and read it. We’ll starts by just writing the value to the console. We read in the plist in the application:didFinishLaunchingWithOptions method.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    
    // setting up the tabs
    UIViewController *viewController1 = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];
    UIViewController *viewController2 = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
    self.tabBarController = [[UITabBarController alloc] init];
    self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, viewController2, nil];
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ plist section ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // IntroViewData.plist code
    // get paths from root direcory
    NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
    // get documents path
    NSString *documentsPath = [paths objectAtIndex:0];
    // get the path to our data/plist file
    NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"IntroViewData.plist"];
    
    // check to see if Data.plist exists in documents
    if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
    {
        // if not in documents, get property list from main bundle
        plistPath = [[NSBundle mainBundle] pathForResource:@"IntroViewData" ofType:@"plist"];
    }
    
    // read property list into memory as an NSData object
    NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
    NSString *errorDesc = nil;
    NSPropertyListFormat format;
    // convert static property list into dictionary object
    NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
    if (!temp)
    {
        NSLog(@"Error reading plist: %@, format: %d", errorDesc, format);
    }
    
    BOOL showIntroView = [[temp objectForKey:@"showIntroView"] boolValue];
    
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ end plist section ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    NSLog(@"showIntroView = %d", showIntroView);
    
    return YES;
}

When we print the value to the console it will be represented by a decimal, 1 for YES, and 0 for NO.

You can run the app at this point and check the console, click the buttons to change the value, close the app, run it again and see that the value is being saved in the plist.

7.) Now let’s create our Intro View. Select File > New File > UIViewController subview and name it IntroViewController. Open the xib and add a button that will dismiss the view.

Create Intro View

8.) Open the IntroViewController.m file and add a method to dismiss the modal view.

- (IBAction)dismissTheModalView
{
    [self dismissModalViewControllerAnimated:YES];
}

Then connect it the button in the view.

9.) Open the app delegate implementation file and import our new IntroViewController.

#import "IntroViewController.h"

Then declare a IntroViewController and add an if block to check for the Boolean value we saved into the plist. If the value it true we’ve display our intro view modally.

 IntroViewController *introView = [[IntroViewController alloc] initWithNibName:@"IntroViewController" bundle:[NSBundle mainBundle]];
    
if (showIntroView) 
{
	[self.window.rootViewController presentModalViewController:introView animated:NO];
}

That is all there is to it. Run the app and try it out. I’m currently working on an app that shows a list in a table view, but only after the user adds some data to be shown in the table. So I show the intro view only when there is not data saved to be shown in the table. If there is data saved the app goes straight to the table.

Final Simulator Shot


Here’s the code.



iPhone App Development – Add a twitter button in your app

This tutorial was originally published in March 2011

This tutorial is going to be about adding a Twitter button to your app. By doing this you allow a user to tweet straight from your app rather than exiting and using another app. This became available in iOS 5, and it really is easy to do. Our app here is going to be very simple, but the steps to do this can be used in any app.

App Screen Shot

1.) Let’s start by creating a single view app.

Create a Single View Appt

Go ahead and name the app TwitterTutorial, we’ll use Automatic Reference Counting but will not be using a Storyboard.

Name the App

2.) Before we do anything else let’s import the twitter framework. If you are unclear about how to do this you can check this tutorial.


Add a framework in Xcode 4 Tutorial

Add the Twitter Framework

Frameworks

3.) Our view is going to be very simple. I’m going to spice it up just a little by changing the background and adding an image, but the main thing is to just add the button.

Our view

3.) Open up the view controller header file and declare an IBAction.

- (IBAction)tweetAway;

That’s all we have to do in the header file.

4.) Now open up the view controller implementation file and import Twitter.h.

#import "ViewController.h"
#import "Twitter/Twitter.h"

5.) Next create implement the IBAction that we declared in the header file

- (IBAction)tweetAway 
{
    
    //This creates the tweet view that we'll present modally when a user clicks the button.
    TWTweetComposeViewController *tweetView = [[TWTweetComposeViewController alloc] init];
    
    //You set a default tweet to show in the view
    [tweetView setInitialText:@"Here's a tweet from your app"];
    
    //You can tweet an image
    [tweetView addImage:[UIImage imageNamed:@"twitter-logo.png"]];
    
    //You can also add a link to the tweet
    [tweetView addURL:[NSURL URLWithString:@"http://www.theappcodeblog.com"]];
    
    // Specify a block to be called when the user is finished. This block is not guaranteed
    // to be called on any particular thread.
    // All we are doing is dismissing the modal view when the tweet is sent.
    tweetView.completionHandler = ^(TWTweetComposeViewControllerResult result)
    {
        [self dismissModalViewControllerAnimated:YES];
    };
    
    //present tweet view modally
    [self presentModalViewController:tweetView animated:YES];
}

6.) Last thing is to hook up the IBAction to our button.

Link IBAction

7.) And finally run the app.

App Screen Shot

Click the Tweet button and the Tweet view will open modally.

App Screen Shot 2


That’s it, here’s the code.



iPhone App Development Tutorial – Customize table with modal view

This tutorial was originally published in March 2012

This is a very simple tutorial I did to answer a specific question I had a little while back. I was doing some cleaning up of my hard drive the other day and came across it. I figured I’d go ahead and upload it now since I never had, just in case someone else has a similar question at some point.

Honestly all this information is available in some of my other tutorials. In fact this is really just a quick combination of these three previous tutorials.

Add a UITableView to an existing view

Pass data from a modal view

Add a UINavigationController programmatically

Because this is kind of a rehash of information I am not going to get into great detail. Here’s what we’re going to do. Start with a View-Based app, add a UITableView and a UINavigationController to it. Add a button to the navigation bar which will take us to a new view that we will create and present modally. Add a UITextField to our new view and save the text added in it to a NSString and then add a button to take us back to the main view. In the main view we will retrieve the text we saved from the text field in the modal view and display it in the UITableView.

1.) Start by creating a new Single-View Application and name it ModalTableTutorial.

Create Single View Application

2.) In the app delegate implementation file we are going to add the navigation controller.


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
    self.window.rootViewController = navController;
    [self.window makeKeyAndVisible];
    return YES;
}

3.) Create a new UIViewController subclass named ModalViewController.

Here’s the header file with a UITextField, NSString and IBAction declared.

#import 

@interface ModalViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (strong, nonatomic) IBOutlet NSString *textValue;

@end

Here’s what I’ve added to the implementation file.

#import "ModalViewController.h"

@implementation ModalViewController
@synthesize textField = _textField;
@synthesize textValue = _textValue;

- (IBAction)goBackToView
{
    self.textValue = self.textField.text;
    [self dismissModalViewControllerAnimated:YES];
}

- (void) textFieldShouldReturn:(UITextField *)theTextField
{
    [theTextField resignFirstResponder];
}

4.) Here’s what the nib file for the new view looks like.

Modal View

Connect Outlets

Connect Outlets

5.) In the ViewController.h file we import the ModalViewController and declare an instance variable for it. Also create an IBOutlet for a UITableView.

#import 
#import "ModalViewController.h"

@interface ViewController : UIViewController

@property (nonatomic, strong) ModalViewController *modalViewController;
@property (nonatomic, weak) IBOutlet UITableView *myTableView;

@end

6.) Switch to ViewController.m and synthesize the variables, add a button to the navigation bar and create a method to push the modal view in to place.

#import "ViewController.h"

@implementation ViewController
@synthesize myTableView = _myTableView;
@synthesize modalViewController = _modalViewController;

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    UIBarButtonItem *modalViewButton = [[UIBarButtonItem alloc] initWithTitle:@"Modal View" style:UIBarButtonItemStyleBordered target:self action:@selector(goToModalView)];
    self.navigationItem.rightBarButtonItem = modalViewButton;
}

- (void)goToModalView
{
    if (self.modalViewController == nil) 
    {
        ModalViewController *temp = [[ModalViewController alloc] initWithNibName:@"ModalViewController" bundle:[NSBundle mainBundle]];
        self.modalViewController = temp;
    }
    
    [self presentModalViewController:self.modalViewController animated:YES];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.myTableView reloadData];
}

7.) Next add the table view delegate methods.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 5;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    [cell.textLabel setText:self.modalViewController.textValue];
    
    return cell;
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    
}

8.) Finally in the ViewController nib file add a UITableView connect it all up.

Connect Outlets

That’s about it. Run the app and try it out.

Go to the modal view and add some put something in the textField.

Simulator View 1

Then go back to the main view and it should populate the tableView.

Simulator View 3

Of course a few more things need to be added to really make this useful. For example I’m sure you don’t want to populate every row in the table with the same value, but you can add that bit yourself.

Here’s the code.



iPhone App Development Tutorial – Add a Navigation Controller Programmatically in Xcode 4.2

This tutorial was originally published in Dec 2011

Well it’s been 2 months since my last tutorial and honestly it feels even longer than that. I’ve been busy settling into a new job, but now I think I’m ready to get back to work on the The AppCode Blog and I’m vowing to make this upcoming year even better than the past. I’m getting back into the swing of things by revisiting an old and popular topic, adding a navigation controller to a view in a tab bar app.

Xcode 4.2

This is the third tutorial I have done on this topic. The first was with Xcode 3.x, the second was with Xcode 4.0 and now with Xcode 4.2 I find myself doing it yet again. The reason is Interface Builder keeps changing, and now with Xcode 4.2 we don’t even have a MainWindow.xib file. So this time we work on adding the navigation controller with code and bypass Interface Builder. This the way Apple recommends you do it and hopefully this method will work well into the future.

There’s really not much to it and it’s pretty easy to do. So here goes.

1.) Create a new Tabbed Application.

Create TabBar Application

2.) Name the Application NavigationTutorial and add it to source control if you wish.

Name the app

Add to Source Control

3.) In the app delegate implementation file go into the didFinishLaunchingWithOptions method and create a UINavigationController and init it with viewController1.

    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController1];

4.) When you set the viewControllers in the tabBarController, still in the same method, change the array to use the new navController instead of the viewController1.

    self.tabBarController.viewControllers = [NSArray arrayWithObjects:navController, viewController2, nil];

Here’s the full method.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    UIViewController *viewController1 = [[TACFirstViewController alloc] initWithNibName:@"TACFirstViewController" bundle:nil];
    UIViewController *viewController2 = [[TACSecondViewController alloc] initWithNibName:@"TACSecondViewController" bundle:nil];
    
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController1];
    
    self.tabBarController = [[UITabBarController alloc] init];

    self.tabBarController.viewControllers = [NSArray arrayWithObjects:navController, viewController2, nil];
    
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

5.) Now if you run the app you’ll see a navigation bar at the top of the first view controller.

Simulator Shot

6.) Next let’s create a new view to push into place. Create a new UIViewController subclass named TACSubView, check the box to create a XIB file at the same time.

Create UIViewController subclass

7.) Change the TACSubView.xib to stand out a little bit.

New View

8.) In TACFirstViewController.m create a UIBarButtonItem and add it to the navigationItem, I’m doing this in the initWithNibName method.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    UIBarButtonItem *nextButton = [[UIBarButtonItem alloc] initWithTitle:@"Next" style:UIBarButtonItemStyleBordered target:self action:@selector(goToSubView)];
    self.navigationItem.rightBarButtonItem = nextButton;
    
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.title = NSLocalizedString(@"First", @"First");
        self.tabBarItem.image = [UIImage imageNamed:@"first"];
    }
    return self;
}

9.) Create a simple goToSubView method to push to the new view into place.

- (void)goToSubView
{
    TACThirdViewController *thirdView = [[TACThirdViewController alloc] initWithNibName:@"TACThirdViewController" bundle:[NSBundle mainBundle]];
    
    [self.navigationController pushViewController:thirdView animated:YES];
}

10.) Run the app and try it out.

Simulator Shot3

Simulator Shot4

That’s all there is to it, short and sweet and pretty darn easy.

As always here’s the code.



iPhone App Development Tutorial – Check for Connectivity

This tutorial was originally published in Oct 2011

This will be a pretty quick and simple tutorial on having your app check for connectivity. This is one thing that Apple wants from your app, and one thing they’ll check for and possibly reject it for if it isn’t in there. You should add code to check for connectivity in your app if there is anything in it at all that needs to connect to the outside world.

Simulator Shot

1.) Create a Tab Bar Application and name it ConnectivityTutorial.

2.) Before we do anything else we need add a class to our project. It is the Reachability class and I got it straight from Apple’s Reachability sample application.

http://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html

Reachability.h

#import 
#import 

typedef enum 
{
	NotReachable = 0,
	ReachableViaWiFi,
	ReachableViaWWAN
} NetworkStatus;

#define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification"

@interface Reachability: NSObject
{
	BOOL localWiFiRef;
	SCNetworkReachabilityRef reachabilityRef;
}


//reachabilityWithAddress- Use to check the reachability of a particular IP address. 
+ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress;

//reachabilityForInternetConnection- checks whether the default route is available.  
//  Should be used by applications that do not connect to a particular host
+ (Reachability*) reachabilityForInternetConnection;

//reachabilityForLocalWiFi- checks whether a local wifi connection is available.
+ (Reachability*) reachabilityForLocalWiFi;

//Start listening for reachability notifications on the current run loop
- (BOOL) startNotifier;
- (void) stopNotifier;

- (NetworkStatus) currentReachabilityStatus;

//WWAN may be available, but not active until a connection has been established.
//WiFi may require a connection for VPN on Demand.
- (BOOL) connectionRequired;

@end

Reachability.m

#import 
#import 
#import 
#import 
#import 
#import 

#import 

#import "Reachability.h"



@implementation Reachability

static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
{
//	NSLog(@"ReachabilityCallback");
	#pragma unused (target, flags)
	NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback");
	NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback");

	//We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively
	// in case someone uses the Reachablity object in a different thread.
	NSAutoreleasePool* myPool = [[NSAutoreleasePool alloc] init];
	
	Reachability *noteObject = (Reachability *) info;
	// Post a notification to notify the client that the network reachability changed.
	[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject];
	
	[myPool release];
}

- (BOOL) startNotifier
{
	BOOL retVal = NO;
	SCNetworkReachabilityContext	context = {0, self, NULL, NULL, NULL};
	if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context))
	{
		if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
		{
			retVal = YES;
		}
	}
	return retVal;
}

- (void) stopNotifier
{
	if(reachabilityRef!= NULL)
	{
		SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
	}
}



+ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress;
{
	SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
	Reachability* retVal = NULL;
	if(reachability!= NULL)
	{
		retVal= [[[self alloc] init] autorelease];
		if(retVal!= NULL)
		{
			retVal->reachabilityRef = reachability;
			retVal->localWiFiRef = NO;
		}
	}
	return retVal;
}

+ (Reachability*) reachabilityForInternetConnection;
{
	struct sockaddr_in zeroAddress;
	bzero(&zeroAddress, sizeof(zeroAddress));
	zeroAddress.sin_len = sizeof(zeroAddress);
	zeroAddress.sin_family = AF_INET;
	return [self reachabilityWithAddress: &zeroAddress];
}

+ (Reachability*) reachabilityForLocalWiFi;
{
	struct sockaddr_in localWifiAddress;
	bzero(&localWifiAddress, sizeof(localWifiAddress));
	localWifiAddress.sin_len = sizeof(localWifiAddress);
	localWifiAddress.sin_family = AF_INET;
	// IN_LINKLOCALNETNUM is defined in  as 169.254.0.0
	localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
	Reachability* retVal = [self reachabilityWithAddress: &localWifiAddress];
	if(retVal!= NULL)
	{
		retVal->localWiFiRef = YES;
	}
	return retVal;
}

#pragma mark Network Flag Handling

- (NetworkStatus) localWiFiStatusForFlags: (SCNetworkReachabilityFlags) flags
{
	BOOL retVal = NotReachable;
	if((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect))
	{
		retVal = ReachableViaWiFi;	
	}
	return retVal;
}

- (NetworkStatus) networkStatusForFlags: (SCNetworkReachabilityFlags) flags
{
	if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
	{
		// if target host is not reachable
		return NotReachable;
	}

	BOOL retVal = NotReachable;
	
	if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
	{
		// if target host is reachable and no connection is required
		//  then we'll assume (for now) that your on Wi-Fi
		retVal = ReachableViaWiFi;
	}
	
	
	if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
		(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
	{
			// ... and the connection is on-demand (or on-traffic) if the
			//     calling application is using the CFSocketStream or higher APIs

			if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
			{
				// ... and no [user] intervention is needed
				retVal = ReachableViaWiFi;
			}
		}
	
	if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
	{
		// ... but WWAN connections are OK if the calling application
		//     is using the CFNetwork (CFSocketStream?) APIs.
		retVal = ReachableViaWWAN;
	}
	return retVal;
}

- (BOOL) connectionRequired;
{
	NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef");
	SCNetworkReachabilityFlags flags;
	if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
	{
		return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
	}
	return NO;
}

- (NetworkStatus) currentReachabilityStatus
{
	NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef");
	NetworkStatus retVal = NotReachable;
	SCNetworkReachabilityFlags flags;
	if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
	{
		if(localWiFiRef)
		{
			retVal = [self localWiFiStatusForFlags: flags];
		}
		else
		{
			retVal = [self networkStatusForFlags: flags];
		}
	}
	return retVal;
}

- (void) dealloc
{
	[self stopNotifier];
	if(reachabilityRef!= NULL)
	{
		CFRelease(reachabilityRef);
	}
	[super dealloc];
}

@end

Just go ahead and add those files to the project. You can do this by adding a new file that is a subclass of NSObject and then copy and paste the above to the respective files.

This Reachability class uses the SystemConfiguration.framework, so you’ll need to add that to the project.

3.) We don’t need to do anything to the user interface. We’ll just be working in the app delegate files. First open up the app delegate header file. We’re going to add the Reachability class using the @class directive.

@class Reachability;

All this does is tell the compiler that Reachability is in fact a class. We won’t import Reachability until the implementation file when we actually referenced.

Then we’ll create an iVar of the Reachability class.

	Reachability *internetReach;

That’s it for the header file, here’s what the full file looks like.

#import 

@class Reachability;

@interface ConnectivityTutorialAppDelegate : NSObject  
{
    Reachability *internetReach;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@end

4.) Now we’ll open the implementation file. Here we will need to import the Reachability class.

#import "ConnectivityTutorialAppDelegate.h"
#import "Reachability.h"

The rest of our work will be done in the applicationDidBecomeActive method.

5.) First we’ll implement our iVars.

    internetReach = [[Reachability reachabilityForInternetConnection] retain];
	[internetReach startNotifier];

6.) Then we’ll set the NetworkStatus variable created in Reachability.

    NetworkStatus netStatus = [internetReach currentReachabilityStatus];

7.) And finally we’ll use the netStatus in a switch block.

    switch (netStatus)
    {            
        case ReachableViaWWAN:
        {
            break;
        }
        case ReachableViaWiFi:
        {
            break;
        }
        case NotReachable:
        {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"We are unable to make a internet connection at this time. Some functionality will be limited until a connection is made." delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
            [alert show];	
            [alert release];
            break;
        }
            
    }

8.) That handles the check when the app becomes active. We can also add a notification for when ever the reachability changes. Just add this line to the didFinishLaunchingWithOptions method in the app delegate.

    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(reachabilityChanged:) name: kReachabilityChangedNotification object: nil];

And then add the reachability method to the same file.

//Called by Reachability whenever status changes.
- (void) reachabilityChanged: (NSNotification* )note
{
	Reachability* curReach = [note object];
	NSParameterAssert([curReach isKindOfClass: [Reachability class]]);
	
    NetworkStatus netStatus = [curReach currentReachabilityStatus];
    switch (netStatus)
    {
        case ReachableViaWWAN:
        {
            break;
        }
        case ReachableViaWiFi:
        {
            break;
        }
        case NotReachable:
        {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"We are unable to make a internet connection at this time. Some functionality will be limited until a connection is made." delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
            [alert show];	
            [alert release];
            break;
        }
    }
}

It can become annoying to send a message overtime the reachability changes if you are going in and out of service, so you can decide how you want to handle this.

8.) Now how do we test this? If you are testing in the simulator you’ll need to disconnect the computer from the internet to test for a failure to connect. If you are using wifi, just turn Airport off.

9.) That’s it. We put this code in our app delegate to check for connectivity when the app becomes active. You could also add this to individual views, for example you could add it to a view controller that displays a UIWebView, or to one that might use a web service. Then of course you can handle the result however you wish.

Full Size Simulator Shot

Here’s the code.



iPhone App Development Tutorial – Core Data relationships part 2

This tutorial was originally published in Sept 2011

In this tutorial we will look at creating a one to many relationship in core data. It’s really very easy and not that much different than creating the one to one relationship. The difference comes in how the relationship allows the entities to interact with each other. In part one we had two entities, Fruit and Source. These two entities had a one to one relationship to each other. For each Fruit there was only one Source, and the inverse was true as well, for each Source there was only one Fruit. Now we are going to create two new entities, Artist and Album. Each artist can have multiple albums, but each album can have only one artist. We’ll just pretend that no artists ever collaborate on albums together for the sake of this tutorial :)

We’re going to use the app that I / we created in the Core Data duplicate entities tutorial. In that tutorial we set up an app that did not allow us to enter the same Artist twice. It seems like a good app to start with, only I’m even going to take care of a few more things for us To save time. I’ve set up the second tab with a UITableView and hooked it up with the view controller. This table view will hold the the list of artists that have been saved in the app. I’ve also created a new view with view controllers that will be used to enter Album information for an artist.

Screen Shot

What is going to happen is when we go to the second tab a search will return all Artists saved and display them in the UITableView. We can then click on an Artist and go to the enter Album informations view. The Artist object will be passed to this new view so that when we enter info for an album and save it, it will be saved with a relationship to the artist we selected. In this way we can create many albums for an artist, but an album can only have one artist. Thus the one to many relationship.

Here’s the code we’ll start with.

Let’s get going.

1.) If you run the app right now you can see the second tab with a hard coded string populating each table view cell.

Select An Artist Table Hardcoded

And though there’s no way to get to the new view yet, here is what it looks like.

Enter Album Info View

So that’s what we are starting with.

2.) The first thing we are going to do is a search in SecondViewController and populate our table with the Artists returned. Open up SecondViewController.h and add an NSMutableArray to hold our Artists and declare an ivar for NSMananagedObjectContext.

#import 

@interface SecondViewController : UIViewController 
{
    NSMutableArray *artistsArray;
}

@property (nonatomic, retain) IBOutlet UITableView *artistTable;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@end

Then go to the implementation file and synthesize the managedObjectContext ivar.

#import "SecondViewController.h"

@implementation SecondViewController

@synthesize artistTable;
@synthesize managedObjectContext;

3.) Go to app delegate header file and import SecondViewController, then declare an ivar of it and

#import 
#import "FirstViewController.h"
#import "SecondViewController.h"

@interface CoreDataTutorial5AppDelegate : NSObject  
{
    
@private
    NSManagedObjectContext *managedObjectContext;
    NSManagedObjectModel *managedObjectModel;
    NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
@property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

finally synthesize it in the app delegate implementation file.

#import "CoreDataTutorial5AppDelegate.h"

@implementation CoreDataTutorial5AppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController, secondViewController;

Then open MainWindow.xib and connect the secondViewController ivar to the SecondViewController under Tab Bar Controller.

Now that they are connected let’s set the managed object context in secondViewController. Do this in the app delegate implementation file application:didFinishLaunchingWithOptions method.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.firstViewController.managedObjectContext = self.managedObjectContext;
    self.secondViewController.managedObjectContext = self.managedObjectContext;
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

4.) We’ve now passed the managedObjectContext to SecondViewController and we can implement the search method. We’re going to implement the viewWillAppear method and do our search there.

- (void)viewWillAppear:(BOOL)animated
{
    NSLog(@"viewWillAppear");
    NSError *error;
    
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    artistsArray = [[NSMutableArray alloc] initWithArray:fetchedObjects];
    
    [fetchRequest release];
    [artistTable reloadData];

}

Then we’ll use our array to set the number of rows in the section.

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

And finally we’ll populate our table view cells with the Artists name. But first import the Artist object.

#import "SecondViewController.h"
#import "Artist.h"

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
    
    // Configure the cell...
    Artist *artist = [artistsArray objectAtIndex:indexPath.row];
    cell.textLabel.text = artist.artistName;
    return cell;
}

Run it and make sure everything is working up to this point.

Select an Artist Table

5.) Now that we have retrieved the Artists and are displaying them, let’s add the new view and pass the Artist to it when selecting a cell in the table view. We’ll need to import EnterAlbumInfoViewController to the SecondViewController header file and declare an ivar of it.

#import 
#import "EnterAlbumInfoViewController.h"

@interface SecondViewController : UIViewController 
{
    NSMutableArray *artistsArray;
}

@property (nonatomic, retain) IBOutlet UITableView *artistTable;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@property (nonatomic, retain) EnterAlbumInfoViewController *enterAlbumInfoViewController;

@end

Then synthesize it in the implementation file.

#import "SecondViewController.h"
#import "Artist.h"

@implementation SecondViewController

@synthesize artistTable;
@synthesize managedObjectContext;
@synthesize enterAlbumInfoViewController;

Now go to the didSelectRowAtIndexPath method and present the new view modally, at the same time we will pass the Artist to it along with the managedObjectContext.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.enterAlbumInfoViewController == nil) 
    {
        EnterAlbumInfoViewController *temp = [[EnterAlbumInfoViewController alloc] initWithNibName:@"EnterAlbumInfoViewController" bundle:[NSBundle mainBundle]];
        self.enterAlbumInfoViewController = temp;
        [temp release];
    }
    
    Artist *artist = [artistsArray objectAtIndex:indexPath.row];
    self.enterAlbumInfoViewController.artistNameString = artist.artistName;
    self.enterAlbumInfoViewController.artist = artist;
    self.enterAlbumInfoViewController.managedObjectContext = self.managedObjectContext;
    
    [self presentModalViewController:self.enterAlbumInfoViewController animated:YES];
}

I forgot to implement a method for the cancel button so let’s do that real quick. This goes in EnterAlbumInfoViewController.

- (IBAction)cancelView
{
    [self dismissModalViewControllerAnimated:YES];
}

6.) Before we go any further we need to add the Album Entity to our Data Model. Open up the visual data model editor by clicking on DataModel.xcdatamodel. Add an Entity named Album with three attributes. Here’s how it should look when done.

Album Entity

Now we need to create the relationships. Select Artist and add a relationship to Album named album. This relationship will be a To-Many Relationship so check that box. This means each artist can have many albums. Also make this Optional since an artist doesn’t need to have an album when we create the artist.

album relationship

Then select Album and create the inverse. This time uncheck the optional checkbox and make sure the To-Many Relationship box is not checked. Each Album will have just one artist.

artist relationship

When done create the Album NSManagedObject subclass, and recreate the Artist object. You’ll also need to change the name of the sqlite db on the back end of the Core Data. I just changed the name in this line of the persistentStoreCoordinator method of the app delegate.

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTutorial63.sqlite"];

After doing this you’ll need to re-enter the Artist info.

7.) Now we should be ready to save the Album info. Implement the saveAlbumInfo in EnterAlbumInfoViewController. You’ll need to import “Album.h” too.

- (IBAction)saveAlbumInfo
{
    NSLog(@"saveAlbumInfo");
    
    
    Album *album = (Album *)[NSEntityDescription insertNewObjectForEntityForName:@"Album" inManagedObjectContext:managedObjectContext];
    album.albumName = self.albumNameTextField.text;
    album.albumReleaseDate = self.albumReleaseDateTextField.text;
    album.albumGenre = self.albumGenreTextField.text;
    album.artist = self.artist;
        
    NSError *error;
    
    // here's where the actual save happens, and if it doesn't we print something out to the console
    if (![managedObjectContext save:&error])
    {
        NSLog(@"Problem saving: %@", [error localizedDescription]);
    }
    
    // **** log objects currently in database ****
    // create fetch object, this object fetch's the objects out of the database
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Album" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    
    for (NSManagedObject *info in fetchedObjects)
    {
        NSLog(@"Album name: %@", [info valueForKey:@"albumName"]);
        NSLog(@"Album age: %@", [info valueForKey:@"albumReleaseDate"]);
        NSLog(@"Album gender: %@", [info valueForKey:@"albumGenre"]);
        
    }
    [fetchRequest release];
    
    [self dismissModalViewControllerAnimated:YES];
}

8.) Go ahead and run the app and test it out to make sure everything is working up to this point.

9.) Now let’s add a third tab that will allow us to search for an artist and display all of their albums. Add a Tab Bar Item to the Tab bar and the view should look like this.

Search View

Where it has a UISearchBar and a UITableView.

10.) We’ll have to pass the managed object context to this view just as we did with the first and second view controllers. Go to the app delegate header file and import the new view controller. Then create an ivar for it.

#import 
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "SearchViewController.h"

@interface CoreDataTutorial5AppDelegate : NSObject  
{
    
@private
    NSManagedObjectContext *managedObjectContext;
    NSManagedObjectModel *managedObjectModel;
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
    
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
@property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;
@property (nonatomic, retain) IBOutlet SearchViewController *searchViewController;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

11.) Synthesize our new ivar in the implementation file. and pass the managed object context in the didFinishLaunchingWithOptions method.

#import "CoreDataTutorial5AppDelegate.h"

@implementation CoreDataTutorial5AppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController, secondViewController, searchViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.firstViewController.managedObjectContext = self.managedObjectContext;
    self.secondViewController.managedObjectContext = self.managedObjectContext;
    self.searchViewController.managedObjectContext = self.managedObjectContext;
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

12.) Open MainWindow.xib and connect the searchViewController outlet to the SearchViewController in the Tab Bar Controller.

13.) Open SearchViewController.h and declare two NSArray ivars. One to hold our fetchedObjects (the search results), and one to hold the list of albums from the artiste returned by our search. Declare an ivar for Artist and import Artist into the header file. Create IBOutlets fort our table view and search bar and finally create ivars for the fetchedResultsController and the managedObjectContext.

#import 
#import "Artist.h"

@interface SearchViewController : UIViewController 
{
    NSArray *fetchedObjects;
    NSArray *albumArray;
    Artist *artist;
}

@property (nonatomic, retain) IBOutlet UITableView *searchResultsTableView;
@property (nonatomic, retain) IBOutlet UISearchBar *mySearchBar;
@property (nonatomic, retain) NSFetchedResultsController    *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

@end

14.) Now open up SearchViewController.m and import both Artist and Album and synthesize all our ivars.

#import "SearchViewController.h"
#import "Artist.h"
#import "Album.h"

@implementation SearchViewController

@synthesize searchResultsTableView, mySearchBar, fetchedResultsController, managedObjectContext;

Then implement viewWillAppear. We’ll use this to clear our search bar.

- (void)viewWillAppear:(BOOL)animated
{
    mySearchBar.text = @""; 
}

15.) Now let’s implement the viewDidLoad method. We’ll set up our core data elements here.

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //    NSFetchRequest needed by the fetchedResultsController
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    
    //    NSSortDescriptor tells defines how to sort the fetched results
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"artistName" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    //    fetchRequest needs to know what entity to fetch
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    [sortDescriptors release];
    [sortDescriptor release];
    
    fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
    
    [fetchRequest release];
}

16.) Next implement the searchBarButtonClicked method. This is where the search takes place and we’ll set the albumArray here.

- (void) searchBarSearchButtonClicked:(UISearchBar *)theSearchBar
{
    NSLog(@"searchBarSearchButtonClicked");
    
    NSError *error = nil;
    
    // We use an NSPredicate combined with the fetchedResultsController to perform the search
    if (self.mySearchBar.text !=nil)
    {
        NSPredicate *predicate =[NSPredicate predicateWithFormat:@"artistName  contains[cd] %@", self.mySearchBar.text];
        [fetchedResultsController.fetchRequest setPredicate:predicate];
    }
    
    if (![[self fetchedResultsController] performFetch:&error])
    {
        // Handle error
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }
    
    // this array is just used to tell the table view how many rows to show
    fetchedObjects = fetchedResultsController.fetchedObjects;
    
    // Handle the case where search returns nothing
    if ([fetchedObjects count] > 0) 
    {
        artist = [fetchedObjects objectAtIndex:0];
        
        NSSet *artistSet = artist.album;
        albumArray = [artistSet allObjects];
    }
    else
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Search Results" message:@"Your search produced no results, please try again." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        [alert show];
        [alert release];

    }
    
    // dismiss the search keyboard
    [mySearchBar resignFirstResponder];
    
    // reload the table view
    [searchResultsTableView reloadData];
}

17.) Use the albumArray to set the numberRowsInSection.

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

18.) And finally implement the cellForRowAtIndexPath method like this to display the artist and their albums.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }
    
    // Configure the cell...
    cell.textLabel.text = artist.artistName;
    
    NSSet *artistSet = artist.album;
    NSArray *objectsArray = [artistSet allObjects];

    for (int i = 0; i < [objectsArray count]; i++) 
    {
        Album *album = [objectsArray objectAtIndex:indexPath.row]; 
        cell.detailTextLabel.text = album.albumName;

    }
    
    return cell;
}

Here's what it looks like when done.

Finished Screen Shot

And here's the code.



iPhone app Development Tutorial – Core Data Duplicate Entities

This tutorial was originally published in Sept 2011

In Core Data there is no such thing as duplicate entries, as least as far as Core Data is concerned. Not in the sense of looking at it from a database point of view. Which makes sense because Core Data is not a database it is an object graph management system. But what if we don’t want to have a duplicate Entity tendered into our app using Core Data? There are a few options at your disposal, I’m going to show you one here. I am not saying it’s the best option, just presenting a way you might be able to handle things in a simple app.

Just so we don’t waste a lot of time with basic UI stuff you can get the beginnings of an app here.

This is what it will look like.

Core Data Duplicate Entities

Just three text fields and a button all of which are connected up to the view controller. SO now we’re ready to start in on the Core Data stuff. If you’ve been doing the other Core Data tutorials some of this will just be review.

1.) Open the app up in Xcode and add the Core Data framework.

Add Core Data Framework

2.) Then add an import to CoreDataTutorial5-Prefix.pch, adding Core Data.

#import 

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif

#ifdef __OBJC__
    #import 
    #import 
    #import 
#endif

3.) Right click on Supporting Files and add a Data Model. I named mine DataModel.

Add Core Data Model

4.) Add an Entity named Artist with three Attributes named artistAge, artistGender & artistName, each of which is a String.

Set up Entity and Attributes

5.) Create a new NSManagedObject subclass of the Artist Entity.

Add NSManagedObject subclass

If you are unsure of how to do any of these first 5 steps and need more details you can check out one of these tutorials.

http://www.theappcodeblog.com/2011/06/09/iphone-core-data-tutorial2-add-core-data-to-a-view-based-app/

http://www.theappcodeblog.com/2011/06/30/iphone-development-tutorial-add-core-data-to-a-tab-bar-application-and-save-user-data/

6.) Open up the app delegate header file and add the core data stuff. While we’re in here we’re also going to import the FirstViewController and create an ivar of it.

#import 
#import "FirstViewController.h"

@interface CoreDataTutorial5AppDelegate : NSObject  
{
    
@private
    NSManagedObjectContext *managedObjectContext;
    NSManagedObjectModel *managedObjectModel;
    NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;


@end

7.) Now add the core data methods and view controller ivar to the app delegate implementation file.

#import "CoreDataTutorial5AppDelegate.h"

@implementation CoreDataTutorial5AppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.firstViewController.managedObjectContext = self.managedObjectContext;
    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

/**
 Returns the URL to the application's Documents directory.
 */
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

- (void)saveContext
{
    
    NSError *error = nil;
    NSManagedObjectContext *objectContext = self.managedObjectContext;
    if (objectContext != nil)
    {
        if ([objectContext hasChanges] && ![objectContext save:&error])
        {
            // add error handling here
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

#pragma mark -
#pragma mark Core Data stack

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext
{
    
    if (managedObjectContext != nil)
    {
        return managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return managedObjectContext;
}

/**
 Returns the managed object model for the application.
 If the model doesn't already exist, it is created from the application's model.
 */
- (NSManagedObjectModel *)managedObjectModel
{
    if (managedObjectModel != nil)
    {
        return managedObjectModel;
    }
    managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
    
    return managedObjectModel;
}

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    
    if (persistentStoreCoordinator != nil)
    {
        return persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTutorial5.sqlite"];
    
    NSError *error = nil;
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return persistentStoreCoordinator;
}

I did not include the rest of the app delegate methods but they are there still.

8.) Open up FirstViewController.h and add ivars for NSManagedObjectContext, and NSFetchedResultsController.

#import 

@interface FirstViewController : UIViewController 
{

}

@property (nonatomic, retain) IBOutlet UITextField *artistNameTextField;
@property (nonatomic, retain) IBOutlet UITextField *artistAgeTextField;
@property (nonatomic, retain) IBOutlet UITextField *artistGenderTextField;

@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;
@property (nonatomic, retain) NSFetchedResultsController    *fetchedResultsController;

- (IBAction)saveArtistInformation;

@end

9.) Then go to the implementation file and synthesize our ivars and fill out the viewDidLoad method, we’re also going to import our custom managed object Artist.

#import "FirstViewController.h"
#import "Artist.h"

@implementation FirstViewController

@synthesize artistAgeTextField, artistNameTextField, artistGenderTextField;
@synthesize managedObjectContext, fetchedResultsController;

- (void)viewDidLoad
{
    [super viewDidLoad];
    //    NSFetchRequest needed by the fetchedResultsController
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    
    //    NSSortDescriptor tells defines how to sort the fetched results
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"artistName" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    //    fetchRequest needs to know what entity to fetch
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    [sortDescriptors release];
    [sortDescriptor release];
    
    fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
    
    [fetchRequest release];

}

10.) At this point we have every set up except the saveArtistInformation method. Nothing has been any different than the other Core Data apps we created. So now let’s work on the save method. The way we are going to handle the duplicates is a with a simple search and then an if block. First we;ll search Core Data for an Artist Entity that has the same artistName as the text we have entered into the Artist Name UITextField. If an artist is found the returned fetchedObjects array will have a size of more than 0 and no save will take place. If no artist is found with that name the array will have a size of less than 1 and we’ll save the new Artist. Here’s the full method. The first part just sets up the search portion.

- (IBAction)saveArtistInformation
{
    NSLog(@"saveArtistInformation");
    
    // search to see if entity already exists
    NSError *error = nil;
    
    // We use an NSPredicate combined with the fetchedResultsController to perform the search
    if (self.artistNameTextField.text !=nil)
    {
        NSPredicate *predicate =[NSPredicate predicateWithFormat:@"artistName  contains[cd] %@", self.artistNameTextField.text];
        [fetchedResultsController.fetchRequest setPredicate:predicate];
    }
    
    if (![[self fetchedResultsController] performFetch:&error])
    {
        // Handle error
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }
    
    // this array is just used to tell the table view how many rows to show
    if ([fetchedResultsController.fetchedObjects count] < 1)
    {
        NSLog(@"Found that Artist already in Core Data");
        Artist *artist = (Artist *)[NSEntityDescription insertNewObjectForEntityForName:@"Artist" inManagedObjectContext:managedObjectContext];
        artist.artistAge = self.artistAgeTextField.text;
        artist.artistGender = self.artistGenderTextField.text;
        artist.artistName = self.artistNameTextField.text;
        
        
        // here's where the actual save happens, and if it doesn't we print something out to the console
        if (![managedObjectContext save:&error])
        {
            NSLog(@"Problem saving: %@", [error localizedDescription]);
        }
    }
    else
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Information" message:@"That Artist already exists" delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
        [alert show];
        [alert release];
    }

    
    
    // **** log objects currently in database ****
    // create fetch object, this object fetch's the objects out of the database
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Artist" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    
    for (NSManagedObject *info in fetchedObjects)
    {
        NSLog(@"Artist name: %@", [info valueForKey:@"artistName"]);
        NSLog(@"Artist age: %@", [info valueForKey:@"artistAge"]);
        NSLog(@"Artist gender: %@", [info valueForKey:@"artistGender"]);
        
    }
    [fetchRequest release];

}

11.) That's it, you can try it out and see how it works. I've added an alert to notify the user that the save isn't taking place.

Core Data no duplicates allowed

Next time we'll expand on this app when we want to start looking at one-to-many relationships.

As always, here's the code.



iPhone Development – Core Data relationships tutorial part 1

I’m going to start a short series on Core Data relationships and maybe throw in some general Core Data stuff too. Here in part one we’re just going to set our app up with core data and add two entities with a simple one to one relationship between them. A one to one relationship means that for every Fruit there will be one source and in our case here the reverse it true too, for every source there is one fruit.

Core Data Relationships

1.) Create a new Tab Bar Application named CoreDataRelationshipsTutorial.

2.) Change FirstView.xib so it looks similar to this.
FirstView.xib

3.) Add the core data framework to the app.
Add Core Data Framework

4.) Right click on Supporting Files and select New File, then choose Core Data select Data Model and hit Next. I just accepted the default name of Model and clicked save.
Add Data Model

5.) Select Model.xcdatamodeld and the visual editor will open. Click Add Entity and name it Fruit. Add an Attribute named fruitName of type String. Add another Entity named Source with an Attribute sourceName, which will also be of type String.

6.) Select Fruit and then click the plus symbol under Relationships. Name the relationship fruitSource. Set the destination to Source, there will be no inverse yet. In the relationship data model inspector uncheck the Optional checkbox. In the delete rule select Cascade.
Edit Data Model

7.) Now select Source and add a relationship named sourceFruit. Destination should be Fruit and set the inverse to artistCareer. Uncheck the Optional checkbox again.
Set up relationships

8.) Select Fruit under ENTITIES and then go under the file menu up top and select New File. Choose Core Data and NSManagedObject subclass,, click Next. Keep the default location and click Create.
Create Managed Objects

Repeat this same process after selecting Source under ENTITIES.

You should now see your new objects listed under Supporting Files.
xcode view

9.) Open up CoreDataRelationshipsTutorial-Prefix.pch and add an import for CoreDate. This saves us from having to import it into every file that will use it.

#import 

#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."
#endif

#ifdef __OBJC__
    #import 
    #import 
    #import 
#endif

10.) Now let’s add all the necessary Core Data code to the app delegate files.

First the header file. Import our FirstViewController, then declare private instance variables for our NSManagedObjectContext, NSManagedObjectModel and NSPersistentStoreCoordinator. Create an IBOutlet with out FirstViewController, and declare two methods that we’ll implement.

#import 
#import "FirstViewController.h"

@interface CoreDataRelationshipsTutorialAppDelegate : NSObject  
{

@private
    NSManagedObjectContext *managedObjectContext;
    NSManagedObjectModel *managedObjectModel;
    NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;

@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;

@end

11.) Now open the app delegate implementation file. Synthesize our firstViewController, then set it’s managedObjectContext to the one created in the app delegate. You may see an error on the line that sets the managedObjectContext because we haven’t set that up in FirstViewController yet.

#import "CoreDataRelationshipsTutorialAppDelegate.h"

@implementation CoreDataRelationshipsTutorialAppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize firstViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    firstViewController.managedObjectContext = self.managedObjectContext;

    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
}

Implement all these methods.

/**
 Returns the URL to the application's Documents directory.
 */
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

- (void)saveContext
{
    
    NSError *error = nil;
    NSManagedObjectContext *objectContext = self.managedObjectContext;
    if (objectContext != nil)
    {
        if ([objectContext hasChanges] && ![objectContext save:&error])
        {
            // add error handling here
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

#pragma mark -
#pragma mark Core Data stack

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext
{
    
    if (managedObjectContext != nil)
    {
        return managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return managedObjectContext;
}

/**
 Returns the managed object model for the application.
 If the model doesn't already exist, it is created from the application's model.
 */
- (NSManagedObjectModel *)managedObjectModel
{
    if (managedObjectModel != nil)
    {
        return managedObjectModel;
    }
    managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
    
    return managedObjectModel;
}

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    
    if (persistentStoreCoordinator != nil)
    {
        return persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataTabBarTutorial.sqlite"];
    
    NSError *error = nil;
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    } 
    
    return persistentStoreCoordinator;
}

12.) Open up FirstViewController.h and let’s set it up with the necessary code and instance variables.

#import 

@interface FirstViewController : UIViewController 
{
    
    NSFetchedResultsController  *fetchedResultsController;
    NSManagedObjectContext      *managedObjectContext;

}

@property (nonatomic, retain) NSString *fruitNameString;
@property (nonatomic, retain) NSString *fruitSourceString;


@property (nonatomic, retain) NSFetchedResultsController    *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext        *managedObjectContext;

- (IBAction) saveData;

@end

13.) Now for the implementation file. Let’s import our managed objects.

#import "FirstViewController.h"
#import "Fruit.h"
#import "Source.h"

Then synthesize the instance variables.

@synthesize fetchedResultsController, managedObjectContext;
@synthesize fruitNameString, fruitSourceString;

Let’s go ahead and set the values of those two strings in ViewDidLoad.

- (void)viewDidLoad
{
    [super viewDidLoad];
    fruitNameString = [[NSString alloc] initWithString:@"Apple"];
    fruitSourceString = [[NSString alloc] initWithString:@"Apple Tree"];
}

14.) Implement the saveData method.

- (IBAction) saveData
{
    NSLog(@"saveData");
    Fruit *fruit = (Fruit *)[NSEntityDescription insertNewObjectForEntityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
    fruit.fruitName = fruitNameString;
    Source *source = (Source *)[NSEntityDescription insertNewObjectForEntityForName:@"Source" inManagedObjectContext:managedObjectContext];
    source.sourceName = fruitSourceString;
    
    // Because we set the relationship fruitSource as not optional we must set the source here
    fruit.fruitSource = source;
    
    NSError *error;
    
    // here's where the actual save happens, and if it doesn't we print something out to the console
    if (![managedObjectContext save:&error])
    {
        NSLog(@"Problem saving: %@", [error localizedDescription]);
    }

    [fruit release];
    [source release];

    // **** log objects currently in database ****
    // create fetch object, this object fetch's the objects out of the database
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    
    for (NSManagedObject *info in fetchedObjects)
    {
        NSLog(@"Fruit name: %@", [info valueForKey:@"fruitName"]);
        Source *tempSource = [info valueForKey:@"fruitSource"];
        NSLog(@"Source name: %@", tempSource.sourceName);
        [tempSource release];
    }
    [fetchRequest release];
}

15.) Release our objects in the dealloc method and set them to nil in viewDidUnload.

- (void)viewDidUnload
{
    [super viewDidUnload];
    fetchedResultsController = nil;
    managedObjectContext = nil;
    fruitNameString = nil;
    fruitSourceString = nil;
}

- (void)dealloc
{
    [fetchedResultsController release];
    [managedObjectContext release];
    [fruitNameString release];
    [fruitSourceString release];

    [super dealloc];
}

16.) Open up FirstView.xib and connect the UIButton to our saveData IBAction.

17.) Open up MainWindow.xib, select the app delegate and connect firstViewController outlet to FirstViewController under the Tab Bar Controller.
Link view controller

18.) Now you can run the app and hit the Save Data button. Look in the console to see the results of the fetch.
console

The important things to note from this tutorial are these.

When we created the relationship from Fruit to Source we made it so that it was not optional. Therefore during our saveData method we had to set the fruitSource to something.

    Fruit *fruit = (Fruit *)[NSEntityDescription insertNewObjectForEntityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
    fruit.fruitName = fruitNameString;
    Source *source = (Source *)[NSEntityDescription insertNewObjectForEntityForName:@"Source" inManagedObjectContext:managedObjectContext];
    source.sourceName = fruitSourceString;

Try commenting out that last line

//    source.sourceName = fruitSourceString;

And then running it again. What happens? Crash and burn. Because the relationship is not optional you must set the sourceName.

You can also see from the block of code above that we use reuse the same managedObjectContext to create both of the managed objects. Then we set the values and just saved the context. Doing this saved both objects (entities in Core Data).

Another thing to take note of happens in the fetch process. You notice that we only fetch the Fruit entity. But because of the relationship between Fruit and Source we can access the Source entity. We don’t need to do a separate fetch on Source.

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Fruit" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];
    NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    
    for (NSManagedObject *info in fetchedObjects)
    {
        NSLog(@"Fruit name: %@", [info valueForKey:@"fruitName"]);
        Source *tempSource = [info valueForKey:@"fruitSource"];
        NSLog(@"Source name: %@", tempSource.sourceName);
        [tempSource release];
    }

Okay that does it for this tutorial. Next time we will look at a one to many relationship.

As always here’s the code.



iPhone Development Tutorial – pass data from a modal view

This tutorial was originally published in Aug 2011

I had someone ask in the comments how you would pass data from a modal view, it’s really not any different than any other view. Here’s a tutorial on doing that, and then passing that data between tabs as well.

Start with the modal app we built in the last tutorial. You can get the code here.

Simulator

1.) Open the app. We are going to change the user interface a bit. Open the FirstView.xib and replace the default labels with two of our own. One to present a username and one to present a password.

First View

2.) Now open TestModalViewController.xib. We are going to replace the label here with two labels and two text fields to accept a username and a password.

Second View

3.) Open up TestModalViewController,h and declare two instance variables for our text fields, be sure to make them IBOutlets. We are also going to declare two NSStrings to hold the values from our text fields.

#import 

@interface TestModalViewController : UIViewController 
{
    UITextField *usernameTextField;
    UITextField *passwordTextField;
    
    NSString *usernameValue;
    NSString *passwordValue;
}

@property (nonatomic, retain) IBOutlet UITextField *usernameTextField;
@property (nonatomic, retain) IBOutlet UITextField *passwordTextField;
@property (nonatomic, retain) NSString *usernameValue;
@property (nonatomic, retain) NSString *passwordValue;;

- (IBAction)goBackToView;

@end

4.) Open up TestModalViewController.m and synthesize our variables.

#import "TestModalViewController.h"

@implementation TestModalViewController

@synthesize usernameTextField;
@synthesize passwordTextField;
@synthesize usernameValue;
@synthesize passwordValue;

5.) Still in the TestModalViewController.m file we are going to set the string variables with the text from the text fields. We’ll do this in the goBackToView method.

- (IBAction)goBackToView
{
    self.usernameValue = usernameTextField.text;
    self.passwordValue = passwordTextField.text;
    [self dismissModalViewControllerAnimated:YES];
}

6.) Open up FirstViewController.h and declare two UILabel instance variables and make them IBOutlets.

#import 
#import "TestModalViewController.h"

@interface FirstViewController : UIViewController 
{
    TestModalViewController *testModalViewController;
    
    UILabel *usernameLabel;
    UILabel *passwordLabel;
}

@property (nonatomic, retain)  TestModalViewController *testModalViewController;

@property (nonatomic, retain) IBOutlet UILabel *usernameLabel;
@property (nonatomic, retain) IBOutlet UILabel *passwordLabel;

- (IBAction)goToModalView;

@end

7.) Now open up FirstViewController.m and synthesize our variables.

#import "FirstViewController.h"
#import "TestModalViewController.h"


@implementation FirstViewController

@synthesize testModalViewController;
@synthesize usernameLabel, passwordLabel;

8.) Next implement the viewWillAppear method. In this method we will check to see if our testModalViewController is nil, if it isn’t we’ll get the values out of the strings and populate our labels with them.

- (void)viewWillAppear:(BOOL)animated
{
    if (self.testModalViewController != nil) 
    {
        usernameLabel.text = testModalViewController.usernameValue;
        passwordLabel.text = testModalViewController.passwordValue;
    }
}

9.) Open up each nib file and connect our IBOutlets to the user interface elements we added to the views.

Hook up outlets

Hook up outlets

10.) That’s all there is to it. You can run the app and test it out.

11.) Let’s take it one step further and pass those values to our SecondViewController and show them on our second tab.

Open up SecondViewController and declare two UILabel instance variables.

#import 

@interface SecondViewController : UIViewController 
{
    UILabel *usernameLabel;
    UILabel *passwordLabel;
}

@property (nonatomic, retain) IBOutlet UILabel *usernameLabel;
@property (nonatomic, retain) IBOutlet UILabel *passwordLabel;

@end

12.) Synthesize the variables in the implementation file.

#import "SecondViewController.h"

@implementation SecondViewController

@synthesize usernameLabel, passwordLabel;

13.) Open up FirstViewController.h and import our SecondViewController and declare an instance variable for it.

#import 
#import "TestModalViewController.h"
#import "SecondViewController.h"

@interface FirstViewController : UIViewController 
{
    TestModalViewController *testModalViewController;
    SecondViewController *secondViewController;
    
    UILabel *usernameLabel;
    UILabel *passwordLabel;
}

@property (nonatomic, retain)  TestModalViewController *testModalViewController;
@property (nonatomic, retain)  SecondViewController *secondViewController;

@property (nonatomic, retain) IBOutlet UILabel *usernameLabel;
@property (nonatomic, retain) IBOutlet UILabel *passwordLabel;

- (IBAction)goToModalView;

@end

14.) Open up FirstViewController.m and synthesize the variable.

#import "FirstViewController.h"
#import "TestModalViewController.h"


@implementation FirstViewController

@synthesize testModalViewController, secondViewController;
@synthesize usernameLabel, passwordLabel;

Then implement the viewWillDisappear method. In this method we will check to see if our secondViewController variable is nil and if it is we’ll create one using a temp variable. Then we’ll set the labels in our secondViewController with the text from our labels in the firstViewController.

- (void)viewWillDisappear:(BOOL)animated
{
    if (self.secondViewController == nil) 
    {
        SecondViewController *temp = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:[NSBundle mainBundle]];
        self.secondViewController = temp;
        [temp release];
    }
    
    secondViewController.usernameLabel.text = self.usernameLabel.text;
    secondViewController.passwordLabel.text = self.passwordLabel.text;
}

15.) Open up SecondViewController.xib and replace the default labels with two labels. One for the username and one for the password. Then connect them to the IBOutlets we created in the header file using the Connections Inspector.

Hook up outlets

16.) Open up ModalViewTutorialAppDelegate.h and import FirstViewController and SecondViewController. Then declare instance variables for them.

#import 
#import "FirstViewController.h"
#import "SecondViewController.h"

@interface ModalViewTutorialAppDelegate : NSObject  
{
    FirstViewController *firstViewController;
    SecondViewController *secondViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
@property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
@property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;

@end

17.) Go to the app delegate implementation file and synthesize the variables.

@synthesize firstViewController, secondViewController;

Then go in the didFinishLaunchingWithOptions method and set the secondViewController variable in FirstViewController with the one we create in the app delegate.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.firstViewController.secondViewController = self.secondViewController;

    self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
    return YES;
} 

18.) Open up MainWindow.xib and connect the IBOutlets we created in the app delegate to the view controllers under Tab Bar Controller. To do this select Modal View Tutorial App Delegate under the Objects section.

Objects Pane

Then drag from the outlets,

Connection Inspector

to the view controllers under Tab Bar Controller

Tab Bar Controller in Objects Pane

19.) That’s it run the app and see it in action.

Simulator1

Simulator2

Simulator3

Simulator4

Here’s the code.