Setting up a UITableView's subclass as it's delegate/dataSource - objective-c

I have a subclassed UITableView, 'subclassChild' which inherits from another subclass 'subclassParent'. 'subclassParent' then inherits from UITableView.
My view controller inherits from a custom View Controller 'GenericTableViewController', and contains my UITableViewController which has the class of 'subclassChild'.
'GenericTableViewController' customises my tables header and footers for all view controllers containing a table view.
My question is, how do I make it so that my UITableView can use 'subclassChild' and 'subclassParent' as its delegate and dataSource. Still making sure that 'GenericTableViewController' customises my tables header and footers.
Sorry if this is a bit confusing, I've tried to describe it as well as I can.
P.S I have added <UITabBarDelegate, UITableViewDataSource> in the header files.
And I have made sure that the table inherits from 'subclassChild' in IB.
Thanks in advance!

What you can do, is have your 'GenericTableViewController' have a delegate itself. It will do most of the work itself, but at the end it shall ask its delegate 'anything else to do'?
And when the delegate says 'Yes, there is something actually', that code will get executed, serving as data source, delegate or whatever you want it to be. As an example (please forgive me for code inaccuracies):
- (void) rowForIndex:(NSInteger)row {
if([delegate respondsToSelector:#selector("TVGetRow:")]) {
return [delegate TVGetRow:row]
} else return Rows[row];
}
This example assumes, that you have an array of tableview cells, but should give you a good idea. You don't need to implement UITableViewDataSource to that subclass header files, but instead use a custom protocol.
Edit: Since apparently I was unclear in what I was aiming to do, allow me to add a little information.
The basic idea is to use 'subclassChild' and through it 'subclassParent' as delegates for GenericTableViewController, instead of using 'subclassChild' as the type for the tableView. (You should never subclass a view, unless you intend to change it's drawing behaviour).
The general hierarchy would work like this:
subclassChild -> delegate -> GenericTableViewController <- DataSource/Delegate <- TableView
You'd need to create an init method, or a setter method, which sets this delegate, and a simple protocol, which allows you to call functions, like so:
#interface GenericTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate> {
id theDelegate;
}
- (id) initWithDelegate:(id)delegate;
#end
#protocol GTVC
#optional
- (UITableViewCell *) getDataForRow:(NSIndexPath *)path;
- (void) rowSelected:(NSIndexPath *) path;
#end
In the .m file, this example would look like this:
#implementation GenericTableViewController
- (id) initWithDelegate:(id)delegate {
self = [super initWithNibName:#"TableView.nib" bundle:nil];
if(self) {
theDelegate = [delegate retain]; //The 'retain' is optional, really.
}
return self;
}
- (void) dealloc {
[theDelegate release]; //In case of retain -> release.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if([theDelegate respondsToSelector:#selector(getDataForRow:)])
return [theDelegate getDataForRow:indexPath];
else {
//Create general UITableViewCell
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if([theDelegate respondsToSelector:#selector(rowSelected:)])
[theDelegate rowSelected:indexPath];
else {
//Do stuff
}
}
#end
This protocol/delegate 'game' can be expanded easily, giving you all levels of control you need...all the while with the GenericTableViewController as a 'backup', if any delegate isn't set, or 'not ready' to answer the request for further instructions.
I hope this helps.

Related

NSTableView ViewBased never calling the needed delegate

I have a NSTableView where I wish to display a list of info.
Currently the viewForTableColumn method delegate never runs, but numberOfRowsInTableView does.
I have the NSTableViewDelegate and NSTableViewDataSource set in the ViewController head. And I set the tableview delegate and datasource to self. Does somebody know why it wouldn't run? I've added a screenshot and code below.
ViewController.h
#interface ViewController : NSViewController <NSTableViewDelegate, NSTableViewDataSource>
#property (strong) IBOutlet NSTableView *tableView;
#property (strong, nonatomic) NSMutableArray<App *> *installedApps;
#end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
_installedApps = [[NSMutableArray alloc] init];
_tableView.dataSource = self;
_tableView.delegate = self;
// Other stuff that populates the array
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return _installedApps.count;
}
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTableCellView *result = [tableView makeViewWithIdentifier:#"appCell" owner:self];
result.textField.stringValue = #"Hello world";
return result;
}
The view is in a container view, I have the 'appCell' identifier set to the Table Cell View.
The array _installedApps is empty and numberOfRowsInTableView: returns 0. Thus, tableView:viewForTableColumn: is not called because there is no row to show. No rows also means no columns.
You should also ensure that you have configured your table view as view based in attributed inspector of the table view.
I can't see it in the screenshots, but...is the highlighted row of the view hierarchy (Table Cell View) the one with the appCell identifier?
[minutes pass...]
Oops; sorry. I see you've noted that above.
The reason I ask is that I made a new project from your code, changing the array type from App to NSString, added a one-column table view to the storyboard, linked it to the code, added a couple enties to the array in -viewDidLoad, and -- once I put the appCell identifier in the right place (duh) -- it all worked fine.
This is super strange, but I had the very same issue, everything connected correctly, number of rows being called, but not viewForTableColumn... In the end the following Answer proved to be the solution:
https://stackoverflow.com/a/13091436/3963806
Basically, I had setup the tableview straight out of the Object library, no layout constraints etc... Once I added layout constraints, the method started to be called... Super strange as I could see and click on the "rows" but they weren't populated correctly... I think it's down to clipping as mention in the linked answer.

UISearchBar in UIView

I'm having difficulty following directions. I have a UISearchBar in a UIView. The user will enter the search string into the UISearchBar and click the search icon to search. The results will display in a new window (UITableView).
My search has shown me this:
A UISearchDisplayController cannot be added to a UIView because it
doesn't inherit from a UIView. You can add a UISearchBar in Interface
builder with an IBOutlet and then create a UISearchDisplayController
with that UISearchBar programmatically.
Just do it in code e.g. (assuming the view controller is vc): [vc
addSubview:mySearchDisplayController.searchBar]; // Note that
searchBar is the view, and mySearchDisplayController only CONTROLS the
searchBar etc.
and also this:
Just make your view controller implement the UISearchBarDelegate. In
your xib file, all you need to do is to add a UISearchBar to your view
and configure it as necessary, create an outlet for it (optional
really but helps to be explicit), and assign the delegate outlet to
your view controller. Then, to respond to the search bar events,
implement the UISearchBarDelegate protocol methods as necessary. For
example:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[self handleSearch:searchBar]; }
(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
[self handleSearch:searchBar]; }
(void)handleSearch:(UISearchBar *)searchBar {
NSLog(#"User searched for %#", searchBar.text);
[searchBar resignFirstResponder]; // if you want the keyboard to go away }
(void)searchBarCancelButtonClicked:(UISearchBar *) searchBar {
NSLog(#"User canceled search");
[searchBar resignFirstResponder]; // if you want the keyboard to go away }
I'm just not getting it! Should I be adding mySearchController to my UIView? or my UISearchBar? Adding it to my UIView, nothing happens; adding it to my UISearchBar really wigs out the application. I don't even get an error - it just hangs.
Then, there is the second part: The delegate. Should I put the delegate in my UIView? Or in the UISearchDisplayController? Not sure which direction to go in and nothing so far is working. Please help.
All I really want at this point is just to get the handleSearch method to get executed. Thank you very much in advance for any help.
Very confused.
The idea is that you create a Search Bar in Interface Builder as an outlet and then use the following code to create UISearchDisplayController:
self.searchDisplayController2 = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
self.searchDisplayController2.delegate = self;
self.searchDisplayController2.searchResultsDelegate = self;
self.searchDisplayController2.searchResultsDataSource = self;
This code would be implemented in your View Controller, say in viewDidLoad. You will also have to implement at least two delegate functions in the same VC:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

tableView:cellForRowAtIndexPath: never being called

I have a UITableView, named tblParentComments in a UIView, of class CBox.
I have definitely set my view as the datasource and delegate of my table view, and my view does implement those protocols. The method tableView:numberOfRowsInSection: does get called and returns a non-zero value. But the function tableView:cellForRowAtIndexPath: is never called.
I noticed that if I put the method tableView:cellForRowAtIndexPath: in comments, Xcode does NOT stop compiling with an error like "tableView:cellForRowAtIndexPath: is required" -- the app just runs and show a empty table.
I don't understand. Any ideas? This is the code for my view:
Interface CBox.h
#interface CBox : UIView <UITableViewDelegate, UITableViewDataSource>
And in the implementation file:
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
tblParentComments = [[UITableView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, frame.size.height)];
tblParentComments.delegate = self;
tblParentComments.dataSource = self;
//tblParentComments.userInteractionEnabled = NO;
tblParentComments.backgroundColor = [UIColor clearColor];
tblParentComments.separatorStyle = UITableViewCellSeparatorStyleNone;
tblParentComments.bounces = NO;
[self addSubview:tblParentComments];
}
return self;
}
#pragma mark - UITableViewDelegate + UITableViewDatasource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"num of rows = %d", parentComents.count);
return 1; // I set a non-zero value for test
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.... // I set a breakpoint here, never been called here
}
YES..i have the same problem... and I just found out the solution.
In my class i use different inits with different parameters.
In my -(void)viewDidLoad i use to alloc the table view with CGRectZero, and ONLY in this case IF u DONT set up the FRAME of the UITableView then:
the numberOfRowsInSection will BE CALLED
the cellForRowAtIndexPath will NEVER BE CALL
I just set up my UITableView frame and it's works.
As I read above comments I can figure out couple of things:
You probably have messed up a bit structure of your code. You should always conform to protocols in your view controller - ! not view. Alternatively, what I like to do (as it gives me better control over my code and it keeps things clean), separate protocols out of view controller - means create new object (model object) that will handle everything what table requires and it will conforms to table delegate and datasource.
If you organise your code wisely, you should avoid situation you described.
Also I believe you may have 2 objects conforming to table protocols, and thats where the things get ugly.

UINavigationControllers: How to pass value to higher (parent?) controller in stack?

I have SendingController which push to nav stack SendingDeatilsController (which one has a TableView). User should should pick in TableView one row (it checked by Checkmark) and I would like to pass the value of this row (let it will NSString object) to the SendingController.
How can I realize this behaviour in my application? And is SendingController parent for SendingDetailController (attribute parentController of SDC refers to SC) ??
If you want to implement this behaviour, pass the SendingDetailController a reference to the previous view controller. This way the detail view controller can send a message to the previous one on the stack.
In your SendingDetailController define a weak reference :
// in .h
SendingController *sendingController;
#property(assign) SendingController *sendingController;
// in .m
#synthesize sendingController;
-(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// retrieve the string and send the message
[sendingController didSelectString:theString];
}
Now before pushing the SendingDetailController on the stack don't forget to set its sendingController property.
// .m
// where you push the vc
if(!sendingDetailController) {
sendingDetailController = [[SendingDetailController alloc]
initWithNibName:#"TheNIBName"
bundle:nil];
sendingDetailController.sendingController = self;
}
[self.navigationController pushViewController:sendingDetailController
animated:YES];
and write the method that will recieve the string.
-(void)didSelectString:(NSString *)aString {
// do anything with string
[self.navigationController popViewControllerAnimated:YES];
}
This should do the job.
For easy asynchronous communication between different UIViewControllers, you may want to look at NSNotification and NSNotificationCenter.
There are plenty tutorials on the web and some good answers here at SO that can show you how to do that exactly.

Passing variables (or similar) to newly loaded view

I am loading new views for a small iphone app, and was wondering how to pass details from one to another?
I am loading a tableview full of data from and xml file, then once clicked a new view is brought in via:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SubInfoViewController *subcontroller = [[SubInfoViewController alloc] initWithNibName:#"SubInfoView" bundle:nil];
[self presentModalViewController:subcontroller animated:YES];
[subcontroller release];
}
Next step would be to tell the newly loaded view which row had just been loaded?
Any idea, thoughts more than welcome, and please be gentle big newbie...
I typically create my own init method to do things like this. I think it would likely be better to pass in the corresponding "model" object represented by the tableView row, rather than the row number itself, like this:
In SubInfoViewController.h
#interface SubInfoViewController : UIViewController {
YourObject *yourObject;
}
#property (nonatomic, retain) YourObject *yourObject;
Then in SubInfoViewController.m:
- (SubInfoViewController*)initWithYourObject:(YourObject*)anObject {
if((self = [super initWithNibName#"SubInfoView" bundle:nil])) {
self.yourObject = anObject;
}
return self;
}
You'd create and present it this way:
// assuming you've got an array storing objects represented
// in the tableView called objectArray
SubInfoViewController *vc = [[SubInfoViewController alloc] initWithYourObject:[objectArray objectAtIndex:indexPath.row]];
[self presentModalViewController:vc animated:YES];
[vc release];
This could be adapted pretty easily to allow you to pass in any type of object or value (such as a row number if you still want to do that).
Add an instance variable to your view controller and declare a property corresponding to it, so after you alloc, init it, set it like subcontroller.foo = Blah Blah.