I've read some good reasons why Storyboards are not ready for prime time. Some of the articles like
Jonathan at toxic software.com simply help me find out that I wasn't doing something wrong, it just wasn't meant for that. But there are some benefits from using a storyboard that I wanted to keep, so I got adamant about finding workarounds. Here's one:
Drill down UITableViews - Standard
1. Provided you've started with a subclassed UITableViewController, or a TableView in a UIViewController that is already the root relationship from a UINavigationController.
2. You link the push outlet from the cell prototype back to the table view controller.
This will automatically provide the control to push-in another of the same ViewController when a cell is clicked.
3. Give the loopback segue an identifier string in the attributes inspector.
4. Instead of implementing tableView:didSelectRowAtIndexPath: in the subclassed viewcontroller you will use
prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
This method will be passed the segue object that is the "tableLoopBack" the sender object for this segue type will be the UITableViewCell.
5. Prepare what data to send to the next instance the the table.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSArray* myObjectArray;
UITableView* myTable;
if ([segue.identifier isEqualToString:@"tableLoopBack"]) {
SubClassedTable* nextSct = segue.destinationViewController;
UITableViewCell* myCell = (UITableViewCell*)sender;
NSIndexPath* idx = [myTable indexPathForCell:myCell];
nextSct.myObject = [self.myObjectArray objectAtIndex:idx.row];
}
}
6. In the TableDataSourceDelegate have it decide how to propagate your tables data array based now on the defined and set value of "myObject". This is very different for everyones data types.
An example, using Core Data and have say AMusicLib > Artists > Albums > Songs > Info
The myObject would ideally be of type NSManagedObject* so that you could be any of the above. When the dataDelegates are called like tableview:numberOfRowsInSection: you would want to implement something like the following to handle the many layers.
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if ([self.myObject isKindOfClass:[Music class]]) {
Music* myMusicLib = self.myObject;
return [myMusicLib.artists count];
} else if ([self.myObject isKindOfClass:[Artist class]]) {
Artist* myArtist = self.myObject;
return [myArtist.albums count];
} else if ([self.myObject isKindOfClass:[Album class]]) {
Album* myAlbum = self.myObject;
return [myAlbum.songs count];
}
}
Now in the implementation of the tableview:cellForRowAtIndexPath: use a similar kindOf conditional so that the cells properties can feed from the difference of the objects at each layer.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = @"musicCell";
UITableViewCell* newCell = [tableView dequeueReusableCellWithIdentifier:cellID];
if ([self.myObject isKindOfClass:[Music class]]) {
Music* myMusicLib = self.myObject;
Artist* aArtist = [[myMusicLib.artists allObjects] objectAtIndex:indexPath.row];
newCell.textLabel.text = aArtist.name;
} else if ([self.myObject isKindOfClass:[Artist class]]) {
Artist* myArtist = self.myObject;
Album* aAlbum = [[myArtist.albums allObjects] objectAtIndex:indexPath.row];
newCell.textLabel.text = aAlbum.title;
newCell.imageView.image = [UIImage imageNamed:aAlbum.art];
} else if ([self.myObject isKindOfClass:[Album class]]) {
Album* myAlbum = self.myObject;
Song* aSong = [[myAlbum.songs allObjects] objectAtIndex:indexPath.row];
newCell.textLabel.text = aSong.name;
NSDate* aTime = [NSDate dateWithTimeIntervalSince1970:[aSong.length intValue]];
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateFormat:@"m:ss"];
newCell.detailTextLabel.text = [dateFormatter stringFromDate:aTime];
}
return newCell;
}
And finally because earlier we used a property self.myObjectArray, this property could have been a consistent type simple array, but because I walked you through handing down different managedObjects this myObjectArray getter would also need to return an NSArray from the different managedObject's intended NSSet. Same as just above, just return [myArtist.albums allObjects] or [myAlbum.songs allSongs] and so on for the prepareForSegue can hand off the right object to the next controller.
Drill Down TableViews - Options
The above is simple but can be limiting. The segue always pushes down to another of the same table view. Now you want options, lets go this why, or that.
1. Don't use the push outlet on the cell prototype. This is whats limiting the control of where we go.
2. Do Implement the tableView:didSelectRowAtIndexPath: this is where we will decide what segue we take, Or none.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (!sec) {
Section* aSect = [[self.sectionFetchedController fetchedObjects] objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:@"tableLoopBack" sender:aSect];
} else {
Clip* aClip = [self.clipFetchedController objectAtIndexPath:indexPath];
[[VideoPlayerController Player] setMoviePlayerForClip:aClip];
}
}
What we are effectively doing here is getting the option to loop into another table, or stay and do something else like load a video in a modal MPMoviePlayerController. Or even call another segue that goes to detail view.
3. Continue to make use of the prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"tableLoopBack"]) {
VideoListController* vlc = segue.destinationViewController;
Section* mySec = (Section*)sender;
vlc.title = mySec.title;
vlc.sec = mySec;
}
}
This is so data is still passed to the new instance of this controller for drill down purposes, even if not all cells drill.
4. And now the hack, if in step 1 we do not link the cell to push to the this viewController, we still need to define a segue and name it "tableLoopBack".
Add a
UIBarButtonItem to the viewController and set the push outlet on it to loopback, identify that new segue connection as "tableLoopBack" and you have access to it when calling performSegue.
5. Add another UIBarButtonItem, and link it to another ViewController like one that layout details level. Identify that segue as another name like "detailSegue" and performSegue from didSelectRowAtIndexPath: where you can pass down the object full of details right into the sender, which will also be handed through the prepareForSegue, so make sure you hand it down again to the desired property of the new destinationViewController.
Helpful? Vote me up:
Stackoverflow.com