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

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];

Related

How can I be notified when a TableView has been removed from the super view?

I have programmed in several other languages but this is basically my first ios app and I am struggling to correctly implement a UITableView.
After reading the documentation, the most common way to accomplish this is to create a class that is a subclass of UITableViewController. I have done this and I have implemented all data source protocol methods as well as the row selection method from the delegate protocol and gave it three properties.
The first one is the number of rows in the tableview,
the second is an array of the items to be displayed as labels in the table view,
and finally there is a property to hold the text of the label from the selected row.
Once the row is selected, I set the property of that holds this label and then I remove the table form the view with [self.view removeFromSuperView].
The above isn't the only view in my app. The app is a color picker assignment, from school, so the main view contains all of the controls to manipulate the displayed color.
What I did after subclassing UITableViewController was, create an instance of this subclass in my main view controller and made it a property. So, on the main view is a recall button that allows the user to choose from a list of previously saved colors. When this button is clicked the this IBAction method is called
-(IBAction)swithToSavedColorsView:(id)sender {
self.savedColorTable.numberOfRows = self.dictionaryOfSavedColors.count;
NSLog( #"Count in switch view is %d", self.dictionaryOfSavedColors.count );
[ self.view addSubview:self.savedColorTable.view ];
}
This presents a list of the available saved colors and I respond to the row selection with
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
self.textFromColorSelection = [ [ NSString alloc ] init ];
UITableViewCell *cell = [ tableView cellForRowAtIndexPath:indexPath ];
self.textFromColorSelection = cell.textLabel.text;
NSLog(#"The value of selection is %# ", self.textFromColorSelection );
[ self.view removeFromSuperview ]; // Go back to main screen.
}
As I was writing this code I was getting an erie feeling that I went about the creation of the UITableView in the completely wrong way from the beginning. Please let me know if I am doing something wrong as far as how I have communication between these objects set up.
The problem I am actually trying to solve is in the above method after i call
[ self.view removeFromSuperView ], how will my other view know when this has happened? What I want to do when the UITableView closes is have my other view get the label property from the instance I created and use that label to retrieve information out of a database.
Thanks for the help, it is greatly appreciated.
If you need several controllers to respond to the dismissal of the table, you will likely want to use NSNotificationCenter in viewWillDisappear or viewDidDisappear. More likely you are presenting from a viewController and only that viewController is waiting to learn what color was selected. I would suggest handling that with a delegate/protocol pattern.
Add something like this to your color pick table myColorPickTableController.h file above the #interface line:
#class myColorPickTableController;
#protocol myColorPickTableControllerDelegate
-(void)myColorPickTableControllerDidSelectColor:(UIColor *)selectedColor sender:(myColorPickTableController *)sender;
#end
Add a property in that header as well, to store reference to the delegate (the controller which is waiting to hear what color was picked).
#property(nonatomic, unsafe_unretained)id<myColorPickTableControllerDelegate> delegate;
Now, replace the line
[ self.view removeFromSuperview ]; // Go back to main screen.
with
[delegate myColorPickTableControllerDidSelectColor:[UIColor whateverColorWasPicked] sender:self]; // Tell main screen user picked a color
Now in the presenting controller, you need to conform to the protocol by adding to your interface line
#interface myPresentingController : UIWhateverControllerIAm <myColorPickTableDelegate> // Add that part between <>
Now, in myPresentingController.m you implement the method
-(void)myColorPickTableControllerDidSelectColor:(UIColor *)selectedColor sender:(myColorPickTableController *)sender
{
[self saveTheSelectedColor:selectedColor];
[sender.view removeFromSuperview]; // I am not so sure about that, should be presenting, maybe modally or use navigation controller. Should work thought, not my first choice.
sender = nil; // Just for good measure
}
Lastly, remember to make your presenting controller the delegate of the myColorPickTableController when you create it. Set delegate as self like so
myColorPickTableController *pickTable = [myColorPickTableController alloc] init];
pickTable.delegate = self.
In your subclassed UITableViewController, viewWillDisappear and viewDidDisappear should be called after your view is removed. Try sending a notification when your view disappears (look into NSNotificationCenter).

UITableView in a UIViewController linked to another ViewController (DetailController)

I have an app on the store that uses a RootViewController linking to a UIViewController (DetailController) and I am working on a new app which basically requires the need for this same feature. But instead, my new app has a UITableView inside a UIViewController linked to a UIViewController. So I thought, i'd copy and paste my RootViewController code into this new UIViewController. So i've linked up the TableView, set delegate and datasource to self and the TableView shows the titles of the items (Hurrah) but when touched, doesn't go to the DetailController? I've used NSLog to determine what part isn't working and of course its the didSelectRowAtIndexPath method… and here is my code
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *theItem = [items objectAtIndex:indexPath.row];
DetailController *nextController = [[DetailController alloc] initWithItem:theItem];
[self.navigationController pushViewController:nextController animated:YES];
[nextController release];
}
The TableViewCell just highlights blue and doesn't link to DetailController.
Thanks in advance!
By "not working" do mean it's not being called, or that it's not pushing the view controller? If the first, then make sure the table view delegate is set correctly. If the second, make sure both nextController and self.navigationController are not nil.

Call UINavigationController from subview in UITableViewCell

I have a table view in which I have subclassed the cells. In these cells I add a subview of a UIView. When sliding the cell I add another UIView to the subclass of UITableViewCell.
I would like to present a ModalViewController when pressing a button inside the second UIView (subview in UITableViewCell). I do not have a navigation controller in this view, therefore I am passing the navigation controller from the view controller my table view is inside of and down to my second UIView.
Here, I call it as you normally would but nothing happens.
ComposeCommentViewController *ccvc = [[ComposeCommentViewController alloc] initWithNibName:#"ComposeCommentViewController" bundle:nil];
[navController presentModalViewController:ccvc animated:YES];
Does anyone have an idea what I might do wrong or have another solution?
EDIT: This is how I set navController
First I pass it to my subclass of UITableViewCell.
if (feedCell == nil)
{
feedCell = [[FeedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[feedCell setNavigationControllerForSlidedView:[self navigationController]];
}
The subclass has the method setNavigationControllerForSlidedView: which looks like this:
- (void)setNavigationControllerForSlidedView:(UINavigationController *)navController
{
[feedSlidedView setNavController:navController];
}
In my FeedSlidedView I have declared and synthesized UINavigationController *navController;
The way you are going about this runs contrary to MVC (model-view-controller) design practices. You have a number of mechanisms for accomplishing what you want within the MVC framework that Apple provides in its SDK. Probably the simplest, in my opinion, would be to add a target-action to the button in the subview of your UITableViewCell. In your view controller's tableView:cellForRowAtIndexPath: method, add something like the following:
[button addTarget:self action:#selector(presentComposeComment:) forControlEvents:UIControlEventTouchUpInside];
In this case, self would be the UIViewController that is responsible for the UITableView in question. You would then include the method for the selector above in that view controller:
- (void)presentComposeComment:(id)sender {
ComposeCommentViewController *ccvc = [[ComposeCommentViewController alloc] initWithNibName:#"ComposeCommentViewController" bundle:nil];
[self presentModalViewController:ccvc animated:YES];
}
Note that I am not sending the presentModalViewController:animated: message to the navigation controller, but rather the view controller.

UIButton doesn't update

I'm trying to change the hidden property of a button and this is done in a method (View one):
-(void)changeSong:(NSString *)songName {
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:[[NSBundle mainBundle] pathForResource:songName ofType:#"mp3"]];
musicPlaying = YES;
playButton.hidden = YES;
pauseButton.hidden = NO;
}
This method is called from another view:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MainMenuController *mainMenu = [[MainMenuController alloc] initWithNibName:#"MainMenu" bundle:nil];
[mainMenu changeSong:[songs objectAtIndex:indexPath.row]];
mainMenu = nil;
[mainMenu release];
[[self navigationController] popViewControllerAnimated:YES];
}
I know that the changeSong method is being called correctly because the music changes. However, the hidden property of the items don't change. I've tried calling [self.view setNeedsDisplay]; but this doesn't do anything.
Thanks
It looks like the MainMenuController hasn't finished initializing by the time you call -changeSong, which is why everything in MainMenuController is nil.
To solve this, either delay your call to -changeSong by using
[mainMenu performSelector:#selector(changeSong:) withObject:[songs objectAtIndex:indexPath.row] afterDelay:0.01];
or make your tableview the MainMenuController's delegate, and when MainMenuController is finished loading from the nib (using - (void)awakeFromNib in MainMenuController), call the delegate's method to change the song.
Since you're delaying the call in both cases, you'll have to be careful not to release the view controller before you do, so you'll have to change that code a little.
When initializing a view controller from a nib using -initWithNibName:bundle:, the actual view and its subviews aren't unarchived until the first time the view controller's view property is accessed, per the documentation:
The nib file you specify is not loaded right away. It is loaded the first time the view controller’s view is accessed. If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad method and perform your tasks there.
Try calling [mainMenu view] right after you initialize it from the nib. That will hydrate the view hierarchy from the nib.
However, I guess I don't understand why you're unarchiving a view controller from a nib and calling one of its methods that affects the UI (i.e., hiding or revealing buttons) without pushing that view controller to a navigation controller or presenting it modally. -changeSong: is a method on MainMenuController, so simply calling it right after you initialize MainMenuController won't have any effect on the buttons that it manages.
(Unrelated: You're setting mainMenu to nil before releasing it, which effectively means mainMenu can never be released. Call -release first, then, optionally, set it to nil.)

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.