Reducing many similar methods to one parametrized method - iphone

I'm studying Objective-C.
A collegue left my company and I "inherited" her code. In a project, in a UIViewController class I found this code:
-(IBAction)goToSect1:(id)sender{
sect1=[[Sect1ViewController alloc]initWithNibName:#"SECT-1" bundle:nil];
[self presentModalViewController:sect1View animated:YES];
}
-(IBAction)goToSect2:(id)sender{
sect2=[[Sect2ViewController alloc]initWithNibName:#"SECT-2" bundle:nil];
[self presentModalViewController:sect2View animated:YES];
}
-(IBAction)goToSect3:(id)sender{
sect3=[[Sect3ViewController alloc]initWithNibName:#"SECT-3" bundle:nil];
[self presentModalViewController:sect3View animated:YES];
}
-(IBAction)goToSect4:(id)sender{
sect4=[[Sect4ViewController alloc]initWithNibName:#"SECT-4" bundle:nil];
[self presentModalViewController:sect4View animated:YES];
}
-(IBAction)goToSect5:(id)sender{
sect5=[[Sect5ViewController alloc]initWithNibName:#"SECT-5" bundle:nil];
[self presentModalViewController:sect5View animated:YES];
}
-(IBAction)goToSect6:(id)sender{
sect6=[[Sect6ViewController alloc]initWithNibName:#"SECT-6" bundle:nil];
[self presentModalViewController:sect6View animated:YES];
}
I consider this a little crazy, since we have six methods doing fundamentally the same thing. Is there a way to have only one parametrized method? How? Should I consider using the sender tag and a switch inside the body of my new method?
Thanks, any help is appreciated.

-(IBAction)goToThisViewControllerByUsingThisSection:(id)sender{
// Here I assume you are using this function with UIButton or other controls.
// If so, then you have to assign a tag for each section button or do as needed by your app.
// If you call this method in didselectrowatindexpath in delegate of UITableView then simply do use:
// indexPath.section instead of sender.tag
NSString *sectionWithViewController=[NSString stringWithFormat:#"Sect%iViewController",sender.tag];
Class classNam=NSClassFromString(sectionWithViewController);
UIViewController *unKnownViewController=[[classNam alloc]initWithNibName:[NSString stringWithFormat:#"SECT-%i",sender.tag] bundle:nil];
[self presentModalViewController:unKnownViewController animated:YES];
}

I wouldn't alter it at all. Those are IBActions, meaning that something in your interface is binded to them, more than likely buttons. So you have 6 buttons on your UI, each lead to a different view controller.
With the current way you have it, you can alter the individual functionality of each button without having to worry about affecting the others. Otherwise you wild be binding all those buttons to the same method, and then inside the method have to do a check for which button it is. At best, you will get rid of 5 lines of code and introduce 6 more. So the change will actually reduce readability and increase your number of lines of code.

In my opinion you should just leave it like that. The other option would be to have a single method like this:
-(IBAction)goToSection:(id)sender{
//Check the sender and then allocate depending on sender...
}
And simply bind the same method to all the buttons...
In my opinion that would look a lot worst.

There is not much you can do as mentioned by others.
Some suggestions:
If you use appropriate naming or change your init this line can be from
[[Sect1ViewController alloc] initWithNibName:#"SECT-1" bundle:nil];
to
[[Sect1ViewController alloc] init];
Either name the xib after the controller like Sect1ViewController or Sect1View or change your init for the class
// Sect1ViewController.m
- (id)init
{
self = [super initWithNibName:#"SECT-1" bundle:nil];
if (self) {
// ...
}
return self
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
return [self init];
}
Generally a viewController has a better idea what xib it should use than any calling code and this keep encapsulation.
May be a bad idea but included so you can see some things you can do
This structure of the method is repeating. I am not 100% you need to hold onto the viewController you are creating as the presenting controller will take a
retain on it.
With this in mind we can DRY this code up slightly like so
-(IBAction)goToSect1:(id)sender{
[self presentClass:[Sect1ViewController class]];
}
-(IBAction)goToSect2:(id)sender{
[self presentClass:[Sect2ViewController class]];
}
-(IBAction)goToSect3:(id)sender{
[self presentClass:[Sect3ViewController class]];
}
-(IBAction)goToSect4:(id)sender{
[self presentClass:[Sect4ViewController class]];
}
-(IBAction)goToSect5:(id)sender{
[self presentClass:[Sect5ViewController class]];
}
-(IBAction)goToSect6:(id)sender{
[self presentClass:[Sect6ViewController class]];
}
- (void)presentClass:(Class)class;
{
UIViewController *viewController = [[class alloc] init];
[self presentModalViewController:viewController animated:YES];
[viewController release]; viewController = nil;
}
}
This does not scale well as for every new button you need to add a new action even though they do the same thing - yuk

try that:
-(IBAction)goToSect:(id)sender{
if (sender /*do your check here*/)
{
sect=[[Sect1ViewController alloc]initWithNibName:#"SECT-1" bundle:nil];
}else if (sender /*another check*/){
sect=[[Sect2ViewController alloc]initWithNibName:#"SECT-2" bundle:nil];
} //other checks
[self presentModalViewController:sect1View animated:YES];
}
you will need all these if-checks because there are different ViewControllers. If they are pretty much the same I'd just leave 1 and pass/change some parameters.
hope it helps

Related

Loading xib from UIAlertView from a viewcontroller

In my app I have a xib that has several viewcontrollers and xibs (with more viewcontrollers)off of it.
I can go from xib to xib and load what ever view controllers I want.
My problem is when I go to a view in side one off my viewcontrollers (be it on the main xib or another) I have a navigation bar at the bottom with a single button marked "back". Now when this button is hit before it takes you back It asks if you want something to happen.
When you click yes a UIAlertView will pop up and tell you wants about to happen when you click "ok". I know my button is firing by NSLogging my tag.
However I need a certain xib file to load and it does not.
This is the code that I am trying to use -- now this works If I want to go from xib to xib from a button. So I am not sure why its not working in an alert view.
- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex {
// the user clicked one of the OK/Cancel buttons
if(alert.tag==1)
{
if (buttonIndex==0) {
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
NSLog(#"index 0");
}
}
}
Adding pic do to a response:
I have had similar problems before, not with a UIViewController, but with a UIActionSheet. When I tried to do this, I wanted to present a UIActionSheet depending on the selection of the UIAlertView. When I pressed something in the UIAlertView, the view was dismissed, but my action sheet was not shown. This was my solution; instead of:
if(alert.tag==1) {
if (buttonIndex==0) {
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
NSLog(#"index 0");
}
}
try something like:
if(alert.tag==1)
if (buttonIndex==0)
[self performSelector:#selector(presentController) withObject:nil afterDelay:.1];
with the method presentController being:
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
NSLog(#"index 0");
The problem seems to be that the UIAlertView takes some time to dismiss itself (very small amount of time) and in that time presenting another view doesn't work.
Also, as Alan Zeino said, if the name of the xib is not the same as the name of the view controller (for example if you have two different xibs- one for ipad and one for iphone), then you do need to specify which to use. If the name of the xib is the same as the view controller, you do not have to specify a name.
Edit:
If you want to use an action sheet and expect the delegate methods to work when their actions are triggered, you'd better set the delegate of the action sheet to self and in your interface file add the protocol.
You need to specify the xib you want to open:
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:#"XibNameHere" bundle:nil];
Since you have indicated that your log statement confirms correctly setting delegate and implementing the methods, the 'UIAlertView locks the screen' theory seems the only expectable one.
As Rickay has said performSelector:AfterDelay: would likely work, but as he has also said that requires a second method to be created. I would say that this is the perfect time for a block.
Here are a couple of options: Either should be copy and paste solutions.
1) Assuming the current main runloop simply has to finish it's current call stack before the new view controller can be pushed you could use:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (alertView.tag == 1) {
if (buttonIndex == 0) {
dispatch_async(dispatch_get_main_queue(), ^{
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
});
}
}
}
This will add a block to the main queue that will create and show your ControllPanelViewController as soon as the main run loop becomes available.
If, however, that doesn't work and it is 'just a matter of time', a block will still work. Like So:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (alertView.tag == 1) {
if (buttonIndex == 0) {
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
ControllPanelViewController *controllpanel = [[ControllPanelViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:controllpanel animated:YES];
});
}
}
}
This will create a block that will 'fire' in the number of seconds specified. Here set to 0.1 seconds.
Again the first one is a cleaner, arguably better, and much easier to read option.

how to fix iOS leak when flipping between two views

My iPhone app badly leaks when flipping back and forth between a main uiviewcontroller and a help uiviewcontroller .
Here is the source of the main view, followed by source of the help view.
MAIN VIEW - FLIP TO HELP.....................
// Changes from operational view to Help view.
- (IBAction)showHelp:(id)sender
{
// End trial mode:
self.stop_trial_if_started;
self.rename_trial_if_edited;
// Switch to trial help:
help_view_context = 0;
HelpView *controller = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
}
HELP VIEW - INIT.............................
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];
help_scroll.editable = FALSE;
return;
}
HELP - RETURN TO MAIN VIEW.........................
// User clicked the button to return to operational view:
- (IBAction)done:(id)sender {
NSLog(#"help- done");
if( help_view_context == 0 ) {
[self.delegate trial_help_DidFinish:self];
}else{
[self.delegate file_help_DidFinish:self];
}
}
MAIN VIEW - RETURN FROM HELP...............................
// Inits operational view when user changes from Help view back to operational view.
- (void)trial_help_DidFinish:(HelpView *)controller {
NSLog(#"trial_help_DidFinish");
[self dismissModalViewControllerAnimated:YES];
self.init_trial_operation;
}
You are creating a controller with ref count of 1 and a local reference each time showHelp: is called:
HelpView *controller = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
you are losing your reference to it at the end of this method.
You happen to have references to it in done: (self) and *_help_didFinish (controller), but you never release it in either of those locations. Dismissing the controller is fine, but you also have to release it.
(Another option would be to never create a second one, and maintain an iVar to the original.)
You could well be leaking on this line
controller.delegate = self;
What is your property declaration for the delegate. If it's anything other than assign, then you either need to change it (preferred option) or make sure you are releasing it in the dealloc method of HelpView controller.

EXC_BAD_ACCESS and Zombies, Yet not really sure why it keeps coming up

I don't know what's going wrong here. The crash happens when switching back and forth between views.
Here's what instruments gives me:
Clicking into it references this code with the first action :
-(IBAction)pushnews; {
NewsViewController *news = [[[NewsViewController alloc]init]autorelease];
news.title =#"Page";
[self.navigationController pushViewController:news animated:YES]; }
I use autorelease sometimes but usually I just release it my self. Should I get rid of autorelease and add [news retain]
What am I doing wrong?
Edit based on answers:
Following EmptyStack's Advice: ViewWillDisappear Code looks like this:
- (void)viewWillDisappear:(BOOL)animated {
webView.delegate = nil; }
This seems to resolve issues (pending more testing)
In viewdidload I said: webView.delegate = self;, which may have been the issue!
My guess is that, there is a UIWebView in NewsViewController, and it is causing the crash. It is possible that, a delegate method of web view is called after the web view is released. If so, try to setwebView.delegate = nil; in NewsViewController's viewWillDisappear: method.
try this instead :
-(IBAction)viewcontroller;
{
NewsViewController *news = [[NewsViewController alloc]init];
news.title =#"Page";
[self.navigationController pushViewController:news animated:YES];
[news release];
}

The dealloc method is not called in the present modal view contrller

It is in My view controller
-(void)doctorsListAction
{
if(isFirst == YES)
{
[self getDoctorsListController];
[[self navigationController] presentModalViewController:doctorListViewNavigationController animated:YES];
[doctorListViewController release];
}
}
-(void)getDoctorsListController
{
//DoctorListViewController *doctorListViewController=[[[DoctorListViewController alloc]initWithNibName:nil bundle:nil]autorelease];
doctorListViewController=[[DoctorListViewController alloc]init];
doctorListViewNavigationController=[[UINavigationController alloc]initWithRootViewController:doctorListViewController];
doctorListViewController.doctorList=doctorList;
doctorListViewNavigationController.navigationBar.barStyle= UIBarStyleBlackOpaque;
[doctorListViewController release];
}
It is in DoctorListViewContrller
-(void)closeAction
{
printf("\n hai i am in close action*******************************");
//[doctorList release];
//[myTableView release];
//myTableView=nil;
printf("\n myTableView retainCount :%d",[myTableView retainCount]);
[[self navigationController] dismissModalViewControllerAnimated:YES];
}
//this method is not called I don't know why if it not called i will get memory issues
- (void)dealloc
{
printf("\n hai i am in dealloc of Doctor list view contrller");
[doctorList release];
[myTableView release];
myTableView=nil;
[super dealloc];
}
this method is not called I don't know
why if it not called i will get memory
issues
When exactly dealloc gets called (i.e. when the object is deallocated) shouldn't really matter to you. What matters is that you pair up each alloc with a release/autorelease. Which you are likely not doing.
The above code doesn't read very well and looks a bit "Java"-ish. Your "get" method doesn't actually return anything, which looks strange. But you normally wouldn't name a method "get___" anyway.
You're probably leaking memory in your getDoctorsListController method on this line:
doctorListViewNavigationController=[[UINavigationController alloc]initWithRootViewController:doctorListViewController];
Since you didn't define doctorListViewNavigationController in this method, and I assume you posted code that compiles, it is either a member (although not necessarily a property) of your class or a static variable somewhere. Which means it could already be pointing to an object. Which means when you assign a new alloc'ed object to it, the old one is lost (leaked).
Here's how you should refactor it.
- (void)doctorsListAction
{
if (isFirst == YES)
{
[self showDoctorsList];
}
}
- (void)showDoctorsList
{
DoctorListViewController* doctorListViewController = [[DoctorListViewController alloc] initWithNibName:nil bundle:nil];
doctorListViewController.doctorList = doctorList;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:doctorListViewController];
navController.navigationBar.barStyle = UIBarStyleBlackOpaque;
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[doctorListViewController release];
}
There might be a lot of other objects 'behind the scenes' that want to keep the DoctorListViewController around. If you just balance out your retains and releases, you should be ok.
Also in -(void)doctorsListAction, shouldn't [doctorListViewController release]; be [doctorListViewNavigationController release]; instead?

presentModalViewController in viewDidLoad on first launch

I've been searching around but unfortunately have had no luck.
My app requires the user to sign in/sign up the first time he or she launches the app. I know how to determine first launch (using NSUserDefaults) but whenever I try to present the modal containing the sign in/ sign up controls, nothing happens.
Here's what I have:
-(void)viewDidLoad {
[self showLogin];
[super viewDidLoad];
}
-(void)showLogin {
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"AccountView" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:controller animated:YES];
[controller release];
}
However, nothing happens. The main view just loads as normal. Any help is greatly appreciated.
-Giles
[UPDATE]
Fixed simply by using..
-(void)viewDidAppear:(BOOL)animated
{
}
instead of
-(void)viewDidLoad
{
}
Thanks anyway!
/idiocy
I had the same issue and ended up using viewDidAppear as well. The only problem with the viewDidAppear approach is that if you load other UIViewControllers on top, then reshow the base, then your setup code gets called over and over. I ended up having to add a boolean value (initialised to YES) to this view controller and check that value before deciding what to do. Hope this helps someone...
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:(BOOL)animated];
if(justLaunched)
{
justLaunched = NO;
if(settingsFileExists)
{
[self displayMainView];
}
else
{
[self displaySetupView];
}
}
}
How about using performSelector:withObject:afterDelay in the viewDidLoad function? That's how I do it, with a short delay of 0.1s.
And invoking this in the viewDidLoad isn't very safe : the sequence viewDidLoad / viewDidUnload can occur at runtime when the iPhone needs to release some views in order to get back some free memory.
The side effect of such sequence would be that your login controller would be shown...
As you said the viewDidAppear looks better but not simply put it at the end of the appDidFinishedLaunching the delegate of your UIApplication?