Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Sign In with Google Sign In with OpenID

Using static table views in iOS 6 using "embed" segues

Duncan CDuncan C Posts: 8,992Tutorial Authors, Registered Users @ @ @ @ @ @ @
edited March 2013 in iPhone SDK Tutorials
I've been studying Storyboards and the things they let you do. Specifically, I wanted to learn how to set up static table views. Static table views don't require a data source. However, a static table view must be managed by a UITableViewController.

The downside of that is that a UITableViewController is set up so it's content view is its table view. You really can't make a UITableViewController manage a complex set of views where a table view is only a part.

I therefore set myself the challenge of making a view controller that managed 2 different static table views, and got notified when the user selected a cell in either table view.

With iOS 6 this is easy. There is a new user interface object called a container view. You drag a container view onto a view controller and it becomes a container that hosts the contents of another view controller. You then right-click-and-drag from the container view to another view controller and tell IB (Interface Builder) you want to create an embed segue, and it does all the housekeeping for you. (Thanks to GHuebner for showing me how to do this.)

So, if you're willing to use iOS 6 or later, it's actually pretty easy to include one or more static table views in your UI design.

The screen from the app looks like this:

image

There is a working project on github (link) that you can clone, or just download for read-only access.

Here is a description of what the project and a very rough outline of how you do it:

Static table view demo project.
Requires iOS 6.0

This project demonstrates several things. It is based on Storyboards, which requires iOS 5.0. It also uses the new embed segue which was added in iOS 6.

1. Using the automatic view controller containment supported with storyboards under iOS 6.0.

Steps to enclose a view controller in another view controller:

a) Display the objects library in the utilities area,
b) Type "container" into the filter bar.
c) Copy a container view onto your view controller.
d) Create a second view controller scene in your storyboard (a table view controller, for this project.)
e) Right-drag/control drag from your container view to your new view controller, and select "embed". This creates an embed segue, which tells the system to set up the "contained" view controller as a child of the containing view controller.

2. Using static table views (Must be managed by a UITableViewController.)

a) Define a custom subclass of UITableViewController in your project. (Let's call it MyTableViewController.)
b) Create a UITableViewController in your storyboard.
c) Go to the identity inspector and switch the class of your table view controller to your custom class.
d) Select the table view inside the new table view controller and select the attributes inspector.
e) Switch the content type to "Static cells"
f) Set up the cells in your table view as desired. (Use custom cell type)
g) If needed, control-drag from the fields of your cells into the header of your table view controller to create outlets and/or actions.

3. Creating links between your parent and child view controller

Saving a link to the child view controller:

a) Click on the embed segue and give it a unique identifier
b) Implement a prepareForSegue method in your parent view controller.
c) In that prepareForSegue method, string match the segue identifier (using the NSString isEqualToString method) and for the desired embed segue, save the destination view controller

4. Setting up the parent view controller as a delegate of the child view controller:

a) Define a protocol for the parent view controller.
b) Add a delegate property in the child view controller that conforms to the parent view controller protocol.
c) Add code to the prepareForSegue method that sets the delegate property of the destination view controller


5. Making taps in table view cells select/deselect the cell:

a) Implement table view tableView:willSelectRowAtIndexPath: and tableView:didSelectRowAtIndexPath: methods.
b) Add logic to toggle selected state of cell if it was selected previously.

6. Passing cell selected actions to parent view controller through delegate property:

a) Add a method to the StaticTableParentProtocol to notify the delegate when the user selects/deselects a cell.
b) Update MyTableViewController's tableView:didSelectRowAtIndexPath method to see if delegate responds to new method, and send message if it does.

7. Construct and display localized message in parent view controller as the user selects/deselects cells in each table view

a) Implement new StaticTableParentProtocol method in parent view controller to display message when user selects/deselects a cell.
b) Use NSLocalizedString for all string constants to localize strings for display.

8. Add buttons to static table view cells

a) Make sure cells are custom type
b) drag a round rect button onto a cell
c) Add a text label (since the custom cell does not have a text label by default.)
d) Make the button shorter to fit inside the table view cell.
e) Duplicate the custom cell with button as many times as needed
f) Put sequential numbered tags on the buttons for each cell, 1..n
g) Create a "cellButtonTapped" IBAction method in the custom table view
h) Link each button's touch up inside action to the cellButtonTapped method.


9. Update MyTableViewController to send a message to parent view controller when user presses buttons on cells, and use add code in MyTableViewController to display a localized message telling the user which button was clicked, and in which table view.

a) Design a new method tableView:clickedButton:withTag:inViewController: in StaticTableParentProtocol to notify parent when user taps a button.
b) Update MyTableViewController's cellButtonTapped method to check delegate to see if it responds to tableView:clickedButton:withTag:inViewController: method, and send the message if it does
c) Implement tableView:clickedButton:withTag:inViewController: method in parent view controller. Make the method construct a localized message about which button tag was clicked, and in which view controller.

10. Add a rounded-corner, colored border around the table views:

a) Add CALayer category with a method setBorderUIColor that takes a UIColor as input and sets the layer's border color with that UIColor's CGColor.
b) Add "User Defined Runtime Attributes" to container views with key/value pairs
"self.layer.borderWidth"/1 (NSNumber)
self.layer.borderUIColor/blue (color)
self.layer.cornerRadius/10 (NSNumber)
self.layer.masksToBorder/YES (BOOL)


The "User Defined Runtime Attributes" look like this:

image
Post edited by Duncan C on
Regards,
Duncan C
WareTo

widehead.gif
Animated GIF created with Face Dancer, available for free in the app store.

I'm available for one-on-one help at CodeMentor

Replies

  • GHuebnerGHuebner Posts: 664Registered Users @ @ @
    Just a thought and looking at your screen shot.

    In iOS5, you could make a static table view with two sections. That would give you the look and feel of your two imbedded controllers in the container. Then for the Bottom button, you could add a view that contained your button to the tableview footer. By putting the custom view in the footer and creating two sections in your static table view, you could accomplish almost the same exact look as in iOS6 with iOS5.

    self.tableView.tableFooterView = myCustomFooterView
  • Duncan CDuncan C Posts: 8,992Tutorial Authors, Registered Users @ @ @ @ @ @ @
    GHuebner said:

    Just a thought and looking at your screen shot.

    In iOS5, you could make a static table view with two sections. That would give you the look and feel of your two imbedded controllers in the container. Then for the Bottom button, you could add a view that contained your button to the tableview footer. By putting the custom view in the footer and creating two sections in your static table view, you could accomplish almost the same exact look as in iOS6 with iOS5.


    self.tableView.tableFooterView = myCustomFooterView
    Sort of. The UI above has 2 separate table views that scroll independently of each other. A sectioned table view doesn't work that way. In a sectioned table view, you scroll between sections, and view the contents of the section(s) that are currently visible. If you're developing a "pick one from column A and one from column B" user interface, a single sectioned table view doesn't really work.

    Sure, your sectioned table view with header views and footer views is another useful approach to have in your bag of tricks. However, it has real limitations.

    I also submit that "You can accomplish the same thing using headers for the stuff above and footer views for the stuff below" is an "any tool can be used as a hammer" solution. Yes, you can cobble together a UI all in a table view that provides other controls, but you could not create a custom UI design like a table view on the left of an iPad form for picking items, and a view on the right that shows pictures of the items (like a photo album.)

    As I have said before, I don't like UITableViewControllers. I think they are just too restrictive to be useful. View controller containment, as outlined in this project, allows me to work around those restrictions.


    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
  • GHuebnerGHuebner Posts: 664Registered Users @ @ @
    I will agree with the UITableViewControllers are restrictive. More so on the iPad than on the iPhone in my experience. I have often added multiple tableviews to a controller on the iPad that fit your scenario better (pick one from column A and one from column B" user interface).

    I do think its restrictive too that static table views are restricted to a UITableViewController.



  • Duncan CDuncan C Posts: 8,992Tutorial Authors, Registered Users @ @ @ @ @ @ @
    GHuebner said:

    I will agree with the UITableViewControllers are restrictive. More so on the iPad than on the iPhone in my experience. I have often added multiple tableviews to a controller on the iPad that fit your scenario better (pick one from column A and one from column B" user interface).

    I do think its restrictive too that static table views are restricted to a UITableViewController.

    I understand why Apple made static table views require a UITableViewController. The smarts to create data source methods to feed content to the static table view must be defined in the UITableViewController base class.

    It would have been better (at least more flexible) if they put that intelligence in the base UIViewController class, but that would be adding tableview specific logic (and probably state variables) to all instances of a class that might never manage a static table view.
    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
  • Duncan CDuncan C Posts: 8,992Tutorial Authors, Registered Users @ @ @ @ @ @ @
    Thanks to a tip from Brian Slick, I've made a modest improvement to this project.

    It is set up so the table view controller class notifies the parent view controller clicks a button in one of the table views. The program used to use a tag on each button to figure out which cell was tapped. That's a pain, since you have to set up the tags manually, and it's also error-prone.

    Brian told me that there is a category on UITableView that lets you ask a table view which cell contains a certain view, and that it used the pixel coordinates of the view to figure it out.

    I don't have access to the table view category, so I created my own. It only adds 1 method to UITableView, called

    indexPathForCellContainingView:

    UITableView has a method indexPathForRowAtPoint: that takes a point as input, and returns the indexPath of the cell that contains that point, or nil if the point is not currently inside a cell. That's the basis of the category.

    Here is the code:
    @implementation UITableView (indexPathForCellContainingView)

    - (NSIndexPath *) indexPathForCellContainingView: (UIView *) view;
    {
    //get the view's center point (which will be in it's parent view's coordinate system
    //And convert it to the table view's coorindate system
    CGPoint viewCenter = [self convertPoint: view.center fromView: view.superview];

    //Ask the table view which cell's index path contains that point.
    return [self indexPathForRowAtPoint: viewCenter];
    }
    I wrote it to use the center of the view, since sometimes views extend outside their parent views slightly. I thought the center point would be more likely to be inside it's table view cell. However, a view's center property is in it's superview's coordinate system, so I had to use the view's superview in the call to the UITableView method indexPathForRowAtPoint:.

    The static table views project has been updated to use the new method instead of relying on view tags.

    I also updated the StaticTableParentProtocol so the clickedButton method now returns an indexPath instead of a row. The new method signature looks like this:
    - (void) tableView: (UITableView *) tableView
    clickedButton: (UIButton *) button
    atIndexPath: (NSIndexPath *) buttonIndexPath
    inViewController: (UITableViewController <StaticTableViewControllerProtocol>*) viewController;
    The code in the MyTableViewController class that responds to a button click action is trivially simple:
    - (IBAction)cellButtonTapped:(UIButton *)sender
    {
    if ([self.delegate respondsToSelector: @selector(tableView:didSelect:cellAtIndexPath:inViewController:)])
    {
    NSIndexPath *buttonIndexPath = [self.tableView indexPathForCellContainingView: sender];
    [self.delegate tableView: self.tableView
    clickedButton: sender
    atIndexPath: buttonIndexPath
    inViewController: self];
    }
    }
    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
Sign In or Register to comment.