Commit d21da35d by Daniel Dahan

reworked MotionContext

parent aa31695e
...@@ -29,118 +29,172 @@ ...@@ -29,118 +29,172 @@
import UIKit import UIKit
public class MotionContext { public class MotionContext {
/// A reference of motion identifiers to source views.
internal var motionIdentifierToSourceView = [String: UIView]() internal var motionIdentifierToSourceView = [String: UIView]()
/// A reference of motion identifiers to destination views.
internal var motionIdentifierToDestinationView = [String: UIView]() internal var motionIdentifierToDestinationView = [String: UIView]()
internal var snapshotViews = [UIView: UIView]()
internal var viewAlphas = [UIView: CGFloat]()
internal var targetStates = [UIView: MotionTargetState]()
internal var superviewToNoSnapshotSubviewMap: [UIView: [(Int, UIView)]] = [:]
internal var defaultCoordinateSpace: MotionCoordinateSpace = .local /// A reference of the snapshot to source/destination view.
internal var viewToSnapshot = [UIView: UIView]()
/// A reference to the view to view alpha value.
internal var viewToAlphas = [UIView: CGFloat]()
/// A reference of view to transition target state.
internal var viewToTargetState = [UIView: MotionTargetState]()
/// A reference of the superview to the subviews snapshots.
internal var superviewToNoSnapshotSubviewMap = [UIView: [(Int, UIView)]]()
/// A reference to the default coordinate space for transitions.
internal var defaultCoordinateSpace = MotionCoordinateSpace.local
/// The container view holding all of the animating views.
public let container: UIView
/// A flattened list of all views from the source view controller.
public var fromViews: [UIView]!
/// A flattened list of all views from the destination view controller.
public var toViews: [UIView]!
/**
An initializer that accepts a container transition view.
- Parameter container: A UIView.
*/
internal init(container: UIView) { internal init(container: UIView) {
self.container = container self.container = container
} }
}
internal func set(fromViews: [UIView], toViews: [UIView]) { internal extension MotionContext {
/**
Sets the from-views and to-views within the transition context.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
func set(fromViews: [UIView], toViews: [UIView]) {
self.fromViews = fromViews self.fromViews = fromViews
self.toViews = toViews self.toViews = toViews
process(views: fromViews, idMap: &motionIdentifierToSourceView) map(views: fromViews, identifierMap: &motionIdentifierToSourceView)
process(views: toViews, idMap: &motionIdentifierToDestinationView) map(views: toViews, identifierMap: &motionIdentifierToDestinationView)
} }
internal func process(views: [UIView], idMap: inout [String: UIView]) { /**
for view in views { Maps the views to their respective identifier index.
view.layer.removeAllAnimations() - Parameter views: An Array of UIViews.
if container.convert(view.bounds, from: view).intersects(container.bounds) { - Parameter identifierMap: A Dicionary of String to UIView pairs.
if let motionIdentifier = view.motionIdentifier { */
idMap[motionIdentifier] = view func map(views: [UIView], identifierMap: inout [String: UIView]) {
for v in views {
v.layer.removeAllAnimations()
if container.convert(v.bounds, from: v).intersects(container.bounds) {
if let i = v.motionIdentifier {
identifierMap[i] = v
} }
if let transitions = view.motionTransitions {
targetStates[view] = MotionTargetState(transitions: transitions) if let i = v.motionTransitions {
viewToTargetState[v] = MotionTargetState(transitions: i)
} }
} }
} }
} }
}
public extension MotionContext {
/** /**
The container holding all of the animating views A subscript that takes a given view and retrieves a
*/ MotionTargetState if one exists.
public let container: UIView - Parameter view: A UIView.
- Returns: An optional MotionTargetState.
/**
A flattened list of all views from source ViewController
*/
public var fromViews: [UIView]!
/**
A flattened list of all views from destination ViewController
*/ */
public var toViews: [UIView]! subscript(view: UIView) -> MotionTargetState? {
get {
return viewToTargetState[view]
}
set {
viewToTargetState[view] = newValue
}
}
} }
// public public extension MotionContext {
extension MotionContext {
/** /**
- Returns: a source view matching the motionIdentifier, nil if not found Retrieves a source view matching the motionIdentifier, nil if not found.
- Parameter for motionIdentifier: A String.
- Returns: An optional UIView.
*/ */
public func sourceView(for motionIdentifier: String) -> UIView? { func sourceView(for motionIdentifier: String) -> UIView? {
return motionIdentifierToSourceView[motionIdentifier] return motionIdentifierToSourceView[motionIdentifier]
} }
/** /**
- Returns: a destination view matching the motionIdentifier, nil if not found Retrieves a destination view matching the motionIdentifier, nil if not found.
- Parameter for motionIdentifier: A String.
- Returns: An optional UIView.
*/ */
public func destinationView(for motionIdentifier: String) -> UIView? { func destinationView(for motionIdentifier: String) -> UIView? {
return motionIdentifierToDestinationView[motionIdentifier] return motionIdentifierToDestinationView[motionIdentifier]
} }
/** /**
- Returns: a view with the same motionIdentifier, but on different view controller, nil if not found Retrieves the matching view with the same motionIdentifier found in the
source and destination view controllers.
- Returns: An optional UIView.
*/ */
public func pairedView(for view: UIView) -> UIView? { func transitionPairedView(for view: UIView) -> UIView? {
if let id = view.motionIdentifier { if let i = view.motionIdentifier {
if sourceView(for: id) == view { if view == sourceView(for: i) {
return destinationView(for: id) return destinationView(for: i)
} else if destinationView(for: id) == view {
return sourceView(for: id) } else if view == destinationView(for: i) {
return sourceView(for: i)
} }
} }
return nil return nil
} }
/** /**
- Returns: a snapshot view for animation Retrieves the snapshot view for a given view.
- Parameter for view: A UIView.
- Returns: A UIView.
*/ */
public func snapshotView(for view: UIView) -> UIView { func snapshotView(for view: UIView) -> UIView {
if let snapshot = snapshotViews[view] { if let v = viewToSnapshot[view] {
return snapshot return v
} }
var containerView = container var containerView = container
let coordinateSpace = targetStates[view]?.coordinateSpace ?? defaultCoordinateSpace let coordinateSpace = viewToTargetState[view]?.coordinateSpace ?? defaultCoordinateSpace
switch coordinateSpace { switch coordinateSpace {
case .local: case .local:
containerView = view containerView = view
while containerView != container, snapshotViews[containerView] == nil, let superview = containerView.superview {
while containerView != container, nil == viewToSnapshot[containerView], let superview = containerView.superview {
containerView = superview containerView = superview
} }
if let snapshot = snapshotViews[containerView] {
if let snapshot = viewToSnapshot[containerView] {
containerView = snapshot containerView = snapshot
} }
case .sameParent: case .sameParent:
containerView = view.superview! containerView = view.superview!
case .global: case .global:
break break
} }
unhide(view: view) unhide(view: view)
// capture a snapshot without alpha & cornerRadius // Capture a snapshot without the alpha & cornerRadius values.
let oldCornerRadius = view.layer.cornerRadius let oldCornerRadius = view.layer.cornerRadius
let oldAlpha = view.alpha
view.layer.cornerRadius = 0 view.layer.cornerRadius = 0
let oldAlpha = view.alpha
view.alpha = 1 view.alpha = 1
let snapshot: UIView let snapshot: UIView
...@@ -149,47 +203,56 @@ extension MotionContext { ...@@ -149,47 +203,56 @@ extension MotionContext {
switch snapshotType { switch snapshotType {
case .normal: case .normal:
snapshot = view.snapshotView(afterScreenUpdates: true)! snapshot = view.snapshotView(afterScreenUpdates: true)!
case .layerRender: case .layerRender:
snapshot = view.slowSnapshotView() snapshot = view.slowSnapshotView()
case .noSnapshot: case .noSnapshot:
if superviewToNoSnapshotSubviewMap[view.superview!] == nil { if nil == superviewToNoSnapshotSubviewMap[view.superview!] {
superviewToNoSnapshotSubviewMap[view.superview!] = [] superviewToNoSnapshotSubviewMap[view.superview!] = []
} }
superviewToNoSnapshotSubviewMap[view.superview!]!.append((view.superview!.subviews.index(of: view)!, view)) superviewToNoSnapshotSubviewMap[view.superview!]!.append((view.superview!.subviews.index(of: view)!, view))
snapshot = view snapshot = view
case .optimized: case .optimized:
#if os(tvOS) #if os(tvOS)
snapshot = view.snapshotView(afterScreenUpdates: true)! snapshot = view.snapshotView(afterScreenUpdates: true)!
#else #else
if #available(iOS 9.0, *), let stackView = view as? UIStackView { if #available(iOS 9.0, *), let stackView = view as? UIStackView {
snapshot = stackView.slowSnapshotView() snapshot = stackView.slowSnapshotView()
} else if let imageView = view as? UIImageView, view.subviews.isEmpty { } else if let imageView = view as? UIImageView, view.subviews.isEmpty {
let contentView = UIImageView(image: imageView.image) let contentView = UIImageView(image: imageView.image)
contentView.frame = imageView.bounds contentView.frame = imageView.bounds
contentView.contentMode = imageView.contentMode contentView.contentMode = imageView.contentMode
contentView.tintColor = imageView.tintColor contentView.tintColor = imageView.tintColor
contentView.backgroundColor = imageView.backgroundColor contentView.backgroundColor = imageView.backgroundColor
let snapShotView = UIView() let snapShotView = UIView()
snapShotView.addSubview(contentView) snapShotView.addSubview(contentView)
snapshot = snapShotView snapshot = snapShotView
} else if let barView = view as? UINavigationBar, barView.isTranslucent {
let newBarView = UINavigationBar(frame: barView.frame)
newBarView.barStyle = barView.barStyle } else if let navigationBar = view as? UINavigationBar, navigationBar.isTranslucent {
newBarView.tintColor = barView.tintColor let newNavigationBar = UINavigationBar(frame: navigationBar.frame)
newBarView.barTintColor = barView.barTintColor newNavigationBar.barStyle = navigationBar.barStyle
newBarView.clipsToBounds = false newNavigationBar.tintColor = navigationBar.tintColor
newNavigationBar.barTintColor = navigationBar.barTintColor
newNavigationBar.clipsToBounds = false
// Take a snapshot without the background.
navigationBar.layer.sublayers![0].opacity = 0
let realSnapshot = navigationBar.snapshotView(afterScreenUpdates: true)!
navigationBar.layer.sublayers![0].opacity = 1
// take a snapshot without the background newNavigationBar.addSubview(realSnapshot)
barView.layer.sublayers![0].opacity = 0 snapshot = newNavigationBar
let realSnapshot = barView.snapshotView(afterScreenUpdates: true)!
barView.layer.sublayers![0].opacity = 1
newBarView.addSubview(realSnapshot)
snapshot = newBarView
} else if let effectView = view as? UIVisualEffectView { } else if let effectView = view as? UIVisualEffectView {
snapshot = UIVisualEffectView(effect: effectView.effect) snapshot = UIVisualEffectView(effect: effectView.effect)
snapshot.frame = effectView.bounds snapshot.frame = effectView.bounds
} else { } else {
snapshot = view.snapshotView(afterScreenUpdates: true)! snapshot = view.snapshotView(afterScreenUpdates: true)!
} }
...@@ -205,12 +268,14 @@ extension MotionContext { ...@@ -205,12 +268,14 @@ extension MotionContext {
view.layer.cornerRadius = oldCornerRadius view.layer.cornerRadius = oldCornerRadius
view.alpha = oldAlpha view.alpha = oldAlpha
if snapshotType != .noSnapshot { if .noSnapshot != snapshotType {
snapshot.layer.allowsGroupOpacity = false snapshot.layer.allowsGroupOpacity = false
if !(view is UINavigationBar), let contentView = snapshot.subviews.get(0) { if !(view is UINavigationBar), let contentView = snapshot.subviews.get(0) {
// the Snapshot's contentView must have hold the cornerRadius value, /**
// since the snapshot might not have maskToBounds set The snapshot's contentView must have the cornerRadius value,
since the snapshot might not have maskToBounds set
*/
contentView.layer.cornerRadius = view.layer.cornerRadius contentView.layer.cornerRadius = view.layer.cornerRadius
contentView.layer.masksToBounds = true contentView.layer.masksToBounds = true
} }
...@@ -241,44 +306,48 @@ extension MotionContext { ...@@ -241,44 +306,48 @@ extension MotionContext {
hide(view: view) hide(view: view)
if let pairedView = pairedView(for: view), let pairedSnapshot = snapshotViews[pairedView] { if let pairedView = transitionPairedView(for: view), let pairedSnapshot = viewToSnapshot[pairedView] {
let siblingViews = pairedView.superview!.subviews let siblingViews = pairedView.superview!.subviews
let nextSiblings = siblingViews[siblingViews.index(of: pairedView)!+1..<siblingViews.count] let nextSiblings = siblingViews[siblingViews.index(of: pairedView)!+1..<siblingViews.count]
containerView.addSubview(pairedSnapshot) containerView.addSubview(pairedSnapshot)
containerView.addSubview(snapshot) containerView.addSubview(snapshot)
for subview in pairedView.subviews { for subview in pairedView.subviews {
insertGlobalViewTree(view: subview) insertGlobalViewTree(view: subview)
} }
for sibling in nextSiblings { for sibling in nextSiblings {
insertGlobalViewTree(view: sibling) insertGlobalViewTree(view: sibling)
} }
} else { } else {
containerView.addSubview(snapshot) containerView.addSubview(snapshot)
} }
containerView.addSubview(snapshot) containerView.addSubview(snapshot)
snapshotViews[view] = snapshot
viewToSnapshot[view] = snapshot
return snapshot return snapshot
} }
/**
Inserts the given view into the global context space.
- Parameter view: A UIView.
*/
func insertGlobalViewTree(view: UIView) { func insertGlobalViewTree(view: UIView) {
if targetStates[view]?.coordinateSpace == .global, let snapshot = snapshotViews[view] { if .global == viewToTargetState[view]?.coordinateSpace, let snapshot = viewToSnapshot[view] {
container.addSubview(snapshot) container.addSubview(snapshot)
} }
for subview in view.subviews {
insertGlobalViewTree(view: subview)
}
}
public subscript(view: UIView) -> MotionTargetState? { for v in view.subviews {
get { insertGlobalViewTree(view: v)
return targetStates[view]
}
set {
targetStates[view] = newValue
} }
} }
public func clean() { /// Restores the transition subview map with its superview.
func clean() {
for (superview, subviews) in superviewToNoSnapshotSubviewMap { for (superview, subviews) in superviewToNoSnapshotSubviewMap {
for (index, view) in subviews.reversed() { for (index, view) in subviews.reversed() {
superview.insertSubview(view, at: index) superview.insertSubview(view, at: index)
...@@ -287,81 +356,133 @@ extension MotionContext { ...@@ -287,81 +356,133 @@ extension MotionContext {
} }
} }
// internal internal extension MotionContext {
extension MotionContext { /**
public func hide(view: UIView) { Hides a given view.
if viewAlphas[view] == nil, self[view]?.snapshotType != .noSnapshot { - Parameter view: A UIView.
*/
func hide(view: UIView) {
guard nil == viewToAlphas[view], .noSnapshot != self[view]?.snapshotType else {
return
}
if view is UIVisualEffectView { if view is UIVisualEffectView {
view.isHidden = true view.isHidden = true
viewAlphas[view] = 1 viewToAlphas[view] = 1
} else { } else {
viewAlphas[view] = view.isOpaque ? .infinity : view.alpha viewToAlphas[view] = view.isOpaque ? .infinity : view.alpha
view.alpha = 0 view.alpha = 0
} }
} }
/**
Shows a given view that was hidden.
- Parameter view: A UIView.
*/
func unhide(view: UIView) {
guard let oldAlpha = viewToAlphas[view] else {
return
} }
public func unhide(view: UIView) {
if let oldAlpha = viewAlphas[view] {
if view is UIVisualEffectView { if view is UIVisualEffectView {
view.isHidden = false view.isHidden = false
} else if oldAlpha == .infinity { } else if oldAlpha == .infinity {
view.alpha = 1 view.alpha = 1
view.isOpaque = true view.isOpaque = true
} else { } else {
view.alpha = oldAlpha view.alpha = oldAlpha
} }
viewAlphas[view] = nil
} viewToAlphas[view] = nil
} }
internal func unhideAll() {
for view in viewAlphas.keys { /// Shows all given views that are hidden.
unhide(view: view) func unhideAll() {
for v in viewToAlphas.keys {
unhide(view: v)
} }
viewAlphas.removeAll()
viewToAlphas.removeAll()
} }
internal func unhide(rootView: UIView) {
/**
Show a given view and its subviews that are hidden.
- Parameter rootView: A UIView.
*/
func unhide(rootView: UIView) {
unhide(view: rootView) unhide(view: rootView)
for subview in rootView.subviews { for subview in rootView.subviews {
unhide(rootView: subview) unhide(rootView: subview)
} }
} }
internal func removeAllSnapshots() { /// Removes all snapshots that are not using .useNoSnapshot.
for (view, snapshot) in snapshotViews { func removeAllSnapshots() {
if view != snapshot { for (k, v) in viewToSnapshot {
// do not remove when it is using .useNoSnapshot if k != v {
snapshot.removeFromSuperview() v.removeFromSuperview()
} }
} }
} }
internal func removeSnapshots(rootView: UIView) {
if let snapshot = snapshotViews[rootView], snapshot != rootView { /**
snapshot.removeFromSuperview() Removes the snapshots for a given view and all its subviews.
- Parameter rootView: A UIVIew.
*/
func removeSnapshots(rootView: UIView) {
if let v = viewToSnapshot[rootView], v != rootView {
v.removeFromSuperview()
} }
for subview in rootView.subviews {
removeSnapshots(rootView: subview) for v in rootView.subviews {
removeSnapshots(rootView: v)
} }
} }
internal func snapshots(rootView: UIView) -> [UIView] {
/**
Retrieves the snapshots for a given view and all its subviews.
- Parameter rootView: A UIView.
- Returns: An Array of UIViews.
*/
func snapshots(rootView: UIView) -> [UIView] {
var snapshots = [UIView]() var snapshots = [UIView]()
for v in rootView.flattenedViewHierarchy { for v in rootView.flattenedViewHierarchy {
if let snapshot = snapshotViews[v] { if let snapshot = viewToSnapshot[v] {
snapshots.append(snapshot) snapshots.append(snapshot)
} }
} }
return snapshots return snapshots
} }
internal func loadViewAlpha(rootView: UIView) {
/**
Sets the alpha values for a given view and its subviews to the
stored alpha value.
- Parameter rootView: A UIView.
*/
func loadViewAlpha(rootView: UIView) {
if let storedAlpha = rootView.motionAlpha { if let storedAlpha = rootView.motionAlpha {
rootView.alpha = storedAlpha rootView.alpha = storedAlpha
rootView.motionAlpha = nil rootView.motionAlpha = nil
} }
for subview in rootView.subviews { for subview in rootView.subviews {
loadViewAlpha(rootView: subview) loadViewAlpha(rootView: subview)
} }
} }
internal func storeViewAlpha(rootView: UIView) {
rootView.motionAlpha = viewAlphas[rootView] /**
Stores the alpha values for a given view and its subviews.
- Parameter rootView: A UIView.
*/
func storeViewAlpha(rootView: UIView) {
rootView.motionAlpha = viewToAlphas[rootView]
for subview in rootView.subviews { for subview in rootView.subviews {
storeViewAlpha(rootView: subview) storeViewAlpha(rootView: subview)
} }
......
...@@ -297,7 +297,7 @@ public extension MotionController { ...@@ -297,7 +297,7 @@ public extension MotionController {
} }
let s = MotionTargetState(transitions: transitions) let s = MotionTargetState(transitions: transitions)
let v = context.pairedView(for: view) ?? view let v = context.transitionPairedView(for: view) ?? view
for a in animators { for a in animators {
a.apply(state: s, to: v) a.apply(state: s, to: v)
......
...@@ -94,9 +94,9 @@ class CascadePreprocessor: BasePreprocessor { ...@@ -94,9 +94,9 @@ class CascadePreprocessor: BasePreprocessor {
let delay = TimeInterval(i) * deltaTime + initialDelay let delay = TimeInterval(i) * deltaTime + initialDelay
func applyDelay(view: UIView) { func applyDelay(view: UIView) {
if context.pairedView(for: view) == nil { if context.transitionPairedView(for: view) == nil {
context[view]?.delay = delay context[view]?.delay = delay
} else if delayMatchedViews, let paired = context.pairedView(for: view) { } else if delayMatchedViews, let paired = context.transitionPairedView(for: view) {
context[view]?.delay = finalDelay context[view]?.delay = finalDelay
context[paired]?.delay = finalDelay context[paired]?.delay = finalDelay
} }
......
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