I have two viewcontrollers which we will call VC1 and VC2. VC1 is a splitscreencontroller that presents VC2 full-screen modal when an image or video is tapped. VC1 implements: -(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation When VC2 is onscreen and the user rotates the device, this is recognized in the VC1 method above which is executed. However, I have some code in that VC1 method that I don't want to execute if VC1 is not on screen. It is otherwise fine, and indeed necessary, that the rotation method of VC1 is called.
What is the best way for VC1 to identify when it is not onscreen such that I can put an if statement in the rotate method that will then only execute certain statements if VC1 is actually onscreen?
You can check if the view controller's view has a window to see if it is onscreen.
if (vc1.view.window != nil) {
// view is onscreen
}
Related
In my case, I am using two view controller VC1 and VC2. Here, VC1 button click to Present Modally and Over Full Screen presentation with Cross Dissolve Transition to presenting VC2. Now, from VC2 dismiss then I didn’t get call VC1 viewWillAppear().
I am not using code base for Present model. I am using Storyboard Segue.
Why it happening and how to fix this?
From Docs,
Note
If a view controller is presented by a view controller inside of a
popover, this method is not invoked on the presenting view controller
after the presented controller is dismissed.
So according to the documentation when a ViewController presents another ViewController modally this method will not be called. To fix this you need to use
func dismiss(animated flag: Bool,
completion: (() -> Void)? = nil)
and move(or repeat) some of viewWillLoad logic to completion handler.
Change presentation to Full screen or If you want to stick to Over Full Screen then make vc2 delegate of vc1 and call delegate method on dismiss.
To understand the concept you can refer to : https://medium.com/livefront/why-isnt-viewwillappear-getting-called-d02417b00396
My target include a lot view need to present different view modally base on each user action. Here what I want to do to get cleaner view hierarchy and better user experience.
Root View Controller present First View Controller modally
When I clicked button on the First View Controller, then the Second View Controller appear modally over it.
As soon as the Second View Controller did appear, I want to dismiss or remove the first one from view hierarchy.
Can I do that? If so, how should i do it?
If not, what is the right way to solve this out cause I will present many modally presented view controllers over each view. I think even if I want to dismiss current view, the previous one will still remain appear when current one dismiss.
UPDATE :
VC1 (Root) > VC 2 (which was present modally) > VC 3 (which was
present modally over VC 2)
When i dismiss VC3, the VC2 is still on view memory. So, I don't want to appear VC2 as soon as I dismiss VC3 and instead I want to see VC1 by removing or dismissing VC2 from view hierarchy.
WANT : At the image, when I dismiss the blue,I don't want see the pink in my view memory and I want to remove it as soon as the blue one appear.
That's what i want to do.
Any Help?Thanks.
So, let's assume that you have a storyboard similar to:
What should happens is:
Presenting the the second ViewController (from the first ViewController).
Presenting the the third ViewController (from the second ViewController).
dismissing to the first ViewController (from the third ViewController).
In the third ViewController button's action:
#IBAction func tapped(_ sender: Any) {
presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
As you can see, by accessing the presentingViewController of the current ViewController, you can dismiss the previous hierarchy of the view controllers:
The view controller that presented this view controller.
By implementing presentingViewController?.presentingViewController? that means that: the presented of the presented current ViewController :)
It might seem a little bit confusing, but it is pretty simple.
So the output should be like (I added background colors to the viewControllers -as vc1: orange, vc2: black and vc3: light orange- to make it appears clearly):
EDIT:
If you are asking to remove the ViewController(s) in the middle (which in this example the second ViewController), dismiss(animated:completion:) does this automatically:
If you present several view controllers in succession, thus building a
stack of presented view controllers, calling this method on a view
controller lower in the stack dismisses its immediate child view
controller and all view controllers above that child on the stack.
When this happens, only the top-most view is dismissed in an animated
fashion; any intermediate view controllers are simply removed from the
stack. The top-most view is dismissed using its modal transition
style, which may differ from the styles used by other view controllers
lower in the stack.
Referring to what are you asking:
I think even if I want to dismiss current view, the previous one will
still remain appear when current one dismiss.
I think that appears clearly on the UI (and I find it ok), but as mentioned in the dismiss documentation discussion, both the third and the second will be removed from the stack. That's the right way.
Here is my opinion in different perspective,
Root View Controller present Second View Controller
Add FirstView onto Second View
Dismiss FirstView Controller when button pressed.
Second View Controller,
class ViewController: UIViewController, FirstViewControllerProtocol {
weak var firstViewController: FirstViewController?
override func viewDidLoad() {
super.viewDidLoad()
print("Not initiated: \(firstViewController)")
firstViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "FirstViewController") as? FirstViewController
addChildViewController(firstVC!)
firstViewController?.delegate = self
view.addSubview((firstViewController?.view)!)
print("Initiated: \(firstViewController)")
}
func dismiss() {
firstViewController?.view.removeFromSuperview()
firstViewController?.removeFromParentViewController()
}
}
FirstViewController,
protocol FirstViewControllerProtocol {
// Use protocol/delegate to communicate within two view controllers
func dismiss()
}
class FirstViewController: UIViewController {
var delegate: FirstViewControllerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func dismiss(_ sender: Any) {
delegate?.dismiss()
}
deinit {
print("BYE")
}
}
What you want is an "unwind segue":
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html#//apple_ref/doc/uid/TP40007457-CH15-SW8
https://developer.apple.com/library/archive/technotes/tn2298/_index.html
It allows you to dismiss multiple view controllers at the same time, without having to know how many there are in the stack.
In VC1 you would implement an IBAction called (for instance) unwindToRoot. Then in the storyboard for VC3, you wire up your Done button to the Exit object and choose the unwindToRoot action.
When that button is pressed, the system will dismiss all the view controllers it needs to bring you back to VC1.
This is better than calling presentingViewController?.presentingViewController?.dismiss(), because VC3 doesn't need to know anything about the view controller hierarchy underneath it.
I have a PresentingViewController (VC1) and a PresentedViewController (VC2).
VC1 --Segue:Present Modally:Over Current Context--> VC2
VC2 --RewindSegue --> VC1
The problem is that when I rewind from VC2 to VC1, it is not being "reloaded". It is nil.
However if I present VC 2 modally: Full Screen, the rewind triggers a reload of VC1 and all is dandy.
Why does a rewind FROM a VC presented "Modally:Over Current Context" not reload the VC you are rewinding to? How can I present over Current Context and also force a reload of VC1 when I rewind?
Any help would be much appreciated. Thank you.
Solved the problem by adding the function I wanted to update in VC1 in the unwinding action
#IBAction func unwindToDetailViewControllerVC1(segue: UIStoryboardSegue) {
println("Unwinding to DetailViewControllerVC1")
rememberWhatSegmentIsPressed()
}
Also remember to tick the 'Defines Context' in the presentingViewController.
See: https://stackoverflow.com/a/27487057/4051036
I am receiving 2 errors while a dismissal is happening in my app.
Warning: Attempt to dismiss from view controller <MyNavigationController> while a presentation or dismiss is in progress!
&
Unbalanced calls to begin/end appearance transitions for <MainViewController>.
I've searched around and everywhere says that there is usually a conflicting dismissal happening where a button is calling the transition both programmatically and through the storyboard. I, however, am receiving these errors when using the normal back button that comes with nav controllers. I don't do anything with the button at all.
The only thing I can link to the errors is that my nav controller is autorotating while trying to dismiss the view controller. If I remove autorotate or set the orientation of both view controllers to be the same then I don't get the error. Problem is, I need one of the view controllers to be portrait and the other to be landscape...
This is how I set the orientation
NavController.m:
- (NSUInteger)supportedInterfaceOrientations {
return self.topViewController.supportedInterfaceOrientations;
}
- (BOOL)shouldAutorotate {
return YES;
}
MainViewController.m:
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
OtherViewController.m:
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeRight;
}
I've noticed that for some reason it doesn't autorotate when going to my "OtherViewController", but it apparently tries to autorotate while returning to the "MainViewController", thus causing the crash.
Since it may be relevant, this is how I load my OtherViewController:
[self performSegueWithIdentifier:titles[indexPath.row] sender:nil];
I have a CollectionViewController that calls a push segue I have set up in the storyboard. Titles is an NSArray of the different segue titles connected to the MainViewController.
Here is the flow of what's going on in my app:
MainViewController : LoadView
MainViewController : ViewWillAppear
MainViewController : ViewDidAppear
//This is where I choose to load the OtherViewController
OtherViewController : LoadView
MainViewController : ViewWillDisappear
OtherViewController : ViewWillAppear
MainViewController : ViewDidDisappear
OtherViewController : ViewDidAppear
//This is where I select the "Back" button
Warning: Attempt to dismiss from view controller NavController while a presentation or dismiss is in progress!
Unbalanced calls to begin/end appearance transitions for MainViewController.
MainViewController : ViewWillDisappear
MainViewController : ViewDidDisappear
For a detailed analysis we need more code, especially where the view controller is dismissed.
As for your error message the dismissal call, whichever you use, is called in the middle of some other workflow of showing or dismissing a view controller. That could be when you present or dismiss a modal view controller or when you push or pop one on the navigation stack in
loadView
viewDidLoad
viewWillAppear
viewDidAppear
viewWillDisappear
viewDidDisappear
This list is certainly incomplete but that should be the most common methods that are called within the process.
So share some more code with us.
Without getting into too much details, you need to find a way to do one after the other. The tricky part is that there is no delegate that tells you when a view was dismissed after a segue. However, after a segue, when viewWillAppear is called, the previous view was dismissed.
Maybe try to rotate the view from the code, and do so in the viewWillAppear?
Suppost I have two viewcontroller, namely vc1 and vc2. Each vc have a button, when press, the background will change from default white to black, and a next button.
So the background of vc1 become black, and I click the next button, it push the vc2 with no problem.
- (IBAction)nextVC:(id)sender {
UIViewController *otherView = [self.storyboard instantiateViewControllerWithIdentifier:#"vc2"] ;
[self presentViewController:otherView animated:YES completion:NULL] ;
}
I do the same thing on vc2, thus the background of vc2 become black. And then I dismiss vc2, and back to vc1, vc1's background keeping black in color.
[self dismissViewControllerAnimated:YES completion:nil] ;
The question is, when I press the next button on vc1, the background color of vc2 is not keeping black, it becomes default white. Is there a way I can resume the status of vc2 that I've dismissed? Without saving the data or setting anything on viewWillApper or etc.
Simply, I am asking a method to resume the view which is dismissed, but not pushing a newly view (vc2).
Thanks for the help.
If you check your code of nextVC, You are creating new instance of second view controller and push it. That is why you always get white background VC which is just initialised.
So, If you want keep your status of second VC, you need to keep referencing it.
Add UIViewController *otherView to your view controller .h file or .m as a local property. And then change your push function.
//Only initialise first time. Later don't need
if (otherView == nil){
otherView = [self.storyboard instantiateViewControllerWithIdentifier:#"vc2"] ;
}
[self presentViewController:otherView animated:YES completion:NULL] ;
It should be working.
You can not just resume the "Dismissed" view controller because when a viewController is dismissed, it is removed from the memory and its retain count becomes 0. Your vc1 is keeping its state because it is never being dismissed, you are just PRESENTING another controller ON it. If you want to save the state of vc2 you can set a flag in a SharedInstance or in AppDelegate and then check it whenever vc2 is presented.