Reloading tableView when subclassed causing crash - iphone

Hey guys! I have some problems reloading my tableView.
I have subclasses my tableView, with a class called RadioTable. I have also subclasses my TableViewCells, but thats not important here.
I need to point out that i'm pretty new, and built my subclass from some tutorials and stuff.
First of all, here is the error message i'm getting when i try to reload my data.
I am reloading it with [self.tableView reloadData].
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UITableViewController loadView] loaded the "MainView" nib but didn't get a UITableView.'
Okay, so the problem is pretty clear. The view(my nib-file) does not have any tableView's connected to the Files Owner. And thats what i tried to solve. I tried to add a IBOutlet in my subclass, and setting the tableView-property there.
(My tableView-subclass is inherited from UITableView, just so thats clear)
Here is my init-code:
- (id)initWithStyle:(UITableViewStyle)style {
if ((self = [super initWithStyle:style])){
RadioTable *aTableView = [[RadioTable alloc] initWithFrame:self.tableView.frame style:style];
[aTableView setDelegate:self];
[aTableView setDataSource:self];
[aTableView setSwipeDelegate:self];
[aTableView setRowHeight:54];
[self setTableView:aTableView];
[self.tableView setScrollEnabled:NO];
[self.tableView setRowHeight:80];
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
[self.tableView setSeparatorColor:[UIColor lightGrayColor]];
[aTableView release];
}
return self;
}
The tableView works fine when i launch the app, it works perfect. But the problem occours when i try to reload it.

I'm not sure if this will solve your problem, but don't create your view controller's view in its init method. Instead, override loadView and create the table view in that method. And assign the table not only to the tableView property but also to the view controller's view property.
See the documentation for loadView and viewDidLoad for more info.

Related

Cannot push TTTableViewController onto Navigation Controller

I have an app where I have a TTTableView Controller inside a Navigation Controller that is Insider a TabBar.
I want it so that if a user selects an item it will push another TTTableView with the items under that category.
The code I have is
-(void)didSelectObject:(id)object atIndexPath:(NSIndexPath *)indexPath {
if ([object isKindOfClass:[TTTableMoreButton class]]) {
[super didSelectObject:object atIndexPath:indexPath];
} else {
CategoryViewController *viewController = [[CategoryViewController alloc] initWithNibName:#"CategoryViewController" bundle:nil];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
}
}
The CategoryViewController is setup as
#interface CategoryViewController : TTTableViewController
and the CategoryViewController.xib file has the datasource & delegate set to the files owner and the view set to the tableview and the tableview class is set to TTTableView.
When I run it I get the following error when selecting a row
2011-10-17 16:18:23.819 Biz Insider[34067:f803] -[CategoryViewController tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x6c93d30
2011-10-17 16:18:23.820 Biz Insider[34067:f803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CategoryViewController tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x6c93d30'
*** First throw call stack:
(0x1893052 0x157ed0a 0x1894ced 0x17f9f00 0x17f9ce2 0xb4cf2b 0xb4f722 0x9ff7c7 0x9ff2c1 0x9b61e 0xa0228c 0xa06783 0x9bb48 0x9b1301 0x1894e72 0x89192d 0x89b827 0x821fa7 0x823ea6 0x823580 0x18679ce 0x17fe670 0x17ca4f6 0x17c9db4 0x17c9ccb 0x1f88879 0x1f8893e 0x972a9b 0x273d 0x26b5)
terminate called throwing an exceptionCurrent language: auto; currently objective-c
If I try and push another view (i have one with a webview on it) then it works fine, or if I go into interface builder and link the File Owner's "tableView" to the TTTableView object it will work fine and push the controller except the "Pull down to refresh" function wont work so I am assuming that the deletage isn't correct when doing it that way.
Any help would be greatly appreciated.
EDIT:
I have a feeling that it has something to do with the following
-(id<UITableViewDelegate>)createDelegate {
return [[[TTTableViewDragRefreshDelegate alloc] initWithController:self] autorelease];
}
This sets the delegate to TTTableViewDragRefreshDelegate which implements the numberOfRowsInSection and all that junk. Is there another way to do this?
Cheers,
Dean
The log says that in your custom class CategoryViewController method tableView:numberOfRowsInSection: is not implemented and so it couldn't call it.
Check it and check types of its parameters.
Well this is an odd one, but I changed it from
CategoryViewController *viewController = [[CategoryViewController alloc] initWithNibName:#"CategoryViewController" bundle:nil];
to
CategoryViewController *viewController = [[CategoryViewController alloc] init];
and it magically works now...dont ask me how or why.
There must have been something in the NIB that was messing it up, but I dont know how the whole layout is working now with no NIB.

How to add uiviewcontroller's view as content view to uitableviewcell?

I want to add a uiviewcontroller's view which has a button and few labels as a content view of a uitableviewcell.
Though i am able to add but the button action causes a crash.
MyViewController *controller = [[MyViewController alloc] initwithnibname:#"MyView" bundle:nil];
[cell.contentview addsubview:controller.view];
[controller release]; // if i comment out this part the button action is received by my view controller.
However there are memory leaks when its removed from view. The dealloc of myviewcontroller is not called.
What is the correct way to do this?
Add a view to a uitableview cell
which has a button and is handled by
the viewcontroller
How to assure memory is released
when the view goes out of scope?
TIA,
Praveen
I think the problem is, that you are releasing the controller and just using the subview which is retained by its superview. The action pattern needs a target which I assume is the released controller. And why should you release your viewController if you only need the view of it? Retain it and keep a reference through a property to it.
My way of adding subviews to a tableview cell would be in a subclass of UITableViewCell. Let's assume you are having a subclass of UITableViewCell, say ButtonTableViewCell. The init of the of cell creates and adds a UIButton to your cell and puts it nicely in its contentView. Decalre a property which references to the button. Like UIButton *myButton. What should be done in the cellForRowAtIndexPath is something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ButtonTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyButtonCell"];
if (cell == nil) {
cell = [[ButtonTableViewCell alloc] initWithReuseIdentifier:#"MyButtonCell"];
}
[cell.myButton addTarget:self action:#selector(onDoSomething) forControlEvents:UIControlEventTouchUpInside];
// Do more cell configuration...
return cell;
}
I've made up the initializer initWithReuseIdentifier which can be easily implemented.
You assure release of memory with the viewDidUnload method of the UIViewController and the dealloc method.
"button action causes a crash" - What is the crash when you tap the button?
Also, you only appear to be using the view of MyViewController (since you add the view to the cell and then release the controller)- what is this controller supposed to do other than contain a view? Why not just use a view?
Also, (wild guess here) the usual constructor of a button does not have new/alloc/copy, and therefore does not warrant a release. I've seen a lot of code crash from inappropriately releasing UIButton's.
Adding a view of a controller as a subview to any other view does not retain the controller.
The controller is released immediately after the release call and any button actions will be sent to deallocated instance.
We can avoid this by maintaining a strong reference to the controller
#Property(nonatomic,strong)MyViewController *controller;
or by adding a view controller as the ChildViewController
[self addChildViewController:controller];

Does this cause memory leak and how do I stop it?

I use a UIViewController XIB to create my UITableViewCell in IB then I implemented it in code like this:
if(cell == nil)
{
UIViewController *viewController = [[UIViewController alloc] initWithNibName:#"TotalViewCell" bundle:nil];
cell = (TotalViewCell *)viewController.view;
//[viewController release];
}
[[cell totalButton] setTitle:#"$100,000" forState:UIControlStateNormal];
// Action when totalButton is tapped
[[cell totalButton] addTarget:self action:#selector(showTotalDetail:) forControlEvents:UIControlEventTouchUpInside];
return cell;
Usually, I would release the viewController but I put a UIButton inside that cell and when the user taps the button, showTotalDetail gets called. This is the code for showTotalDetail:
-(void)showTotalDetail:(id)sender
{
// Move the totalTableView up!
CGRect totalDetailTableViewFrame = CGRectMake(20, 200, 280, 200);
[totalTableView setFrame:totalDetailTableViewFrame];
// Reload the new totalTableView
[self viewWillAppear:YES];
}
The function basically resizes the tableView and moves it to a different location on the screen. So, if I release the viewController, I get EXC_BAD_ACCESS error. It works if I don't release but I'm afraid I will have memory leaks.
Any advice?
Thanks.
You’re creating a UIViewController for each UITableViewCell? That’s pretty non-standard behavior. If you need to customize the behavior of a UITableViewCell, it’s probably better to subclass it than to use a view controller.
If you need to load a table view cell from a nib, look at Apple’s Table View Programming Guide for iOS. That example is in there without using a view controller.
As to your question: you’re right that in this scenario, the UIViewController will leak.

iPhone: App crashes when I click on a cell in table view

Compiling my application works—everything is fine. The only errors I get are by deprecated functions (setText).
The only problem is now, is that when I tap on a cell in my table, the app crashes, even though it's meant to push to the next view in the stack.
Any solutions are appreciated, if you need any code, just ask.
Also, how can I only make sure that one cell goes to only one view? For example:
When I tap on CSS, it takes me to a new table with different levels of CSS. WHen I tap on an item in that new view, it comes up with an article on what I just selected.
Regards,
Jack
Here's my code at the didSelectRowAtIndexPath method:
-(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row==0){
NextViewController *nextController = [[NextViewController alloc]
initWithNibName:#"NextView" bundle:nil];
[self.navigationController pushViewController:nextController
animated:YES];
[nextController changeItemTable:[arryClientSide
objectAtIndex:indexPath.row]];
}
}
#end
(as requested in the comments).
When we create new UIViewControllerSubClass with xib-interface, xib file is created as sourcecode.xib (can be seen in get info of that xib file): Change sourcecode.xib to "file.xib" and see the magic :)
Terminating app due to uncaught
exception
'NSInternalInconsistencyException',
reason: -[UIViewController
_loadViewFromNibNamed:bundle:] loaded the "NextView" nib but the view outlet
was not set.'
Open NextView with Interface Builder
Set Class value at : "NextViewController" to your File's Owner
Connect the View outlet (Ctrl click and drag - a blue line should appear - from the File Owner to the UIView and select "view" in options)
you need to update your code like this :
-(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row==0){
NextViewController *nextController = [[NextViewController alloc]
initWithNibName:#"NextView" bundle:nil];
[nextController changeItemTable:[arryClientSide
objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:nextController
animated:YES];
}
else{ }
}
try using this code...
Error description from comments:
Terminating app due to uncaught
exception
'NSInternalInconsistencyException',
reason: '-[UIViewController
_loadViewFromNibNamed:bundle:] loaded the "NextView" nib but the view outlet
was not set.'
If your view controller is loaded from nib file and its view is not set exception then exception is thrown. So when you create your view in IB you must connect view outlet to your view controller object (likely - file owner in IB).
Edit: So basically in IB in your nib file you need to do the following:
1. set file owner's type to NextViewController
2. connect NextViewController's view outlet to a View object
release that NextViewController you alloc!
[nextController release];
It's leaking.
Don't use IB. Instead, make alloc init with nothing, then in load view:
self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];

How do I display a placeholder image when my UITableView has no data yet?

I have an application that, on load, displays a UITableView with the user's data in it.
However, when the user first loads the application (before they've created any data), I'd like to display, instead of an empty table, a background image (with an arrow pointing to the 'add a record' navbar button). Once the user has added their first record, the tableview is displayed instead. I've seen numerous apps do this - the only example I can think of at present is Cha-Ching, before you have any budgets/etc set up. I can't for the life of me work out how to do this, though.
I initially added a navigationcontroller to the main window's xib, the rootViewController of which was a custom viewcontroller/xib. This rootViewController contained the background image with a hidden tableview above it, and a custom tableviewcontroller that managed the tableview. This seemed to work just fine, and if there was data it would load and display in the table. However, if I was to scroll the data offscreen, the app would crash, with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'*** -[UITextEffectsWindow tableView:cellForRowAtIndexPath:]: unrecognized selector sent to instance 0xd2d130'
I have no clue what a UITextEffectsWindow is, or why it was trying to manage my tableview. I presume something may be hooked up incorrectly in my view hierarchy...
If there's a much simpler/more straightforward way of doing this, I'd be very grateful if someone could explain it. How would you do this?
Thanks in advance.
Here's one solution that I've been satisfied with so far.
First, I created a transparent view that was the same size as my TableView. I add this view as a sibling of the TableView whenever the TableView's data source has no data in it. I completely remove the view when data is available because transparency can affect the smoothness of the scrolling animation with TableViews.
I simply added a transparent label to this view that says something to the effect of "No Data Available". Adding a little shadowing to this label really helped to reinforce the concept that the label was 'floating' over top of the empty TableView.
I like your method of using an image though. Done properly, it might save you some work if you don't have to localize a string like I currently do.
To achieve this using a UITableViewController subclass as my only view (within a UINavigationController as per the Apple template) I used the following approach:
Create a UIView of the size of my tableView in the XIB that contains your UITableViewController and tableView.
Add an ImageView set with my placeholder image to the UIView.
Wire up the UIView as an IBOutlet (in the example code below, I called it emptyTableView)
When it is time to show the placeholder from within the UITableViewController subclass :
[self.tableView addSubView:emptyTableView];
[self.tableView setScrollEnabled:FALSE];
Disabling the scroll is necessary otherwise the user will be able to move the placeholder image up and down. Just remember to enable it once the user adds an item.
To remove the image view
[emptyTableView removeFromSuperview];
To do this, I use the following controller instead of UITableViewController. It will automatically place a view over the table when it is empty, and remove it when it is filled.
Just call [self reloadData] instead of [self.tableView reloadData] so that it can check if the table became empty.
In your subclass, implement a makeEmptyOverlayView function that will create the view to show over an empty table.
#interface MyTableViewController : UITableViewController
{
BOOL hasAppeared;
BOOL scrollWasEnabled;
UIView *emptyOverlay;
}
- (void) reloadData;
- (void) checkEmpty;
#end
#implementation MyTableViewController
- (void)viewWillAppear:(BOOL)animated
{
[self reloadData];
[super viewWillAppear: animated];
}
- (void)viewDidAppear:(BOOL)animated
{
hasAppeared = YES;
[super viewDidAppear: animated];
[self checkEmpty];
}
- (void)viewDidUnload
{
if (emptyOverlay)
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
- (void) reloadData
{
[self.tableView reloadData];
if (hasAppeared &&
[self respondsToSelector: #selector(makeEmptyOverlayView)])
[self checkEmpty];
}
- (void) checkEmpty
{
BOOL isEmpty(YES);
id<UITableViewDataSource> src(self.tableView.dataSource);
NSInteger sections(1);
if ([src respondsToSelector: #selector(numberOfSectionsInTableView:)])
sections = [src numberOfSectionsInTableView: self.tableView];
for (int i(0); i<sections; ++i)
{
NSInteger rows([src tableView: self.tableView numberOfRowsInSection: i]);
if (rows)
isEmpty = NO;
}
if (!isEmpty != !emptyOverlay)
{
if (isEmpty)
{
scrollWasEnabled = self.tableView.scrollEnabled;
self.tableView.scrollEnabled = NO;
emptyOverlay = [self makeEmptyOverlayView];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
else
{
self.tableView.scrollEnabled = scrollWasEnabled;
[emptyOverlay removeFromSuperview];
emptyOverlay = nil;
}
}
else if (isEmpty)
{
// Make sure it is still above all siblings.
[emptyOverlay retain];
[emptyOverlay removeFromSuperview];
[self.tableView addSubview: emptyOverlay];
[emptyOverlay release];
}
}
#end
If you use Three20, you can easily set any image you want as a place holder prior to your table being populated.
So, to solve this I did as discussed in the comments above:
I created a normal UIViewController subclass, which contained a UIImageView and a UITableView. The viewController conforms to the UITableViewDelegate and UITableViewDatasource protocols, and looks after the tableView. The viewController class simply shows or hides the imageView depending on whether data is available.
I was going wrong before by trying to manage both these views with a UITableViewController. A UITableViewController has to have a tableView as its view, whereas, with this solution, a viewController can contain both the image and the tableView, and implement the necessary protocols to manage the tableView.
Thanks for all the help!