SubView transition resizing after transition - swift

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)
}

Related

Working with 2 UINavigationControllers inside a ViewController as Child causes only 1 to respond, the other does not respond

I'm working with a project where I start with a ContainerViewController having 2 controllers added as a child:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
window?.rootViewController = ContainerViewController()
window?.makeKeyAndVisible()
}
The ContainerViewController:
class ContainerViewController: UIViewController {
var panGestureRecognizer: UIPanGestureRecognizer!
private var menuWidth: CGFloat = UIScreen.main.bounds.width - 50
let menuController = SideMenuViewController()
let mainController = MainViewController()
override func viewDidLoad() {
super.viewDidLoad()
addChildVCs()
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(panGestureRecognizer)
}
private func addChildVCs() {
addChild(menuController)
menuController.view.frame = CGRect(x: 0 - view.frame.size.width, y: 0, width: menuWidth, height: view.frame.size.height)
view.addSubview(menuController.view)
menuController.delegate = self
menuController.didMove(toParent: self)
addChild(mainController)
mainController.delegate = self
view.addSubview(mainController.view)
mainController.didMove(toParent: self)
}
With this panGesture I just show/hide a SideMenu, nothing strange here.
Inside this MainViewController, I have a TabBarController named mainController and another UINavigationController that store user conversations, named ConversationViewController(), both added as a child like this:
class MainViewController: UIViewController {
let mainController = MainTabController()
let conversationNavigationController = UINavigationController(rootViewController: ConversationViewController())
override func viewDidLoad() {
super.viewDidLoad()
addChildVCs()
}
private func addChildVCs() {
addChild(mainController)
mainController.menuDelegate = self
view.addSubview(mainController.view)
mainController.didMove(toParent: self)
addChild(conversationNavigationController)
conversationNavigationController.view.frame = CGRect(x: view.frame.size.width, y: 0, width: view.frame.size.width, height: view.frame.size.height)
view.addSubview(conversationNavigationController.view)
conversationNavigationController.didMove(toParent: self)
}
The conversationNavigationController origin.x point is starting at the end of the mainController (tabBar) so I can panGesture to the right and show/hide this other UINavigationController.
The problem is when I start the execution I can interact with the mainController(tabBarController) but when I swipe to the right and I have the conversationNavigationController on screen, it doesn't respond to any actions.
Basically I'm working with 2 navigationControllers at the same time but I don't understand why 1 is working and the other one does not respond to any actions.
Any clue on what's the problem? I can provide more code if needed!
Thanks in advance
If you just add your child view controller's content views to your MainViewController's content view, Auto Layout doesn't know what to do with them. At the drop of a hat, Auto Layout will resize your child view controllers in unexpected and undesired ways.
I'm guessing that your view controllers' view frames are getting messed up. It might be that there is some other problem with your view controllers that's preventing them from responding to events, but first I would rule out view layout problems.
I would suggest adding container views to your main view controller, including Auto Layout constraints to put them where you want them.
Then add your child view controllers' views as subviews of those container views, and anchor all 4 edges of your child view controller's view to the edges of their parent views.
When you're first working on it, it is worth adding a borderWidth and borderColor to your different views' layers so you can see what's going on.

Segue transitions to the wrong page Swift Xcode

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.

Transition to a new ViewController embedded in an UINavigationController causes animation problem

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.

How to modify slide transition

I had asked a question similar to this once before but I didn't realize and don't think I can use the same method due to the nuances of the two methods. (How to stop slide transition into Second VC before it covers UIView?)
I ultimately learned how to do the transition using APPCoda's lesson(http://www.appcoda.com/custom-segue-animations/)
The original question resulted in an answer providing a solution using container views and hard coded views. What I am wondering is if I can get the same effect using two separate view controllers and linking them through a segue with a gesture recognizer.
What I would like to accomplish is:
Have my initial view controller
Tap Button and have Second View Controller Overlap the first View Controller Partially (By partially I mean I have a UIView on the first View Controller that I want to remain visible. So the top of the second view controller will slide up until it hits the bottom of the UIView).
What I currently have is the original view controller being pushed up and out of the screen by the second view controller sliding up from the bottom of the screen.
Code that handles the transition using a segue from one VC to the sliding VC:
import Foundation
class CustomSegueToSecondVC: UIStoryboardSegue
{
override func perform() {
let originalVC = self.sourceViewController .view as UIView!
let slidingVC = self.destinationViewController.view as UIView!
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
slidingVC.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(slidingVC, aboveSubview: originalVC)
UIView.animateWithDuration(0.4, animations: { () -> Void in
originalVC.frame = CGRectOffset(originalVC.frame, 0.0, -screenHeight)
slidingVC.frame = CGRectOffset(slidingVC.frame, 0.0, -screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
}
}
};

UIView Animation not shown after segue

I have 2 View Controllers. The first one is a Collection View Controller, the second a plain View Controller. When clicking a cell in the Collection View Controller, the second View Controller appears. When the view starts to load in the 2nd view controller, I want to show a UILabel sliding from the top to the center of the screen
.
Problem: the animation doesn't take place. The UIlabel appears directly in the center, then disappears as coded after 3 seconds. What am I’m doing wrong?
override func viewDidLoad() {
super.viewDidLoad()
// Add a label to the view
var dynamicLabel: UILabel = UILabel()
dynamicLabel.frame = CGRectMake(0, 0, self.view.frame.width, 60)
dynamicLabel.backgroundColor = UIColor.orangeColor()
dynamicLabel.textColor = UIColor.blackColor()
dynamicLabel.textAlignment = NSTextAlignment.Center
dynamicLabel.text = "test label"
self.view.addSubview(dynamicLabel)
// animation
UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity:3.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: ({dynamicLabel.center.y = self.view.frame.height/2}), completion: { animationFinished in
// when complete, remove the UILabel from the parent view
self.delay(3.0) {
dynamicLabel.removeFromSuperview()
}
})
}
Move your Animation block into viewDidAppear()