To provide the interaction pop gesture in full view, i have a UIPanGestureRecognizer in my controller with which we can swipe from left to right any where in the view controller to pop the controller, instead of using the default NavigationController pop gesture.
When i use the gesture with keyboard open in the view the keyboard also dismissing(not occurring with default NavigationController pop gesture) with the interaction, which looks weird.
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .pop {
return PopControllerTransition()//My transition
}
return nil
}
How can i prevent the keyboard dismiss while pop viewController with my custom pop transition.
//transition code
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let fromController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
toController.view.transform = CGAffineTransform(translationX: -(toController.view.bounds.width/3), y: 0)
containerView.insertSubview(toController.view, belowSubview: fromController.view)
let view = GradientView(frame: containerView.frame)
view.horizontal = true
view.backgroundColor = UIColor.clear
view.transform = CGAffineTransform(translationX: -toController.view.bounds.width, y: 0)
view.gradientLayer.colors = [UIColor(white: 0.0, alpha: 0.2).cgColor, UIColor(white: 0.0, alpha: 0.5).cgColor]
containerView.insertSubview(view, belowSubview: fromController.view)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
toController.view.transform = .identity
fromController.view.transform = CGAffineTransform(translationX: fromController.view.bounds.width, y: 0)
view.transform = .identity
view.alpha = 0.0
}) { (finish) in
if !transitionContext.transitionWasCancelled {
fromController.view.removeFromSuperview()
}
view.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
So I noticed that keyboard transition is different on iOS 13 compared to iOS 12 so I had to do it manually like that:
let animations: () -> Void = {
fromView.frame.origin.x += fromView.bounds.width
toView.frame.origin.x += fromView.bounds.width
// Keyboard behavior changed on iOS 13. It dismisses ignoring transition
// so need to fix that manually.
if #available(iOS 13.0, *) {
let keyboardWindow = UIApplication.shared.windows
.first { String(describing: type(of: $0)) == "UIRemoteKeyboardWindow" }
if let keyboardWindow = keyboardWindow, let keyboardImage = keyboardWindow.getImage() {
let imageView = UIImageView(image: keyboardImage)
keyboardWindow.addSubview(imageView)
keyboardWindow.frame.origin.x += keyboardWindow.frame.width
}
}
}
You can check implementation I did - https://github.com/APUtils/Animators/blob/master/Animators/Classes/Right%20Slide%20Animation/RightSlideOutAnimator.swift
Related
In iPhone 7 simulator, I have a view called cartView which looks like so...
On pressing the next button on the cartView at the bottom, another view is presented like so...
This presented view is called presentedView.
The code written on the press of the next button on the cartView is this...
let vc = PresentedUserDetailsViewController()
vc.modalPresentationStyle = .custom
present(vc, animated: true, completion: nil)
In the presentedView, these have been declared before the viewDidLoad...
lazy var backdropView: UIView = {
let bdView = UIView(frame: self.view.bounds)
bdView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return bdView
}()
let menuHeight = UIScreen.main.bounds.height / 2
var isPresenting = false
init() {
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .custom
transitioningDelegate = self
}
Finally, at the end, an extension is also given like so...
extension PresentedUserDetailsViewController: UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
guard let toVC = toViewController else { return }
isPresenting = !isPresenting
if isPresenting == true {
containerView.addSubview(toVC.view)
menuView.frame.origin.y += menuHeight
backdropView.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
self.menuView.frame.origin.y -= self.menuHeight
self.backdropView.alpha = 1
}, completion: { (finished) in
transitionContext.completeTransition(true)
})
} else {
UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
self.menuView.frame.origin.y += self.menuHeight
self.backdropView.alpha = 0
}, completion: { (finished) in
transitionContext.completeTransition(true)
})
}
}
}
But when I run the app in a plus type iPhone model (say iPhone 6 plus or iPhone 7 plus) and press the next button on the cartView this is what I'm getting...
Here, I've just colored the presentedView to make it distinct. In this case, the presentedView not only does not fill the entire screen size but part of the cartView is also seen behind including the next button of the cartView.
How can I make the presentedView to appear properly on a plus type iPhone model like they appear in the iPhone 7 model (the 2nd screenshot from top)
EDIT 1: THE SCREENSHOT ON iPhone 6 plus AFTER MAKING CHANGES..
EDIT: 2 As seen in iPad Air 2...
you need to set constraints when you add subview, in this case.
containerView.addSubview(toVC.view)
use autolayout, to set constraints
**UPDATED: **
extension UIView{
public func attachTo(view : UIView, animated : Bool = true){
if(animated){
self.alpha = 0
}
else{
self.alpha = 1
}
view.addSubview(self)
// self.frame = view.bounds
self.translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: self.superview!.topAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: self.superview!.bottomAnchor).isActive = true
self.leadingAnchor.constraint(equalTo: self.superview!.leadingAnchor, constant: 0).isActive = true
self.trailingAnchor.constraint(equalTo: self.superview!.trailingAnchor, constant: 0).isActive = true
if(animated){
UIView.animate(withDuration: 0.3, animations: {
self.alpha = 1
})
}
}
}
now, remove
containerView.addSubview(toVC.view)
and add this :
toVC.view.attachTo(view: containerView)
I think maybe you get wrong frame in let bdView = UIView(frame: self.view.bounds). try to use autolayout
After containerView.addSubview(toVC.view) add following:
toVC.view.autoresizingMasks = [.flexibleWidth, .flexibleHeight]
I have been at this for hours and cant understand what i am doing wrong. I have rigged up custom tab bar controller transitions by conforming to UITabBarControllerDelegate as described in my previous Swift: Problems with custom UIView.transition?
I don't use the normal storyboard tab bar buttons, I switch selectedIndex programmatically. My problem is that only with this implemented:
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = ModalTransition()
animator.fromView = fromVC.view
animator.toView = toVC.view
return animator
}
the animations AND switching of index occurs only every other time. I have custom buttons to switch the index and every other time, nothing happens when I click the switch button. Here is my animation:
//
// ModalTransition.swift
// Adventures In Design
//
// Created by Skylar Thomas on 8/28/17.
//
import UIKit
class ModalTransition: NSObject, UIViewControllerAnimatedTransitioning {
weak var transitionContext: UIViewControllerContextTransitioning?
var fromView = UIView()
var toView = UIView()
var duration = 1.1
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
containerView.addSubview(toView)
containerView.sendSubview(toBack: toView)
print("ANIMATING")
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .curveLinear, animations: {
self.fromView.center.y += 900
}, completion: {
finished in
//only works every OTHER click
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
self.fromView.center.y -= 900
})
}
}
What is causing this? Is it something
I think you are not assigning your toView and FromView. Try something like this
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let fromView = fromViewController.view
let toView = toViewController.view
let container = transitionContext.containerView
container.addSubview(toView!)
// Replace your animations here
toView?.frame = transitionContext.finalFrame(for: toViewController)
toView?.alpha = 0
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseInOut, animations: {
toView?.alpha = 1
fromView?.alpha = 0
}, completion: { finished in
toView?.alpha = 1.0
fromView?.alpha = 1
fromView?.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
In my app i want to slide in and out viewcontroller. So for that i am using custom transition but the app crashes.I am not able to find out what the exact issue is.Please do help me out.
Code
class CustomTransition: NSObject, UIViewControllerAnimatedTransitioning,UIViewControllerTransitioningDelegate {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)! // the app crashes here saying nil founded
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
let animationDuration = self .transitionDuration(using: transitionContext)
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenLeft = CGAffineTransform(translationX: -container.frame.width, y: 0)
toView.transform = offScreenRight
// add the both views to our view controller
container.addSubview(toView)
container.addSubview(fromView)
UIView.animate(withDuration: animationDuration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: [] , animations: {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransform.identity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
}
How i am using
let vc = self.getViewControllerFromStoryBoard(storyBoardName: FORGOT_PASSWORD_SB, identifier: FORGOT_PASSWORD_IDENTIFIER) as! ForgotPassword
let a = CustomTransition()
vc.transitioningDelegate = a
vc.modalPresentationStyle = .custom
self.present(vc, animated: true, completion: nil)
The app crashes let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)! saying that nil founded.
Am i missing out something?
Due to API changes in Apple in iOS 13 update. I was facing same problem Add this line fixed my issue.
vc.modalPresentationStyle = .fullScreen
I have written a custom animation controller to animate my view controller transition. Code as follows :
class PopupAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var backgroundColorView: UIView!
let duration: TimeInterval = 0.35
var dismiss = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if !dismiss {
animatePresentTransition(using: transitionContext)
} else {
animateDismissTransition(using: transitionContext)
}
}
func animatePresentTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else {
return
}
let containerView = transitionContext.containerView
let finalFrameForVC = transitionContext.finalFrame(for: toVC) // What is this about?
let bounds = UIScreen.main.bounds
let snapshotView = fromVC.view.snapshotView(afterScreenUpdates: false)
snapshotView?.frame = bounds
backgroundColorView = UIView(frame: bounds)
backgroundColorView.backgroundColor = UIColor.black.withAlphaComponent(0)
// Put the window at the bottom of the screen
toVC.view.frame = finalFrameForVC.offsetBy(dx: 0, dy: bounds.size.height)
// Layer out the views
containerView.addSubview(snapshotView!)
containerView.addSubview(backgroundColorView)
containerView.addSubview(toVC.view)
// The animation happens here
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.backgroundColorView.backgroundColor = UIColor.black.withAlphaComponent(0.4)
toVC.view.frame = finalFrameForVC
}, completion: {
finished in
transitionContext.completeTransition(true)
})
}
func animateDismissTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else {
return
}
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.backgroundColorView.backgroundColor = UIColor.black.withAlphaComponent(0.0)
let bounds = UIScreen.main.bounds
fromVC.view.frame = fromVC.view.frame.offsetBy(dx: 0, dy: bounds.size.height)
}, completion: {
finished in
transitionContext.completeTransition(true)
})
}
}
I have implemented the UIViewControllerTransitioningDelegate methods in the controller that will use this transition like so :
extension PopupViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transitionAnimator.dismiss = false
return transitionAnimator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transitionAnimator.dismiss = true
return transitionAnimator
}
}
When I'm not using a spring in the animation (eg damping set to 1) it works perfectly. However if I include spring in the animation like in this example the screen flashes black when the controller is dismissed:
Not sure why this is happening. Any ideas on what I might be doing wrong where? Any pointers would be greatly appreciated!
I'm presenting a view from another using a UIViewControllerTransitioningDelegate instance as transitioning delegate and modalPresentationStyle = Custom.
I'm using a TableViewController using static table view cells with UITextfields inside. Now when tapping over a text field whose borders are close to the the keyboard's frame, part of the tableView beneath the keyboard shows up. I also removed a UITapGestureRecognizer that I added to the semi-transparent background to make sure it's not part of the problem but the issue it's still there. Any ideas? Below is the animateTransition() method
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
let containerView = transitionContext.containerView()
if let presentedViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) {
let presentedView = presentedViewController.view
let fromView = transitionContext.viewForKey(UITransitionContextFromViewControllerKey)
let centre = presentedView.center
if isPresenting {
presentedView.center = containerView.center
presentedView.frame = presentedView.bounds.rectByInsetting(dx: 30, dy: 150)
transitionContext.containerView().addSubview(presentedView)
dimmingView.frame = containerView.bounds
dimmingView.alpha = 0.0
containerView.insertSubview(dimmingView, atIndex: 0)
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({
context in
self.dimmingView.alpha = 1.0
}, completion: nil)
}
else {
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({
context in
self.dimmingView.alpha = 0.0
}, completion: {
context in
self.dimmingView.removeFromSuperview()
})
}
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10.0, options: nil,
animations: {
presentedView.center = centre
}, completion: {
_ in
transitionContext.completeTransition(true)
})
}
}