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

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.

Related

iPhone : Custom tabbar without the awful More menu

So i wanted to build a tabbar that has more than 5 items and is scrollable and found this article.
Easy done by subclassing the UITabBarController and hide the existing tabbar:
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UITabBar class]])
{
view.hidden = YES;
break;
}
}
Then i just add a UIScrollView and throw some buttons in there based on the items-collection of the tabbarcontroller.
int i = 0;
for (UITabBarItem *item in self.tabBar.items)
{
UIView *tab = [[[UIView alloc] initWithFrame:CGRectMake(i*60, 0, 60, 60)] autorelease];
UIButton *btn = [[[UIButton alloc] initWithFrame:CGRectMake(7.5, 1, 45, 45)] autorelease];
[btn setImage:item.image forState:UIControlStateNormal];
btn.tag = i;
[btn addTarget:self action:#selector(didSelectTabrBarItem:) forControlEvents:UIControlEventTouchUpInside];
[tab addSubview:btn];
[self.scrollView addSubview:tab];
i++;
if(self.selectedViewController == nil)
[self setSelectedIndex:0];
}
I am overriding the setSelectedindex/ViewController since i need some addition drawing.
-(void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
[self startTimer];
}
-(void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
[self startTimer];
}
The problem is that when I am pressing button number 5, 6 or 7, the tabbarcontroller opens the More view. How do i get rid of that and make the last three items act like the other ones? - Could it be the call to the super?
My guess would be to completely kill the UITabBarController and implement my own custom tabbar. But is it possible to disable the more menu and have the UITabBarController select item 5, 6 and 7 as normal?
So, since I'm too lousy to write a completely new tab bar i decided to investigate and try to hack UITabBarController.
And here's the solution:
The actual problem is that when you touch a tab bar item with index above 4, the UITabBarController vil actually display the moreNavigationController. This is a UINavigationController containing a view of type UIMoreViewControllerList, which is a type from the private Cocoa framework together with an instance of the ViewController you selected.
So how do we get rid of the More button?
Simply remove the UIMoreViewControllerList from the moreNavigationController collection, leaving only the ViewController you selected.
-(void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
if([self.moreNavigationController.viewControllers count] > 1)
{
//Modify the view stack to remove the More view
self.moreNavigationController.viewControllers = [[[NSArray alloc] initWithObjects:self.moreNavigationController.visibleViewController, nil] autorelease];
}
}
Well that leaves us with a Edit button in the top right corner (Titlebar).
How do you get rid of that, then?
Yeah. Thats another dirty hack. To remove the Edit button I'd actually have to implement one method from the UINavigationControllerDelegate for the moreNavigationController on my custom UITabBarController.
//navigationController delegate
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([navigationController isKindOfClass:NSClassFromString(#"UIMoreNavigationController")])
{
// We don't need Edit button in More screen.
UINavigationBar *morenavbar = navigationController.navigationBar;
UINavigationItem *morenavitem = morenavbar.topItem;
morenavitem.rightBarButtonItem = nil;
}
}
And thats the way to kill off the default More functionality. I really think Apple pissed their own pants here creating a UITabBarController that both handles logic and UI stuff.
Why not create a controller that has the logic to preload the ViewControllers and switch between then, and then an implementation you can use if you want the More thingie. - Or even better: Make it possible to disable the More functionality.
This is pretty close, it may help you
https://github.com/iosdeveloper/InfiniTabBar
I should finalize this thread. I have been having the stuff above in a App Store for a year and it has caused massive problems in the long run. It works, but it quirks when you rely on the built in features of UITabbarcontroller as it messes around with the view stack.
After going around this hot ash for over a year we decided to build our own tabbarcontroller/menucontroller. Thta took like a day and have freed us from all the fixes and quirks.
My hack works, but I recommend building your own navigation class - it will pay off in the long run :-)
I had a similar problem and solved it by using a UITabBarController and hiding the tab bar. Then I drew a custom tab bar over the top of it and when a button for a tab was clicked, call
tabbar.selectedIndex = index
Where 'tabbar' is the original UITabBarController. Setting the selectedIndex property of a UITabBarController changes the currently displayed view controller to the controller at that index. This way you still get all the functionality of a UITabBarController but you can have as many tabs as you want and customize it however you want.
Otherwise, I don't think there is a way to remove the "more" functionality.

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.

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

Display Custom UIView On Top of UIViewController

I'm looking for opinions on the best way to implement the following functionality. I have a UIViewController with a UITableView in Grouped Format. Each TableViewCell contains an NSString. When the User taps on a cell I'd like to in my didSelectRowForIndexPath method popup a UIView with a single textfield, that's prepopulated with the NSString in the given cell that was selected. The reason for displaying the UIVIew is I want it to be about 280 x 380 frame and still see some of the UITableView.
The goal being that it behaves like a ModalViewController except for the iPhone. Does anyone have any suggestions on how to implement this behavior or if there is a better implementation?
Create the UIView (with the UITextField inside) beforehand, and make it hidden:
// Of course, these instance variable names are made up, and should be changed
self.myModalView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 280, 380)] autorelease];
[self.view addSubview:self.myModalView]
self.myTextField = [[[UITextField alloc] init] autorelease];
[self.myModalView addSubview:self.myTextField];
self.myModalView.hidden = YES;
Then, when the user selects a row, populate the text field and show the modal view:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
// Replace stringAtIndexPath with however your data source accesses the string
NSString* myString = [self stringAtIndexPath:indexPath];
self.myTextField.text = myString;
self.myModalView.hidden = NO;
}
If you want to get fancy, you can do some CATransition stuff before showing the modal view.
I think you can use "addSubView" in UITableView. Add the ModalView to your UITableView directly. It will work.
Use some animation to implement this effect. When the UIView appears, it will lift the UITableView, like some keyboard behavior. So, you have to addSubView on the self.view and modify the UITableView's frame. And, the UITableView should be a child view of self.view, if self.view is the same as the UITableView, then you has no self.view for adding this UIView.

UITextField subview of UITableViewCell to become first responder?

I have a core data application which uses a navigation controller to drill down to a detail view and then if you edit one of the rows of data in the detail view you get taken to an Edit View for the that single line, like in Apples CoreDataBooks example (except CoreDataBooks only uses a UITextField on its own, not one which is a subview of UITableViewCell like mine)!
The edit view is a UITableviewController which creates its table with a single section single row and a UITextfield in the cell, programatically.
What I want to happen is when you select a row to edit and the edit view is pushed onto the nav stack and the edit view is animated moving across the screen, I want the textfield to be selected as firstResponder so that the keyboard is already showing as the view moves across the screen to take position. Like in the Contacts app or in the CoreDataBooks App.
I currently have the following code in my app which causes the view to load and then you see the keyboard appear (which isn't what I want, I want the keyboard to already be there)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[theTextField becomeFirstResponder];
}
You can't put this in -viewWillAppear as the textfield hasn't been created yet so theTextField is nil. In the CoreDataBooks App where they achieve what i want they load their view from a nib so they use the same code but in -viewWillAppear as the textfield has already been created!
Is there anyway of getting around this without creating a nib, I want to keep the implementation programatic to enable greater flexibility.
Many Thanks
After speaking with the Apple Dev Support Team, I have an answer!
What you need to do is to create an offscreen UITextField in -(void)loadView; and then set it as first responder then on the viewDidLoad method you can set the UITextField in the UITableViewCell to be first responder. Heres some example code (remember I'm doing this in a UITableViewController so I am creating the tableview as well!
- (void)loadView
{
[super loadView];
//Set the view up.
UIView *theView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = theView;
[theView release];
//Create an negatively sized or offscreen textfield
UITextField *hiddenField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, -10, -10)];
hiddenTextField = hiddenField;
[self.view addSubview:hiddenTextField];
[hiddenField release];
//Create the tableview
UITableView *theTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewStyleGrouped];
theTableView.delegate = self;
theTableView.dataSource = self;
[self.view addSubview:theTableView];
[theTableView release];
//Set the hiddenTextField to become first responder
[hiddenTextField becomeFirstResponder];
//Background for a grouped tableview
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//Now the the UITableViewCells UITextField has loaded you can set that as first responder
[theTextField becomeFirstResponder];
}
I hope this helps anyone stuck in the same position as me!
If anyone else can see a better way to do this please say.
Try do it in viewDidAppear method, works for me.
I think the obvious solution is to create the textfield in the init method of the view controller. That is usually where you configure the view because a view controller does require a populated view property.
Then you can set the textfield as first responder in viewWillAppear and the keyboard should be visible as the view slides in.
have you tried using the uinavigationcontroller delegate methods?:
navigationController:willShowViewController:animated: