iPhone access parent class/delegate from UITableViewCell controller - iphone

When creating a custom UITableView cell I manually define a delegate variable in the #interface of a UITableViewCell class (each cell must communicate with parent class so the connection betweet two classes is needed). Then when a cell is attached to a UITableView, I just set that delegate variable.
#interface MyCellClass : UITableViewCell {
MyParentController *delegat;
}
#property (nonatomic, retain) MyParentController *delegat;
Is there a better way of doing it?

That could be ok but be sure you don't make retain cycles. (A retains B, B retains A, none of them get never released)
If you are just using only a functionality of MyParentController then, it is a good idea to make a protocol and set a delegate as an object that adopts (implements) that #protocol, It is not mandatory but it is good practice and better design this way.
You could create your protocol like:
//MyProtocol.h
#protocol MyProtocol <NSObject>
#required
- (void) requiredMethod:(NSString *)param;
#optional
- (void) optionalMethod:(NSString *)param;
#end
And Make your class:
#import "MyProtocol.h"
#interface MyCellClass : UITableViewCell {
id<MyProtocol> delegate;
}
#property (nonatomic, assign) id<MyProtocol> delegate; //see I changed it to assign?
Also, when you have delegates you should avoid making them retain or you will have retain cycles and hence, memory leaks.

That's the way I usually do it. Just create the cell and set its delegate before returning it in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

Related

Local Declaration Hides Instance Variable - tableView Not Synthesized

I have read about using underscores to fix compiler warnings that say "Local declaration hides instance variable," but I have not been able to implement a fix. The solutions I have read about say to use an underscore in my .h file, and the #synthesize part of in my .m file. However, I do not synthesize my tableView. Please see my header file below:
.h File
#interface ListViewController : GAITrackedViewController <UISearchDisplayDelegate,
UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate> {
IBOutlet UITableView *tableView;
}
.m File
//SYNTHESIZE
#synthesize listItems, filteredListItems, savedSearchTerm, savedScopeButtonIndex,
searchWasActive, mapView, loadingImageView, loadingActivity;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//FIRST WARNING for tableView HERE
if (tableView == self.searchDisplayController.searchResultsTableView){
return [self.filteredListItems count];
}
else{
return [self.listItems count];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
//SECOND WARNING for tableView HERE
[tableView setRowHeight: 60.00];
return 1;
}
You have an outlet/property named tableView and your delegate method also contains tableView.
So you need to change either of them. Typically you go to change the argument names of delegate and datasource as
- (NSInteger)tableView:(UITableView *)aTableView
numberOfRowsInSection:(NSInteger)section {
Change :
IBOutlet UITableView *tableView;
To :
IBOutlet UITableView *tableView1;
Make the same required changes in your .m file also. This warning is coming because in below delegate method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
There is a reference of UITableView with name tableView and you have given the same name. Scope of this variable is function level where as scope of your variable declare in .h is class level.
Dont use same name : IBOutlet UITableView *tableView; use tableview or table_View
I do also spent lot of time on this issue. Finally I could solve this problem.
The thing is we should take care of outlets which are created by us. If you change outlets names then you must connect them properly after change.In my case after lot of struggle I could finish this issue hope it will help others....
For example if You have an outlet/property named tableView and your delegate method also contains tableView.
So you need to change either of them. Typically you go to change the argument names of delegate and datasource as
Change :
IBOutlet UITableView *tableView;
To :
IBOutlet UITableView *tableView1;
For more information visit this link->Local Declaration Hides Instance Variable - tableView Not Synthesized

Where do I put delegate methods and how do I invoke them?

As a new iOS developer, I have finally stumbled across delegates. I'm trying to follow a tutorial: http://gabriel-tips.blogspot.com/2011/05/input-accessory-view-how-to-add-extra.html, But I'm having difficulty understanding where I am supposed to put the actual delegate methods.
Secondly, would anyone mind providing a dumbed down explanation of how a delegate method is invoked?
Thanks!
A delegate is simply a class that agrees to do work for another class. The delegate methods are invoked by the delegating class. The delegate must therefore, provide an implementation of the appropriate method. Let's make a simple view controller with a table view.
// MyViewController.h
#interface MyViewController : UIViewController <UITableViewDelegate>
#property (nonatomic, retain) UITableView *myTableView;
#end
Here in the MyViewController.h file I have declared my view controller to be a delegate of type UITableViewDelegate (it really means it implements the UITableViewDelegate protocol. More on this later). I have thus agreed to respond to requests to my view controller. The requests will come from the table view called myTableView. However, simply stating that I adhere to UITableViewDelegate does not make my view controller a delegate of anything. I must specify that directly:
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView
{
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
myTableView.delegate = self;
self.view = myTableView;
}
#end
Here I specifically set MyViewController to be the delegate of myTableView. Now whenever the table view wants to ask its delegate to do something, it will send that message to my view controller. Thus, MyViewController MUST provide implementations of the appropriate delegate methods:
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView
{
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
myTableView.delegate = self;
self.view = myTableView;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Selected section:%i row:%i", indexPath.section, indexPath.row);
}
#end
Here I have provided an implementation for the delegate method tableView:didSelectRowAtIndexPath: which will be called by myTableView when it is appropriate (the user selects a row).
Here you can find all the delegate methods defined in UITableViewDelegate. Some are required and others are optional:
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableViewDelegate_Protocol/Reference/Reference.html
In order to be a delegate of a class you should know which methods you are required to provide implementations for.
If you wanted to create your own delegate definitions, you would create a new protocol. You do not retain your delegates (see the property declaration), as this creates a retain cycle:
// MyViewController.h
#class MyViewController;
#protocol MyViewControllerDelegate
- (void)viewController:(MyViewController *)viewController didChangeSelection:(NSIndexPath *)newIndexPath;
#end
#interface MyViewController : UIViewController <UITableViewDelegate>
#property (nonatomic, retain) UITableView *myTableView;
#property (nonatomic, assign) id<MyViewControllerDelegate> delegate;
#end
Here we have created a new protocol. Any class that wants to be respond to the viewController:didChangeSelection: message could now do so. Just like with the table view above it would set the delegate to itself and then implement the method. Now that you have a delegate, you can invoke the method at an appropriate time.
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView
{
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
myTableView.delegate = self;
self.view = myTableView;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Selected section:%i row:%i", indexPath.section, indexPath.row);
indexPath = [NSIndexPath indexPathForRow:1 inSection:0];
[self.delegate viewController:self didChangeSelection:indexPath];
}
#end
Now the delegate can receive the message and do what it wants knowing that my view controller changed the selection.
A delegate pattern is a convenient way to allow for the communication between independent controller that allows for loose coupling. So let's say you have a pattern like this:
A
/ \
B C
Where A instantiates B and C. Communicating between A to B and A to C are easy but how would you communicate between B and C? B to A? C to A? There are a couple different ways you could do so such as Key-Value Observing or Block Callbacks. Delegation, though, are still most frequently used although Blocks are coming on strong.
In this example, object A instantiates object B to create an object and fill it with info. How would object B pass the new object back to A as you want to keep things loose? Well, with these 9 easy steps, you can do it too! It may not make sense but we'll start with ClassB…
// ClassB.h
#protocol ClassBDelegate; //(1)
#interface ClassB : NSObject
#property (nonatomic, weak) id<ClassBDelegate>bDelegate; //(2)
-(void)makeNewObjectAndSendBack;
#end
#protocol ClassBDelegate : NSObject //(3)
-(void) classB:(Class B *)theClassB finishedWithObject:(id)finishedObject; //(4)
#end
ClassB.m
#implementation
#synthesize bDelegate = _bDelegate; //(5)
-(void)makeNewObjectAndSendBack {
//something something something
[self.bDelegate classB:self finishedWithObject:newObject]; //(6)
}
#end
define the protocol that will be established later
set an instance of an object that will conform to that protocol
set up the protocol
set up the method call that you'll use to send the finishedObject back to A.
synthesize the delegate
after you do what you need to do, you send it back using the method
you defined in 4
// ClassA.h
#interface ClassA : NSObject <ClassBDelegate> //(7)
#property (nonatomic, strong) ClassB theClassB;
-(void)yourMethodToDoSomething;
#end
ClassA.m
#implementation
#synthesize theClassB = _theClassB;
-(void)randomMethod {
self.theClassB = [ClassB new];
self.theClassB.bDelegate = self; //(8)
[self.theClassB makeNewObjectAndSendBack];
}
-(void) classB:(Class B *)theClassB finishedWithObject:(id)finishedObject { //(9)
[self doSomethingWithFinishedObject:finishedObject]; //ta-da!
}
#end
7.Conform to the ClassBDelegate protocol. This basically says that you
will implement the methods defined in the protocol definition.
8.Set the classB object's delegate object as self! This is crucial and
often skipped.
9.Implement the delegate method for when you get the new object back.
So the process, in short is: A instanciate B. A sets B's delegate as self. A tells B to do something. B does something and sends object back via delegate method. A gets it back.
For more information, including what you can do with protocols, check out:
Big Nerd Ranch talk about Protocols
Part 1
Part 2
Part 3
Good luck!
It seems you might be a little confused as to what delegates are and how they are used. Here are two links to Apple documentation you might find useful: A conceptual overview and a more in depth explaination.

Objective-C: Problems accessing objects in other UIViewControllers

So I have a Custom UITableViewCell that holds a reference to its containing view controller (the VC that has its table in it).
// MyCell.h
#import <UIKit/UIKit.h>
#import "RootViewController.h"
#interface MyCell : UITableViewCell
#property (nonatomic, strong) IBOutlet RootViewController *rootViewController;
-(IBAction)checkBoxClicked:(UIButton*)sender;
// MyCell.m
#implementation MyCell
#synthesize rootViewController = _rootViewController;
-(IBAction)checkBoxClicked:(UIButton*)sender
{
[self setCheckBoxChecked:!_checkBoxChecked];
[_rootViewController refreshVisibleViewForCellTagged:self.tag];
}
In my cell I have a button that changes a variable and then calls a function in my rootViewController. The method is actually called however when I try to access any object in the RootViewController inside of the refreshVisibleViewForCellTagged method they are are '0x0' / nil;
// RootViewController.h
#interface RootViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) IBOutlet UITableView *myTableView;
// RootViewController.m
- (void) refreshVisibleViewForCellTagged:(NSInteger)cellTag
{
UITableView *tableView = self.myTableView; // nil
NSIndexPath *indexPath = [self.myTableView indexPathForSelectedRow]; // nil
MyCell *selectedCell = (MyCell*)[self.myTableView cellForRowAtIndexPath:indexPath]; // nil
if (selectedCell.tag == cellTag) {
NSLog(#"Refresh one way.");
} else {
NSLog(#"Do something else.");
}
}
Can anyone shed some light as to why I cant access any objects/variables in the RootController from within the method 'refreshVisibleViewForCellTagged'?
Please and thank you!
** My big question is Why can't I access any objects when calling a method in a view controller From a different view controller. There is some great programming truth that I am not aware of here, is it a permissions issue? Im not using #class (forward classing) in this instance.
As #trojanfoe said, delegation is a better way to do it.
Instead of #import "RootViewController.h", it is better to adop delegation. Because UITableViewCell is a child and RootViewController is the parent view. You don't want the child to talk directly with the parent.
To adopt delegation:
in MyCell.h file
remove #import "RootViewController.h".
revise MyCell.h as follows:
#protocol MyCellDelegate; // if you need to have forward declaration
#interface MyCell : UITableViewCell
// #property (nonatomic, strong) IBOutlet RootViewController *rootViewController;
#property (nonatomic) id <MyCellDelegate> delegate;
#end
#protocol MyCellDelegate <NSObject>
- (void)refreshVisibleViewForCellTagged:(NSInteger)cellTag;
#end
in MyCell.m.
#synthesize delegate;
-(IBAction)checkBoxClicked:(UIButton*)sender {
[self setCheckBoxChecked:!_checkBoxChecked];
//[_rootViewController refreshVisibleViewForCellTagged:self.tag];
[self.delegate refreshVisibleViewForCellTagged:self.tag];
}
in RootViewController.h adopt the delegation of MyCell
#import "MyCell.h"
#interface RootViewController : UIViewController <MyCellDelegate>
in RootViewController.m.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = // your implementation
//assuming all your cells are of MyCell kind
// set RootViewController as the delegate of each cell
((MyCell *)cell).delegate = self;
return cell;
}
implement the delegate method in RootViewController.m.
- (void)refreshVisibleViewForCellTagged:(NSInteger)cellTag {
// whatever you have
}
P.S. The above codes are for illustration. I didn't run them. If some part doesn't work, let me know, and I'll revise it.
The reason those objects in RootViewController are nil in the way you call, is because you are not accessing the same instance of RootViewController. It is a different (new) instance and hence all objects are nil.
Ignore the fact that view controllers are even involved. What you have are OBJECTS, connected together in a certain pattern. Accessing data in another view controller is no different from accessing data in any other object. There's no "magic" with view controllers, other than they have a few standardized connections to other objects.
IMHO, this is a poor design. For starters, your cell shouldn't need a reference to the view controller that the table it's in is in (read that twice, it barely makes sense just because the very idea of it is confusing). You have a strong reference to this view controller. So what happens when the OS tries to deallocate your view controller? It will never be able to, because the table view cell as a strong reference to it, keeping its retain count at 1. The same situation holds true for the cell. You risk running into a retain cycle here. Generally, child views should have weak references to their parents.
But this isn't even really a true parents/child relationship. I would suggest instead an approach like this, which all occurs in your view controller that contains the table view:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Assuming you set a reuse identifier "cellId" in the nib for your table view cell...
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:#"cellId"];
if (!cell) {
// If you didn't get a valid cell reference back, unload a cell from the nib
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:nil options:nil];
for (id obj in nibArray) {
if ([obj isMemberOfClass:[MyCell class]]) {
// Assign cell to obj, and add a target action for the checkmark
cell = (MyCell *)obj;
[cell.checkMarkButton addTarget:self action:#selector(checkPressed:) forControlEvents:whateverEventYouWant];
break;
}
}
}
// Set the tag of the cell here, since we may get a different cell back from the reuse queue
cell.checkMarkButton.tag = indexPath.row;
return cell;
}
Now set up the method for the clicking of the checkmark button
- (void)checkPressed:(id)sender {
UIButton *checkmark = (UIButton *)sender;
// This will give you the row of the checked button
int checkedCellRow = checkmark.tag;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:checkedCellRow inSection:0];
// Now you can grab a reference to that cell if you need to
MyCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
}
This way, you keep all of the controller-related stuff in your controller class (i.e. how to handle the checkmark button being pressed), and you don't need to deal with this whackiness of referencing the view controller of your cell's table.
EDIT: I guess I should also help answer your questions...First of all, if you're saying that in your refreshVisibleViewForCell method, you're getting a nil value for self.myTableView, are you sure it is hooked up properly in IB? Even if it's hooked up, click the little x to unhook it and hook it up again to be sure. Also make sure you've #synthesized your myTableView property. Without seeing more code, an IB issue is my best guess as to why you're getting a nil value for tableView. A nil value here will result in a nil indexPath and selectedCell, also. As for your big question, you can access properties of objects within your view controller. Those properties can, of course, be objects. So in your example, if you have a tag property on selectedCell, you can access it from anywhere that you have a valid reference to selectedCell. If selectedCell is nil, the property will be nil. #class is better suited for header files. For instance, if you wanted to make your custom cell a property of your view controller, you might say:
#import <UIKit/UIKit.h>
#class MyCell;
#interface RootViewController : UIViewController
#property (nonatomic, strong) MyCell *cell;
#end
Then, in your implementation file, you would actually import MyCell.h. Giving the #class forward declaration just keeps you from having to import all of the details about the MyCell class in your header file. The header doesn't need to know about all of the properties and methods of MyCell, just that you intend on using it in the implementation file. So you #class in the header, #import in the implementation.
in RootViewController.h:
#interface RootViewController : UITableViewController <UITableViewDelegate>
in RootViewController.m:
- (void) refreshVisibleViewForCellTagged:(NSInteger)cellTag {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
MyCell *selectedCell = (MyCell*)[self.myTableView cellForRowAtIndexPath:indexPath]; // nil
etc...
I'm not seeing declarations of myTableView in your RootViewController. But if your RootViewController implements UITableViewController, you can use self.tableView to access the tableview. You don't need to keep a reference to it by yourself.
#RachelD, if your RootView is more complicated than just a UITableViewController consider using a separate class, such as RootTableViewController. Then in your RootView xib, create IBOutlet for RootTableViewController to reference it. Like this:
// RootTableViewController definition
#interface RootTableViewController : UITableViewController
{
}
// RootViewController definition
#interface RootViewController : UIViewController
{
RootTableViewController *table_c;
}
#property (nonatomic, retain) IBOutlet RootTableViewController *table_c;
Note that you need to drag an "Object" into the "Objects" section (for RootViewController) in the interface builder, and type RootTableViewController in the Custom Class section for this object. Right click this object, make sure its IBOutlet, view, 2 delegates are correctly set.
The reason why your myTableView is nil is because it's not properly initialized. I mean, if you don't use UITableViewController you are responsible for assigning it manually via interface builder or something.

I am losing my default UIScrollViewdelegate methods when I subclass it

I try to subclass the UScrollview but it ends up losing the default UIScrollview delegate method.
#import <UIKit/UIKit.h>
#protocol myscrollviewDelegate <NSObject>
-(void) myscrollview_return;
#end
#interface myscrollview : UIScrollView <UIScrollViewDelegate> {
id<myscrollviewDelegate> delegate;
}
#property(nonatomic, assign) id<myscrollviewDelegate> delegate;
#end
(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
never get called when scroll.
what's wrong? Can I subclass the UIScrollview and add additonal delegate and at the same times keeping the original delegates??
You are not adding a property, but overriding it, as UIScrollView already has a delegate property. When you set a delegate using the new property, the reference will be stored in the instance variable you added, not in the private instance variable of the original UIScrollView.
My theory is that the implementation of UIScrollView accesses the instance variable without using the property. I haven't verified it, but try not adding a new ivar and overriding the delegate property.
You can do this without creating a second delegate property.
First, make your delegate protocol inherit from UIScrollViewDelegate:
#protocol myscrollviewDelegate <NSObject, UIScrollViewDelegate>
Then, declare the delegate property in your header for your class:
#interface myscrollview : UIScrollView <UIScrollViewDelegate>
#property(nonatomic, assign) id<myscrollviewDelegate> delegate;
And the key is to not synthesize the property, but rather make it dynamic in your implementation file.
#implementation myscrollview
#dynamic delegate;
...
This is because You implement the delegate methods with id id delegate; I hope so
so change the name of delegate. instead using delegate use other name like "delegateSomeClass" etc
Now the delegates method of UIscrollView calls
hope it will clear :)

Change the searchDisplayController table view style to grouped?

I have a searchDisplayController that searches a UITableView.
After entering the search terms, I can see another UITableView that contains the search results. However, I want this UITableView to be GROUPED, not PLAIN (like it is by default).
How do I do this?
This worked for me (iOS 5.0):
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
[self.searchController setValue:[NSNumber numberWithInt:UITableViewStyleGrouped]
forKey:#"_searchResultsTableViewStyle"];
If - like me - you think the plain TableView was way too ugly, you can also abandon the use of SearchDisplayController.
I just:
inserted in an empty View a searchBar and a TableView as we usually do for IBOutlet
selected the File's owner as delegate for both of them.
At the beginin the number of section is 0 ([myTab count]) then I used reloadData and this time myTab is populated by the result.
[self.resultTableView reloadData];
Here you can find all the method I used from the delegates
#interface MyViewController : UIViewController <UIApplicationDelegate, UISearchBarDelegate> {
IBOutlet UISearchBar *searchBar;
IBOutlet UITableView *resultTableView;
}
#property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
#property (nonatomic, retain) IBOutlet UITableView *resultTableView;
//For your searchBar the 2 most important methods are
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBarClicked;
- (BOOL)searchBarTextDidEndEditing;
//For your TableView the most important methods are in my case:
//number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
//HEADERS
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;
//different numbers of row by section
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
//the cells themselves
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
#end
After all, the simplest solution is often the best...
This works for me:
Create a class which extends UISearchDisplayController:
#interface RVSearchDisplayController : UISearchDisplayController
#end
#implementation RVSearchDisplayController
-(UITableView *) searchResultsTableView {
[self setValue:[NSNumber numberWithInt:UITableViewStyleGrouped]
forKey:#"_searchResultsTableViewStyle"];
return [super searchResultsTableView];
}
#end
Then add a UISearchDisplayController to your table using IB, and change its Custom Class to RVSearchDisplayController in Identity Inspector.
You could try to create a subclass of UISearchDisplayController and make searchResultsTableView searchable
in any .h file add:
#interface YourUISearchDisplayController : UISearchDisplayController {
UITableView * searchResultsTableView;
}
#property (nonatomic) UITableView * searchResultsTableView;
#end;
Then just use YourUISearchDisplayController instead od UISearchDisplayController.
Note: you might have to use (nonatomic, retain), (nonatomic, assign), or (nonatomic, copy). I'm not really sure
This is not possible as the searchResultsTableView property is readonly.
Overriding -searchResultsTableView won't work, because UISearchDisplayController accesses its table view instance variable directly, without calling the method.
The designated initializer for UISearchDisplayController appears to be a private method, -initWithSearchBar:contentsController:searchResultsTableViewStyle:, which sets the _searchResultsTableViewStyle instance variable. This instance variable is used in creating the search results table view. The public initializer calls this method, passing UITableViewStylePlain.
Directly calling the private designated initializer or setting the instance variable would likely get an application rejected from the App Store, so you might instead try overriding the public initializer and calling
[self setValue:[NSNumber numberWithInt:UITableViewStyleGrouped]
forKey:#"searchResultsTableViewStyle"];