The app I'm making utilizes multiple views. such as a disclaimer view, a view to display answer so on and so forth.Up until now this is the code that I've been using to switch from one view to another
-(IBAction)swichtogain:(id)sender{
gainview *second = [[gainview alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:second animated:YES];
[second release];
}
I found this method in a tutorial, I was wondering, is this the best way to do it ? I use the same code to switch back n forth from one view to another for eg.
-(IBAction)swichtoview1:(id)sender{
view1 *view = [[gainview alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:view animated:YES];
[view release];
}
and when in view1 if the user hits the back button the following code gets executed
-(IBAction)swichtomainview:(id)sender{
mainview *view = [[gainview alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:view animated:YES];
[view release];
}
I haven't edited anything in the appdelegate files and this is a view based app. Does this method cause it to use more memory ? During the activity monitor test using the instruments , I noticed the memory usage gets higher every time I go from the main menu to another view and back to the main menu !. Is there a better way than this ?. Also one of the view is a calculator so when the user hits the calculate button it switches to the next view while changing the textfield to the answer, below is the code for that !
-(IBAction)calculate{
MyClass *setnum = [[MyClass alloc]init];
setnum.grade_num = grade;
setnum.stage_num = stage;
setnum.ex_lym = ex_ly;
setnum.pos_lym = pos_ly;
setnum.er_num = er;
setnum.noderatio = pos_ly/ex_ly;
if(text1.text.length <=0 ||text2.text.length <=0||text3.text.length<=0||text4.text.length<=0||text5.text.length <=0){
UIActionSheet *action = [[UIActionSheet alloc]initWithTitle:#"Incomplete Values" delegate:self cancelButtonTitle:#"Ok" destructiveButtonTitle:nil otherButtonTitles:nil];
[action showInView:self.view];
[action release];
}else{
answer *ans =[[answer alloc]initWithNibName:nil bundle:nil];
[self presentModalViewController:ans animated:YES];
float i = calc_gain(setnum.grade_num, setnum.noderatio, setnum.stage_num, setnum.er_num);
NSString *result = [NSString stringWithFormat:#"%f",i];
ans.answer1.text = result;
ans.bar.hidden = NO;
[ans release];
}
[setnum release];
}
You should consider using one of the provided container view controllers (UITabBarController, UINavigationBarController or UISplitViewController on the iPad and so on).
The way you use presentModalViewController is most likely the wrong way. For one, calling presentModalViewController will retain your views. Keeping allocating new controllers and displaying their views via presentModalView is therefore increasing your memory footprint with each navigation step.
In general, a viewcontroller which shows another modal viewcontroller is also responsible for dismissing it again. The way to dismiss a modal view controller is therefore to let the presented controller inform its parent through delegation and ask the parent to dismiss (often on tapping a 'done' button).
I'm not even sure whether stacking modalViewControllers is a supported scenario, but at least didn't find anything stated otherwise in the documentation.
Asked here yesterday:
Switching views for iphone application - is this the right way?
I think another good way to go about this is to do this and add a univanigationcontroller:
[self.navigationController pushViewController:second animated:YES];
Related
I have a ViewController, VC1a, that presents VC2:
VC1a -> presentViewController: VC2
Is it possible to change VC1a into VC1b so that when dissmissViewControllerAnimated is called, it animates to VC1b instead of VC1a?
The reason I ask is because I want to return to a different screen without it animating back to VC1a. This relates to portrait/landscape changes.
Warning. This is potentially a bad/confusing UI choice for your users. But if you must...
You may be able to put VC1a inside a UINavigationController and modify the navigation stack while VC2 is in the foreground. Something like:
// in VC1a.m
[self presentViewController:VC2 animated:YES completion:^{
NSMutableArray *navigationStack = [[NSMutableArray alloc] init];
for (UIViewController *viewController in self.navigationController.viewControllers)
{
if (viewController != self.navigationController.viewControllers.lastItem)
{
[navigationStack addObject:viewController];
}
else
{
VC1b *viewControllerToSwapIn = [[VC1b alloc] init];
// probably some more initialization here
[navigationStack addObject:viewControllerToSwapIn];
}
}
self.navigationController.viewControllers = navigationStack;
}];
or possibly a better idea:
// in VC1a.m
[self presentViewController:VC2 animated:YES completion:^{
VC1b *viewControllerToSwapIn = [[VC1b alloc] init];
// probably some more initialization here
[navigationStack addObject:viewControllerToSwapIn];
[self.navigationController popViewControllerAnimated:NO];
[self.navigationController pushViewController:viewControllerToSwapIn animated:NO];
}];
OK, I implemented a Container view controller with two children - one for portrait, one for landscape and this took care of my problem.
I have spent days trying to figure out how to handle rotation correctly within iOS6 and I didn't realise there was a bug in rotating UITableViewController when pushed from a UINavigationController.
Thanks for the response #paulrehkugler - I was getting pretty desperate when I asked this question. There just doesn't seem to be any good examples of how to handle orientation changes while UINav/TableViewControllers are presented from a main view. I certainly wasn't wanting to implement such a hack.
So I have an interesting design question regarding an app I'm developing for the iPhone. I am creating an app that manipulates images, and there are different types of manipulations that can be performed. So the user opens the app, and selects what type of manipulation they want to perform, and are taken through a step by step process to perform the manipulation.
A lot of the manipulations are similar, so code can be reused here. So instead of creating a view controller for each window of each manipulation, I decided to create one view and one view controller. The view contains the steps of each image manipulation, and each time it is incremented to the next step, it reorganizes itself appropriately. The view controller is controlled by a navigation controller, and each time the user advances to the next step of whatever image manipulation they're trying to perform (ie pushed a new view controller on the stack), I make a copy of my view object, set it to reorganize its components to the appropriate step, then send it to the new view controller which will display it.
So my question is this, for some stages of the manipulations, I need to add some buttons to a universal toolbar that is attached to view controller (since this is a modal view, this tool bar will have a home button that will enable the user to exit back to the main screen). Basically, I have a couple of questions on how I should approach this:
1) Should I simply add the toolbar to the view that I'm using, instead of the view controller. If so, how would I have the home button on the toolbar exit the modal view?
2) Should I keep the toolbar on the view controller, and have my view return a set of buttons to be added to it when the view loads? Then I guess I would have to list all of my action methods in my view controller?
3) Should I keep the toolbar on the view controller, but send a pointer from the toolbar to my view object, then add the buttons within my view class? Would I be able to add my action methods in my view class then?
Anyhow, sorry if this is complicated, and if you have any follow up questions please let me know.
1) Ok.
For dismissing, does your view have a pointer to the view controller? How about something like this:
[self.viewController.parentViewController dismissModalViewControllerAnimated:YES];
Not sure if I understand exactly how your hierarchy is organized. That's just my guess.
2) That seems kludgy to me. You'd have to define some sort of data structure that describes what the view wants in a button, make a list of them. The view controller has to request that list, go through them.
3) That seems like the best option. But I wouldn't have your view directly add subviews to the toolbar. Create a ToolbarView custom view. Give it some kind of addButton method, with parameters that describe what essential attributes you want the button to have, like title and the target and action maybe. Let the ToolbarView decide what it looks like, where it's positioned, etc.
Can your action methods go on your view class? Yeah I guess, but they shouldn't. The recommended iPhone design pattern is that views shouldn't do anything, they should just show things. Methods that do things should be on view controllers, even if the only thing they do is change what views are being shown.
I finally came up with a solution for this. What I did was create a universal view controller called UIMainViewController that obviously inherits from UIViewController. I implement the toolbar like follows:
- (void) viewDidLoad
{
[super viewDidLoad];
[self assembleToolbarButtons];
[[self navigationController] setToolbarHidden:NO];
[self setToolbarItems: toolbarButtons];
[[[self navigationController] toolbar]setBarStyle:UIBarStyleBlack];
}
- (void) assembleToolbarButtons
{
NSMutableArray *toolbarButtonsTemp = [[NSMutableArray alloc] init];
[self setToolbarButtons: toolbarButtonsTemp];
[toolbarButtonsTemp release];
if ([self mode] == UIMainViewControllerMainMode)
{
UIBarButtonItem *createAPictureButton = [[UIBarButtonItem alloc] initWithTitle:#"Create" style: UIBarButtonItemStyleBordered target:self action:#selector(loadCreateAPictureModalViewController)];
UIBarButtonItem *accountButton = [[UIBarButtonItem alloc] initWithTitle:#"Account" style: UIBarButtonItemStyleBordered target:self action:#selector(loadAccountModalViewController)];
UIBarButtonItem *helpButton = [[UIBarButtonItem alloc] initWithTitle:#"Help" style: UIBarButtonItemStyleBordered target:self action:#selector(loadHelpModalViewController)];
[[self toolbarButtons] addObject: createAPictureButton];
[[self toolbarButtons] addObject: accountButton];
[[self toolbarButtons] addObject: helpButton];
[createACaptionButton release];
[accountButton release];
[helpButton release];
}
else
{
UIBarButtonItem *homeButton = [[UIBarButtonItem alloc] initWithTitle:#"Home" style: UIBarButtonItemStyleBordered target:self action:#selector(exitModalViewController)];
[[self toolbarButtons] addObject: homeButton];
[homeButton release];
}
}
-(void) loadCreateAPictureModalViewController
{
CreateAPictureViewController *createAPictureViewController = [[CreateAPictureViewController alloc] initWithMode:UIMainTableViewControllerModeModal];
UINavigationController *createAPictureNavController = [[UINavigationController alloc] initWithRootViewController: createAPictureViewController];
[[createAPictureNavController navigationBar] setBarStyle:UIBarStyleBlack];
[self presentModalViewController:createAPictureNavController animated:YES];
[createAPictureNavController release];
[createAPictureViewController release];
}
-(void) loadAccountModalViewController
{
AccountViewController *accountViewController = [[AccountViewController alloc] initWithMode:UICaptionDistractionTableViewControllerModeModal];
UINavigationController *accountNavController = [[UINavigationController alloc] initWithRootViewController: accountViewController];
[[accountNavController navigationBar] setBarStyle:UIBarStyleBlack];
[self presentModalViewController: accountNavController animated:YES];
[accountNavController release];
[accountViewController release];
}
-(void) loadHelpModalViewController
{
HelpViewController *helpViewController = [[HelpViewController alloc] initWithMode:UICaptionDistractionTableViewControllerModeModal];
UINavigationController *helpNavController = [[UINavigationController alloc] initWithRootViewController: helpViewController];
[[helpNavController navigationBar] setBarStyle:UIBarStyleBlack];
[self presentModalViewController: helpNavController animated:YES];
[helpNavController release];
[helpViewController release];
}
-(void) exitModalViewController
{
[self dismissModalViewControllerAnimated:YES];
}
So for my app, on each viewcontroller it will have a toolbar at the bottom that have the basic buttons for creating a picture, accessing the account, or accessing help. If one of these buttons is accessed, it will launch a modal view which will have the home button to exit the modal view (when the UIMainViewController is created, one of it's parameters tells it which mode it is in, and thus which toolbar buttons to add.
But the main thing is I created a class mutablearray varialbe to store the toolbar buttons and then the buttons are created in "assembleToolbarButtons". Now any class that inherits from UIMainViewController can override the assembleToolbarButtons in order to add it's own buttons on top of the main ones that have already been added.
As far as what I mentioned initially in using one UIView and having it reorganize itself, and only one uiviewcontroller, I avoided this and instead just created separate view controllers for each step and separate views so as to adhere to MVC more.
So I feel like a serious rookie right now, but I have a problem I can't seem to figure out. I have a barebones app, with literally nothing in it except a login screen and a second view containing a tableview. When I add the second view after logging in (I have done this like 4 times before...), the table view goes through its delegates and appears that it's going to load, but something happens. I have enabled my NSZombies, and it appears to be deallocating the new view, right before it appears.
After tracing through it, and building up again piece by piece, it appears to happen after I wire the table to the view as the datasource/delegate in IB. I have set the view as a UITableViewDelegate, and the methods indeed get fired. Does anyone have any idea what might be causing this behavior?
Have you added the 'second'view to an exisitng view using addSubview: or added it to some form of UINavigationController or UITabBarController? When you do this it will automatically increase the retain count and whatever code you have releasing the view won't cause is to be deallocated.
In my AppDelegate application:didFinishLaunchingWithOptions I have something like;
LoginViewController *login = [[LoginViewController alloc] init];
[login setDelegate:self];
loginNavController = [[UINavigationController alloc]
initWithRootViewController:login];
[window addSubview:[loginNavController view]];
And then once login has occured (and succeeded using a protocol/delegate to send the message back to AppDelegate) I call this code;
UIViewController *newView1 = [[UIViewController alloc] init];
UIViewController *newView2 = [[UIViewController alloc] init];
UIViewController *newView3 = [[UIViewController alloc] init];
myTabBarController = [[UITabBarController alloc] init];
myNavController = [[UINavigationController alloc]
initWithRootViewController:newView1];
// nav controller now retaining
[newView1 release];
NSArray *viewControllers = [NSArray arrayWithObjects:myNavController,
newView2,
newView3,
nil];
[myTabBarController setViewControllers:viewControllers animated:YES];
[[myTabBarController view] setFrame:[[UIScreen mainScreen] applicationFrame]];
[window addSubview:[tabBarController view]];
// tab bar controller now retaining
[newView2 release];
[newView3 release];
// remove login from application
[[loginNavController view] removeFromSuperview];
The AppDelegate has the following declared in the header file;
LoginViewController *loginViewController;
UITabBarController *myTabBarController;
UINavigationController *myNavController;
In the dealloc method for the AppDelegate these are released.
This gives me my login page and then when that has processed my views with a top nav all controlled using the bottom tab bar.
Hope this helps in some way.
You have either too many release (or autorelease) calls - or not enough retain calls - in your view loading/transitioning code, but it's impossible to be more specific without seeing that code.
What's probably happening is the autorelease pool is being flushed between your view loading and your view being shown, and that's what's leading the behaviour you describe.
In order to speed up my app, I've create three different UIViewController in AppDelegate and it has readonly property for the controllers. Those controllers are used for navigation controller.
If I tap a button on the root view, I just show another view using pushViewController method. Let me show you some code for this here.
UIViewController* controller = delegate.anotherViewController;
[delegate.navigationController pushViewController:controller animated:YES];
At first time, this work well, but if I navigate back and tap the button again, I've got a signal 'EXC_BAD_ACCESS' at second line.
What's wrong? And, how can I prepare all of my view controllers at the beginning, not create them when they are needed?
Most of the time EXC_BAD_ACCESS means that you've released an object and you're trying to reuse it without retaining it.
Look if you have released your viewController too early and whether you are (re)using it the right way or not...
I had the same problem. My code was
AddMedia *info = [[AddMedia alloc] initWithStyle:UITableViewStyleGrouped];
[self.navigationController pushViewController:info animated:YES];
[info release];
I was releasing my viewCOntroller which was crashing the app.
When I commented that line, It worked seamlessly. The code after the change is:
AddMedia *info = [[AddMedia alloc] initWithStyle:UITableViewStyleGrouped];
[self.navigationController pushViewController:info animated:YES];
// [info release];
I need to display a couple of view controllers (eg, login screen, registration screen etc). What's the best way to bring each screen up?
Currently for each screen that I'd like to display, I call a different method in the app delegate like this:
Code:
- (void) registerScreen
{
RegistrationViewController *reg = [[RegistrationViewController alloc] initWithNibName:#"RegistrationViewController" bundle:nil];
[window addSubview:reg.view];
}
- (void) LoginScreen
{
LoginViewController *log = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[window addSubview:log.view];
}
It works, but I cant imagine it being the best way.
I'd recommend reading the View Controller Programming Guide if you haven't: http://developer.apple.com/iphone/library/featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
It sounds like presenting a view controller modally may be your best bet - but you'll probably want to wrap it in an UINavigationController first.
eg
UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:theControllerYouWantToPresent] autorelease];
[self presentModalViewController:navController animated:YES];
I've often wondered if this is the best way myself, but when I'm not using IB's built-in stuff (like a NavigationController) I have a single method in the AppDelegate, switchToViewController:(UIViewController *)viewController that I pass...well, it's pretty self-explanatory I guess. This way there's only one place where it's done, and I can easily define transitions in that method once the app nears completion.
Also, don't forget to remove the previous views in your methods, otherwise you're liable to run out of memory. Something like this:
-(void) switchToViewController:(UIViewController *)c {
if(c == currentController) return;
[currentController.view removeFromSuperview];
[window addSubview:c.view];
[currentController release];
currentController = [c retain];
}