Commit 6df970aa by Daniel Dahan

reworked TransitionPreprocessor

parent 5c4a028f
......@@ -29,43 +29,34 @@
import UIKit
public enum CascadeDirection {
case topToBottom
case bottomToTop
case leftToRight
case rightToLeft
case radial(center:CGPoint)
case inverseRadial(center:CGPoint)
var comparator: (UIView, UIView) -> Bool {
switch self {
case .topToBottom:
return { return $0.frame.minY < $1.frame.minY }
case .bottomToTop:
return { return $0.frame.maxY == $1.frame.maxY ? $0.frame.maxX > $1.frame.maxX : $0.frame.maxY > $1.frame.maxY }
case .leftToRight:
return { return $0.frame.minX < $1.frame.minX }
case .rightToLeft:
return { return $0.frame.maxX > $1.frame.maxX }
case .radial(let center):
return { return $0.center.distance(center) < $1.center.distance(center) }
case .inverseRadial(let center):
return { return $0.center.distance(center) > $1.center.distance(center) }
}
}
init?(_ string: String) {
switch string {
case "bottomToTop":
self = .bottomToTop
case "leftToRight":
self = .leftToRight
case "rightToLeft":
self = .rightToLeft
case "topToBottom":
self = .topToBottom
default:
return nil
case topToBottom
case bottomToTop
case leftToRight
case rightToLeft
case radial(center:CGPoint)
case inverseRadial(center:CGPoint)
var comparator: (UIView, UIView) -> Bool {
switch self {
case .topToBottom:
return { return $0.frame.minY < $1.frame.minY }
case .bottomToTop:
return { return $0.frame.maxY == $1.frame.maxY ? $0.frame.maxX > $1.frame.maxX : $0.frame.maxY > $1.frame.maxY }
case .leftToRight:
return { return $0.frame.minX < $1.frame.minX }
case .rightToLeft:
return { return $0.frame.maxX > $1.frame.maxX }
case .radial(let center):
return { return $0.center.distance(center) < $1.center.distance(center) }
case .inverseRadial(let center):
return { return $0.center.distance(center) > $1.center.distance(center) }
}
}
}
}
class CascadePreprocessor: MotionPreprocessor {
......
......@@ -36,6 +36,7 @@ public enum MotionTransitionType {
case down
}
case none
case auto
case push(direction: Direction)
case pull(direction: Direction)
......@@ -49,261 +50,330 @@ public enum MotionTransitionType {
case zoom
case zoomOut
indirect case selectBy(presenting: MotionTransitionType, dismissing: MotionTransitionType)
indirect case selectBy(presenting: MotionTransitionType, dismissing: MotionTransitionType)
public static func autoReverse(presenting: MotionTransitionType) -> MotionTransitionType {
return .selectBy(presenting: presenting, dismissing: presenting.reversed())
}
case none
func reversed() -> MotionTransitionType {
switch self {
case .push(direction: .up):
return .pull(direction: .down)
case .push(direction: .right):
return .pull(direction: .left)
case .push(direction: .down):
return .pull(direction: .up)
case .push(direction: .left):
return .pull(direction: .right)
case .pull(direction: .up):
return .push(direction: .down)
case .pull(direction: .right):
return .push(direction: .left)
case .pull(direction: .down):
return .push(direction: .up)
case .pull(direction: .left):
return .push(direction: .right)
case .cover(direction: .up):
return .uncover(direction: .down)
case .cover(direction: .right):
return .uncover(direction: .left)
case .cover(direction: .down):
return .uncover(direction: .up)
case .cover(direction: .left):
return .uncover(direction: .right)
case .uncover(direction: .up):
return .cover(direction: .down)
case .uncover(direction: .right):
return .cover(direction: .left)
case .uncover(direction: .down):
return .cover(direction: .up)
case .uncover(direction: .left):
return .cover(direction: .right)
case .slide(direction: .up):
return .slide(direction: .down)
case .slide(direction: .down):
return .slide(direction: .up)
case .slide(direction: .left):
return .slide(direction: .right)
case .slide(direction: .right):
return .slide(direction: .left)
case .zoomSlide(direction: .up):
return .zoomSlide(direction: .down)
case .zoomSlide(direction: .down):
return .zoomSlide(direction: .up)
case .zoomSlide(direction: .left):
return .zoomSlide(direction: .right)
case .zoomSlide(direction: .right):
return .zoomSlide(direction: .left)
case .pageIn(direction: .up):
return .pageOut(direction: .down)
case .pageIn(direction: .right):
return .pageOut(direction: .left)
case .pageIn(direction: .down):
return .pageOut(direction: .up)
case .pageIn(direction: .left):
return .pageOut(direction: .right)
case .pageOut(direction: .up):
return .pageIn(direction: .down)
case .pageOut(direction: .right):
return .pageIn(direction: .left)
case .pageOut(direction: .down):
return .pageIn(direction: .up)
case .pageOut(direction: .left):
return .pageIn(direction: .right)
case .zoom:
return .zoomOut
case .zoomOut:
return .zoom
default:
return self
/**
Sets the presenting and dismissing transitions.
- Parameter presenting: A MotionTransitionType.
- Returns: A MotionTransitionType.
*/
public static func autoReverse(presenting: MotionTransitionType) -> MotionTransitionType {
return .selectBy(presenting: presenting, dismissing: presenting.reversed())
}
}
/// Returns a reversal transition.
func reversed() -> MotionTransitionType {
switch self {
case .push(direction: .up):
return .pull(direction: .down)
case .push(direction: .right):
return .pull(direction: .left)
case .push(direction: .down):
return .pull(direction: .up)
case .push(direction: .left):
return .pull(direction: .right)
case .pull(direction: .up):
return .push(direction: .down)
case .pull(direction: .right):
return .push(direction: .left)
case .pull(direction: .down):
return .push(direction: .up)
case .pull(direction: .left):
return .push(direction: .right)
case .cover(direction: .up):
return .uncover(direction: .down)
case .cover(direction: .right):
return .uncover(direction: .left)
case .cover(direction: .down):
return .uncover(direction: .up)
case .cover(direction: .left):
return .uncover(direction: .right)
case .uncover(direction: .up):
return .cover(direction: .down)
case .uncover(direction: .right):
return .cover(direction: .left)
case .uncover(direction: .down):
return .cover(direction: .up)
case .uncover(direction: .left):
return .cover(direction: .right)
case .slide(direction: .up):
return .slide(direction: .down)
case .slide(direction: .down):
return .slide(direction: .up)
case .slide(direction: .left):
return .slide(direction: .right)
case .slide(direction: .right):
return .slide(direction: .left)
case .zoomSlide(direction: .up):
return .zoomSlide(direction: .down)
case .zoomSlide(direction: .down):
return .zoomSlide(direction: .up)
case .zoomSlide(direction: .left):
return .zoomSlide(direction: .right)
case .zoomSlide(direction: .right):
return .zoomSlide(direction: .left)
case .pageIn(direction: .up):
return .pageOut(direction: .down)
case .pageIn(direction: .right):
return .pageOut(direction: .left)
case .pageIn(direction: .down):
return .pageOut(direction: .up)
case .pageIn(direction: .left):
return .pageOut(direction: .right)
case .pageOut(direction: .up):
return .pageIn(direction: .down)
case .pageOut(direction: .right):
return .pageIn(direction: .left)
case .pageOut(direction: .down):
return .pageIn(direction: .up)
case .pageOut(direction: .left):
return .pageIn(direction: .right)
case .zoom:
return .zoomOut
case .zoomOut:
return .zoom
public var label: String? {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
let valuesMirror = Mirror(reflecting: associated.value)
if !valuesMirror.children.isEmpty {
let parameters = valuesMirror.children.map { ".\($0.value)" }.joined(separator: ",")
return ".\(associated.label ?? "")(\(parameters))"
}
return ".\(associated.label ?? "")(.\(associated.value))"
default:
return self
}
}
return ".\(self)"
}
}
class TransitionPreprocessor: MotionPreprocessor {
/// A reference to a MotionContext.
/// A reference to a MotionContext instance.
weak var context: MotionContext!
weak var motion: Motion?
init(motion: Motion) {
self.motion = motion
}
/// A reference to a Motion instance.
weak var motion: Motion?
func shift(direction: MotionTransitionType.Direction, isAppearing: Bool, size: CGSize? = nil, transpose: Bool = false) -> CGPoint {
let size = size ?? context.container.bounds.size
let rtn: CGPoint
switch direction {
case .left, .right:
rtn = CGPoint(x: (direction == .right) == isAppearing ? -size.width : size.width, y: 0)
case .up, .down:
rtn = CGPoint(x: 0, y: (direction == .down) == isAppearing ? -size.height : size.height)
/**
An initializer that accepts a given Motion instance.
- Parameter motion: A Motion instance.
*/
init(motion: Motion) {
self.motion = motion
}
if transpose {
return CGPoint(x: rtn.y, y: rtn.x)
/**
Shifts the transition by a given size.
- Parameter direction: A MotionTransitionType.Direction.
- Parameter isAppearing: A boolean indicating whether it is appearing
or not.
- Parameter size: An optional CGSize.
- Parameter transpose: A boolean indicating to change the `x` point for `y`
and `y` point for `x`.
- Returns: A CGPoint.
*/
func shift(direction: MotionTransitionType.Direction, isAppearing: Bool, size: CGSize? = nil, transpose: Bool = false) -> CGPoint {
let size = size ?? context.container.bounds.size
let point: CGPoint
switch direction {
case .left, .right:
point = CGPoint(x: (.right == direction) == isAppearing ? -size.width : size.width, y: 0)
case .up, .down:
point = CGPoint(x: 0, y: (.down == direction) == isAppearing ? -size.height : size.height)
}
if transpose {
return CGPoint(x: point.y, y: point.x)
}
return point
}
return rtn
}
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func process(fromViews: [UIView], toViews: [UIView]) {
guard let motion = motion else { return }
var defaultAnimation = motion.defaultAnimation
let inNavigationController = motion.isNavigationController
let inTabBarController = motion.isTabBarController
let toViewController = motion.toViewController
let fromViewController = motion.fromViewController
let presenting = motion.isPresenting
let fromOverFullScreen = motion.fromOverFullScreen
let toOverFullScreen = motion.toOverFullScreen
let toView = motion.toView
let fromView = motion.fromView
let animators = motion.animators
func process(fromViews: [UIView], toViews: [UIView]) {
guard let m = motion else {
return
}
var defaultAnimation = m.defaultAnimation
let isNavigationController = m.isNavigationController
let isTabBarController = m.isTabBarController
let toViewController = m.toViewController
let fromViewController = m.fromViewController
let isPresenting = m.isPresenting
let fromOverFullScreen = m.fromOverFullScreen
let toOverFullScreen = m.toOverFullScreen
let toView = m.toView
let fromView = m.fromView
let animators = m.animators
if case .auto = defaultAnimation {
if inNavigationController, let navAnim = toViewController?.navigationController?.motionNavigationTransitionType {
defaultAnimation = navAnim
} else if inTabBarController, let tabAnim = toViewController?.tabBarController?.motionTabBarTransitionType {
defaultAnimation = tabAnim
} else if let modalAnim = (presenting ? toViewController : fromViewController)?.motionModalTransitionType {
defaultAnimation = modalAnim
}
}
if case .auto = defaultAnimation {
if isNavigationController, let navAnim = toViewController?.navigationController?.motionNavigationTransitionType {
defaultAnimation = navAnim
} else if isTabBarController, let tabAnim = toViewController?.tabBarController?.motionTabBarTransitionType {
defaultAnimation = tabAnim
} else if let modalAnim = (isPresenting ? toViewController : fromViewController)?.motionModalTransitionType {
defaultAnimation = modalAnim
}
}
if case .selectBy(let presentAnim, let dismissAnim) = defaultAnimation {
defaultAnimation = presenting ? presentAnim : dismissAnim
}
if case .selectBy(let presentAnim, let dismissAnim) = defaultAnimation {
defaultAnimation = isPresenting ? presentAnim : dismissAnim
}
if case .auto = defaultAnimation {
if animators!.contains(where: { $0.canAnimate(view: toView, isAppearing: true) || $0.canAnimate(view: fromView, isAppearing: false) }) {
defaultAnimation = .none
} else if inNavigationController {
defaultAnimation = presenting ? .push(direction:.left) : .pull(direction:.right)
} else if inTabBarController {
defaultAnimation = presenting ? .slide(direction:.left) : .slide(direction:.right)
} else {
defaultAnimation = .fade
}
}
if case .auto = defaultAnimation {
if animators!.contains(where: { $0.canAnimate(view: toView, isAppearing: true) || $0.canAnimate(view: fromView, isAppearing: false) }) {
defaultAnimation = .none
} else if isNavigationController {
defaultAnimation = isPresenting ? .push(direction:.left) : .pull(direction:.right)
} else if isTabBarController {
defaultAnimation = isPresenting ? .slide(direction:.left) : .slide(direction:.right)
} else {
defaultAnimation = .fade
}
}
if case .none = defaultAnimation {
return
}
if case .none = defaultAnimation {
return
}
context[fromView] = [.timingFunction(.standard), .duration(0.35)]
context[toView] = [.timingFunction(.standard), .duration(0.35)]
context[fromView] = [.timingFunction(.standard), .duration(0.35)]
context[toView] = [.timingFunction(.standard), .duration(0.35)]
let shadowState: [MotionTransition] = [.shadow(opacity: 0.5),
.shadow(color: .black),
.shadow(radius: 5),
.shadow(offset: .zero),
.masksToBounds(false)]
switch defaultAnimation {
case .push(let direction):
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false) / 3),
.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .pull(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true) / 3),
.overlay(color: .black, opacity: 0.1)])
case .slide(let direction):
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false))])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true))])
case .zoomSlide(let direction):
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)), .scale(to: 0.8)])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)), .scale(to: 0.8)])
case .cover(let direction):
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .uncover(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1)])
case .pageIn(let direction):
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.scale(to: 0.7),
.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .pageOut(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.scale(to: 0.7),
.overlay(color: .black, opacity: 0.1)])
case .fade:
// TODO: clean up this. overFullScreen logic shouldn't be here
if !(fromOverFullScreen && !presenting) {
context[toView] = [.fade]
}
let shadowState: [MotionTransition] = [.shadow(opacity: 0.5),
.shadow(color: .black),
.shadow(radius: 5),
.shadow(offset: .zero),
.masksToBounds(false)]
switch defaultAnimation {
case .push(let direction):
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false) / 3),
.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .pull(let direction):
m.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true) / 3),
.overlay(color: .black, opacity: 0.1)])
case .slide(let direction):
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false))])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true))])
case .zoomSlide(let direction):
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)), .scale(to: 0.8)])
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)), .scale(to: 0.8)])
case .cover(let direction):
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .uncover(let direction):
m.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1)])
case .pageIn(let direction):
context[toView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: true)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.scale(to: 0.7),
.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .pageOut(let direction):
m.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(to: shift(direction: direction, isAppearing: false)),
.shadow(opacity: 0),
.beginWith(transitions: shadowState)])
context[toView]!.append(contentsOf: [.scale(to: 0.7),
.overlay(color: .black, opacity: 0.1)])
case .fade:
// TODO: clean up this. overFullScreen logic shouldn't be here
if !(fromOverFullScreen && !isPresenting) {
context[toView] = [.fade]
}
#if os(tvOS)
context[fromView] = [.fade]
#else
if (!presenting && toOverFullScreen) || !fromView.isOpaque || (fromView.backgroundColor?.alphaComponent ?? 1) < 1 {
context[fromView] = [.fade]
}
#endif
#if os(tvOS)
context[fromView] = [.fade]
#else
if (!isPresenting && toOverFullScreen) || !fromView.isOpaque || (fromView.backgroundColor?.alphaComponent ?? 1) < 1 {
context[fromView] = [.fade]
}
#endif
context[toView]!.append(.preferredDurationMatchesLongest)
context[fromView]!.append(.preferredDurationMatchesLongest)
case .zoom:
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.scale(to: 1.3), .fade])
context[toView]!.append(contentsOf: [.scale(to: 0.7)])
case .zoomOut:
context[toView]!.append(contentsOf: [.scale(to: 1.3), .fade])
context[fromView]!.append(contentsOf: [.scale(to: 0.7)])
default:
fatalError("Not implemented")
context[toView]!.append(.preferredDurationMatchesLongest)
context[fromView]!.append(.preferredDurationMatchesLongest)
case .zoom:
m.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.scale(to: 1.3), .fade])
context[toView]!.append(contentsOf: [.scale(to: 0.7)])
case .zoomOut:
context[toView]!.append(contentsOf: [.scale(to: 1.3), .fade])
context[fromView]!.append(contentsOf: [.scale(to: 0.7)])
default:
fatalError("Not implemented")
}
}
}
}
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