I am trying to create a series of pages that work in tandem using custom segues. These segues work fine until I have them execute in a certain series. From VC1 I go to VC2 via the default modal segue (it pops up over the original segue). Then, from VC2 I go to VC3 using a custom horizontal segue (code below). Finally, I go back from VC3 to VC1 using a custom unwind horizontal segue. The problem is that when I go back to VC1, VC2 appears instead. I checked and ViewDidLoad does not execute in VC2 when it appears, but I can still interact with it when triggered. My best guess as to what is happening is that VC2 covers VC1 so when I go back to VC1, VC2 is displayed on top of it. Even if this is the problem, I don't know how to fix it. Code below:
Horizontal Segue:
class HorizontalSegue: UIStoryboardSegue {
override func perform() {
let src = self.source as UIViewController
let dst = self.destination as UIViewController
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.2, delay: 0.0, options: [.curveEaseOut], animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
})
}
}
Unwind Horizontal Segue:
class UnwindHorizontalSegue: UIStoryboardSegue {
override func perform() {
let src = self.source as UIViewController
let dst = self.destination as UIViewController
src.view.superview?.insertSubview(dst.view, belowSubview: src.view)
src.view.transform = CGAffineTransform(translationX: 0, y: 0)
UIView.animate(withDuration: 0.2, delay: 0.0, options: [.curveEaseIn], animations: {
src.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
},
completion: { finished in
src.dismiss(animated: false, completion: nil)
})
}
}
The first thing I noticed is that you said ViewDidLoad is not called for VC2. Please see if ViewDidAppear (or will appear) is being called instead.
ViewDidLoad is only called when the view loads (initially, when it wasn't there before). ViewDidAppear should fire always, when the view is brought into the user's screen.
After that, log dst in your custom UIStoryBoardSegue. If that's an instance of VC2, you are simply pushing the wrong view controller.
I also see you're using src.present and src.dismiss in your UIStoryboardSegue. This means you're not actually pushing views, but presenting them "on top" of an active view controller. Try to rethink that logic, since this is very much likely where the problem lies.
I would rather try and push the view controllers (normally, instead of 'presenting' them) and change the UIStoryboardSegue appearance to fake the animation if that's what you're after.
Related
So i have a viewcontroller which is basically an alert window which is supposed to be a popup and be dismissed by the tap on outside its frame.
But whenever i call that VC, it is always displayed as fullscreen and not as a pop up window.
I have tried a couple of ways to do this, namely as mentioned below.
if let exp : String = expiredVehicles[i] {
expiredVehicleNumber = expiredVehicles[i]
let popUpVC = SubscriptionExpired()
popUpVC.modalTransitionStyle = .crossDissolve
popUpVC.modalPresentationStyle = .popover // also tried other presentation styles but none work and it is still fullscreen
popUpVC.view.backgroundColor = UIColor.white.withAlphaComponent(0.8)
self.present(popUpVC, animated: true, completion: nil)
}
in case anyone need to see the definition of that VC, i will be glad to share it
i feel i should mention that the VC to be displayed as a popup is inheriting UIViewController
Any insight that might help would be great.
Thanks for the input
One potential way is to add a tap gesture recognizer to your View, which dismisses the VC.
But this will only be helpful if this popup has read-only info and doesn't require any further action from the user.
func addTapRecognizer(){
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap))
self.view.addGestureRecognizer(tap)
}
#objc func handleTap(){
// dismiss the VC here
}
}
You can call following method to show popup:
let popupVC = SubscriptionExpired()
popupVC.modalPresentationStyle = .overCurrentContext
self.addChild(popupVC)
popupVC.view.frame = self.view.frame
self.view.addSubview(popupVC.view)
popupVC.didMove(toParent: self)
}
Then, for removing that popup you can use:
UIView.animate(withDuration: 0.25, animations: {
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0
}, completion: { (finished) in
if finished {
self.view.removeFromSuperview()
}
})
In that case I have a button inside popup and whenever that button pressed above method triggers. Can you please try it? I can edit my answer according to your needs.
You need to implement this in you presenting view controller:
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
// Return no adaptive presentation style, use default presentation behaviour
return .none
}
UIPopoverPresentationController displaying popover as full screen
I use a rootViewController and I want to move to another ViewController. The transition to the newViewController works with that code.
A problem occurs when the newViewController is embedded in an UINavigationController. Then the navigation bar is animating during the animation and changes the position.
The navigation bar is animating from the top left to the correct position.
fileprivate func animateTransition(to newViewController: UIViewController) {
currentViewController.willMove(toParent: nil)
addChild(newViewController)
newViewController.view.frame = view.bounds
transition(from: currentViewController, to: newViewController, duration: 2, options: [.transitionCrossDissolve, .curveEaseOut], animations: {
self.currentViewController.removeFromParent()
newViewController.didMove(toParent: self)
self.currentViewController = newViewController
}, completion: nil)
}
How is it possible to move to another UINavigationController with a "fade" animation and how can the navigation bar be at the correct position right from the beginning of the animation?
First, you should move your view controller clean-up code call from the animations closure into the completion closure:
currentViewController.willMove(toParent: nil)
addChild(newViewController)
newViewController.view.frame = view.bounds
transition(from: currentViewController, to: newViewController, duration: 2, options: [.transitionCrossDissolve, .curveEaseOut], animations: {
// this is intentionally blank
}, completion: { _ in
self.currentViewController.removeFromParent()
newViewController.didMove(toParent: self)
self.currentViewController = newViewController
})
You don't want to indicate the transition is done until the animation is complete.
To the navigation bar issue, rather than letting transition(from:to:duration:...) handle the manipulation of the view controller hierarchy, you can add it to your view hierarchy and then animate the unhiding of it. Usually you'd use the .showHideTransitionViews option, but transition is still doing something curious with the appearance methods that is confusing the navigation controller, so it's best to just animate it yourself:
currentViewController.willMove(toParent: nil)
addChild(newViewController)
newViewController.view.frame = view.bounds
newViewController.view.alpha = 0
view.addSubview(newViewController.view)
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseOut, animations: {
newViewController.view.alpha = 1
}, completion: { _ in
self.currentViewController.view.removeFromSuperview()
self.currentViewController.removeFromParent()
newViewController.didMove(toParent: self)
self.currentViewController = newViewController
})
That will allow it to present the navigation bar correctly from the beginning and just fade it in.
try putting this underneath the class declaration of the newViewController
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
if you already have a viewDidLoad() in your ViewController, then just use the last part.
If that doesn't work let me know.
I have three controllers (FirstVC, SecondVC, ThirdVC) inside storyboad, and navigation is sequential: a user can present modally SecondVC from FirstVC. and then present modally ThirdVC from SecondVC. Now, I need to make some button that will open ThirdVC from FirstVC but will also put SecondVC on in between, so when a user will press back/Cross from ThirdVC he will be returned to SecondVC. So, I don’t need animation from FirstVC to SecondVC, just need to go SecondVC and then animate only transition to ThirdVC.
I found same question for push sague How to push two view controllers but animate transition only for the second one?. I need same behaviour with present modally.
Here will be the hierarchy
First VC presents second VC
let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondVC") as! SecondVC
present(vc, animated: true, completion: nil)
present(vc, animated: true, completion: nil)
second VC will add third VC as subview and make it visible
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ThirdVC") as! ThirdVC
self.addChild(vc)
self.view.addSubview(vc.view)
}
when dismiss third VC remove it from super view with animation and reveal second VC
UIView.animate(withDuration: 0.5, animations: { [weak self] in
self?.view.frame = CGRect(x: 0, y: self?.view.frame.height ?? 0.0, width: self?.view.frame.width ?? 0.0, height: self?.view.frame.height ?? 0.0)
}, completion: { [weak self] _ in
self?.view.removeFromSuperview()
})
dismiss second VC and reveal first VC
dismiss(animated: true, completion: nil)
You cannot present a view controller until the view of the presenting view controller has rendered. Therefore, it's not possible to present two view controllers simultaneously where the second presentation originates from the first--the first must first render its view.
But the solution is still rather basic. If the user is going from A to B, you present B from A like normal. If the user is going from A to C, you present C from A but C is a container view controller with two children (B and C, C obviously on top). Therefore, when the user dismisses C, he is not really dismissing anything, but rather animating C out of view to show B (using the same animation as the dismiss). To the user, it looks all the same.
I'm using the performSegue function built into XCode to transfer between two views. I originally go from the view with the navigation bar to one without a navigation bar and it works perfectly. When I go back to the view with the Navigation Bar, the Bar just disappears and everything just moves up to fit the screen.
performSegue(withIdentifier: "goToCameraSegue", sender: self)
performSegue(withIdentifier: "goToMainControllerSegue", sender: self)
I use these segues to transfer between the different views. I am also using a custom class to get the move the view from the right to left as design.
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: -src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: .curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { finished in
src.present(dst, animated: false, completion: nil)
}
)
}
I have a container view controller and child view controllers (Similar to UITabViewController). When transitioning between the view of one child view controller and another child view controller's view, I am using:
let oldView = // get reference to old view
let newView = // get reference to new view
UIView.transition(from: oldView!, to: newView!, duration: 0.3,
options: .transitionCrossDissolve, completion: nil)
The issue here, is that the resizing of the newView happens after the transition animation completes, which looks unsightly.
This bad behavior is only happening when the new child view controller is loaded for the very first time. It seems that the viewDidLayoutSubviews method is only called after the transition.
How do I get the newView to be resized BEFORE the transition and not after.
Thanks In Advance.
in your ViewController override func viewWillTransition
so it will be something like this
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let oldView = // get reference to old view
let newView = // get reference to new view
UIView.transition(from: oldView!, to: newView!, duration: 0.3,
options: .transitionCrossDissolve, completion: nil)
}