iphone - access uitableviewcontroller from uitableviewcell - iphone

I have a structure like this....
UITableViewController -> UITableViewCell -> UIView
I need the UIView to access a HashTable (NSMutableDictionary) in the UITableViewController
Is there a way to access the ViewController simply from the ViewCell (using [ViewCell superview] obviously won't work) ....Do I need to go down through the AppDelegate using [[UIApplication sharedApplication] delegate]?
Thanks!

This should do the trick:
UITableView *tv = (UITableView *) self.superview.superview;
UITableViewController *vc = (UITableViewController *) tv.dataSource;

I usually maintain a weak reference from my UIView to my UIViewController if I need one, usually by creating a method something like this:
-(MyView*)initWithController:(CardCreatorViewController*) aController andFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
controller = aController;
// more initialisation here
}
return self;
}
You could also use a delegate pattern if you want a more decoupled solution. I tend to think this is overkill for a view and its controller, but I would use it with a system of controllers and subcontrollers.

You can create a category for this:
#implementation UITableViewCell (FindTableViewController)
- (id<UITableViewDataSource>)tableViewController
{
UIView *view = self;
while (!(view == nil || [view isKindOfClass:[UITableView class]])) {
view = view.superview;
}
return ((UITableView *)view).dataSource;
}
#end
Then you can simply access self.tableViewController from the cell itself (assuming you have included this category). You may need to cast it to your table view controller's class tho.

Since the UITableViewCell is somewhere in the view hierarchy, you can access your root view by retrieving view.superview until you get it. If you don't want to add any properties to your view, you can access its controller through the view's nextResponder property. You would have to cast it to whatever class you need, of course, and it may not be the cleanest use of the property. It's a quick-n-dirty hack to get to it.
If you're looking for something you can show your children though, I'd aim for going through your app delegate, or if your view controller happens to be a singleton, just implement the singleton design pattern and access it through that.

Some modification from Kare Morstol answer :
The hierarchy of tableviewcell is in iOS 5.0(my test version)
cell.superview = tableview
cell.superview.superview = UIViewControllerWrapperView
So, use cell.superview to get tableview. And the tableview and tableviewController has a relation of delegate, dataSource in default.
You can get tableviewController reference by tableview.delegate or tableview.dataSource.
UITableView *tableView = cell.superview;
UITableViewController *tableViewController = tableView.delegate; // or tableView.dataSource`

Related

Delegation and dismissal of a view controller

I'm doing an app that has a UITableViewController populated with a list of products. Each row segues to a UIViewController that presents the details of the product in the row. Now since tapping on each row and going back to see the details of the next product might be too tedious for the user, we decide to add this feature: when a user swipes on the UIViewController of a product, then the UIViewController with the details for the next product is pushed.
But, as of now, I'm not sure of the best way to implement this. I'm tempted to pass the array of products to the UIViewController so that the swiping is achieved but this will be a violation of the MVC framework,right? Views cannot own the data they're presenting. The product details UIViewController should only know about the specific product that's passed to it, not the rest, right?
I think this can be accomplished using delegation but I'm not sure how. Can anybody help me? Thanks!
EDIT:
Rob Mayoff's code was really helpful so I decided to implement it. But for the meantime, instead of implementing a swipe, I'll just use a simple round rect button to call the functions.
- (IBAction)showNextProduct:(id)sender {
[self.productsTVC goToProductAtIndex:self.productIndex + 1];
}
- (IBAction)showPriorProduct:(id)sender {
[self.productsTVC goToProductAtIndex:self.productIndex - 1];
}
But every time I click any of the buttons, my app crashes with the message: Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted. Unbalanced calls to begin/end appearance transitions for <ProductDetailsViewController: 0x6e510c0>.
Let's say you have a CatalogViewController (which is a subclass of UITableViewController) and a ProductViewController (which is a subclass of UIViewController).
The simplest way to implement “swipe to next product” is to give the ProductViewController a reference to the CatalogViewController. It should be weak (if using ARC) or assign (if not using ARC). You will also want a property that holds the index of the product in the catalog:
#interface ProductViewController
#property (nonatomic, weak) CatalogViewController *catalogViewController;
#property (nonatomic) NSInteger productIndex;
#end
Then in the action method for a swipe, you send a message to the CatalogViewController asking it to go to the next (or prior) product in the catalog:
#implementation ProductViewController
- (IBAction)showNextProduct:(id)sender {
[self.catalogViewController goToProductAtIndex:self.productIndex + 1];
}
- (IBAction)showPriorProduct:(id)sender {
[self.catalogViewController goToProductAtIndex:self.productIndex - 1];
}
In CatalogViewController, whenever you create a ProductViewController, you need to set those properties:
#implementation CatalogViewController
- (ProductViewController *)productViewControllerForProductAtIndex:(NSInteger)index {
if (index < 0 || index >= self.products.count)
return nil;
ProductViewController *vc = [[ProductViewController alloc] initWithProduct:[self.products objectAtIndex:index]];
vc.catalogViewController = self;
vc.productIndex = index;
return vc;
}
and you implement the goToProductAtIndex: method like this:
- (void)goToProductAtIndex:(NSInteger)index {
ProductViewController *vc = [self productViewControllerForProductAtIndex:index];
if (!vc)
return;
NSMutableArray *vcs = [[self.navigationController viewControllers] mutableCopy];
while (vcs.lastObject != self)
[vcs removeLastObject];
[vcs addObject:vc];
[self.navigationController setViewControllers:vcs animated:YES];
}
You can use the same method to handle a table row selection:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self goToProductAtIndex:indexPath.row];
}
If you want to get more software-engineery, you can create a protocol around the goToProductAtIndex: method and use that to avoid making ProductViewController know about the CatalogViewController class.

didSelectRowAtIndexPath not called. UITableViewController subclassed

Tried programming more in an OO manner.
NOTE:
Seems the problem is related to having a UITableView(which is a type of scrollview) ontop of another UIScrollView. Trying to get clarification on how to make this work.
So i made a UITableViewController subclass and add that as an object in my Mainview class.
#interface NRTableVC : UITableViewController <UITableViewDelegate, UITableViewDataSource>
{
NSArray * tableLabelsArray;
}
#end
The table scrolls and displays OK.
But my
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Did select row");
}
method never gets called.
in my Mainview class I use it as follows
RTable = [[[NRTableVC alloc] initWithStyle:UITableViewStylePlain]autorelease];
[scrollview addSubview:RTable.view];
I was wondering so I need to somehow set self as the delegate or something like that.
Anybody able to give me some hints as to what is wrong?
Many Thanks,
Code
Have you set delegate for tableview? Without delegate set it won't call any delegate methods.
What I usually do is, in the View I want the tableview is create an instance
I do this in ViewDidLoad or the LoadView methods
UITableView *tableView = [UITableView alloc] init...
tableView.delegate = self;
tableView.datasource = self;
Then I add it as a subview of the current view.
This means your MainView needs to implement the tableView datasource and delegate methods.
Edit:
If you use interface builder, remember to drag the datasource and delegates to the fileowner
Is anything retaining RTable? You're autoreleasing it but you don't show any code that indicates it's being retained anywhere.

adding a UITableView programmatically to a UIViewController

I'm loading a UIViewController into one of my Nav controller's hierarchies, which will contain some text and some images. At the bottom, I will want to create a expandable and collapsable tableview.
First off, is this idea possible? If it is, how do I add it and where do I place the data source and delegate methods?
Can I just make a separate subclass of the TableViewController and then add it to my ViewController as a subview?
Yes, you can create a UITableView whose delegate, datasource, and parent view are not necessarily a UITableViewController. Since the UITableView is a UIView, you can add it as a subview of any other UIView. Any NSObject can be the delegate or datasource, as long as you implement the required protocol methods.
#interface MyViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
In fact, in my experience, not many people even use UITableViewControllers. When was the last time you wanted your table view to take up the entire usable space? In general, I create a plain old UIViewController and add a UITableView as a subview of its view, in addition to other subviews.
/************************************************/
/************* MyCustomController.m *************/
/************************************************/
#interface MyCustomController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *tableView;
#end
#implementation MyCustomController
- (id)initWithNibName:(NSString*)nibName bundle:(NSString*)bundleName
{
self = [super initWitNibName:nibName bundle:bundleName];
if (self)
{
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
tableView.datasource = self;
tableView.delegate = self;
[self.view addSubview:self.tableView];
}
return self;
}
#pragma mark - UITableViewDataSource Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// return number of rows
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// return cell
}
#pragma mark - UITableViewDelegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// handle table view selection
}
#end
It's pretty easy, in something like your viewDidLoad method:
UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:tableView];
Just remember that a UITableViewController is a subclass of UIViewController only with the tableview set as the controller's view.
So yes definitely possible and used quite frequently when you want to have a tableview but also other custom UI elements which prevent you from using the UITableViewController.
I'd normally choose to add it to my view controller's view in either its initialisation method or viewDidLoad method. This will vary based on whether you're creating your views from a NIB or entirely programatically.
In case of NIBs:
- (id)initWithNibName:(NSString*)nibName bundle:(NSBundle*)bundleName
{
if ((self = [super initWitNibName:nibName bundle:bundleName]))
{
self.theTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewWhateverStyleYouWantHere];
theTableView.dataSource = self, theTableView.delegate = self;
[self.view addSubview:theTableView];
[theTableView release];
}
}
And then you can set the frame of your tableview in your viewDidLoad method.
I'd personally prefer to do the whole thing in interface builder as you'd achieve the same result with way less code to maintain.
If you're like me and already had created a UITableViewController and then realizing that you did so much work on it that re-writing it would be a pain, you can just do the following to add the UITableViewController to the UIViewController as a subview.
UITableViewController* tableViewController = [[UITableViewController alloc] init];
[self.view addSubview:tableViewController.tableView];
All the other answers above works great. I figure I'd add to this for those that have a heavily invested implementation of a UITableViewController and feel like refactoring would be a pain.

didSelectRowAtIndexPath not get called

I have a UITableView inside a UIViewController like so:
.h
#interface OutageListViewController : UIViewController<UITableViewDelegate,UITableViewDataSource> {
IBOutlet UITableView *outageTable;
.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Selected");
}
I have customized table cell:
//EDIT
I have fire UILabel side by side on my customized view, as well as a background spreading the entire area. But after resizing/removing the background image and label I putted on the customized cell, "didSelectRowAtIndexPath" is still not being called.
//END EDIT
#interface AbstractSummaryListViewCell : UITableViewCell {...}
and
#interface FiveColumnSummaryCell : AbstractSummaryListViewCell {...}
This UIView is inside another UIView:
#interface CustomTabBarController : UIViewController {
OutageListViewController *outageListViewController;
And my AppDelegate add this to the window:
[window addSubview:[customTabBarController view]];
Now I'm trying to determine which cell get clicked and didSelectRowAtIndexPath doesn't get called, I have dataSource and delegate connect from the UITableView to File's Owner, in fact the data populates correctly as my "cellForRowAtIndexPath" specifies, any ideas how can I fix this?
Thanks!
I solved it: forgot to check User Interaction Enabled in my customized cell xib. What a fool!
Are the following properties of UITableView all YES?
allowsSelection
allowsSelectionDuringEditing
Edit:
I think Paul is right. The delegate property has some problem. You can check the delegate property of tableView inside -(void)viewDidLoad. As you said, they should be connected to FileOwner in xib. So the following codes won't obtain nil.
- (void)viewDidLoad {
[super viewDidLoad];
// They should not be nil.
NSLog(#"delegate:%# dataSource:%#", self.tableView.delegate, self.tableView.dataSource);
}
It's possible that the view controller has not been connected to the delegate property of the outageTable anywhere.
You can make a quick test... Remove the "big" label and see if the didSelectRowAtIndexPath is called.

Are there Anyone that use TableViewController without subclassing?

I am just curious. In IB, we can put a tableviewcontroller. However, as far as I know, we always subclass that tableview controller right? That way we can implement delegate, etc.
However, it seems that for some "default" behavior, IPhone intended tableviewcontroller to be used as is. Otherwise, why would IB let us put tableViewController like that?
Are there any sample code where people use tableViewController without subclassing?
Where does they implement things like what cells to draw, etc. then?
I guess the right answer of the question is that it's simply ridiculous to use a UITableViewController without sub classing. No body is doing it. Please correct me if I am wrong. I am just curious.
Whether you use a subclass of UITableViewController or UIViewController you need to set the data your table is going to display, otherwise, what's the point of a blank table? To achieve that you have to subclass and implement some methods. It's also a good idea to keep the delegate and the datasource in the same controller, unless the complexity really asks for different classes.
That being said, I always create my own table controllers as a subclass of UIViewController and implement the table controllers methods myself, because it gives you more flexibility. Matt Gallagher has several posts on how and why. See UITableView construction, drawing and management (revisited).
If you want to give it a try, create a subclass of UIViewController with a XIB and add the following sample code:
// interface
#import <UIKit/UIKit.h>
#interface SettingsVC : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#property (nonatomic, retain) NSMutableArray *array;
#end
// implementation
#synthesize tableView = _tableView;
#synthesize array = _array;
# pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.array count];
}
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
int row = [indexPath row];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [self.array objectAtIndex:row];
return cell;
}
Then add a UITableView object to the XIB, link the tableView of the controller to the UITableView object, and link the delegate and datasource of the UITableView to the controller.
No, this is not necessary to inherit your class with tableViewController. You can use table view by simply
putting TableViewController in xib.
and setting its delegate and datasourse to file's owner you can draw the table cells.
I don't think you can use a UITableViewController as is, it's like using a UIViewController without subclassing it : you can't set any inner mechanics.
But you can have a UITableView without using a UITableViewController.
Sure you can use UITableViewController without subclassing it.
Samplecode is very easy and straight forward.
For example like this:
- (IBAction)selectSomeOption:(id)sender {
UITableViewController *tableViewController = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped];
tableViewController.tableView.dataSource = self;
tableViewController.tableView.delegate = self;
tableViewController.title = "Select some option";
[self.navigationController pushViewController:tableViewController animated:YES];
}
and the UITableViewDatasource and Delegate methods go into the same class.
Sure, if you like pain you could create a UIViewController in code and add a tableView on your own.
Or create a subclass for such an easy task.
The use of a non subclassed UITableViewController is sometimes convenient.