Tuesday 29 November 2011

iOS 5 Storyboard for Beginners: How To use Segues, Scenes and Static Content UITableViews

With iOS 5, Storyboards represent a significant change to how we, as developers, construct our user interfaces using Interface Builder. So far my experience of Storyboards has been extremely positive however, resources are thin on the ground and with this post I hope to pass on some of my initial experiences. I have also recently created a practical example around todo lists with a backend system and a full explanation of how to use Storyboards in a working application please check it out here.
There are several key concepts that this article will cover the first is the concept of the Scene. Within Storyboards every UIViewController represent one screen of content. These scenes are linked together with Segues, these define the transitions between one scene to another. You can specify how you wish this transition to be made by using one of the preset transitions, Push, Modal and Popover or you can also create your own custom transitions which can bring a very unique and specific look/feel to your application.
Other extremely useful features are, Static Cells as your UITableView Content and Prototype Cells. These concepts are illustrated in the subsequent Scenes and Segues, Prototype Cells and Static Content, and General Storyboard examples,
Scenes and Segues
As previously stated a scene represents one screen of content. You can embed these scenes in UINavigationControllers or UITabBarControllers by dragging on a UINavigationController or a UITabBarController and dropping your UIViewController into it or alternatively, choose Editor->Embed In in the Xcode menu and choose there relevant component. Using the Embed In method is very handy and prevents you having to rejig things in your storyboard particularly when you have created several different Segues.
In the image below you will see an example of a blank scene that has been placed within a UINavigationController. The relationship between the UINavigationController and the ViewController is shown by a relationship link.

To construct transitions between scenes we use Segues. These are created by right clicking on the element that will initiate the transition and dragging over to the scene we want to transition to. In the image below you will see the popup that appears allowing you to specify the transition type and the resulting Segue created. In this case I have created a push Segue.

As a result whenever someone clicks on the contacts UIBarButton the ContactsViewController will be pushed onto the screen without the need for any additional code. To pass the ContactsViewController data we must implement the prepareForSegue method. First we have some setup we have to do in Interface Builder so that we can identify which segue has been executed. The Segue identifier can be set in the Attributes Inspector.

Now in your prepareForSegue method you can identify which Segue has been executed and provided the destinationView with the correct information.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([[segue identifier] isEqualToString:@"ContactsViewControllerSegue"]){
         ContactsViewController *cvc = (ContactsViewController *)[segue destinationViewController];
     }
}
Now we know what the destinationViewController is we can set its data properties. To receive information back from a scene we use delegation. Both are shown simply in the following code snipped.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([[segue identifier] isEqualToString:@"ContactsViewControllerSegue"]){
         ContactsViewController *cvc = (ContactsViewController *)[segue destinationViewController];
         cvc.contacts = self.contacts;
         cvc.delegate = self;
     }
}
A final note about UIStoryboardSegue is that when you choose a popover transition you must get access to the popover in order to dismiss it. You can get this by casting the Segue to a UIStoryboardPopoverSegue but only after you have checked the identity of the Segue to ensure you are making the correct cast.




Prototype Cells and Static Content
Other neat features of Storyboards are Prototype Cells and Static Content UITableViews. Lets first take a look at Prototype cells.
In the past we have to create our UITableViewCells in a nib and load them from the nib at runtime. This could result in lots of Cell nib files culturing up our project. Now we can create Prototype Cells directly inside our UITableViewController scene. In the following screenshot you can see our contacts UITableViewController scene where we have a custom prototype cell with a UIImageView and two labels.

To use your UITableViewCell prototypes you must provide them with a unique identifier that you set in Interface Builder. You can then dequeue the correct cell and set the content that it is to display in your cellForRowAtIndexPath method.
 static NSString *CellIdentifier = @"ContactsCell";
    ContactsTableViewCell *cell = (ContactsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    cell.contact = [contacts objectAtIndex:indexPath.row];
Note: That you can have as many different prototype cells as you wish the only requirement is that you set each one with a unique cell identifier.
It is also possible to create Segues between cells and ViewControllers to transition to a new scene when a cell is tapped. In the following screen shot you can see how to connect a cell to a UIViewController and by specifying Push as the transition mode we now have a detailed view pushed on whenever a cell is tapped.

As before we must specify an identifier for this Segue and pass data through to the view being shown via the prepareForSegue. This provides the newly presented view with the necessary data to be displayed.
Static Content UITableViews are an awesome new feature included in Storyboards. With Static UITableViews, cells can be designed inline without the need for an additional data source. Instead you can hook up UI elements that appear on different cells to IBOutlets in you UITableViewController class. In order to create a Static TableView layout you must change the Content type in the Attributes Inspector to Static as shown in the screen shot below.

I have also chosen to set the separator to none however you can leave the separator in if you wish to keep the UITableView look. Now that the view is laid out as we would like we can hook up the various UITextAreas and the UIImageView to your controller class. Finally I am going to finish with some additional Storyboard tips and resources.




General Storyboard Tips and Resources
UIStoryboard is a runtime representation of everything configured inside Interface Builder as such any individual scene can be loaded using either,
[UIStoryboard instantiateInitalViewController]
[UIStoryboard InstantiateViewControllerWithIdentifier]
Should you choose to split your application across multiple Storyboards these can be loaded using,
[UIStoryboard storyboardWithName:bundle:]
To launch your main story board at the predefined entry starting point of your applications user interface you can just specify the name of the story board in your info plist (Key UIMainStoryboardFile).

Useful resources
WWSC 2011 Storyboard Example

Monday 28 November 2011

Combining UITabBarControllers, UITableViews and UINavigationControllers using Storyboard

Combining tab bars and table views: Again, I am assuming you know the basics of iPhone development and are trying to use Xcode 4.2 to get a jump on the future of iOS development. If people ask for tutorials, I will create some explaining some things here I graze over. Okay so let’s get started.
First and foremost, I can be a lazy developer at times (i.e. I’d rather use some templates provided by Xcode, just to save 20 minutes). So to start off, let’s create a new project using the Tabbed Application Template.
Of course, make sure the “Use Storyboard” option is checked. We will be presented with the following:
You will see the App Delegate, Storyboard, and View Controllers for both tabs. The template also include icons for us (both for lower resolution iPhones, and the higher iPad, iPad 2 and iPhone 4). Delete the icons here, as some will be added later on, or keep them (If you want to follow this tutorial to the T, we will be adding some I created). I really prefer the layout of this compared to the older Xcodes and their Tab Bar templates. It helps split everything up for you, and I feel it is less crazy when they are spaced out, since it helps you concentrate on one tab’s view at a time. If you wish to follow along, created a new group labeled “Images” and place images uploaded below inside.
http://www.mediafire.com/?vodo13ex33c52x0

Lets focus on the First View first.
Select the bottom part of the Tab Bar, navigate to the Attributes inspector, and change the title to “UIViewController” and the image to “1.png”.
We are done with the first view. Since this tutorial does not really have much to do with UIViewControllers, we can just leave this how it is. Now we are going to get our hands a little dirty.  First, scroll down to the Second View Controller and delete it. Don’t be worried, adding items to the Tab bar is painless. Now, go into your Objects on the bottom right, and drag out a Navigation Controller (the gold spherical one).

You will notice that two windows will appear on the screen. The left one is the screen that actually contains the Navigation Controller, and the right is the Root Controller. The root controller is what you wish to display on the screen alongside the navigation controller. To do some minor tidying up, and to keep my OCDness at bay, I am going to change “Root View Controller” to “Table View”. Now, just because we have the Navigation Controller in the storyboard, does not mean it is connected to any implementation/header files whatsoever. So, let’s get the Navigation Controller all hooked up. First, Ctrl + Drag from the Tab Bar Controller straight down onto the Navigation Controller. You will notice that two options come up, viewControllers and performSegueWithIdentifier. Select viewControllers, and you will notice that you now have another tab added, and and arrow leading from your Tab Bar Controller to your new Navigation Controller. Be sure to also change the text to “UITableView” and the image to “2.png”.
Now we need to have this tab actually display something when this tab is selected. To do this, click on the bottom-rightmost scene, titled “View Controller – Root View” and make sure that the View is selected on the left Scene Panel (You can also do this by just double clicking on the view). Go to your objects and grab a Table View (NOT the controller) and drag it within your selected view. Now, with the View Controller – Table View selected, go to the Identity inspector and change the class to “SecondViewController”, as shown below:
This now means that the table view will be getting it’s data from the SecondViewController.m/.h files. Before we get to programming, make sure you Ctrl + Drag from the Table View to Second View Controller – Table View and select both Delegate and Data Source. Without this, the data we create will not get loaded into the table view. Now, let’s actually start doing some programming! Since the Table View is getting its data from the SecondViewController files, we need to also write the delegate functions. Lets start with the SecondViewController.h file. We will be creating an NSMutableArray of dummy objects, along with using a setupArray function to create the array. I have learned that not much code should be done in ViewDidLoad, and that if you just have function calls it sometimes helps solve some problems later on.
SecondViewController.h:

SecondViewController.m:


To anyone that has done programming with table views, this should all look familiar so I will not be going into it. If you need assistance, I can write a tutorial about it (I probably will anyways). If you run the application in the simulator, you will see something similar to what is shown below:

However, you’ll also notice that if you select a cell, nothing gets pushed onto the screen. This is because the didSelectRowAtIndexPath is empty, and I left it like that for a reason. The Apple developers in Cupertino changed the way the delegate function deals with views, since normally you would just pop a view onto the screen using a modal view controller or navigation controllers, there is a slight difference now. So lets go back to the storyboard file. So we will be using a View Controller as our detail view, so grab one out of our objects and place it next to the Table View Controller. Now if you click on the actual View Controller and go to the Attributes Inspector you will see something new: “Identifier”. This is basically a way to init certain views with certain scenes from the storyboard. Let’s just call this “Detail”.
Now lets create some .h/.m files for this new view controller. Go to File > New > New File and select UIViewController subclass. Make sure the “With XIB for user interface” is not checked, and call it “DetailViewController”. Now, go back to the View Controller we added in the storyboard, and change the class to “DetailViewController”. Lets go to the DetailViewController.h file, and add a IBOutlet UILabel.
Also, do not forget to @synthesize the rowNum in the DetailViewController.m, as well as connecting the IBOutlet in the storyboard from Detail View Controller to UILabel. Alright, now to look at some new Xcode 4.2 functions. First, #include “DetailViewController” at the beginning of your SecondViewController.m file, after your initial #include. Lets look at the code I entered below, and I will try to explain it as best as I can.
So if you begin to look at it, it looks pretty normal until you see the [self.storyboard instantiateViewControllerWithIdentifier:@"Detail"]; Well, I think the self.storyboard is obvious, you are calling this because this is the container for all of your Scenes (or nibs). Now remember when we set the Identifier for our DetailViewController Scene? That was for this. I like to think of it as using an initWithNibName:, in that the Identifier is sort of like the scene’s name, and that is how they are associated within Xcode. Next, we just push the view with the navigation controller, followed by changing the text in each of the detail views to correspond with the row that was selected. And that should be it! I hope this was informative, and I hope you can use this knowledge again in the future. If you have any questions, please feel free to post a comment, and if you want to see the source code for reference, it is below.

http://www.mediafire.com/?5aphfxx8q42hlgx

UITableView – Expand the Cells in the form of a dropdown list

When creating iOS applications, the need to pick a value from a list while displaying an UITableView comes up quite often. One obvious solution to that matter, and the hard one in most cases, is to load another UIView which may contain an UIPickerView or an UITableView that will list the values you need, let the user pick the value and then get back into your table and set the picked (by the user) value.

Another solution, more attractive and probably easier for the user is to display the list you need inside the UITableView that's been shown. Perhaps that approach sounds more difficult, but once you try it you'll find out yourself how fast, easy and beautiful solution is. Because an image equals to a thousand  words, the following image might give an idea of what I mean:






Implementing a drop down list inside an UITableView is not hard and it complies with the way that an UITableView works. For this example we'll create a new project, named TblCellsTestApp (you can name it anything you like, I' ll give it that name). Note that I use XCode 4.2 (which I'd suggest you use it as well, if you don't already do), so if you use an older version of the XCode you may notice some differences in the interface and in the view controller names.

Begin a single view application and make sure while creating the project NOT to check the three options at the bottom of the window:


When your project is ready, go to design the view in the one and only XIB file in the Interface Builder. Add an UITableView to the view and for the purposes of this example set its style to "Grouped":



Also, don't forget to make the table delegate and datasource connections:


Before we go any further, let's discuss for a moment what the idea is. To make it more clear we'll base the explanation on this example.

In this project, we're going to have three sections in our UITableView. The first and the third section will just exist in there, we don't care about them and we'll use them just to make our table view more rich. So, we are going to give in both of these sections just one row, with a single, standard text inside them.

What we care about is the second section. While being in normal state, which means that no picking item from a list is needed, we're going to display only one row, containing the selected value by the user, or the pre-selected value by the view when it loads.

On the other hand, while being in picking item state, the number of rows for the specific section that will be displayed will match the number of our data, which will reside into a NSMutableArray array.

The transition between the two states will be taking place as simply as possible. By tapping on the  single row, the section will animate an expand and all the values of our array will be shown, one in each row. By tapping again on any value, the list of the rows will collapse and the picked value will show up in the single row.

We're going to know what our state is at any time, simply by using a Boolean variable, or in other words a flag. Inside our project that flag will take the name "isShowingList". When that flag is True (YES), then all of our demo values/items are being displayed in our section,  one in each row. When that flag is False (NO), then the list is not shown and we're in normal mode.

Let's begin with the implementation.

Go to the view controller's header file (.h) and make it look like the next one:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController{
    UITableView *table; 
    NSString *dataForSection0;
    NSString *dataForSection2;
    NSMutableArray *demoData;
    int selectedValueIndex;
    bool isShowingList;
}

@property (retain, nonatomic) IBOutlet UITableView *table;
@property (retain, nonatomic) NSString *dataForSection0;
@property (retain, nonatomic) NSString *dataForSection2;
@property (retain, nonatomic) NSMutableArray *demoData;
@property (nonatomic) int selectedValueIndex;
@property (nonatomic) bool isShowingList;

@end



The objects we declared are:
  • table: the UITableView we'll use.
  • dataForSection0: The string value that will be displayed into the row of the first section.
  • dataForSection2: The string value that will be displayed into the row of the third section.
  • demoData: An array that will keep the values/items we need to display or to pick from.
  • selectedValueIndex: An integer value that will show at any time the index of the selected value inside the demoData array.
  • isShowingList: Already mentioned about it. Our state indicator.
Next, go to the .m file and synthesize those objects:

#import "ViewController.h"

@implementation ViewController
@synthesize table;
@synthesize dataForSection0;
@synthesize dataForSection2;
@synthesize demoData;
@synthesize selectedValueIndex;
@synthesize isShowingList;

If you don't want to forget to release the objects, do it now:

- (void)viewDidUnload
{
    [self setTable:nil];
    [self setDataForSection0:nil];
    [self setDataForSection2:nil];
    [self setDemoData:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
    [table release];
    [dataForSection0 release];
    [dataForSection2 release];
    [demoData release];
    [super dealloc];
}

Now, that's a good point to go to the Interface Builder and connect the table object with the UITableView we added at the beggining:


Keep coding now. Inside the .m file, in the viewDidLoad method add the following (the comments are quite explanatory):

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // For the beginning, let's give some values to the strings we declared.
    // Those values will be displayed in the first and the third sections.
    dataForSection0 = @"This is some cell content.";
    dataForSection2 = @"This is another cell content.";
    
    // Let's preprare our actual test data.
    demoData = [[NSMutableArray alloc] init];
    // We'll give five values, from one to five.
    [demoData addObject:@"One"];
    [demoData addObject:@"Two"];
    [demoData addObject:@"Three"];
    [demoData addObject:@"Four"];
    [demoData addObject:@"Five"];
    
    // Initially, the isShowingList value will be set to NO.
    // We don't want the list to be dislplayed when the view loads.
    isShowingList = NO;
    
    // By default, when the view loads, the first value of the five we created
    // above will be set as selected.
    // We'll do that by pointing to the first index of the array.
    // Don't forget that for the five items of the array, the indexes are from
    // zero to four (0 - 4).
    selectedValueIndex = 0;
}

Add some standard UITableView methods:

// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // We are going to have only three sections in this example.
    return 3;
}

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    // The number of the rows is depending on the section index.
    // The sections with indexes 0 and 2 will have only one row.
    if (section == 0 || section == 2) {
        return 1;
    }
    else{
        // For the number of rows of the section with index 1 (our test section) there
        // are two cases.
        //
        // First case: If the isShowingList variable is set to NO, then no list
        // with values should be displayed (the values of the demoData array) and
        // it should exist only one row.
        //
        // Second case: If the isShowingList variable is set to YES, then the
        // demoData array values should be displayed as a list and the returned
        // number of rows should match the number of the items in the array.
        if (!isShowingList) {
            return 1;
        }
        else{
            return [demoData count];
        }
    }
    
}

How about adding some header titles in our sections?

// Add header titles in sections.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
    if (section == 0) {
        return @"My first section";
    }
    else if (section == 1){
        return @"My demo section";
    }
    else{
        return @"Another section";
    }
}

Let's specify the height of our rows:

// Set the row height.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 60.0;
}

Let's go now to something really important. What the cells are going to display:

// Customize the appearance of table view cells.
- (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.
    cell.selectionStyle = UITableViewCellSelectionStyleBlue;
    cell.accessoryType = UITableViewCellAccessoryNone;
    
    // Let's set another font for the cells.
    [[cell textLabel] setFont:[UIFont fontWithName:@"Marker Felt" size:16.0]];
    
    // Depending on the section, the appropriate data will be displayed.
    // For the demo section especially, the data display requires a different
    // handling.
    if ([indexPath section] == 0) {
        // We'll set the dataForSection0 string value to the cell of that section.
        [[cell textLabel] setText:dataForSection0];
    }
    else if ([indexPath section] == 2){
        // We'll set the dataForSection2 string value to the cell of that section.
        [[cell textLabel] setText:dataForSection2];
    }
    else{
        // Depending on the isShowingList variable value, we'll display either
        // the selected value of the demoData array only, or the whole array's
        // contents.
        // Remember that if the isShowingList is set to NO, then only a single row
        // is displayed, containing the selected value.
        // If the isShowingList is set to YES, then a list of values is displayed
        // and all the items of the demoData array should be used.
        if (!isShowingList) {
            // Not a list in this case.
            // We'll only display the item of the demoData array of which array 
            // index matches the selectedValueList.
            [[cell textLabel] setText:[demoData objectAtIndex:selectedValueIndex]];
            
            // We'll also display the disclosure indicator to prompt user to
            // tap on that cell.
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        }
        else{
            // Listing the array items.
            [[cell textLabel] setText:[demoData objectAtIndex:[indexPath row]]];
            
            // We'll display the checkmark next to the already selected value.
            // That means that we'll apply the checkmark only to the cell
            // where the [indexPath row] value is equal to selectedValueIndex value.
            if ([indexPath row] == selectedValueIndex) {
                cell.accessoryType = UITableViewCellAccessoryCheckmark;
            }
            else{
                cell.accessoryType = UITableViewCellAccessoryNone;
            }
        }
    }
    
    return cell;
}

Of course, we should't forget what is going to happen when the user taps on a table cell:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    // We don't care about taps on the cells of the section 0 or section 2.
    // We want to handle only the taps on our demo section.
    if ([indexPath section] == 1) {
        // The job we have to do here is pretty easy.
        // 1. If the isShowingList variable is set to YES, then we save the
        //     index of the row that the user tapped on (save it to the selectedValueIndex variable),
        // 2. Change the value of the isShowingList variable.
        // 3. Reload not the whole table but only the section we're working on.
        // 
        // Note that only that last two steps are necessary when the isShowingList
        // variable is set to NO.
        
        // Step 1.
        if (isShowingList) {
            selectedValueIndex = [indexPath row];
        }
        
        // Step 2.
        isShowingList = !isShowingList;
        
        // Step 3. Here I chose to use the fade animation, but you can freely
        // try all of the provided animation styles and select the one it suits
        // you the best.
        [table reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
    }
    else{
        return;
    }
    
}

Ready to go! Our test app is ready to run now! Test it and see the results.





That's all! I hope that post will help you and you find it really useful.

Happy coding hours!