UITableView registerNib:forCellReuseIdentifier: - iphone

I saw this used in a WWDC video but only very briefly. They didn't go in to how to create the actual xib file.
I've got a UITableViewCell subclass called MyCustomCell. In this I have several properties UILabels, UIImageViews, etc... all set up as IBOutlets.
Now, in my xib file...
What do I set as the file's owner? Where do I reference my MyCustomCell class is this the file's owner?
Once I've set the file's owner how do I link it with the root view of the xib?
I've tried a few settings but I keep getting errors when using it.
Oh, the code I'm using to register it is...
self.cellNib = [UINib nibWithNibName:#"MyCustomCell" bundle:nil];
[self.tableView registerNib:self.cellNib forCellReuseIdentifier:#"CustomCell"];
Thanks

Normally you don't have to bother about the File's owner in that case, because when the tableView instantiates the cell from the provided/associated UINib along with the reuseIdentifier. It will load all the top-level objects of the nib, and use only the first top-level object that is of class UITableViewCell (or maybe just the first top-level-object regardless of the class? but in general you only have your UITableViewCell in your XIB anyway — without counting the File's Owner and the First Responder which are only "proxies").
In fact, the tableView will try to dequeue a cell and if it doesn't find a reusable one, it will create a new one using the UINib you provided. It will be something similar to this:
NSArray* topLevelObjects = [self.cellNib instantiateWithOwner:nil options:0];
cell = [topLevelObjects objectAtIndex:0];
(That's of course a simplified version just to show the idea, I don't know if it actually calls these exact lines, but it should be quite close)
So the File's Owner is not used in this particular case, and you only need to put a simple custom UITableViewCell as the only top-level-object of your XIB file next to the existing File's Owner anf First Responder (that, again, are only "proxies" / "External Objects references" and won't be instantiated and won't be part of the top-level-objects returned by instantiateWithOwner:options:).
If it still doesn't work:
Ensure that you correctly filled the reuseIdentifier of your UITableViewCell in IB (in the Object Inspector pane on the right once you selected your cell in IB), and used the exact same value for this reuseIdentifier property in IB that the one you use in your code.
If still no luck, please provide more info, especially what kind of error, log message or exception you have.

I've found the code given in the question is fine, but you can't refer to self.tableView in the init method if you're using storyboards. There's some discussion about it in another question.
So the first line goes in the init:
self.cellNib = [UINib nibWithNibName:#"MyCustomCell" bundle:nil];
But this line should go in viewDidLoad or similar:
[self.tableView registerNib:self.cellNib forCellReuseIdentifier:#"CustomCell"];
That fixes my mysterious error, e.g. "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle <Foo.app> (loaded)' with name 'Ogf-Sj-1ej-view-bBf-Ti-Dda''"
And, yes, I'm doing something very similar to scoop things out of Storyboards and place them in xibs for reuse across view controllers!

In addition to Answer from "AliSoftware" Note the least obvious thing out here is that the UITableViewCell has to be the Root View *ABOVE* even the UIView. By default when I selected a new IB file it creates a UIView. So deleting the UIView helps. Thanks.
PS: I wanted to comment on that answer but for some strange reason I can't comment, can only answer. Guess I need a certain rating.

Related

Storyboard warning: prototype table cells must have reuse identifiers

I am getting this warning from storyboard - prototype table cells must have reuse identifiers.
I have renamed the identifier in the attributes inspector but it does not seem to have removed the warning.
Any suggestions?
To shut up the warning you have to give a name to the cell identifier :
Another way is to set the Table View 'Prototype Cells' property to zero in Attributes Inspector, if you are defining the cell using a .xib programatically.
As storyboard is actually XML file, so another trick is to open your storyboard with any text editor (not Xcode!) and try to find all tableViewCell nodes. For example press CMD+F, type <tableViewCell contentMode="scaleToFill" and press Enter. You will probably find out, the rows same to this one:
<tableViewCell contentMode="scaleToFill" selectionStyle="blue" accessoryType="disclosureIndicator" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="GenericCellID" id="kBr-Qn-Tki">
Please pay your attention on reuseIdentifier="GenericCellID" key value.
The rows which fire the warning (without reuse identifiers) will not have such key value. Look nodes above in your text editor and you will see tableViewController node with class name you need to check in Xcode's storyboard editor and fix, according to aleroot' answer.
If your Xcode warning still doesn't go away even after you have set the reuseIdentifier, then try Menu "Product -> Clean". This helped for me and after that, the warning didn't show up anymore.
Since double-clicking this warning can lead to a bit of a wild goose chase (the specific UITableViewCell doesn't get selected), just wanted to add that while some of these warnings will auto-resolve, some will not.
If you are confident you've tagged all your cells in Storyboard, rebuild the project - your warning may disappear (as did mine).
A hail mary: restarting XCode worked for me (but it was probably just a manual rebuild that was required).
I got this warning for Xcode 7. I am working with Swift.
I did not added the navigation controller for the initial view controller. When I did so.. I got this warning from StoryBoard.
What I did is:
There are two segues
from Navigation Controller to Root view Controller and
from Root View controller to your initial View controller.
Select the segue connecting the root view controller to the
initial view controller.
Name the identifier
Select the "Prototype" written part of the controller--> there
you will see identifier as an option.
Paste the same name of the identifier as entered earlier.
The warning goes.. if not clean the sdk :)
Be sure that you are setting the Reuse Identifier on the object, rather than the Restoration ID. Both are available via the storyboard, and easily confused.
In the storyboard XML file you'll want to be setting reuseIdentifier, not restorationIdentifier.
I've noticed that this error occurs when you have multiple prototype cells (in the tableview properties) and have not given all of them re-use identifiers.
The identifier is the name you refer to in you .m file. When it is not filled it is not possible to reference the cell.
This for setting reuse identifier by programmatically
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath*)indexPath
{
static NSString *cellIdentifier = #"wot";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle: someStyle reuseIdentifier: cellIdentifier];
return cell;
}

'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the GameView nib but the view outlet was not set

This is not the same situation as the multitude of other similar questions here.
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the GameView nib but the view outlet was not set.'
You might be thinking "do as it says, connect the File's Owner to the View in IB!". But the thing is, I don't even HAVE a GameView.xib in my project or even in the project directory.
I do have a "GameViewController.m" and matching "GameViewController.xib" in my project. Using that GameViewController is what brings up this error, but I don't understand where it gets the idea to try and load "GameView.xib". Shouldn't it use "GameViewController.xib" instead?
If I grep my project directory, I do see it referenced from "UserInterfaceState.xcuserstate".
<string>file://localhost/Users/bemmu/Dropbox/b2/iphone/ValleyStory/ValleyStory/GameView.xib</string>
This mentioned file does not exist. I might have had a file with that name before and renamed/deleted it, but it's not being referenced to from anywhere that I can see in IB.
Did I manage to confuse xcode?
My solution was a little different.
Click on the xib in interface builder
Select File's Owner on the left
Open the File's Owner's connections inspector
If the view property isn't yet wired, control-drag it to the view icon (under the file's owner and first responder icons).
Check any nib files you're using (like MainWindow.xib). If you are loading GameViewController from a nib, check the file it's loading from (under the info tab in the inspector). Make sure it's set to "GameViewController" and not "GameView".
I had this issue as well, but had to solve it a different way. Basically, I have a view controller name MainViewController, which has a xib named MainViewController.xib. This nib has it's view property set to the File Owner which was MainViewController.
I also made a MainView.xib that contained a view that was going to be programmatically added to the view defined in MainViewController.xib and it's view. It basically encapsulated an internal view that would be in the MainViewController.xib's view, and also had it's File Owner set to MainViewController.
So basically, I wanted MainViewController.xib to load as the nib for the MainViewController object, and inside MainViewController, at some later point, I would add the internal view specified by MainView.xib.
A couple issues arose:
1.) I found in the Apple docs that when loading a view controller via storyboard or nib:
"If the view controller class name ends with the word “Controller”, as
in MyViewController, it looks for a nib file whose name matches the
class name without the word “Controller”, as in MyView.nib.
It looks for a nib file whose name matches the name of the view
controller class. For example, if the class name is MyViewController,
it looks for a MyViewController.nib file."
Therefore, you cannot have a nib called MainView.xib if you also have a nib called MainViewController and want MainViewController.xib to be the primary nib for MainViewController.
2.) Even if you delete MainView.xib or rename it to something else (MainInternalView.xib in this case), you MUST delete / clean your iOS simulator as the old nib file (MainView.xib) will still remain in the application. It doesn't overwrite the whole application package when you rebuild / rerun your application.
If you don't want to reset your content settings (perhaps you have some data you want to preserve), then right-click on your application in your iOS Simulator folder, Show Package Contents, find MainView.nib, and delete it. Xcode will NOT do this automatically for you when you rebuild, so we need to manually remove the old nib.
Overall, don't make nibs named MainViewController and MainView, i.e. nibs with the same prefix. Call MainView.xib something else, like MainInternalView.xib.
I recently solved this issue. Make sure you back up your project before following the steps given here (just in case). These steps solved my issue
Quit Xcode
Navigate to UserInterfaceState.xcuserstate located at .xcodeproj/project.xcworkspace/xcuserdata/<username>.xcuserdata and delete the file.
Reopen Xcode. Xcode will create a new UserInterfaceState.xcuserstate which will be clean.
In my case this error was produced by dumb mistake - I delete _view view
In my case, I was not using a xib at all. I needed remove the .m file from Build Phases > Compile Sources and added it back.
Given you referenced it previously it sounds like xcode hasn't ackowledged it no longer exists. From the Product menu select "Clean" and then "Build" hopefully this will get past the old reference for you.
Face the same Problem, had to change the view's name in code:
MyViewController *controller = [[MyViewController alloc] initWithNibName:#"WrongViewName" bundle:nil];
To
MyViewController *controller = [[MyViewController alloc] initWithNibName:#"RightViewName" bundle:nil];
I had multiple views, and by accident (I don't know how this happenned) but my background view didn't have a file owner, so for anyone else who has this problem in the future, make sure all your views have a file owner.
I was gettint the same error then check the classname from interface builder and see that I typed the view controller class name at the custom class attribute.
UIViewController searches for a nib with the same name as the controller when passed nil to initWithNibNamed:bundle: Check that the file name that you pass to the initializer is correct and exists!
For example:(e.g. [[CCVisitorsController alloc] initWithNibName:nil bundle:nil] then UIViewController tries to load nib with name CCVisitorsController as default.
If that file does not exist then the error you mentioned is thrown.
I had this problem because I was doing something bad in
(id) initWithCoder:(NSCoder *) coder
which the NIB loads.

UITableViewController instantiation through Interface Builder

I am starting with iOS developments here and integrate Interface Builder within my projects.
I would have a question that is something I am experiencing right now, but I am not sure if in first place if I am doing it properly.
First Case
I have created all my UITableViewController inside IB and then changed the class to my custom CurrencyTableViewController. so for adding this inside my window.rootViewController at MainWindow_iPhone.xib I am just creating a variable straight away from inside the AppDelegate and calling the instance created in another .xib file. This works pretty well, but one of my concerns here is about memory management.
//Use the instance initiated by IB
CurrencyTableViewController *currencyTableViewController = [[[NSBundle mainBundle] loadNibNamed:#"CurrencyTableView" owner:nil options:nil] objectAtIndex:0];
So the question is: by using the method above to load instances created on IB are they released automatically after that? Or do I need to declare it somewhere else?
Second Case
This second case was an alternative trying to think on how to release the object, hence the creation of the instance instead the use of one instance already created. But unfortunately it seems to throw an error which says that I am not attaching the view to the controller. And this made me think that UITableViewController even being a subclass of UIViewController doesn't support the initWithNibName method...at least this is not declared on the documentation.
//Fails with: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "CurrencyTableView" nib but the view outlet was not set.'*** Call stack at first throw:
CurrencyTableViewController *currencyTableViewController = [[CurrencyTableViewController alloc] initWithNibName:#"CurrencyTableView" bundle:nil];
So, could one please confirm if it's not supported indeed and the only possible way to create an object and attach it to a nib file is if that element is UIViewController? if not, how the code bellow could work?
Thanks in advance for your support and time.
Regarding my first case:
It seems that when you create something from IB it is released automatically, no sure though.
Regarding the second case
It wasn't working because the controller was located on the nib as well so the first thing present inside a programmatically create instance of CurrencyTableViewController was another CurrencyTableViewController instead a view for that. so you can either remove the controller and just leave the tableView itself inside the nib or declare the instance as a representation of the nib element like the example on case one.
If someone have different alternatives please let me know.

UITableViewCells loaded from NIB always nil

I'm trying to create a simple form using a UITableViewController as documented in the Apple Developer Documentation here.
What I'm trying to do is located in the section entitled: "The Technique for Static Row Content"
I've created a couple of UITableViewCells and added them to my nib, but when I try and access them to add them to the UITableView (in the cellForRowAtIndexPath: method) they are always null.
It's like they are not being properly loaded from the nib. I've double/triple/quadruple checked my code to make sure I'm doing it exactly as detailed in the docs, but no luck.
Is there something obvious I'm missing here?
Have you made sure you've connected the IBOutlets in the nib file?
In your ViewController.h file you should have:
IBOutlet UITableViewCell *specialCell1;
IBOutlet UITableViewCell *specialCell2;
and then each of these should be "wired up" to the corresponding cell in the nib file. If not, they won't exist!
Figured it out...
When I added my parent view controller, I was using the simple "init". Switching to "initWithNibName" resolved the issue...
Still learning... :)

UITableView issue when using separate delegate/dataSource

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.