I have a UITableViewController, nib based, that is presented in a UIPopoverController when running on iPad and presented 'natively' when running on iPhone.
When running on the iPad, the self.tableView property of the controller is nil throughout it's lifecycle. The delegate messages that pass in the tableView as part of their normal operation work fine and have a reference to a table view.
On the iPhone, the property is set and the reference is valid as you would expect.
Anything specific that can cause the self.tableView property to be nil? I've double checked the connections in Interface Builder - the Nib just contains a table view and the related UISearchBar stuff. The table view in the Nib is set as the view controller's view.
This is on 4.2.1 on both devices. Banging my head on this and can't find my error.
I figured out my problem and of course, it's a programming error.
In this case, an errant #synthesize tableView at the top of the implementation file, generating conflicting getters and setters.
Apologies and thanks for those that looked at this.
Try this... in one of the methods that returns the tableView object do "assert(self.view == tableView);" and see what happens. If the assert doesn't fire, and I suspect that it wont fire in either the iPhone or iPad, then simply cast self.view into a UITableView and do what you need to do.
Related
I have 2 xibs, one for iPad and one for iPhone.
However, currently I have only one view controller for those 2 xibs that works for both iPhone&iPad.
Inside my iPad Xib I have an IBOutlet that doesn't belong to the iPhone xib.
How should I define that outlet ?
I notice that if I put inside my deallc method, something like this :
-(void) dealloc
{
[outletOnlyForIpad release]
}
The app crashes on the iPhone. Apparently cause it doesn't instantiates well on the iPhone. (I hoped it would stay nil, but it's not the case)
I didn't find any preprocessor macro that I can use so I can declare that Outlet only for iPad.
Is the only way to do it is by checking in runTime something like :
isIpad()
[outletOnlyForIpad SomeMethodOnTheOutlet]
In every place in my controller ?
Your code is correct. That outlet should be staying nil, so sending it a release message should be harmless. Rather than work around the error, I suggest you check where this outlet is being set and fix the cause of the problem. Are you certain you aren't connecting anything to it in the iPhone nib?
You have to check at run-time for the device and handle the outlets then if you want separate outlets.
If I create an app with different controls on different devices, I tend to create them programmatically to avoid connection problems such as this.
in your dealloc to avoid the crash on iPhone, and connect the IBOutlet as usual in your iPad xib:
-(void) dealloc
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[outletOnlyForIpad release]
}
}
When you initialize your class, all instance variables are automatically initialised to zero. Thus this is the equivalent of adding:
outletOnlyForIpad = nil;
To your init method.
This nil value will be overwritten when the view loads for the iPad but not the iPhone (unless you have other code that sets it). However your [outletOnlyForIpad release] crash shows that outletOnlyForIpad is not nil at this point, which means that something has given it a value. You need to find out what this something is - it may be your interface builder file.
I have a main window which has the UITabViewController as its root controller. I am using a nib file for this. In Interface Builder, two of the tabs have been wired to Controller_A, Nib_A and Controller_B, Nib_B but the 3rd tab only knows about Controller_C.
I assumed that this would mean that the loadView method of Controller_C would be automatically called since I haven't bothered to specify the NIB file. I want to lay this piece out programmatically. And it DOES indeed get called as I've confirmed by placing a breakpoint inside this method.
BUT when I switched over to Controller_C in the simulator, it comes up empty!
Here's what the loadView of Controller_C looks like:
- (void)loadView
{
[super loadView];
...
[self setTableView:formTableView];
[view addSubview:formTableView];
[self setView:view];
}
Any tips? What am I ignoring?
Looks like you are searching for:
-(void)viewDidLoad {
Here are the main things to check.
Click on your Controller_C in interface builder. In the Identity Inspector, make sure that the Class field is set to Controller_C.
In the Attributes Inspector, make sure the NIB Name field is blank.
If you have an existing Controller_C.xib laying around in your project, remove and delete it. The default implementation of loadView loads this file even if
Remove [super loadView]. Since you're building your view hierarchy in code, you shouldn't invoke the default implementation. You should explicitly allocate the controller's view as a local variable in loadView and set it using setView:.
Also, your comment on the other answer suggests that you may be confused about when/why loadView and initWithNibName:bundle: get called, so let me clarify:
loadView gets called to lazy load your controller's view the first time its view property gets accessed. This is true whether your view controller was constructed as an object in a NIB, or whether you constructed it yourself in code using initWithNibName:bundle:. The default implementation of loadView loads the NIB that was specified in initWithNibName:bundle: or in the NIB Name property in IB. If a NIB name wasn't specified, the default implementation looks for any NIB in the bundle that has the same name as your class and loads that NIB if one is found. If no appropriate NIB is found, then the default implementation of loadView just creates an empty view and sets that as your controller's view. When we build our own view hierarchy explicitly in loadView, we don't want any of these default behaviors, so we don't call [super loadView].
It seems loadView works as expected, the reason that my view was blank is because the datasource for my tableview was actually NIL. Earlier, I did not consider this scenario as I thought that it would at least present an empty table in such a case but apparently that was an incorrect assumption on my part. All this mistakenly led me to believe that the view wasn't being initialized properly.
#cduhn: Thanks, I had been following steps 1-3 already and it was really good to hear someone else give the same advice. The rest of what you said was educational for me as well.
Thanks Everyone.
I am attempting to use NSKeyedArchiver to store the state of my UITabBarController. Inside the tabs are UINavigationControllers which contain UITableViewControllers.
The archiving appears to work without any problem.
When unarchiving I am running into strange problems with my UITableViewControllers. initWithCoder: is called correctly, and the first thing I am doing inside there is calling [super initWithCoder:]
The tableView appears to be correctly recreated, and has a delegate and dataSource property already set. If my initWithCoder: does nothing but call super and return self then my table view ends up empty and my delegate methods are not called. If I hookup the delegate and dataSource properties of self.tableView I see my content correctly, but didSelectRowAtIndexPath is not called upon selecting, and in one instance I am unable to scroll until the tableView is reloaded.
If in initWithCoder: I create a new UITableView everything works but I don't believe I should need to do this.
Am I doing something I shouldn't be in attempting this, or am I missing something obvious that I need to add to get things working correctly?
Update: setting self.tableView = [decoder decodeObjectForKey:#"tableView"] inside my controller seems to solve the scroll/select problem, but the resulting tableView is always UITableViewStylePlain and still seems like the wrong thing to do as the property is already set.
Your design pattern is certainly unusual. Normally, you wouldn't try to archive the table view, just the data used to create it. Then when you need the table view again, you would recreate it from those data, using the same code you used to create it in the first place.
I'm playing around with the iPad SplitView template and it was working fine before I started swapping out view objects in my RootViewController. When it was working fine, the application:didFinishLaunchingWithOptions method would be called and would setup my persistant store objects, then the RootViewController:viewDidLoad method would be called to populate my rootView with data from my store. I opened up IB and started swapping out view objects in my RootView and now the application:didFinishLaunchingWithOptions method never gets called, but the RootViewController:viewDidLoad method still does. Obviously, the app crashes because the viewDidLoad method depends on the successful execution of the didFinishLauchingWIthOptions method to setup the persistent store objects. Does anyone have any thoughts on what is causing this or how I can go about investigating what's causing this?
I'm obviously new to iPhone OS development, so I apologize if this questions is absurd in any way. Thanks so much in advance for your help!
This is propabaly caused by the fact that in MainWindow.xib, your application delegate object is not connected to File's Owner (UIApplication). You can open the MainWindow.xib and right click on your App Delegate to see if it has a connection in Referencing Outlet to File's Owner. If not, set it to. And this will fix your problem.
-viewDidLoad is not called from -application:didFinishLaunchingWithOptions:. They are independent. The call hierarchy could be summarized as:
load app; call -application:didFinishLaunchingWithOptions:
window is visible, load views of view controllers.
call -viewDidLoad.
General Description:
To start with what works, I have a UITableView which has been placed onto an Xcode-generated view using Interface Builder. The view's File Owner is set to an Xcode-generated subclass of UIViewController. To this subclass I have added working implementations of numberOfSectionsInTableView: tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: and the Table View's dataSource and delegate are connected to this class via the File Owner in Interface Builder.
The above configuration works with no problems. The issue occurs when I want to move this Table View's dataSource and delegate-implementations out to a separate class, most likely because there are other controls on the View besides the Table View and I'd like to move the Table View-related code out to its own class. To accomplish this, I try the following:
Create a new subclass of UITableViewController in Xcode
Move the known-good implementations of numberOfSectionsInTableView:, tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: to the new subclass
Drag a UITableViewController to the top level of the existing XIB in InterfaceBuilder, delete the UIView/UITableView that are automatically created for this UITableViewController, then set the UITableViewController's class to match the new subclass
Remove the previously-working UITableView's existing dataSource and delegate connections and connect them to the new UITableViewController
When complete, I do not have a working UITableView. I end up with one of three outcomes which can seemingly happen at random:
When the UITableView loads, I get a runtime error indicating I am sending tableView:cellForRowAtIndexPath: to an object which does not recognize it
When the UITableView loads, the project breaks into the debugger without error
There is no error, but the UITableView does not appear
With some debugging and having created a basic project just to reproduce this issue, I am usually seeing the 3rd option above (no error but no visible table view). I added some NSLog calls and found that although numberOfSectionsInTableView: and numberOfRowsInSection: are both getting called, cellForRowAtIndexPath: is not. I am convinced I'm missing something really simple and was hoping the answer may be obvious to someone with more experience than I have. If this doesn't turn out to be an easy answer I would be happy to update with some code or a sample project. Thanks for your time!
Complete steps to reproduce:
Create a new iPhone OS, View-Based Application in Xcode and call it TableTest
Open TableTestViewController.xib in Interface Builder and drag a UITableView onto the provided view surface.
Connect the UITableView's dataSource and delegate-outlets to File's Owner, which should already represent the TableTestViewController-class. Save your changes
Back in Xcode, add the following code to TableTestViewController.m:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"Returning num sections");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Returning num rows");
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Trying to return cell");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
cell.text = #"Hello";
NSLog(#"Returning cell");
return cell;
}
Build and Go, and you should see the word Hello appear in the UITableView
Now to attempt to move this UITableView's logic out to a separate class, first create a new file in Xcode, choosing UITableViewController subclass and calling the class TableTestTableViewController
Remove the above code snippet from TableTestViewController.m and place it into TableTestTableViewController.m, replacing the default implementation of these three methods with ours.
Back in Interface Builder within the same TableTestViewController.xib-file, drag a UITableViewController into the main IB window and delete the new UITableView object that automatically came with it
Set the class for this new UITableViewController to TableTestTableViewController
Remove the dataSource and delegate bindings from the existing, previously-working UITableView and reconnect the same two bindings to the new TableTestTableViewController we created.
Save changes, Build and Go, and if you're getting the results I'm getting, note the UITableView no longer functions properly
Solution:
With some more troubleshooting and some assistance from the iPhone Developer Forums, I've documented a solution! The main UIViewController subclass of the project needs an outlet pointing to the UITableViewController instance. To accomplish this, simply add the following to the primary view's header (TableTestViewController.h):
#import "TableTestTableViewController.h"
and
IBOutlet TableTestTableViewController *myTableViewController;
Then, in Interface Builder, connect the new outlet from File's Owner to TableTestTableViewController in the main IB window. No changes are necessary in the UI part of the XIB. Simply having this outlet in place, even though no user code directly uses it, resolves the problem completely. Thanks to those who've helped and credit goes to BaldEagle on the iPhone Developer Forums for finding the solution.
I followed your steps, recreated the project and ran into the same problem. Basically you are almost there. There are 2 things missing (once fixed it works):
You need to connect the tableView of the TableTestTableViewController to the UITableView you have on the screen. As I said before because it is not IBOutlet you can override the tableView property and make it and IBOutlet:
#interface TableTestTableViewController : UITableViewController {
UITableView *tableView;
}
#property (nonatomic, retain) IBOutlet UITableView *tableView;
Next thing is to add a reference to the TableTestTableViewController and retain it in the TableTestViewController. Otherwise your TableTestTableViewController may be released (after loading the nib with nothing hanging on to it.) and that is why you are seeing the erratic results, crashes or nothing showing. To do that add:
#interface TableTestViewController : UIViewController {
TableTestTableViewController *tableViewController;
}
#property (nonatomic, retain) IBOutlet TableTestTableViewController *tableViewController;
and connect that in the Interface Builder to the TableTestTableViewController instance.
With the above this worked fine on my machine.
Also I think it would be good to state the motivation behind all this (instead of just using the UITableViewController with its own UITableView). In my case it was to use other views that just the UITableView on the same screenful of content. So I can add other UILabels or UIImages under UIView and show the UITableView under them or above them.
I just spent many hours pulling my hair out trying to figure out why a UITableView wouldn't show up when when I had it embedded in a separate nib instead of in the main nib. I finally found your discussion above and realized that it was because my UITableViewController wasn't being retained! Apparently the delegate and datasource properties of UITableView are not marked "retain" and so my nib was loading but the controller was getting tossed... And due to the wonders of objective-c I got no error messages at all from this... I still don't understand why it didn't crash. I know that I've seen "message sent to released xxx" before... why wasn't it giving me one of those?!?
I think most developers would assume that structure that they build in an interface builder would be held in some larger context (the Nib) and not subject to release. I guess I know why they do this.. so that the iPhone can drop and reload parts of the nib on low memory. But man, that was hard to figure out.
Can someone tell me where I should have read about that behavior in the docs?
Also - about hooking up the view. First, if you drag one in from the UI builder you'll see that they hook up the view property (which is an IBOutlet) to the table view. It's not necessary to expose the tableView, that seems to get set internally. In fact it doesn't even seem to be necessary to set the view unless you want viewDidLoad notification. I've just broken the view connection between my uitableview and uitableviewcontroller (only delegate and datasource set) and it's apparently working fine.
Yes for some reason (please chime in if anybody knows why...) tableView property of the UITableViewController is not exposed as an IBOutlet even though it is a public property. So when you use Interface Builder, you can't see that property to connect to your other UITableView. So in your subclass, you can create a tableView property marked as an IBOutlet and connect that.
This all seems hacky and a workaround to me, but it seems to be the only way to separate a UITableViewController's UITableView and put it somewhere else in UI hierarchy. I ran into the same issue when I tried to design view where there are things other than the UITableView and that was the way I solved it... Is this the right approach???
I was able to make this work. I built a really nice dashboard with 4 TableViews and a webview with video. The key is having the separate tableView controllers and the IBOutlets to the other tableview controllers defined in the view controller. In UIB you just need to connect the other tableview controllers to the file owner of the view controller. Then connect the tables to the corresponding view controllers for the datasource and delegate.