Swift UIView zPosition issue, views overlap on other views - swift

I am writing a calendar view, I added three other views to its super view, so the calendar view should be on the topper layer, it looks fine on the simulator
but later on, I found that I can't click the button, then I checked the view hierarchy, found that the other three views overlap on the calendar, it is so weird as the 3d preview and view on the simulator are different
Here is the code of UI Part
func updateUI() {
if calendarView != nil {
self.view.addSubview(calendarView!)
self.calendarView!.frame.origin = calendarViewPosition!
self.calendarView!.layer.zPosition = 5
self.calendarView!.layer.cornerRadius = self.setting.itemCardCornerRadius
self.calendarView!.layer.masksToBounds = true
self.calendarView!.setViewShadow()
UIView.animate(withDuration: 0.8, delay: 0, options: .curveEaseOut, animations: {
self.calendarView!.frame.origin.y += 50
}) { _ in
var newCalendarPageCordiateYDifference: CGFloat = 10
var newCalendarPageSizeDifference: CGFloat = 20
var zPosition: CGFloat = 4
for _ in 1 ... 3 {
let newCalendarPage = UIView()
newCalendarPage.frame = CGRect(x: self.calendarView!.frame.origin.x + newCalendarPageSizeDifference / 2, y: self.calendarView!.frame.origin.y, width: self.calendarView!.frame.width - newCalendarPageSizeDifference, height: self.calendarView!.frame.height - newCalendarPageSizeDifference)
newCalendarPage.backgroundColor = .white
newCalendarPage.layer.zPosition = zPosition
newCalendarPage.layer.cornerRadius = self.setting.itemCardCornerRadius
newCalendarPage.setViewShadow()
self.view.addSubview(newCalendarPage)
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
newCalendarPage.frame.origin.y -= newCalendarPageCordiateYDifference
})
zPosition -= 1
newCalendarPageCordiateYDifference += 10
newCalendarPageSizeDifference += 50
}
}
}
}

Remove self.view.addSubview(newCalendarPage) this from for loop and
Add subview like this
self.view.insertSubview(newCalendarPage, belowSubview: calendarView)
Another way is to disable user interaction like
newCalendarPage.isUserInteractionEnabled = false

Related

How to redraw a subviews in landscape

Good afternoon Community,
I am trying to add two subviews to a viewcontroller by means of an extension, but when rotating the device it does not respect the layout, any option on how to make it look correct?
I attach my code below and a couple of example photos, the first photo is how I would like it to look, and the rest of the ui is lost.
This is my code:
func presentCustomAlertController(on ViewController:UIViewController,toPresentCustomView customView:UIView,withBackgroundView backGroundView:UIView){
customView.translatesAutoresizingMaskIntoConstraints = false
guard let targetView = ViewController.view else {return}
backGroundView.alpha = 0
backGroundView.backgroundColor = .black
backGroundView.frame = targetView.bounds
targetView.addSubview(backGroundView)
targetView.addSubview(customView)
customView.setNeedsLayout()
customView.setNeedsDisplay()
customView.layoutIfNeeded()
customView.layoutSubviews()
targetView.setNeedsLayout()
targetView.setNeedsDisplay()
targetView.layoutIfNeeded()
targetView.layoutSubviews()
backGroundView.setNeedsDisplay()
backGroundView.setNeedsLayout()
backGroundView.layoutIfNeeded()
customView.frame = CGRect(x: targetView.center.x, y: targetView.center.y, width: targetView.frame.size.width-80, height: 300)
UIView.animate(withDuration: 0.25, animations: {
backGroundView.alpha = 0.6
},completion: { done in
UIView.animate(withDuration: 0.25, animations: {
customView.center = targetView.center
})
})
}
}

UIViewPropertyAnimator's bounce effect

Let's say I have an animator that moves a view from (0, 0) to (-120, 0):
let frameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.8)
animator.addAnimations {
switch state:
case .normal: view.frame.origin.x = 0
case .swiped: view.frame.origin.x = -120
}
}
I use it together with UIPanGestureRecognizer, so that I can resize the view continuously along with the finger movements.
The issue comes when I want to add some sort of bouncing effect at the start or at the end of the animation. NOT just the damping ratio, but the bounce effect. The easiest way to imagine this is Swipe-To-Delete feature of UITableViewCell, where you can drag "Delete" button beyond its actual width, and then it bounces back.
Effectively what I want to achieve, is the way to set fractionComplete property outside of [0, 1] segment, so when the fraction is 1.2, the offset becomes 144 instead of its 120 maximum.
And right now the maximum value for fractionComplete is exactly 1.
Below are some examples to have this issue visualized:
What I currently have:
What I want to achieve:
EDIT (19 January):
Sorry for my delayed reply. Here are some clarifications:
I don't use UIView.animate(...), and use UIViewPropertyAnimator instead for a very specific reason: it handles for me all the timings, curves and velocities.
For example, you dragged the view halfway through. This means that duration of the remaining part should be two times less than total duration. Or if you dragged though the 99% of the distance, it should complete the remaining part almost instantly.
As an addition, UIViewPropertyAnimator has such features as pause (when user starts dragging once again), or reverse (when user started dragging to the left, but after that he changed his mind and moved the finger to the right), that I also benefit from.
All this is not available for simple UIView animations, or requires TONS of effort at best. It is only capable of simple transitions, and this is not the case.
That's why I have to use some sort of animator.
And as I mentioned in the comments thread in the answer that was removed by its publisher, the most complex part for me here is to simulate the friction effect: the further you drag, the less the view actually moves. Just as when you're trying to drag any UIScrollView outside of it's content.
Thanks for your effort guys, but I don't think any of these 2 answers is relevant. I will try to implement this behaviour using UIDynamicAnimator whenever I have time. Probably in the nearest week or two. I will publish my approach in case I have any decent results.
EDIT (20 January):
I just uploaded a demo project to the GitHub, which includes all the transitions that I have in my project. So now you can actually have an idea why do I need to use animators and how I use them: https://github.com/demon9733/bouncingview-prototype
The only file you are actually interested in is MainViewController+Modes.swift. Everything related to transitions and animations is contained there.
What I need to do is to enable user to drag the handle area beyond "Hide" button width with a damping effect. "Hide" button will appear on swiping the handle area to the left.
P.S. I didn't really test this demo, so it can have bugs that I don't have in my main project. So you can safely ignore them.
you need to allow pan gesture to get to needed x position and at the end of pan an animation is needed to be triggered
one way to do this would be:
var initial = CGRect.zero
override func viewDidLayoutSubviews() {
initial = animatedView.frame
}
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initial
let open = initial.offsetBy(dx: -120, dy: 0)
// 1 manage panning along x direction
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 2 animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = closed
})
} else {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = open
})
}
}
}
Edit 20 Jan
For simulating dampening effect and make use of UIViewPropertyAnimator specifically,
var initialOrigin = CGRect.zero
override func viewDidLayoutSubviews() {
initialOrigin = animatedView.frame
}
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initialOrigin
let open = initialOrigin.offsetBy(dx: -120, dy: 0)
// 1. to simulate dampening
var multiplier: CGFloat = 1.0
if animatedView?.frame.origin.x ?? CGFloat(0) > closed.origin.x || animatedView?.frame.origin.x ?? CGFloat(0) < open.origin.x {
multiplier = 0.2
} else {
multiplier = 1
}
// 2. animate panning
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x * multiplier, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 3. animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = closed.origin.x
})
animate.startAnimation()
} else {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = open.origin.x
})
animate.startAnimation()
}
}
}
Here is possible approach (simplified & a bit scratchy - only bounce, w/o button at right, because it would much more code and actually only a matter of frames management)
Due to long delay of UIPanGestureRecognizer at ending, I prefer to use UILongPressGestureRecognizer, as it gives faster feedback.
Here is demo result
The Storyboard of used below ViewController has only gray-background-rect-container view, everything else is done in code provided below.
class ViewController: UIViewController {
#IBOutlet weak var container: UIView!
let imageView = UIImageView()
var initial: CGFloat = .zero
var dropped = false
private func excedesLimit() -> Bool {
// < set here desired bounce limits
return imageView.frame.minX < -180 || imageView.frame.minX > 80
}
#IBAction func pressHandler(_ sender: UILongPressGestureRecognizer) {
let location = sender.location(in: imageView.superview).x
if sender.state == .began {
dropped = false
initial = location - imageView.center.x
}
else if !dropped {
if (sender.state == .changed) {
imageView.center = CGPoint(x: location - initial, y: imageView.center.y)
dropped = excedesLimit()
}
if sender.state == .ended || dropped {
initial = .zero
// variant with animator
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut) {
let stickTo: CGFloat = self.imageView.frame.minX < -100 ? -100 : 0 // place for button at right
self.imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: self.imageView.frame.origin.y), size: self.imageView.frame.size)
}
animator.isInterruptible = true
animator.startAnimation()
// uncomment below - variant with UIViewAnimation
// UIView.beginAnimations("bounce", context: nil)
// UIView.setAnimationDuration(0.2)
// UIView.setAnimationTransition(.none, for: imageView, cache: true)
// UIView.setAnimationBeginsFromCurrentState(true)
//
// let stickTo: CGFloat = imageView.frame.minX < -100 ? -100 : 0 // place for button at right
// imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: imageView.frame.origin.y), size: imageView.frame.size)
// UIView.setAnimationDelegate(self)
// UIView.setAnimationDidStop(#selector(makeBounce))
// UIView.commitAnimations()
}
}
}
// #objc func makeBounce() {
// let bounceAnimation = CABasicAnimation(keyPath: "position.x")
// bounceAnimation.duration = 0.1
// bounceAnimation.repeatCount = 0
// bounceAnimation.autoreverses = true
// bounceAnimation.fillMode = kCAFillModeBackwards
// bounceAnimation.isRemovedOnCompletion = true
// bounceAnimation.isAdditive = false
// bounceAnimation.timingFunction = CAMediaTimingFunction(name: "easeOut")
// imageView.layer.add(bounceAnimation, forKey:"bounceAnimation");
// }
override func viewDidLoad() {
super.viewDidLoad()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: "cat")
imageView.contentMode = .scaleAspectFill
imageView.layer.borderColor = UIColor.red.cgColor
imageView.layer.borderWidth = 1.0
imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = true
container.addSubview(imageView)
imageView.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 1).isActive = true
imageView.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 1).isActive = true
let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(pressHandler(_:)))
pressGesture.minimumPressDuration = 0
pressGesture.allowableMovement = .infinity
imageView.addGestureRecognizer(pressGesture)
}
}

How can i set the same IBAction to multiple buttons?

I'm trying to link multiple buttons with the same IBAction, to run similar but different code. The code is to set an image that was clicked on another view controller into the UIImageView under the button.
All the buttons link to the same view controller but with a different segue.
I tried to write a if statements but I didn't seem to have it right. I have named each corresponding UIImage view: technologyImageViewTwo, technologyImageViewThree ...etc
below is the code I used for the first button which works with the corresponding UIImageView named technologyImageView
#IBAction func setTechnology(segue:UIStoryboardSegue) {
dismiss(animated: true) {
if let technology = segue.identifier{
self.persona.technology = technology
self.technologyView.technologyImageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
self.technologyView.technologyImageView.transform = scaleUp
self.technologyView.technologyImageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
self.technologyView.technologyImageView.transform = .identity
self.technologyView.technologyImageView.alpha = 1
}, completion: nil)
}
I expect that each button should go to the segued view controller and the image selected will show up under the corresponding button. E.g if I click on the button 'Technology 2' and choose an image, the image shows up in the UIImageview named technologyImageViewTwo.
There are a couple of options, that you can choose from:
Option 1:
This option would be more preferred, that is to use the tag property on the component, this will allow you to identify the index of the button when it has been actioned.
https://developer.apple.com/documentation/uikit/uiview/1622493-tag
#IBAction func action(_ sender: Any) {
dismiss(animated: true) {
var imageView: UIImageView!
let index = (sender as? UIView)?.tag ?? 0
switch index {
case 1:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageViewTwo
case 2:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageViewThree
default:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageView
}
if let technology = persona.technology {
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
})
}
}
Option 2:
If you want to continue with the code you have then you could use a segue identifier that contains components that you can be split out and enable you to identify them more efficiently:
segueIdentifier-1, segueIdentifier-2, segueIdentifier-3
func setTechnology(segue: UIStoryboardSegue) {
dismiss(animated: true) {
var imageView: UIImageView!
let identifierComponents = segue.identifier?.components(separatedBy: "-")
let index = Int(identifierComponents?.last ?? "0")
switch index {
case 1:
imageView = technologyView.technologyImageViewTwo
case 2:
imageView = technologyView.technologyImageViewThree
default:
imageView = technologyView.technologyImageView
}
if let technology = identifierComponents?.first {
self.persona.technology = technology
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
})
}
}
You could use three separate IBActions that all call the same function with the UIImageView number as a parameter.
func setTechnology(segue:UIStoryboardSegue, imageViewNumber: Int) {
dismiss(animated: true) {
var imageView: UIImageView!
if imageViewNumber == 0 {
imageView = self.technologyView.technologyImageView
} else if imageView == 1 {
imageView = self.technologyView.technologyImageViewTwo
} else {
imageView = self.technologyView.technologyImageViewThree
}
if let technology = segue.identifier{
self.persona.technology = technology
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
}, completion: nil)
}
And then call this function from inside the three separate IBActions.
#IBAction func tappedButtonZero(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 0)
}
#IBAction func tappedButtonOne(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 1)
}
#IBAction func tappedButtonTwo(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 2)
}
You could drag multiple buttons to your #IBAction and assign a unique tag value to each button. Then, use a switch statement to do whatever's unique to the button you pressed.
#IBAction func tappedButton(_ sender: UIButton) {
switch sender.tag {
case 1:
print("one")
case 2:
print("two")
case 3:
print("three")
default:
break
}
}

Custom transition in Swift 3 does not translate correctly

I've implemented a Navigation controller to incorporate an rotating-disc type of layout (imagine each VC laid out in a circle, that rotates as a whole, into view sequentially. The navigation controller is configured to use a custom transition class, as below :-
import UIKit
class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting :Bool
let duration :TimeInterval = 0.5
let animationDuration: TimeInterval = 0.7
let delay: TimeInterval = 0
let damping: CGFloat = 1.4
let spring: CGFloat = 6.0
init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//Get references to the view hierarchy
let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController)
let containerView: UIView = transitionContext.containerView
if self.isPresenting { // Push
//1. Settings for the fromVC ............................
// fromViewController.view.frame = sourceRect
fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view)
toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
toViewController.view.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
(animated: Bool) -> () in transitionContext.completeTransition(true)
})
} else { // Pop
//1. Settings for the fromVC ............................
fromViewController.view.frame = sourceRect
fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
// toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
toViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toViewController.view.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toViewController.view.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
containerView.insertSubview(toViewController.view, belowSubview:fromViewController.view)
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
toViewController.view.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
//When the animation is completed call completeTransition
(animated: Bool) -> () in transitionContext.completeTransition(true)
})
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration;
}
}
A representation of how the views move is show in the illustration below... The two red areas are the problems, as explained later.
The presenting (push) translation works fine - 2 moves to 1 and 3 moves to 2. However, the dismissing (pop) translation does not, whereby the dismissing VC moves out of view seemingly correctly (2 moving to 3), but the presenting (previous) VC arrives either in the wrong place, or with an incorrectly sized frame...
With the class as-is, the translation results in 2 moving to 3 (correctly) but 1 then moves to 4, the view is correctly sized, yet seems offset, by a seemingly arbitrary distance, from the intended location. I have since tried a variety of different solutions.
In the pop section I tried adding the following line (commented in the code) :-
toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
...but the VC now ends up being shrunk (1 moves to 5). I hope someone can see the likely stupid error I'm making. I tried simply duplicating the push section to the pop section (and reversing everything), but it just doesn't work!
FYI... Those needing to know how to hookup the transition to a UINavigationController - Add the UINavigationControllerDelegate to your nav controller, along with the following function...
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transition: SwingTransition = SwingTransition.init(isPresenting: ( operation == .push ? true : false ))
return transition;
}
The diagram below shows how all views would share the same originating point (for the translation). The objective is to give the illusion of a revolver barrel moving each VC into view. The top centre view represents the viewing window, showing the third view in the stack. Apologies for the poor visuals...
The problem is that one of the properties in the restored view controller's view isn't getting reset properly. I'd suggest resetting it when the animation is done (you probably don't want to keep the non-standard transform and anchorPoint in case you do other animations later that presume the view is not transformed). So, in the completion block of the animation, reset the position, anchorPoint and transform of the views.
class RotaryTransition: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting: Bool
let duration: TimeInterval = 0.5
let delay: TimeInterval = 0
let damping: CGFloat = 1.4
let spring: CGFloat = 6
init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let from = transitionContext.viewController(forKey: .from)!
let to = transitionContext.viewController(forKey: .to)!
let frame = transitionContext.initialFrame(for: from)
let height = frame.size.height
let width = frame.size.width
let angle: CGFloat = 15.0 * .pi / 180.0
let rotationCenterOffset: CGFloat = width / 2 / tan(angle / 2) / height + 1 // use fixed value, e.g. 3, if you want, or use this to ensure that the corners of the two views just touch, but don't overlap
let rightTransform = CATransform3DMakeRotation(angle, 0, 0, 1)
let leftTransform = CATransform3DMakeRotation(-angle, 0, 0, 1)
transitionContext.containerView.insertSubview(to.view, aboveSubview: from.view)
// save the anchor and position
let anchorPoint = from.view.layer.anchorPoint
let position = from.view.layer.position
// prepare `to` layer for rotation
to.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset)
to.view.layer.position = CGPoint(x: width / 2, y: height * rotationCenterOffset)
to.view.layer.transform = self.isPresenting ? rightTransform : leftTransform
//to.view.layer.opacity = 0
// prepare `from` layer for rotation
from.view.layer.anchorPoint = CGPoint(x: 0.5, y: rotationCenterOffset)
from.view.layer.position = CGPoint(x: width / 2, y: height * rotationCenterOffset)
// rotate
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, animations: {
from.view.layer.transform = self.isPresenting ? leftTransform : rightTransform
to.view.layer.transform = CATransform3DIdentity
//to.view.layer.opacity = 1
//from.view.layer.opacity = 0
}, completion: { finished in
// restore the layers to their default configuration
for view in [to.view, from.view] {
view?.layer.transform = CATransform3DIdentity
view?.layer.anchorPoint = anchorPoint
view?.layer.position = position
//view?.layer.opacity = 1
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
}
I did a few other sundry changes, while I was here:
eliminated the semicolons;
eliminated one of the duration properties;
fixed the name of the parameter of the completion closure of animate method to finished rather than animated to more accurately reflect what it's real purpose is ... you could use _, too;
set the completeTransition based upon whether the animation was canceled or not (because if you ever make this interactive/cancelable, you don't want to always use true);
use .pi rather than M_PI;
I commented out my adjustments of opacity, but I generally do that to give the effect a touch more polish and to ensure that if you tweak angles so the views overlap, you don't get any weird artifacts of the other view just as the animation starts or just as its finishing; I've actually calculated the parameters so there's no overlapping, regardless of screen dimensions, so that wasn't necessary and I commented out the opacity lines, but you might consider using them, depending upon the desired effect.
Previously I showed how to simplify the process a bit, but the resulting effect wasn't exactly what you were looking for, but see the previous rendition of this answer if you're interested.
Your problem is a common one that happens when you do custom view controller transitions. I know this because I've done it a lot :)
You're looking for a problem in the pop transition, but the actual problem is in the push. If you inspect the view of the first controller in the stack after the transition, you'll see that it has an unusual frame, because you've messed about with its transform and anchor point and layer position and so forth. Really, you need to clean all that up before you end the transition, otherwise it bites you later on, as you're seeing in the pop.
A much simpler and safer way to do your custom transitions is to add a "canvas" view, then to that canvas add snapshots of your outgoing and incoming views instead and manipulate those. This means you have no cleanup at the end of the transition - just remove the canvas view. I've written about this technique here. For your case, I added the following convenience method:
extension UIView {
func snapshot(view: UIView, afterUpdates: Bool) -> UIView? {
guard let snapshot = view.snapshotView(afterScreenUpdates: afterUpdates) else { return nil }
self.addSubview(snapshot)
snapshot.frame = convert(view.bounds, from: view)
return snapshot
}
}
Then updated your transition code to move the snapshots around on a canvas view instead:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//Get references to the view hierarchy
let fromViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let sourceRect: CGRect = transitionContext.initialFrame(for: fromViewController)
let containerView: UIView = transitionContext.containerView
// The canvas is used for all animation and discarded at the end
let canvas = UIView(frame: containerView.bounds)
containerView.addSubview(canvas)
let fromView = transitionContext.view(forKey: .from)!
let toView = transitionContext.view(forKey: .to)!
toView.frame = transitionContext.finalFrame(for: toViewController)
toView.layoutIfNeeded()
let toSnap = canvas.snapshot(view: toView, afterUpdates: true)!
if self.isPresenting { // Push
//1. Settings for the fromVC ............................
// fromViewController.view.frame = sourceRect
let fromSnap = canvas.snapshot(view: fromView, afterUpdates: false)!
fromView.removeFromSuperview()
fromSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromSnap.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toSnap.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
toSnap.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
(animated: Bool) -> () in
containerView.insertSubview(toViewController.view, belowSubview:canvas)
canvas.removeFromSuperview()
transitionContext.completeTransition(true)
})
} else { // Pop
//1. Settings for the fromVC ............................
fromViewController.view.frame = sourceRect
fromViewController.view.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
fromViewController.view.layer.position = CGPoint(x: fromViewController.view.frame.size.width/2, y: fromViewController.view.frame.size.height * 3);
//2. Setup toVC view...........................
let toSnap = canvas.snapshot(view: toView, afterUpdates: true)!
toSnap.layer.anchorPoint = CGPoint(x: 0.5, y: 3);
toSnap.layer.position = CGPoint(x: toViewController.view.frame.size.width/2, y: toViewController.view.frame.size.height * 3);
toSnap.transform = CGAffineTransform(rotationAngle: -15 * CGFloat(M_PI / 180));
//3. Perform the animation...............................
UIView.animate(withDuration: animationDuration, delay:delay, usingSpringWithDamping: damping, initialSpringVelocity: spring, options: [], animations: {
fromViewController.view.transform = CGAffineTransform(rotationAngle: 15 * CGFloat(M_PI / 180));
toSnap.transform = CGAffineTransform(rotationAngle: 0);
}, completion: {
//When the animation is completed call completeTransition
(animated: Bool) -> () in
containerView.insertSubview(toViewController.view, belowSubview: canvas)
canvas.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration;
}
}
This particular transition is pretty simple so it's not too difficult to reset the properties of the view frames, but if you do anything more complex then the canvas and snapshot approach works really well, so I tend to just use it everywhere.

Word by Word UILabel animation from left to right in swift

I have a uilabel with a text suppose " This is a label." I want this label to be displayed one word at a time flying from outside screen to the UILable position..
something like
label.
a label.
is a label.
This is a label.
How can i get such animation
I have found a way to do as i wished.
class ViewController: UIViewController {
#IBOutlet var sampleLabel: UILabel!
var slogan = "This is a slogan."
var xdir = 250
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let sampleLabelFrame = sampleLabel.frame
let ypos = sampleLabelFrame.origin.y
var sloganArray = slogan.componentsSeparatedByString(" ")
sloganArray = sloganArray.reverse()
var i = 0.0
for word in sloganArray{
let label: UILabel = UILabel(frame: CGRect(x: -100, y:ypos, width: 60, height: 20))
label.text = word
view.addSubview(label)
let width = label.intrinsicContentSize().width
var labelFramewidth = label.frame
labelFramewidth.size.width = width
label.frame = labelFramewidth
self.xdir = self.xdir - Int(width)-4
UIView.animateWithDuration(0.7, delay: i, options: .CurveEaseOut, animations: {
var labelframe = label.frame
labelframe.origin.x = CGFloat(self.xdir)
label.frame = labelframe
}, completion: { finished in
})
i+=0.5
}
}
}
hope this helps others in need of something like this.
This might help you on the way to achieve what you want:
http://helpmecodeswift.com/animation/creating-animated-labels
EDIT:
Flying label code:
meme1.text = "Brace yourself!"
meme1.font = UIFont.systemFontOfSize(25)
meme1.textColor = UIColor.whiteColor() // This is all just styling the Label
meme1.sizeToFit()
meme1.center = CGPoint(x: 200, y: -50) // Starting position of the label
view.addSubview(meme1) // Important! Adding the Label to the Subview, so we can actually see it.
//Animation options. Play around with it.
UIView.animateWithDuration(0.9, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:50 ) // Ending position of the Label
}, completion: nil)
UIView.animateWithDuration(0.6, delay: 1.1, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:150+90 )
}, completion: nil)
I have no experience with it myself, but it seems to get your job done. You can eventually se the starting point outside the screen and let it animate over to the screen. That should give the desired effect.