Overriding UITableViewController's cellForRowAtIndexPath in Objective-C - objective-c

I have a view controller MainVC which contains a view where I want to switch between various tables. I have an "abstract" class called InfoVC which extends UITableViewController and contains several methods that must be overridden or else they throw an exception. Finally, I have several classes which extend InfoVC and implement the "abstract" methods in InfoVC, along with methods that override tableView's numberOfRowsInSection and cellForRowAtIndexPath.
This is how I load a table in MainVC. In this case, TemperatureInfoVC extends InfoVC, self.subVC is of type InfoVC, and switchView is a view in MainVC:
TemperatureInfoVC *sVC = [[TemperatureInfoVC alloc] initWithStyle:UITableViewStylePlain];
self.subVC = sVC;
[sVC release];
[switchView insertSubview:self.subVC.view atIndex:0];
My problem is that the table always loads empty. From setting breakpoints, I can see that TemperatureInfoVC's numberOfRowsInSection is being called, but neither TemperatureInfoVC's or InfoVC's cellForRowAtIndexPath are being called. However, if I change the first line in the code above to:
InfoVC *sVC = [[InfoVC alloc] initWithStyle:UITableViewStylePlain];
then InfoVC's numberOfRowsInSection and cellForRowAtIndexPath are called properly. Does anyone know why TemperatureInfoVC's cellForRowAtIndexPath is not being called?

I assume, that you are not setting the delegate (UITableViewDelegate) and the datasource (UITableViewDatasource). Usually the delegate and the datasource implementation are with-in the custom UITableViewController, so (if not wiring up in IB), there need to be this somewhere:
tableView.delegate = self;
tableView.dataSource = self;
often within the -loadView or -viewDidLoad:

Oops, figured it out. I was loading data from an NSMutableArray in TemperatureInfoVC which was set in MainVC, but I was setting the data before I initialized subVC, so of course the table had no data in it.

Related

self.TableView not working for a TableViewController inside a ViewController?

I am making a ViewController with 2 tableviews inside it. I am structuring it like
ViewController -> 2 x TableViewController classes. What I don't understand is that if I do [self.tableView reloadData] in the TableViewControllers it doesn't do anything.
If I do [tableViewA reloadData] in the ViewController it will execute the datasource methods in the TableViewController.
How do I call reloadData within the TableViewControllers?
Thanks,
Alan
Edit - This is how I setup the ViewController
if(self.reviewController == nil)
{
self.reviewController = [[ReviewerTableViewController alloc] init];
}
if(self.approverController == nil)
{
self.approverController = [[ApproverTableViewController alloc] init];
}
[self.reviewerTableView setDataSource:reviewController];
[self.approversTableView setDataSource:approverController];
[self.reviewerTableView setDelegate:reviewController];
[self.approversTableView setDelegate:approverController];
self.reviewController.view = self.reviewController.tableView;
self.approverController.view = self.approverController.tableView;
It seems like the datasource methods run once when I initialize them, but reloadData does not work inside.
I am basically just using the datasource methods in the UITableViewControllers and I am calling a method to pull data from the net. Once I get the data, I call reloadData, but the datasource methods are not executed.
Be sure the TableViewControllers' tableView properties are connected to the correct table views.
Are you calling reloadData from the rootViewController? if so, you call
[self.reviewerTableView reloadData];
I would create a UIVIewController that implements the UITableViewDataSource and UITableViewDelegate protocols. Create two outlets, one for each tableview and make sure to wire them up in the interface builder.
then call [tableView1 reloadData];
This seems very strange: you're trying to set the child UITableViewControllers' dataSource and delegates to be some local UITableViews declared (presumably) in your master view controller. It isn't supposed to work like this - the child UITableViewControllers are already the dataSource and delegate to their own implicit table views. There's no need to wired them up to separate UITableViews.
EDIT: I am assuming your "TableViewController" classes are UITableViewControllers. If not, then what you're doing may be valid, but I'd need to see all the code, headers included.

The right way of setting up MapKit's delegate in a separate class

What is the proper way of setting up a separate delegate class for MapKit?
I have MapView class subclassing MKMapView and bare MapDelegate class conforming MKMapViewDelegate protocol having only one initializer method.
Here is the extract from MapView initialization method I use:
# MapView.m ...
#implementation MapView
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// [self setShowsUserLocation:YES];
[self setDelegate:[[MapDelegate alloc] initWithMapView:self]];
The only method MapDelegate class has is
# MapDelegate.m ...
- (id)initWithMapView:(MapView *)aMapView {
self = [super init];
self.mapView = aMapView;
return self;
}
Having [self setShowsUserLocation:YES]; commented, all works fine - I see the map. If I uncomment this line, my application begins to crash.
What my MapDelegate class is missing?
UPDATE 1: if I don't use a separate class MapDelegate and set just setDelegate:self - all works.
UPDATE 2: Now I understand, that the problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that I need MapDelegate class to live longer than it does now (delegate property has weak attribute). If I do the following:
#property (strong) id delegateContainer;
....
[self setDelegateContainer:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateContainer];
...it works! Is there a better way of retaining MapDelegate life cycle along with the one of MKMapView?
Thanks!
After waiting enough for any answers that could appear here and ensuring original problematic behavior twice more times, I am posting my own answer based on the second update from the question:
The problem with [self setDelegate:[[MapDelegate alloc] initWithMapView:self]]; string is that MapDelegate class should be able to be kept alive outside of the scope of question's initWithFrame method because delegate property has weak attribute. The possible solution is to create an instance variable serving as a container for a delegate class, for example:
#property (strong) id delegateClass;
....
[self setDelegateClass:[[MapDelegate alloc] init]];
[self setDelegate:self.delegateClass];
This solves the original problem.
LATER UPDATE
Though it is possible to set MKMapView's delegate in a separate class, I now realize that such model should not be used:
Currently I always prefer to use my controllers (i.e. controller layer in MVC in general) as delegates for all of my View layer classes (map view, scroll view, text fields): controller level is the place where all the delegates of different views can meet - all situated in controller layer, they can easily interact with each other and share their logic with the general logic of your controller.
On the other hand, if you setup your delegate in a separate class, you will need to take additional steps to connect your separate delegate with some controller, so it could interact with a rest part of your logic - this work have always led me to adding additional and messy pieces of code.
Shortly: do not use separate classes for delegates (at least view classes delegates provided by Apple), use some common places like controllers (fx for views like UIScrollView, MKMapView, UITableView or models like NSURLConnection).
I think viewDidLoad would be a better place to set up the map view. It's just a guess, but perhaps the crash is due to the view not being loaded yet.
Of course subclassing MKMapView isn't recommended at all. You would generally put your map as a subview, and set the main view to be the delegate. From the docs:
Although you should not subclass the MKMapView class itself, you can get information about the map view’s behavior by providing a delegate object.
Finally, if you really want to have a separate delegate class, you don't need to set its mapView, as all delegate methods pass the map as an argument.

Values not passed back after dismissModalViewController

I'm passing a string variable for the sake of testing (isLoggedIn) as well as an NSManagedObject (userObject). However, when I dismiss the VC and it comes back to the root, I do not have the new data that was set in the variables in the loginViewController.
LoginViewController *loginVC = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
loginVC.managedObjectContext = self.managedObjectContext;
loginVC.userObject = self.userObject;
loginVC.isLoggedIn = self.isLoggedIn;
[self presentModalViewController:loginVC animated:YES];
[loginVC release];
I later dismiss the view with:
[self dismissModalViewControllerAnimated:YES];
Update:
Ended up using delegates as someone suggested. I used the following post as a guideline:
UIViewController parentViewController access properties
I ended up using delegates as someone above suggested. I used the following post as a guideline:
UIViewController parentViewController access properties
I may not be following you properly so this could be irrelevant.
I am guessing you mean that:
You set your ivars after alloc/init
You make some changes inside LoginViewController
You expect those changes to be reflected in self.userObject and self.isLoggedIn of the class that instantiated LoginViewController
which may or may not happen if you act on the objects themselves or you reassign the pointers
e.g.
If you call self.userObject.name = #"Test"; inside LoginViewController then the change will be reflected in the class that instantiated LoginViewController and LoginViewController because the ivars are pointing to the same object in memory and you are manipulating the object.
OR
If you call self.userObject = theResultOfSomeNewFetch; then the change will not be reflected as you now have a pointer in LoginViewController that is pointing to a different userObject to the pointer in the class that called LoginViewController
Hopefully I have not lost the plot completely and this is somewhere near what you mean.

UIViewController and UIImagePickerController: Unable to create and managing views as expected

I have a UIViewController subclass that contains an instance of UIImagePickerController. Let's call this controller CameraController. Among other things, the CameraController manages the UIImagePickerController instance's overlayView, and other views, buttons, labels etc. that are displayed when the UIImagePickerController, let's call this instance photoPicker, is displayed as the modal controller.
The photoPicker's camera overlay and the elemets that are part of the CameraController view hierarchy display and function as expected. The problem I'm having is that I cannot use UIViewController's default initializer to create the CameraController's view heirarchy.
I am initializing CameraController from within another UIViewController. Let's call this controller the WebViewController. When the user clicks on a button in a view managed by WebViewController, the launchCamera method is called. It currently looks like this:
- (void) launchCamera{
if (!cameraController) {
cameraController = [[CameraController alloc] init];
// cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
// bundle:[NSBundle mainBundle]];
cameraController.delegate = self;
}
[self presentModalViewController:cameraController.photoPicker animated:NO];
}
I want to be able to create CameraController by calling initWithNibName:bundle: but it's not working
as I'll explain.
CameraController's init method looks like this:
- (id) init {
if (self == [super init]) {
// Create and configure the image picker here...
// Load the UI elements for the camera overlay.
nibContents = [[NSBundle mainBundle] loadNibNamed:#"CameraController" owner:self options:nil];
[nibContents retain];
photoPicker.cameraOverlayView = overlay;
// More initialization code here...
}
return self;
}
The only way I can get the elements to load from the CameraController.xib file is to call loadNibNamed:owner:options:. Otherwise the camera takes over but no overlay nor other view components are displayed. It appears that a side-effect of this problem is that none of the view management methods on CameraController are ever called, like viewDidLoad, viewDidAppear etc.
However, all outlets defined in the nib seem to be working. For example, when the camera loads a view is displayed with some instructions for the user. On this view is a button to dismiss it. The button is declared in CameraController along with the method that is called that dismisses this instructions view. It is all wired together through the nib and works great. Furthermore, the button to take a picture is on the view that servers as photoPicker's overlay. This button and the method that is called when it's pressed is managed by CameraController and all wired up in the nib. It works fine too.
So what am I missing? Why can't I use UIViewController's default initializer to create the CameraController instance. And, why are none of CameraController's view mangement methods ever called.
Thanks.
Your problem is easy but need some steps.
Well... First, if overlay is an IBOutlet, it can not be loaded at init time. So move picker and co in viewDidLoad. Place also here all other items that your say that they are not loaded. They should be loaded there (viewDIDLoad). Check that outlets are connected.
Second, call
cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
bundle:nil];
and ensure that CameraController contains (just) a view, and CameraController inherits UIViewController. Check also file's owner.
And at some time, you may consider that calling :
[self presentModalViewController:cameraController.photoPicker animated:NO];
does not make the CameraController control your picker. Does that make sense to you ?
What does that do regarding your problem ?
It seems you are confusing some things. I try to explain in another way :
The one that controls the picker is the one that is its delegate. Your may consider creating in a MAIN view.
The controller of the overlay (added as subview) is the one that own its view in File's Owner. That may be created from the MAIN view, adding its view as subview of the controller. Basically, it is loaded just to get the overlay, but viewDidLoad, ... won't be called.
That's all and I belive those steps are not ok in your code.
That should give something like :
MainController
Loadcamera {
self.picker = [UIImagePicker alloc] init.....];
self.picker.delegate = self;
SecondController* scnd = [[SecondController alloc] initWithNibName:#"SecondController" bundle:nil];
[self.picker addOverlay:scnd.view];
[self presentModalViewController:self.picker animated:NO];
}
/// And here manage your picker delegate methods
SecondController
// Here manage your IBActions and whatever you want for the overlay

Why is this iPhone program not calling -loadView?

I am trying to work my way through basic iPhone programming and I have a good basic understanding of how Interface Builder works, so I decided to try my hand at doing the views programmatically. I have gone through the ViewController Apple guide and searched everywhere and I cannot seem to find a solution to my problem. This leads me to believe it is a very simple solution, but I am just really banging my head against the wall. Basically all I am trying to do is create a view that gets main window as a subview. I know that if self.view is not defined then the loadView method is supposed to be called, and everything is supposed to be set up there. Here is the current state of my code:
The delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
StartMenuViewController *aViewController = [[StartMenuViewController alloc] init];
self.myViewController = aViewController;
[aViewController release];
UIView *controllersView = [myViewController view];
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[window setBackgroundColor:[UIColor redColor]];
[window addSubview:controllersView];
[window makeKeyAndVisible];
}
The view controller:
- (id)init {
if (self = [super init]) {
self.title = #"Start Menu";
}
return self;
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
UIView *startView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[startView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[startView setBackgroundColor:[UIColor greenColor]];
self.view = startView;
[startView release];
}
Thanks for the help in advance!
Are you sure that you're inheriting from UIViewController and not overriding the implementation of - (UIView*)view?
EDIT: More info:
UIViewController has a special implementation of the "-(UIView*) view" message so that when it's called, the loadView method is called if the view member variable is not set. So, if you provide an implementation of "- (id)view" in your subclass, (or a property named view) it will break the auto-calling of "- loadView".
Just to document a "loadView is not called" case:
I wrote a 2 UITableViewController(s) to handle detail data for a master ViewController. Since the devil was in #2, I made a simple UITableViewController for #1, and referenced it in the XIB for the "master" ViewController.
When I was done with #2, I could simply copy the code to #1, remove the complicated code, and go on with life.
But to my dismay and several days work, no matter what I did, viewLoad was not being called for my simple #1 UITableViewController.
Today I finally realised that I was referencing the UITableViewController in the XIB to the master ViewController program. - and of course, loadView was never being called.
Just to help some other dork that makes the same mistake....
Best Regards,
Charles
viewDidLoad only if the view is unarchived from a nib, method is invoked after view is set.
loadView only invoked when the view proberty is nil. use when creating views programmatically. default: create a UIView object with no subviews.
(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:[UIScreen
mainScreen].applicationFrame];
[view setBackgroundColor:_color];
self.view = view;
[view release];
}
By implementing the loadView method, you hook into the default memory management behavior. If memory is low, a view controller may receive the didReceiveMemoryWarning message. The default implementation checks to see if the view is in use. If its view is not in the view hierarchy and the view controller implements the loadView method, its view is released. Later when the view is needed, the loadView method is invoked
again to create the view.
I would strongly recommend you use interface builder for at least your initial Window/View.
If you create a new project in XCode you should be able to select from one of many pre-defined iPhone templates that come with everything setup.
Unless I am reading this wrong, you did not associate any view with the the controller's view property like this
myViewController.view = controllersView;
So as far as Cocoa is concerned the view you are setting in the window has no controller to call loadView on. loadView is a View controller, not view, method. The view you assign to the window is not associated with any view controller. So your view controller loadView method is never called. Get it? The view you are trying to display, has no view controller associated with it.
When you use interface builder to create views you can link the UIView object you created in IB to the view property in the controller in IB which the framework automatically
But if not done in IB you have to set it