Hi I'm having a weird problem.
My app is based on the samplecode of "PageControl" (the Apple example).
It uses a horizontal scrollview in which most of the stuff is happening.
At he bottom I have a UIToolbar from which I call a modal viewcontroller.
On XCode 4 everything worked like a charm, after the upgrade to XCode 4.2 (with the new SDK) I get a "exc_bad_access" on dimissModalViewcontroller.
The funniest thing is that it does not happen rightaway but only after 2 or 3 times presenting and dismissing the modalViewcontroller.
To simplify things I went back to the original samplecode and tried to implement the modalVieWcontroler in that context. No luck so far.
In the original PageControl Code I changed the type of "ContentController" from NSObject to UIViewController like so:
#interface ContentController : UIViewController
{
NSArray *contentList;
}
I call presentModalViewcontroller in a sub class (from ContentController) named PhoneContentController like so:(I use a notification so I can call it from anywhere)
-(void) showExplanationsModal:(NSNotification*)notification{
ExplanationsViewController *xplViewController = [[[ExplanationsViewController alloc] initWithNibName:#"Explanations" bundle:nil]autorelease];
[self presentModalViewController:xplViewController animated:YES];
}
The dismissal of the modalViewcontroller is called from the modal view itself like so:
(the notification is used tot initiate some other stuff)
- (IBAction)onClose
{
[self dismissModalViewControllerAnimated:YES];
[[NSNotificationCenter defaultCenter]postNotificationName:#"dismissExplanationsModal" object:self];
}
This code works fine with iOS4 SDK but renders occasional excec_bad_access with iOS5 SDK.
When I compile the app with iOS4 SDK it also rus fine on iOS5 devices.
I tried using Zombies but this does not point to a specific over-released object.
I'm sort of stuck on this one for a few days already ...
I have put up a copy of a sample project that illustrates the problem here http://www.sesni.biz/pagecontrol.zip
It seems for me that problem is in the onClose method. Try first sending the message, without the object (this object will be invalidated soon).
- (IBAction)onClose
{
[[NSNotificationCenter defaultCenter]postNotificationName:#"dismissExplanationsModal" object:nil];
[self dismissModalViewControllerAnimated:YES];
}
Found the problem: I changed the type of ContententController from NSObject to UIViewcontroller. This worked fine with the iOS4 SDK but crashes with iOS5 SDK.
Related
I am relatively new to iOS development. I developed an app for Android first, and now am porting it to iOS. One thing that I can't figure out is how to make a ViewController "go away". I'm accustomed to the finish() method in Android. With that method, the current activity ends itself and the user is presented with the the previous screen that was open prior to opening the current screen.
What I'm trying to accomplish is making my "create" screen go away after a record is saved. In the Android world, I would just call the finish() method and that would be taken care of. What is the iOS equivalent?
I have tried the following code in my iOS app, hoping that the view would be animated away.
[self.navigationController popToRootViewControllerAnimated:YES];
Edit:
The view was presented as below.
SettingsViewController *vc = [[SettingsViewController alloc] initWithNibName:#"SettingsView"];
controllers = [NSArray arrayWithObject:vc];
self.sideMenu.navigationController.viewControllers = controllers;
[self.sideMenu setMenuState:MFSideMenuStateClosed];
where controllers is defined as such:
NSArray *controllers = nil;
How did you present this view controller?
Did you use presentViewController:animated:completion:? If so, you want something like this:
[self dismissViewControllerAnimated: YES];
If you are using pushViewController:animated:, you are not talking about a modal view. You are talking about a normal ViewController you pushed onto the stack. To "undo" this, you need to pop the view controller:
[self.navigationController popViewControllerAnimated: YES];
Though finish() and [self dismissViewControllerAnimated:YES] are similar in terms of functionality, but they are not exactly same, when we call finish() method in the Android, we are programatically telling the Android system to destroy the activity completely from the memory, when we override the onDestroy() activity life cycle callback and add a log, then logs are shown when finish() is called. onDestroy() is also called when the system needs resources and it frees the memory by finishing the activity. But
[self dismissViewControllerAnimated: YES];
does not remove the UIViewController instance from the memory, the equivalent callback method for onDestroy() in IOS is viewDidUnload() which is not called on the message dismissViewController. I think the IOS system can only free the memory for the UIViewController instance when system needs resources but we can't programatically do that.
after pushing your viewcontroller call this method
func finish(){
var navigationArray = self.navigationController?.viewControllers //To get all UIViewController stack as Array
navigationArray!.remove(at: (navigationArray?.count)! - 2) // To remove previous UIViewController
self.navigationController?.viewControllers = navigationArray!
}
I've used this code to force an orientation change back to portrait when the user is finished watching the video (it allows viewing in landscape mode), before popping the video view controller off the navigation controller:
//set statusbar to the desired rotation position
[[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationPortrait animated:NO];
//present/dismiss viewcontroller in order to activate rotating.
UIViewController *mVC = [[[UIViewController alloc] init] autorelease];
[self presentModalViewController:mVC animated:NO];
[self dismissModalViewControllerAnimated:NO];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
This worked perfectly until iOS 5.1.1. I've even tried to use the new present/dismiss methods after reading in another post that those should be used now:
[self presentViewController:mVC animated:NO completion:NULL];
[self dismissViewControllerAnimated:NO completion:NULL];
The problem is it doesn't work at all. After I rotated the video viewer to landscape and then pop it, my settings view (table view controller) comes back, but also in landscape mode.
I've even tried the tip from Here
"The setStatusBarOrientation:animated: method is not deprecated outright. However it now works only if the supportedInterfaceOrientations method of the topmost full screen view controller returns 0. This puts the responsibility of ensuring that the status bar orientation is consistent into the hands of the caller."
So I've experimented with setting a flag to force supportedInterfaceOrientations to return 0 (before calling the first code block above) but it doesn't work either.
Does anybody have a solution for this?
Thanks for your time and effort.
setStatusBarOrientation method has changed behaviour a bit. According to Apple documentation:
The setStatusBarOrientation:animated: method is not deprecated
outright. It now works only if the supportedInterfaceOrientations
method of the top-most full-screen view controller returns 0
Your root view controller should answer false to the method shouldAutorotate in order that your app responds to setStatusBarOrientation:animated
From Apple Documentation: "if your application has rotatable window content, however, you should not arbitrarily set status-bar orientation using this method"
To understand that, put a breakpoint in the shouldAutorotate method and you will see that it is called juste after setting the status bar orientation.
Here is how I fixed.
https://stackoverflow.com/a/14530123/1901733
The current question is linked with the question from the url above.
The statusBarOrientation is a real problem in ios6.
What is the best way to display a view (in my case a login screen) on app resume. From looking around, I've been playing with the applicationDidBecomeActive event in my AppDelegate, but I cannot seem to get my head around how to properly display a view from here.
I've tried to grab the current window by using self.window and/or it's subviews, but from the AppDelegate self.window is nil.
So far this application seems to be wired up correctly, but I am baffled by two things.
A) why is self.window nil from within my AppDelegate's applicationDidBecomeActive event handler.
B) what is the correct/normal way of display a login view (or the like) on application resume.
Implement a custom UIViewController for all of your applications to inherent from. In this view controller implement logic in the viewWillAppear message to determine and show the login screen if necessary.
//CustomViewController.h
#interface CustomViewController : UIViewController
#end
//CustomerViewController.m
#implementation CustomViewController
-(void)viewWillAppear:(BOOL)animated{
if(login_required){
LoginViewController *loginView = [[LoginViewController alloc] initWithNibName:#"LoginView" bundle:nil];
[self presentModalViewController:loginView animated:false];
}
}
#end
Then, simply, in your login view controller make sure you call:
[self dismissModalViewControllerAnimated:false];
The benefits of this approach are two fold. Firstly, it's a very simple implementation. However, most compellingly, having a base class for an application's view controller presents the opportunity to extract common logic.
Jason,
I have worked on a security tutorial provided by Chris Lowe on raywenderlich.com that was intended to demonstrate how to use basic iOS security to lock the application.
The premise behind this tutorial though was that the application would prompt for login upon first launch and if application was resumed upon unlocking the device through the use of NSNoftificationCenter in viewDidLoad and subscribe the the notifications: deviceWillLock and deviceWillUnlock. All of this assumes the device is set to lock.
Basic iOS Security Tutorial Part 2 - This is the part that has the NSNotification registration.
Basic iOS Security Tutorial Part 1 - This is the first part of the tutorial for clarity.
I also ran into this problem and came across this question whilst researching a solution. I didn't want to create the intermediate super class for my views and I wasn't sure how it would work out with navigation controllers. I have come up with another solution that works well for me - so thought I would share it. It is based around the use of NSNotificationCenter .
In your app delegate create a property to hold a reference to the currently displayed view controller - say currentViewController.
Then in the applicationDidFinishLaunching method, register a block observer to update the currentViewController property like this:
[[NSNotificationCenter defaultCenter] addObserverForName:#"CurrentViewChanged"
object:nil
queue:nil
usingBlock:^(NSNotification *note)
{self.currentViewController = (UIViewController *)note.object;} ];
In your view controller implementations, update the viewDidAppear methods to notify the observer that a new view controller is being displayed by adding the following line
[[NSNotificationCenter defaultCenter] postNotificationName:#"CurrentViewChanged" object:self];
Finally, include code in the applicationDidBecomeActive method in your app delegate to force the modal display of your login screen.
UIStoryboard *mainStoryBoard = self.window.rootViewController.storyboard;
UnlockViewController *uvc = [mainStoryBoard instantiateViewControllerWithIdentifier:#"modalUnlockView"];
uvc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.currentViewController presentViewController:uvc animated:YES completion:NULL];
A couple of additional items to note :-
You can disable the login screen display at anytime by posting a notification where the view controller passed is nil.
You only need to post the notification once for a navigation view controller at the top level. All view controllers in the navigation controller stack will be covered. I haven't checked, but I suspect the same is true for a tab view controller.
If you want to display the login screen the first time you enter the app after startup then include the following line in the applicationDidFinishLaunching method.
self.currentViewController = self.window.rootViewController;
I hope this is of some use.
Thanks
So, any clue on this? I had to use [self dismiss modalviewcontroller to dismiss modalviews. Funny fact: when dismissing a tabbarcontroller I could still use the reference to parentviewcontroller, when dismissing a regular viewcontroller, not.
On iOS 5 you will need to use the presentingViewController selector instead of the parentViewController selector.
-(UIViewController *)getParentViewController{
float currentVersion = 5.0;
float sysVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVersion >= currentVersion) {
// iOS 5.0 or later version of iOS specific functionality hanled here
return self.presentingViewController;
}
else {
//Previous than iOS 5.0 specific functionality
return self.parentViewController;
}
}
The app i have in the store was built using ios SDK 4.3 and uses self.parentViewController dismissModalViewControllerAnimated:YES. It continues to work with IOS 5 devices. I thought it would since it was built on sdk 4.3. Now when i'm updating it with the new xcode and ios 5.0 sdk, it will not work as is and i have to change all the view closing stuff to use the conditional selector workaround mentioned above.(yuck!)
Just thought i'd mention that dismissing from the parent should work on ios 5 (at least in my case with the ios 4.3 sdk). I can't speak for previous sdks or other selectors with parentViewController.
I have built a category that add presentingViewController on iOS 4.
It disables itself on iOS 5.
You can use it seamlessly. Please see backward-modal.
For your particular use case of dismissing the modal view controller, you may want to keep in mind the second paragraph of the Discussion section in Apple's documentation for -dismissModalViewControllerAnimated:.
The parent view controller is responsible for dismissing the modal
view controller it presented using the
presentModalViewController:animated: method. If you call this method
on the modal view controller itself, however, the modal view
controller automatically forwards the message to its parent view
controller.
If you call this method on the modal view controller itself, however,
the modal view controller automatically forwards the message to its
parent view controller.
Jason's solution is also great and helpful! Thanks!
I found a nice blog post explaining this issue:
http://omegadelta.net/2011/11/04/oh-my-god-they-killed-parentviewcontroller/
Following this post I created a category method for UIViewController:
- (UIViewController*) myParentViewController {
UIViewController* ret = [self parentViewController];
if(ret == nil) {
if([self respondsToSelector:#selector(presentingViewController)]) {
ret = [self presentingViewController];
}
}
return ret;
}
My problem seems to be a bit weird, I have a custom UITarBar which manages several UINavigationControllers with a UIViewController that presents those NavControllers modally on a UITabBar button touchUpInside, so in iOS 5 my app is crashing because of the dismissModalViewControllerAnimated: method ... And if change the dismiss method to the new one on iOS 5 (dismissViewControllerAnimated:completion:) it does not dismiss the NavController. Here is some code on how I'm changing controllers:
- (void) changeController
{
if ([self.generalViewController respondsToSelector:#selector(dismissViewControllerAnimated:completion:)]) {
[self.generalViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self.generalViewController dismissModalViewControllerAnimated:NO];
}
[self.anotherNavController.view addSubview:customTabBar];
if ([self.generalViewController respondsToSelector:#selector(presentViewController:animated:completion:)]) {
[self.generalViewController presentViewController:anotherNavController animated:NO completion:nil];
} else {
[self.generalViewController presentModalViewController:anotherNavController animated:NO];
}
Everything is managed in the AppDelegate. Thanks in advice.
EDIT: I found something on this post dismissModalViewControllerAnimated: (and dismissViewControllerAnimated) crashing in iOS 5 and did what he did (presented the first viewControllerAnimated animated) and then everything like I had before the iOS 5 checks for new presentViewController's selectors and everything works fine on simulator but not on device ..
I am unsure about your first lines, and think the first lines should perhaps be:
if ([self.generalViewController respondsToSelector:#selector(dismissViewControllerAnimated:completion:)]) {
[self.generalViewController dismissViewControllerAnimated:YES completion:nil];
You have an extraneous .presentingViewController in the second line of the above presently. I see in your comment above you have a rationale for it, but have you tried the alternative?
Regardless, I had a related problem with the move to iOS 5, and found that the solution was to simply send the dismiss commands thusly:
[self dismissViewControllerAnimated:YES];
I suggest you try it. It worked for me, and also still worked in iOS 4.2 as well when I ran the code there...
Edit: Correction, it was dismissModalViewControllerAnimated that I was correcting. However, in that case I found that just sending the message to self rather than targeting a view controller was what fixed the problem of not being able to dismiss the view, and worked in both iOS 4 and 5... Couldn't hurt to just try.