Commit 1e63fdbd by Daniel Dahan

development: added transitionView to MotionTransition animations

parent e46c5a84
......@@ -32,6 +32,39 @@ import UIKit
/// Grid extension for UIView.
extension UIView {
/// A property that accesses the backing layer's masksToBounds.
@IBInspectable
open var masksToBounds: Bool {
get {
return layer.masksToBounds
}
set(value) {
layer.masksToBounds = value
}
}
/// A property that accesses the backing layer's opacity.
@IBInspectable
open var opacity: Float {
get {
return layer.opacity
}
set(value) {
layer.opacity = value
}
}
/// A property that accesses the backing layer's anchorPoint.
@IBInspectable
open var anchorPoint: CGPoint {
get {
return layer.anchorPoint
}
set(value) {
layer.anchorPoint = value
}
}
/// A property that accesses the frame.origin.x property.
@IBInspectable
open var x: CGFloat {
......
......@@ -52,6 +52,7 @@ public enum MotionAnimationKeyPath: String {
case zPosition
case width = "bounds.size.width"
case height = "bounds.size.height"
case size = "bounds.size"
}
public enum MotionAnimation {
......@@ -87,6 +88,7 @@ public enum MotionAnimation {
case zPosition(Int)
case width(CGFloat)
case height(CGFloat)
case size(CGSize)
}
extension CALayer {
......@@ -190,6 +192,22 @@ extension CALayer {
var tf = timingFunction
var d = duration
var px: CGFloat = s.position.x
var py: CGFloat = s.position.y
for v in animations {
switch v {
case let .x(x):
px = x + w / 2
case let .y(y):
py = y + h / 2
case let .point(x, y):
px = x + w / 2
py = y + h / 2
default:break
}
}
for v in animations {
switch v {
case let .timingFunction(timingFunction):
......@@ -239,12 +257,9 @@ extension CALayer {
a.append(Motion.translateY(to: to))
case let .translateZ(to):
a.append(Motion.translateZ(to: to))
case let .x(x):
a.append(Motion.position(to: CGPoint(x: x + w / 2, y: s.position.y)))
case let .y(y):
a.append(Motion.position(to: CGPoint(x: s.position.x, y: y + h / 2)))
case let .point(x, y):
a.append(Motion.position(to: CGPoint(x: x + w / 2, y: y + h / 2)))
case let .x(_), .y(_), .point(_, _):
let position = Motion.position(to: CGPoint(x: px, y: py))
a.append(position)
case let .position(x, y):
a.append(Motion.position(to: CGPoint(x: x, y: y)))
case let .shadow(path):
......@@ -261,6 +276,8 @@ extension CALayer {
a.append(Motion.width(w))
case let .height(h):
a.append(Motion.height(h))
case let .size(size):
a.append(Motion.size(size))
default:break
}
}
......@@ -523,14 +540,26 @@ extension Motion {
/**
Creates a CABasicAnimation for the position key path.
- Parameter to point: A CGPoint.
- Parameter x: A CGFloat.
- Parameter y: A CGFloat.
- Returns: A CABasicAnimation.
*/
public static func position(to point: CGPoint) -> CABasicAnimation {
public static func position(x: CGFloat, y: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: point)
animation.toValue = NSValue(cgPoint: CGPoint(x: x, y: y))
return animation
}
/**
Creates a CABasicAnimation for the position key path.
- Parameter to point: A CGPoint.
- Returns: A CABasicAnimation.
*/
public static func position(to point: CGPoint) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: point)
return animation
}
/**
Creates a CABasicAnimation for the shadowPath key path.
......@@ -586,4 +615,15 @@ extension Motion {
animation.toValue = NSNumber(floatLiteral: Double(height))
return animation
}
/**
Creates a CABasicaAnimation for the height key path.
- Parameter size: A CGSize.
- Returns: A CABasicAnimation.
*/
public static func size(_ size: CGSize) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .size)
animation.toValue = NSValue(cgSize: size)
return animation
}
}
......@@ -123,7 +123,7 @@ extension UIView {
open class MotionTransitionPresentationController: UIPresentationController {
open override func presentationTransitionWillBegin() {
guard let containerView = containerView else {
guard nil != containerView else {
return
}
......@@ -139,7 +139,7 @@ open class MotionTransitionPresentationController: UIPresentationController {
}
open override func dismissalTransitionWillBegin() {
guard let containerView = containerView else {
guard nil != containerView else {
return
}
......@@ -161,62 +161,10 @@ open class MotionTransitionPresentationController: UIPresentationController {
open class MotionTransitionDelegate: NSObject {
open var isPresenting = false
open var transitionContext: UIViewControllerContextTransitioning!
open var containerView: UIView!
open var toView: UIView!
open var toViews: [UIView] {
var views: [UIView] = 0 < toViewController.view.motionTransitionIdentifier.utf16.count ? [toViewController.view] : []
subviews(of: toViewController.view, views: &views)
return views
}
open var toViewController: UIViewController!
open var toViewStartFrame: CGRect!
open var toViewFinalFrame: CGRect!
open var fromView: UIView!
open var fromViews: [UIView] {
var views: [UIView] = 0 < fromViewController.view.motionTransitionIdentifier.utf16.count ? [fromViewController.view] : []
subviews(of: fromViewController.view, views: &views)
return views
}
open var fromViewController: UIViewController!
open var fromViewFinalFrame: CGRect!
}
extension MotionTransitionDelegate {
@objc(animateTransition:)
open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
}
@objc(transitionDuration:)
open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
open func animationEnded(_ transitionCompleted: Bool) {
// print("MotionTransitionAnimator", #function)
}
}
extension MotionTransitionDelegate {
fileprivate func subviews(of view: UIView, views: inout [UIView]) {
for v in view.subviews {
if 0 < v.motionTransitionIdentifier.utf16.count {
views.append(v)
}
subviews(of: v, views: &views)
}
}
}
extension MotionTransitionDelegate: UIViewControllerTransitioningDelegate {
......@@ -266,206 +214,115 @@ extension MotionTransitionDelegate: UITabBarControllerDelegate {
}
}
open class MotionTransitionInteractiveDelegate: UIPercentDrivenInteractiveTransition {
open var isPresenting = false
open class MotionTransitionAnimator: MotionTransitionDelegate {
open var transitionContext: UIViewControllerContextTransitioning!
open var containerView: UIView!
open var toView: UIView!
open var toViewController: UIViewController!
open var toViewStartFrame: CGRect!
open var toViewFinalFrame: CGRect!
open var delay: TimeInterval = 0
open var duration: TimeInterval = 0
open var fromView: UIView!
open var fromViewController: UIViewController!
open var fromViewFinalFrame: CGRect!
open var containerView: UIView!
open var transitionView = UIView()
open var panGesture: UIPanGestureRecognizer!
public var toViews: [UIView] {
var views: [UIView] = 0 < toViewController.view.motionTransitionIdentifier.utf16.count ? [toViewController.view] : []
subviews(of: toViewController.view, views: &views)
return views
}
@objc(startInteractiveTransition:)
open override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
guard let tView = transitionContext.view(forKey: .to) else {
return
}
public var fromViews: [UIView] {
var views: [UIView] = 0 < fromViewController.view.motionTransitionIdentifier.utf16.count ? [fromViewController.view] : []
subviews(of: fromViewController.view, views: &views)
return views
}
}
extension MotionTransitionAnimator: UIViewControllerAnimatedTransitioning {
@objc(animateTransition:)
open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let tVC = transitionContext.viewController(forKey: .to) else {
return
}
guard let fView = transitionContext.view(forKey: .from) else {
return
}
guard let fVC = transitionContext.viewController(forKey: .from) else {
return
}
self.transitionContext = transitionContext
containerView = transitionContext.containerView
containerView.addSubview(transitionView)
transitionView.frame = containerView.bounds
toView = tView
toViewController = tVC
fromView = fView
fromViewController = fVC
toViewStartFrame = transitionContext.initialFrame(for: toViewController)
toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController)
preparePanGesture()
}
open func animationEnded(_ transitionCompleted: Bool) {
// print("MotionTransitionAnimator", #function)
}
}
extension MotionTransitionInteractiveDelegate {
fileprivate func preparePanGesture() {
panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(recognizer:)))
panGesture.maximumNumberOfTouches = 1
containerView.addGestureRecognizer(panGesture)
@objc(transitionDuration:)
open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return delay + duration
}
}
extension MotionTransitionInteractiveDelegate {
@objc
fileprivate func handlePanGesture(recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
panGesture.setTranslation(.zero, in: containerView)
case .changed:
let translation = panGesture.translation(in: containerView)
/**
Compute how far the gesture recognizer tranveled on the
vertical axis.
*/
let percentageComplete = fabs(translation.y / containerView.bounds.height)
update(percentageComplete)
case .ended:
finish()
containerView.removeGestureRecognizer(panGesture)
default:break
extension MotionTransitionDelegate {
fileprivate func subviews(of view: UIView, views: inout [UIView]) {
for v in view.subviews {
if 0 < v.motionTransitionIdentifier.utf16.count {
views.append(v)
}
subviews(of: v, views: &views)
}
}
}
open class MotionTransitionAnimator: MotionTransitionDelegate, UIViewControllerAnimatedTransitioning {
@objc(animateTransition:)
open override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// guard let tView = transitionContext.view(forKey: .to) else {
// return
// }
guard let tVC = transitionContext.viewController(forKey: .to) else {
return
}
// guard let fView = transitionContext.view(forKey: .from) else {
// return
// }
guard let fVC = transitionContext.viewController(forKey: .from) else {
return
}
self.transitionContext = transitionContext
fileprivate func snapshotView(for view: UIView) -> UIView {
view.isHidden = false
containerView = transitionContext.containerView
// capture a snapshot without cornerRadius
let oldCornerRadius = view.cornerRadius
view.cornerRadius = 0
let v = view.snapshotView(afterScreenUpdates: false)!
view.cornerRadius = oldCornerRadius
// toView = tView
toViewController = tVC
// fromView = fView
fromViewController = fVC
let contentView = v.subviews.first!
contentView.cornerRadius = view.cornerRadius
contentView.masksToBounds = true
toViewStartFrame = transitionContext.initialFrame(for: toViewController)
toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController)
v.motionTransitionIdentifier = view.motionTransitionIdentifier
v.cornerRadius = view.cornerRadius
v.zPosition = view.zPosition
v.opacity = view.opacity
v.isOpaque = view.isOpaque
v.anchorPoint = view.anchorPoint
v.layer.masksToBounds = view.layer.masksToBounds
v.borderColor = view.borderColor
v.borderWidth = view.borderWidth
v.shadowRadius = view.shadowRadius
v.shadowOpacity = view.shadowOpacity
v.shadowColor = view.shadowColor
v.shadowOffset = view.shadowOffset
var duration = transitionDuration(using: nil)
v.layer.transform = view.layer.transform
transitionContext.containerView.addSubview(toViewController.view)
view.isHidden = true
for v in toViews {
if 0 < v.motionTransitionIdentifier.utf16.count {
for a in v.motionTransitionAnimations {
switch a {
case let .duration(dur):
if dur > duration {
duration = dur
}
default:break
}
}
v.motion(v.motionTransitionAnimations)
}
}
Motion.delay(duration) {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return v
}
}
open class MotionTransitionPresentedAnimator: MotionTransitionDelegate, UIViewControllerAnimatedTransitioning {
open class MotionTransitionPresentedAnimator: MotionTransitionAnimator {
@objc(animateTransition:)
open override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// guard let tView = transitionContext.view(forKey: .to) else {
// return
// }
guard let tVC = transitionContext.viewController(forKey: .to) else {
return
}
// guard let fView = transitionContext.view(forKey: .from) else {
// return
// }
guard let fVC = transitionContext.viewController(forKey: .from) else {
return
}
self.transitionContext = transitionContext
containerView = transitionContext.containerView
super.animateTransition(using: transitionContext)
// toView = tView
toViewController = tVC
// fromView = fView
fromViewController = fVC
toViewStartFrame = transitionContext.initialFrame(for: toViewController)
toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController)
var delay: TimeInterval = 0
var duration = transitionDuration(using: nil)
transitionContext.containerView.addSubview(toViewController.view)
for v in toViews {
for v2 in fromViews {
if v.motionTransitionIdentifier == v2.motionTransitionIdentifier {
for toView in toViews {
for fromView in fromViews {
if toView.motionTransitionIdentifier == fromView.motionTransitionIdentifier {
var t: TimeInterval = 0
var d: TimeInterval = 0
var a = [CABasicAnimation]()
var tf = MotionAnimationTimingFunction.easeInEaseOut
var w: CGFloat = 0
var h: CGFloat = 0
for ta in v.motionTransitionAnimations {
for ta in toView.motionTransitionAnimations {
switch ta {
case let .delay(time):
if time > delay {
......@@ -477,55 +334,38 @@ open class MotionTransitionPresentedAnimator: MotionTransitionDelegate, UIViewCo
duration = time
}
d = time
case let .width(width):
w = width
case let .height(height):
h = height
default:break
}
}
// var w: CGFloat = toView.bounds.width
// var h: CGFloat = toView.bounds.height
// var px: CGFloat = toView.position.x + w / 2
// var py: CGFloat = toView.position.y + h / 2
// a.append(Motion.position(x: px, y: py))
// a.append(Motion.width(w))
// a.append(Motion.height(h))
var px: CGFloat = v.position.x
var py: CGFloat = v.position.y
// let rotate = Motion.rotate(angle: toView.layer.value(forKeyPath: MotionAnimationKeyPath.rotation.rawValue) as? CGFloat ?? 0)
// rotate.fromValue = fromView.layer.value(forKeyPath: MotionAnimationKeyPath.rotation.rawValue)
// a.append(rotate)
for ta in v.motionTransitionAnimations {
switch ta {
case let .x(x):
px = x + w / 2
case let .y(y):
py = y + h / 2
case let .point(x, y):
px = x + w / 2
py = y + h / 2
default:break
}
}
a.append(Motion.background(color: toView.backgroundColor ?? .clear))
// a.append(Motion.corner(radius: toView.cornerRadius))
let snapshot = snapshotView(for: fromView)
snapshot.isHidden = false
snapshot.bounds = fromView.bounds
snapshot.position = fromView.superview?.convert(fromView.position, to: nil) ?? fromView.position
transitionView.addSubview(snapshot)
Motion.delay(t) {
for ta in v.motionTransitionAnimations {
for ta in toView.motionTransitionAnimations {
switch ta {
case let .timingFunction(timingFunction):
tf = timingFunction
case let .rotate(angle):
let rotate = Motion.rotate(angle: angle)
let radians = CGFloat(atan2f(Float(v2.transform.b), Float(v2.transform.a)))
rotate.fromValue = v2.layer.value(forKeyPath: MotionAnimationKeyPath.rotation.rawValue)
a.append(rotate)
case let .backgroundColor(color):
a.append(Motion.background(color: color))
case let .corners(radius):
a.append(Motion.corner(radius: radius))
case let .x(_), .y(_), .point(_, _):
a.append(Motion.position(to: CGPoint(x: px, y: py)))
case let .position(x, y):
a.append(Motion.position(to: CGPoint(x: x, y: y)))
case let .shadow(path):
a.append(Motion.shadow(path: path))
case let .width(w):
a.append(Motion.width(w))
case let .height(h):
a.append(Motion.height(h))
default:break
}
}
......@@ -535,53 +375,31 @@ open class MotionTransitionPresentedAnimator: MotionTransitionDelegate, UIViewCo
g.isRemovedOnCompletion = false
g.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
v.animate(g)
snapshot.animate(g)
}
}
}
}
Motion.delay(delay + duration) {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
Motion.delay(transitionDuration(using: transitionContext)) { [weak self] in
defer {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
guard let s = self else {
return
}
// s.transitionView.removeFromSuperview()
s.containerView.addSubview(s.toViewController.view)
}
}
}
open class MotionTransitionDismissedAnimator: MotionTransitionDelegate, UIViewControllerAnimatedTransitioning {
open class MotionTransitionDismissedAnimator: MotionTransitionAnimator {
@objc(animateTransition:)
open override func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// guard let tView = transitionContext.view(forKey: .to) else {
// return
// }
guard let tVC = transitionContext.viewController(forKey: .to) else {
return
}
// guard let fView = transitionContext.view(forKey: .from) else {
// return
// }
guard let fVC = transitionContext.viewController(forKey: .from) else {
return
}
self.transitionContext = transitionContext
containerView = transitionContext.containerView
// toView = tView
toViewController = tVC
// fromView = fView
fromViewController = fVC
toViewStartFrame = transitionContext.initialFrame(for: toViewController)
toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController)
var delay: TimeInterval = 0
var duration = transitionDuration(using: nil)
super.animateTransition(using: transitionContext)
for v in fromViews {
for v2 in toViews {
......@@ -606,22 +424,22 @@ open class MotionTransitionDismissedAnimator: MotionTransitionDelegate, UIViewCo
d = time
case let .timingFunction(timingFunction):
tf = timingFunction
case let .rotate(angle):
case .rotate(_):
let radians = CGFloat(atan2f(Float(v2.transform.b), Float(v2.transform.a)))
let rotate = Motion.rotate(angle: radians * 180 / CGFloat(M_PI))
rotate.fromValue = v.layer.value(forKeyPath: MotionAnimationKeyPath.rotation.rawValue)
a.append(rotate)
case let .backgroundColor(color):
case .backgroundColor(_):
a.append(Motion.background(color: .clear))
case let .corners(radius):
case .corners(_):
a.append(Motion.corner(radius: v2.cornerRadius))
case let .x(_), .y(_), .point(_, _), .position(_, _):
case .x(_), .y(_), .point(_, _), .position(_, _):
a.append(Motion.position(to: nil == v2.superview ? v2.position : v2.superview!.convert(v2.position, to: nil)))
case let .shadow(path):
a.append(Motion.shadow(path: path))
case let .width(w):
case .width(_):
a.append(Motion.width(v2.bounds.width))
case let .height(h):
case .height(_):
a.append(Motion.height(v2.bounds.height))
default:break
}
......@@ -651,3 +469,94 @@ open class MotionTransitionInteractiveAnimator: MotionTransitionInteractiveDeleg
}
}
open class MotionTransitionInteractiveDelegate: UIPercentDrivenInteractiveTransition {
open var isPresenting = false
open var transitionContext: UIViewControllerContextTransitioning!
open var containerView: UIView!
open var toView: UIView!
open var toViewController: UIViewController!
open var toViewStartFrame: CGRect!
open var toViewFinalFrame: CGRect!
open var fromView: UIView!
open var fromViewController: UIViewController!
open var fromViewFinalFrame: CGRect!
open var panGesture: UIPanGestureRecognizer!
@objc(startInteractiveTransition:)
open override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
guard let tView = transitionContext.view(forKey: .to) else {
return
}
guard let tVC = transitionContext.viewController(forKey: .to) else {
return
}
guard let fView = transitionContext.view(forKey: .from) else {
return
}
guard let fVC = transitionContext.viewController(forKey: .from) else {
return
}
self.transitionContext = transitionContext
containerView = transitionContext.containerView
toView = tView
toViewController = tVC
fromView = fView
fromViewController = fVC
toViewStartFrame = transitionContext.initialFrame(for: toViewController)
toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController)
preparePanGesture()
}
open func animationEnded(_ transitionCompleted: Bool) {
// print("MotionTransitionAnimator", #function)
}
}
extension MotionTransitionInteractiveDelegate {
fileprivate func preparePanGesture() {
panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(recognizer:)))
panGesture.maximumNumberOfTouches = 1
containerView.addGestureRecognizer(panGesture)
}
}
extension MotionTransitionInteractiveDelegate {
@objc
fileprivate func handlePanGesture(recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
panGesture.setTranslation(.zero, in: containerView)
case .changed:
let translation = panGesture.translation(in: containerView)
/**
Compute how far the gesture recognizer tranveled on the
vertical axis.
*/
let percentageComplete = fabs(translation.y / containerView.bounds.height)
update(percentageComplete)
case .ended:
finish()
containerView.removeGestureRecognizer(panGesture)
default:break
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment