ViewController as delegate of custom view's tableview - ios

I have a custom UIView, MyView, with a couple of tableviews in it - tv1 and tv2 - both added programmatically.
I have a custom UIViewController, MyController, which creates an instance of MyView on loadView.
My goal is the following. myView should be the datasource and delegate for myView.tv1, since it doesn't touch my data model and is static.
myController should be the delegate of myView.tv2, since its content will depend on the datasource.
I have added the following to each header:
#interface MyController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#interface MyView : UIView <UITableViewDelegate, UITableViewDataSource>
and added to each the required delegate methods.
In MyView's init, I add that table views, and set the delegate of the first one to self:
tv1 = [[UITableView alloc] initWithFrame:frame1 style:UITableViewStyleGrouped];
tv1.delegate = self;
tv1.datasource = self;
tv2 = [[UITableView alloc] initWithFrame:frame2 style:UITableViewStyleGrouped];
And in my Controller I connect up the second tableview:
- (void)loadView{
self.view = [[MyView alloc] initWithFrame:myFrame];
[(MyView *)self.view tv2].delegate = self;
[(MyView *)self.view tv2].dataSource = self;
}
When I run this, I get a whole mess of runtime errors. Sometimes it complains about a paging control, other times NSCFArray, othertimes no text at all. It always crashes.
I have determined that it is setting the datasource that causes the problem, not the delegate. If I comment out the datasource line in laodView, it runs fine (but with no content of course).
What could cause my to experience a different runtime error each time, and where might I be going wrong?

myView should be the datasource and delegate for myView.tv1, since it
doesn't touch my data model and is static
That's not really MVC, right ?
I think you'll be able to track the problems faster (or easily avoid it) if you prepared separate combined delegate&datasource classes for each table. The table1 datasource might be an internal class of MyView.
It's critically important to keep the delegate/datasource references retained by the table owner as the table will not retain it itself (that might be not so important for the case when you set the delegate/datasource to self both for table1 (MyView) and table2 (MyController), but you need to keep it in mind if you create separate delegate&datasource classes). Anyway you'll need to retain the array you are using as a datasource.

Related

Where to initialize subview?

I am new to iOS development and I am currently reading the book : iOS Programming (Objective C) by Big Nerd Ranch.
I am confused as in where to initialize subviews such as UIButtons, UIImageView while creating views programtically:
Should the intialization be done in the Main UIView i.e in the
initWithFrame method and maintain a additional weak reference to the subview in the UIView.
or
should I do it in the UIViewControllers loadView method and maintain a weak reference to the subview in the uiviewcontroller (Same approach used while creating UIVew using the interface builder).
I have seen both the approaches being used in various stackoverflow posts but no post that explains which approach is the right one.
you can initialize as per your app's requirement. If any view or button or anything is part of initial setup of your app then you should initialize it in viewDidload.
Now, for example there is requirement like user press button and then new view will be created then you can initialize view in button's click method etc.
So, it's depends on your requirement.
Static views which will live from start to and of app should be initialize in viewdidload, because this is the first method getting called of viewcontroller.
hope this will help :)
It dependes on which architecture you are using. Apple raises the flag of Model-View-Controller, but in fact, UIViewControllers are the View.
For Example:
Let's say that you have a pretty LoginViewController. When you instantiate it, you will be doing something like
LoginViewController *loginVC = [[LoginViewController alloc] init];
At this point, no view is loaded. Your ViewController has just executed the init method, nothing else. When the system calls
loginVC.view
the first method to be executed will be
- (void)loadView;
there you should do exactly that, load your view. So, the approach i like is to have an additional LoginView.
- (void)loadView
{
// you should have a property #property (nonatomic, strong) LoginView *loginView;
self.loginView = [[LoginView alloc] init];
self.view = self.loginView;
}
and in the LoginView init method, you should put your code to build up the view.
However, you could eliminate LoginView, and instantiate all your subviews like this:
- (void)loadView
{
self.view = [[UIView alloc] init];
UIButton *button = [[UIButton alloc] initWithTargetBlaBlaBla...];
[self.view addSubview:button];
// add more fancy subviews
}
In my experience, the first approach is much cleaner than the second one. It also makes version control a lot easier (try to merge a xib, I dare you). I always use MyView.m to build the view (a.k.a setup constriants, style) and use MyViewController.m things like animations, lifeCycle. I like to think that MyView.m is the programatic xib, so anything that you can do with xibs, you should me able to do it inside your view.
Hope it helps!!

Swapping UITableViews in a single UIViewController

I have 1 view controller where depending on the button that is clicked, a view at the bottom is swapped between 4 different table views. These table views are in their own separate UITableViewController's in the storyboard. I add the tableViews like this:
Tracks_TVC *tracksTVC = [[Tracks_TVC alloc] init];
tracksTVC.view.frame = _postView.frame;
tracksTVC.view.tag = kTagPostView;
[self.view addSubview:tracksTVC.view];
I get this error message when the code is run:
NSInternalInconsistencyException', reason: 'UITableView dataSource
must return a cell from tableView:cellForRowAtIndexPath:
I've implemented the data source in the separate UITableViewController's properly (I believe so) with all the required methods so I'm confused as to why I'm receiving this error. My only thought is that the added table view isn't using the methods in it's own UITableViewController..? Any help on this matter would be greatly appreciated!
You should create the property of TableViewController such as -
#property(nonatomic, strong) Tracks_TVC *tracksTVC;
Now in viewDidLoad initialise the same -
-(void)viewDidLoad
{
[super viewDidLoad];
self.tracksTVC = [[Tracks_TVC alloc] init];
self.tracksTVC.view.frame = _postView.frame;
self.tracksTVC.view.tag = kTagPostView;
[self.view addSubview:self.tracksTVC.view];
}
Note : Also make sure that you are returning the cell in Data source.

iOS and Xcode: incompatible type error when setting delegate and datasource in a UITableView

I'm trying to make an app programmatically that includes a UITableView that makes a list of items based on the files in the app's Documents directory. I have been able to make the files be read into an array _filepathsArray, but the compilation crashes and Xcode throws warnings when I try to use the array to fill the table.
Xcode points out problems with the following lines:
_tableView.delegate = self;
_tableView.dataSource = _filepathsArray;
Both of these throw "semantic issues". The first throws
`Assigning to 'id<UITableViewDataSource>' from incompatible type 'NSArray *__strong'`,
while the second throws
`Assigning to 'id<UITableViewDelegate>' from incompatible type 'BrowserViewController *const __strong'`.
If I remove these lines, the app will compile properly (but of course does not use the data to fill the table), so I assume that the problem has to do with these.
I'm a beginner with Objective C and Xcode, so I can't quite figure out what I'm doing wrong here. Thanks for the help.
UPDATE
I have changed the line _tableView.dataSource = _filepathsArray; to _tableView.dataSource = self; as explained by several answers below. Now, both lines throw the same error:
`Assigning to 'id<UITableViewDelegate>' from incompatible type 'BrowserViewController *const __strong'`.
Could this error be the result of the way that the view controller is configured? In the header file, it is defined as a UIViewController
#interface BrowserViewController : UIViewController
I then include a UITableView as a subview.
You should declare a UITableViewDataSource, which is an object that implements that protocol and supplies data to your table.
_tableView.dataSource = self;
From Apple Docs
dataSource
The object that acts as the data source of the receiving table view.
#property(nonatomic, assign) id<UITableViewDataSource> dataSource
Discussion
The data source must adopt the UITableViewDataSource protocol. The data source is not retained.
Updated: Please define your class like below as per my first line in the answer that You should declare a UITableViewDataSource:
#interface BrowserViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
You get these warnings because you didn't declare that you would implement the data source and delegate methods in the .h file of the object that you want to be the data source and delegate. Normally, this would be a subclass of UITableViewController or UIViewController, although any object that implements the protocols could be the data source or delegate. A UITableViewController already conforms to those two protocols, so you don't need to declare anything, but if you're using a UIView controller, you should put this (though it's not strictly necessary) in the .h file:
#interface YourCustomClassName : UIViewController <UITableViewDataSource,UITableViewDelegate>
In the .m file you should set self as both the data source and delegate (again, this is the usual pattern, but one object doesn't have to provide both roles):
self.tableView.delegate = self;
self.tableView.dataSource = self;
_tableView.dataSource = _filepathsArray; // <- this is the problem because its type is controller not array,
// add this to your interface
#interface BrowserViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
because you are currently not confirming the UITableView protocols
always use this like
_tableView.delegate = self;
_tableView.dataSource = self;
and your _filepathsArray will be used in numberofRows delegate for getting the row count and int cellForRowIndexPath to show the data like
cell.titleLabel.text = _filepathsArray[indexPath.row];

How to initialize a custom view(controller) so it works both programmatically and in Interface Builder?

Suppose you implement a custom table view and a custom view controller (which mostly mimics UITableViewControllers behaviour, but when initialized programmatically, ...
#interface Foo : MyCustomTableViewController ...
Foo *foo = [[Foo alloc] init];
... foo.view is kind of class MyCustomTableView instead of UITableView:
// MyCustomTableView.h
#protocol MyTableViewDelegate <NSObject, UITableViewDelegate>
// ...
#end
#protocol MyTableViewDataSource <NSObject, UITableViewDataSource>
// ...
#end
#interface MyCustomTableView : UITableView
// ...
#end
// MyCustomTableViewController.h
#interface MyCustomTableViewController : UIViewController
// ...
#end
How should you implement/override init methods in correct order/ways so that you could create and use an instance of MyCustomTableView both by subclassing MyCustomTableViewController programmatically or from any custom nib file by setting custom class type to MyCustomTableView in Interface Builder?
It important to note that this is exactly how UITableView (mostly UIKit for that matter) works right now: a developer could create and use either programmatically or by creating from nib, whether be it File owner's main view or some subview in a more complex hierarchy, just assign data source or delegate and you're good to go...
So far I managed to get this working if you subclass MyCustomTableViewController, where I will create an instance of MyCustomTableView and assign it to self.view in loadView method; but couldn't figure out how initWithNibName:bundle:, initWithCoder:, awakeFromNib, awakeAfterUsingCoder:, or whatever else operates. I am lost in life cycle chain and end up with a black view/screen each time.
Thanks.
It is a real mystery how the UITableViewController loads its table regardless of if one is hooked up in interface builder, however I have came up with a pretty good way to simulate that behavior.
I wanted to achieve this with a reusable view controller that contains a MKMapView, and I figured out a trick to make it happen by checking the background color of the view.
The reason this was hard is because any call to self.view caused the storyboard one to load or load a default UIView if didnt exist. There was no way to figure out if inbetween those 2 steps if the user really didn't set a view. So the trick is the one that comes from a storyboard has a color, the default one is nil color.
So now I have a mapViewController that can be used in code or in storyboard and doesn't even care if a map was set or not. Pretty cool.
- (void)viewDidLoad
{
[super viewDidLoad];
//magic to work without a view set in the storboard or in code.
//check if a view has been set in the storyboard, like what UITableViewController does.
//check if don't have a map view
if(![self.view isKindOfClass:[MKMapView class]]){
//check if the default view was loaded. Default view always has no background color.
if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor){
//switch it for a map view
self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
self.mapView.delegate = self;
}else{
[NSException raise:#"MapViewController didn't find a map view" format:#"Found a %#", self.view.class];
}
}
The strategy I've used when writing such classes has been to postpone my custom initialization code as late as possible. If I can wait for viewDidLoad or viewWillAppear to do any setup, and not write any custom code in init, initWithNibName:bundle: or similar methods I'll know that my object is initialized just like the parent class no mater what way it was instantiated. Frequently I manage to write my classes without any overrides of these init methods.
If I find that I need to put my initialization code in the init methods my strategy is to write just one version of my initialization code, put that in a separate method, and then override all the init methods. The overridden methods call the superclass version of themselves, check for success, then call my internal initialization method.
If these strategies fail, such that it really makes a difference what way an object of this class is instantiated, I'll write custom methods for each of the various init methods.
This is how I solved my own issue:
- (void)loadView
{
if (self.nibName) {
// although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
[super loadView];
}
else {
self.view = // ... whatever UIView you'd like to create
}
}

iOS 5, Storyboards,ARC: Creating and setting custom delegate and datasource to program. created UITableView

The prequel of this problem is here.
I am trying to create and set a custom delegate and datasource to my programmatically created UITableView. I've googled around, but couldn't find any clear solution for my problem.
Meanwhile, I've created a new class that conforms to UITableViewDelegate,UITableViewDataSource
protocols. In this class:
tableView numberOfRowsInSection: 20
tableView cellForRowAtIndexPath: cell.textLabel.text=#"Nominals";
Class that contains UIViews:
Method that creates UITableView:
-(IBAction)segmentValueChaged:(id)sender
{
if(self.segment.selectedSegmentIndex==0)
{
[self.coinageView removeFromSuperview];
[self.view addSubview:nominalsView];
[self populateNominals:self.subCountryID];
}
else
{
[self.nominalsView removeFromSuperview];
[self.view addSubview:coinageView];
[self populateCoinages:self.subCountryID];
}
}
-(void)populateNominals:(int)subCountryID
{
NominalsTableViewDelegate *del=[[NominalsTableViewDelegate alloc]init];
UITableView *nominalsTableView=[[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 372) style:UITableViewStylePlain];
[nominalsTableView setDelegate:del];
[nominalsTableView setDataSource:del];
[self.nominalsView addSubview:nominalsTableView];
[nominalsTableView reloadData];
}
Finally, I'm getting EXC_BAD_ACCESS.
The evil is in [nominalsTableView setDelegate:del]; [nominalsTableView setDataSource:del]; rows. What's wrong with them.
Help is much appreciated. Thanks in advance.
I am not sure if you are adding a new UITableView to your current view or not, but I will assume you are doing so.
If you have a class that conforms to the UITableViewDelegate. But when i needed to create a UITableView programmatically, I create a new UITableView class (with the .h and .m) then make a MutableArray and exposing it as a property to the parent view so it can set the data source.
From there, you can create an instance of the class along with the datasource (which you exposed from the child object). Finally, you then add the view onto your current view. This method you dont need to set the delegate because your child class conforms to the tableview delegate protocol.
If you are just modifying the data inside the current tableview then, you use a NSMutableArray and then change the data in it. After then do a
[self.tableView reloadData];
Hope this helps!
EDITED
I might have misunderstood your question, what you (most likely) need to do is create a property of the delegate class then create an instance of your delgate class and assign it to the property.
Then do a
[myTableView setDelegate:self.myProperty];
[myTableView setDatasource:self.myProperty];
This, I believe, would solve you bad access problem.
EDITED AGAIN
Create a property inside your .h of the tableview class as such:
#property (nonatomic, retain) NominalsTableViewDelegate *myDelegate;
From there then inside your .m file, you do something similar to this:
NominalsTableViewDelegate* delegateClass = [[NominalsTableViewDelegate alloc] init];
[self setMyDelegate:delegateClass];
[delegateClass release];
Then you can set your tableview datasource as such:
[myTableView setDelegate:self.myDelegate];
[myTableView setDatasource:self.myDelegate];
Note: I currently have no access to a machine to test this, but just something to point you towards.

Resources