I have several swipe and pan gestures to navigate through some views. When I pan the view, a new ViewController will be instantiated and placed at the edge of the window. Along my pan, the view will come into view on top of the current view. If the pan has passed halfway, the new view will automatically finish and replaces the current view and on completion removing the old view.
var newViewController: UIViewController! {
didSet {
if let newView = NewViewController {
addChildViewController(newView)
newView.view.frame.origin.x = view.bounds.width }
view.addSubview(newView.view)
newView.didMoveToParentViewController(self)
}
}
}
var currentViewController: UIViewController! {
didSet(oldView) {
oldViewController = oldView
newViewController = nil
}
}
var oldViewController: UIViewController!
func removeViewController() {
if let oldView = oldViewController {
oldView.willMoveToParentViewController(nil)
oldView.view.removeFromSuperview()
oldView.removeFromParentViewController()
oldViewController = nil
}
}
newViewController = NewVIewController()
currentViewController = newViewController
UIView.animateWithDuration(2.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.currentViewController.view.frame.origin.x = 0 }) { _ in
self.removeViewController()
}
In the completion of the animation, the old view will be taken care of. But to make my app to be more responsive, I'd like to call removeViewController right away and still keeping a "snapshot" of the oldView so the transition will still look the same.
Should I use some other method for transitioning the views?
It's too late to follow the pan gesture once the swipe event has been fired. Instead consider using uiscreenedgegesturerecognizer
The problem is that this is not how you do an interactive view controller transition. You need to trigger an actual view controller transition (e.g. call presentViewController) and supply a UIViewControllerInteractiveTransitioning object.
Related
I have seen a lot of tutorial but did not see any interesting me. I want to make custom segue that moves only one view, not the whole page.
Something like this:
Top view is just poping without any animation. But bottom view normally slides on bottom viewcontroler
I have tried:
swift
override func perform() {
scale()
}
func scale(){
let toViewController = self.destination
let fromViewController = self.source
let containerView = fromViewController.view.superview
let originalCenter = fromViewController.view.center
toViewController.view.viewWithTag(1)?.viewWithTag(2)?.transform = CGAffineTransform(translationX:self.source.view.frame.width,y:0)
toViewController.view.center = originalCenter
containerView?.addSubview(toViewController.view)
UIView.animate(withDuration: 0.5, delay:0, options: .showHideTransitionViews ,animations:{
toViewController.view.viewWithTag(1)?.viewWithTag(2)?.transform = CGAffineTransform.identity
},completion:{success in fromViewController.present(toViewController,animated: false,completion: nil)})
}
If you do not understand, ask me and help me please.
I think you need this hierarchy
MainView
-topView
-containerView
-navigation
-firstvc
-secondvc
and set navigationBar to hidden in storyboard
I have a storyboard segue, and while showing the new view, I want an UIView to always stay on top, so the segue does not affect it. Tried animating insertSubview, but it does not have the push from bottom animation.
Here is some code I quickly whipped up showing two view controllers, the one you are going to and one from. I put the animation in a function called buttonPressed, but it should go whatever function that calls the transition. Rest of the code is pretty self explanatory.
For this to work both view controllers will need a view with same name (or different names but keep track of which is which), I use IBOutlet staticView. And then in interface builder make sure they have the same constraints or frame so that when you set on vc's static view to another it sticks to same spot.
class ViewControllerFrom: UIViewController {
#IBOutlet weak var staticView: UIView!
#IBAction func buttonPressed(_ sender: Any) {
let toVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "toVC") as! ViewControllerTo
//Add nextVC's view to ours as subview
self.view.addSubview(toVC.view)
//Set its starting height to be below current view.
toVC.view.frame = CGRect(x: 0, y: view.frame.height, width: view.frame.width, height: view.frame.height)
//This is important to make sure staticView stays in front of view animating in
view.bringSubview(toFront: staticView)
if toVC.staticView != nil {
toVC.staticView.isHidden = true
}
//I use animation duration 0.4, close to default animations by iOS.
UIView.animate(withDuration: 0.4, animations: {
toVC.view.frame.origin.y = 0
}, completion: { (success) in
if success {
//Now that view is in place, set static view from old vc to new vc and reshow it. Then do the actual presentation unanimated.
toVC.staticView.isHidden = false
toVC.staticView = self.staticView
self.present(toVC, animated: false, completion: nil)
}
})
}
}
class ViewControllerTo : UIViewController {
#IBOutlet weak var staticView: UIView!
}
Apple discusses how to have a container view controller transition between two child view controllers in this document. I would like to animate a simple push vertical slide up identical to UIModalTransitionStyleCoverVertical in UIModalTransitionStyle. However, transitionFromViewController only allows use of UIViewAnimationOptions, not transition styles. So how would one animate sliding a view up?
It's odd that to transition between child view controllers you can't call a simple push method similar in UINavigationController to animate the transition.
Load child view, set frame with origin.y under bottom screen. After change it to 0 in animation block. Example:
enum Animation {
case LeftToRight
case RightToLeft
}
func animationForLoad(fromvc: UIViewController, tovc: UIViewController, with animation: Animation) {
self.addChildViewController(tovc)
self.container.addSubview(tovc.view)
self.currentVC = tovc
var endOriginx: CGFloat = 0
if animation == Animation.LeftToRight {
tovc.view.frame.origin.x = -self.view.bounds.width
endOriginx += fromvc.view.frame.width
} else {
tovc.view.frame.origin.x = self.view.bounds.width
endOriginx -= fromvc.view.frame.width
}
self.transition(from: fromvc, to: tovc, duration: 0.35, options: UIViewAnimationOptions.beginFromCurrentState, animations: {
tovc.view.frame = fromvc.view.frame
fromvc.view.frame.origin.x = endOriginx
}, completion: { (finish) in
tovc.didMove(toParentViewController: self)
fromvc.view.removeFromSuperview()
fromvc.removeFromParentViewController()
})
}
Above code is transition between 2 child view with push and pop horizontal animation.
In my UIStoryboardSegue subclass, I use presentViewController at the end of the perform method, this cause viewDidAppear/viewWillAppear to be called twice?
How can I prevent this?
Thanks
Current code:
override func perform() {
// Assign the source and destination views to local variables
let sourceView = sourceViewController.view as UIView!
let destView = destinationViewController.view as UIView!
// Get the screen width and height
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view
destView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
// Add the destination view to the window
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(destView, aboveSubview: sourceView)
// Animate the transition
UIView.animateWithDuration(0.7, animations: { () -> Void in
// Scale down the source view
sourceView.transform = CGAffineTransformScale(sourceView.transform, 0.90, 0.90)
}) { (Finished) -> Void in
}
UIView.animateWithDuration(1.0, delay: 0.2, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.7, options: .CurveEaseOut, animations: { () -> Void in
destView.frame = CGRectOffset(destView.frame, 0.0, -screenHeight)
}) { (finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
}
}
I see now that without presentViewController the new controller is destroyed soon after the animation custom segue. I see why we need it.
The reason why viewDidAppear gets called twice is that you first insert the viewcontroller as a subview into the keyWindow. (which triggers the viewDidAppear)
and then present the viewcontroller. (which triggers the viewDidAppear also)
This brings also some side effects to your app because you will end up with a viewHierachy like:
-- KeyWindow
---- FirstVC View
---- SecondVC View
---- UITransitionView (because of the presentVC)
------- SecondVC View
If you want to achieve this swipe/scroll transition why not just use a scrollView and have childviewcontrollers in it?
The other solution for this would be to use a custom transition and simply call presentViewController with a custom transition as described here: http://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions
I am building an app and recently discovered a huge memory leak caused by traditional segues.
Therefore I learned about unwind segue. Everything works just fine if I simply use:
#IBAction func prepareForUnwindToMainFromFriends(segue: UIStoryboardSegue) {
}
Memory leak is solved and 'everything is awesome'. But this solution looks ugly on a UI point of view. So I implemented this function from this website. And changed it a little.
override func segueForUnwindingToViewController(toViewController: UIViewController, fromViewController: UIViewController, identifier: String?) -> UIStoryboardSegue {
return UIStoryboardSegue(identifier: identifier, source: fromViewController, destination: toViewController) {
let fromView = fromViewController.view
let toView = toViewController.view
if let containerView = fromView.superview {
let initialFrame = fromView.frame
var offscreenRect = initialFrame
var offscreenRectFinal = initialFrame
offscreenRect.origin.x += CGRectGetWidth(initialFrame)
offscreenRectFinal.origin.x -= CGRectGetWidth(initialFrame)
toView.frame = offscreenRect
containerView.addSubview(toView)
let duration: NSTimeInterval = 1.0
let delay: NSTimeInterval = 0.0
let options = UIViewAnimationOptions.CurveEaseInOut
let damping: CGFloat = 0.9
let velocity: CGFloat = 4.0
UIView.animateWithDuration(duration, delay: delay, usingSpringWithDamping: damping,
initialSpringVelocity: velocity, options: options, animations: {
toView.frame = initialFrame
fromView.frame = offscreenRectFinal
}, completion: { finished in
fromView.removeFromSuperview()
if let navController = toViewController.navigationController {
navController.popToViewController(toViewController, animated: false)
}
})
}
}
}
But now I get an error message:
2015-05-12 08:47:31.841 PING0.4[4343:1308313] Warning: Attempt to present <NotificationViewController: 0x177030b0> on <PING0_4.ViewController: 0x16271000> which is already presenting <NotificationViewController: 0x1a488170>
And I am blocked in my app. I can go from VC1 to VC2, then back to VC2 but then I cannot get back to VC1 again. It looks like I can only use this segue once.
Any one has any idea of what is going on?
Created Sample code for unwind segue with above transition animation code. Checkout SampleUnwind project that will help you to understand unwind segue(and how simple it is).
In project there is one navigation controller and inside it there are three view controller (Home->First->second).
In Home controller following unwind action is created, which will be called when 'Home' button of second controller is tapped(simple unwind stuff).
#IBAction func unwindToHomeViewController(segue:UIStoryboardSegue) {
}
I have created TempNavigationController subclassing UINavigationController and set that TempNavigationController to navigation controller in storyboard.
Above method you given is pesent inside it as this will be container of fromViewController as per following referance.
Reference: Apple documentation about Transitioning Between Two Child View Controllers.
You can compare this with your project and maybe you can find any duplicate(or multiple/wrong) segue in your project.