Commit 6df970aa by Daniel Dahan

reworked TransitionPreprocessor

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