Commit 9465da01 by Daniel Dahan

added MotionTransition and default processors

parent b205f8d1
......@@ -30,16 +30,38 @@
import UIKit
public class DefaultMotionTransitionPreprocessor: MotionTransitionPreprocessor {
public class AnimationPreprocessor: TransitionPreprocessor {
/// A reference to the Motion instance.
fileprivate var motion: Motion
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processors.
Initializer that accepts a Motion instance.
- Parameter motion: A Motion instance.
*/
init(motion: Motion) {
self.motion = motion
}
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
/*
let inNavigationController = motion.isNavigationController
let inTabBarController = motion.isTabBarController
let toViewController = motion.toViewController
let fromViewController = motion.fromViewController
let presenting = motion.isPresenting
let fromOverFullScreen = motion.isFromViewFullScreen
let toOverFullScreen = motion.isToViewFullScreen
let fromView = motion.fromView
let toView = motion.toView
let animators = motion.animators
*/
}
}
// The MIT License (MIT)
//
// Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
public class DurationPreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
var duration: TimeInterval = 0
duration = applyOptimizedDuration(for: fromViews)
duration = max(duration, applyOptimizedDuration(for: toViews))
set(duration: duration, for: fromViews)
set(duration: duration, for: toViews)
}
}
extension DurationPreprocessor {
fileprivate func set(duration: TimeInterval, for views: [UIView]) {
for view in views where .infinity == context.viewToMotionTransitionState[view]?.duration {
context.viewToMotionTransitionState[view]?.duration = duration
}
}
fileprivate func applyOptimizedDuration(for views: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
for v in views {
guard var state = context.viewToMotionTransitionState[v] else {
continue
}
if state.duration == nil {
state.duration = optimizedDuration(for: v)
}
if state.duration! == .infinity {
duration = max(duration, optimizedDuration(for: v))
} else {
duration = max(duration, state.duration!)
}
}
return duration
}
fileprivate func optimizedDuration(for view: UIView) -> TimeInterval {
guard let state = context.viewToMotionTransitionState[view] else {
return 0
}
return view.optimizedDuration(fromPosition: context.container.convert(view.layer.position, from: view.superview), toPosition: state.position, size: state.size, transform: state.transform)
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public class MatchPreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews {
guard let identifier = tv.motionIdentifier else {
return
}
guard let fv = context.sourceIdentifierToView[identifier] else {
continue
}
var tvState = context.viewToMotionTransitionState[tv] ?? MotionTransitionState()
var fvState = context.viewToMotionTransitionState[fv] ?? MotionTransitionState()
if let v = tvState.startStateIfMatched {
tvState.append(.startWith(animations: v))
}
if let v = fvState.startStateIfMatched {
fvState.append(.startWith(animations: v))
}
fvState.motionIdentifier = identifier
fvState.arc = tvState.arc
fvState.duration = tvState.duration
fvState.timingFunction = tvState.timingFunction
fvState.delay = tvState.delay
fvState.spring = tvState.spring
tvState.motionIdentifier = identifier
tvState.opacity = 0
if !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1 {
fvState.opacity = 0
} else {
fvState.opacity = nil
if !fv.layer.masksToBounds && fvState.displayShadow {
tvState.displayShadow = false
}
}
context.viewToMotionTransitionState[tv] = tvState
context.viewToMotionTransitionState[fv] = fvState
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import MetalKit
internal struct KeySet<Key: Hashable, Value: Hashable> {
/// A reference to the dictionary storing the key / Set pairs.
fileprivate var dictionary = [Key: Set<Value>]()
/**
Subscript for matching keys and returning the corresponding set.
- Parameter key: A Key type.
- Returns: A Set<Value> type.
*/
subscript(key: Key) -> Set<Value> {
mutating get {
if nil == dictionary[key] {
dictionary[key] = Set<Value>()
}
return dictionary[key]!
}
set(value) {
dictionary[key] = value
}
}
}
internal extension CGSize {
internal var center: CGPoint {
return CGPoint(x: width / 2, y: height / 2)
}
internal var point: CGPoint {
return CGPoint(x: width, y: height)
}
internal func transform(_ t: CGAffineTransform) -> CGSize {
return applying(t)
}
internal func transform(_ t: CATransform3D) -> CGSize {
return applying(CATransform3DGetAffineTransform(t))
}
}
internal extension CGRect {
var center: CGPoint {
return CGPoint(x: origin.x + size.width / 2, y: origin.y + size.height / 2)
}
var bounds: CGRect {
return CGRect(origin: CGPoint.zero, size: size)
}
init(center: CGPoint, size: CGSize) {
self.init(x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height)
}
}
internal extension CGFloat {
func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat {
return self < a ? a : self > b ? b : self
}
}
internal extension CGPoint {
func translate(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
return CGPoint(x: x + dx, y: y + dy)
}
func transform(_ t: CGAffineTransform) -> CGPoint {
return applying(t)
}
func transform(_ t: CATransform3D) -> CGPoint {
return applying(CATransform3DGetAffineTransform(t))
}
func distance(_ b: CGPoint) -> CGFloat {
return sqrt(pow(x - b.x, 2) + pow(y - b.y, 2))
}
}
internal func +(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
internal func -(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
internal func /(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x / right, y: left.y / right)
}
internal func /(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x / right.x, y: left.y / right.y)
}
internal func *(left: CGPoint, right: CGFloat) -> CGPoint {
return CGPoint(x: left.x * right, y: left.y * right)
}
internal func *(left: CGPoint, right: CGSize) -> CGPoint {
return CGPoint(x: left.x * right.width, y: left.y * right.width)
}
internal func *(left: CGFloat, right: CGPoint) -> CGPoint {
return right * left
}
internal func *(left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x * right.x, y: left.y * right.y)
}
internal prefix func -(point: CGPoint) -> CGPoint {
return CGPoint.zero - point
}
internal func abs(_ p: CGPoint) -> CGPoint {
return CGPoint(x: abs(p.x), y: abs(p.y))
}
internal func *(left: CGSize, right: CGFloat) -> CGSize {
return CGSize(width: left.width * right, height: left.height * right)
}
internal func *(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width * right.width, height: left.height * right.width)
}
internal func /(left: CGSize, right: CGSize) -> CGSize {
return CGSize(width: left.width / right.width, height: left.height / right.height)
}
internal func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
var lhs = lhs
var rhs = rhs
return memcmp(&lhs, &rhs, MemoryLayout<CATransform3D>.size) == 0
}
internal func != (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
return !(lhs == rhs)
}
......@@ -41,6 +41,12 @@ fileprivate struct MotionInstance {
/// An optional reference to the motion animations.
fileprivate var animations: [MotionAnimation]?
/// An optional reference to the motion transition animations.
fileprivate var transitions: [MotionTransition]?
/// An alpha value.
fileprivate var alpha: CGFloat?
}
extension UIView {
......@@ -48,7 +54,7 @@ extension UIView {
fileprivate var motionInstance: MotionInstance {
get {
return AssociatedObject.get(base: self, key: &MotionInstanceKey) {
return MotionInstance(isEnabled: true, identifier: nil, animations: nil)
return MotionInstance(isEnabled: true, identifier: nil, animations: nil, transitions: nil, alpha: 1)
}
}
set(value) {
......@@ -78,7 +84,7 @@ extension UIView {
}
}
/// The animations to run while in transition.
/// The animations to run.
open var motionAnimations: [MotionAnimation]? {
get {
return motionInstance.animations
......@@ -87,6 +93,27 @@ extension UIView {
motionInstance.animations = value
}
}
/// The animations to run while in transition.
open var motionTransitions: [MotionTransition]? {
get {
return motionInstance.transitions
}
set(value) {
motionInstance.transitions = value
}
}
/// The animations to run while in transition.
@IBInspectable
open var motionAlpha: CGFloat? {
get {
return motionInstance.alpha
}
set(value) {
motionInstance.alpha = value
}
}
}
extension UIView {
......@@ -168,6 +195,27 @@ extension UIView {
}
extension UIView {
internal func optimizedDuration(fromPosition: CGPoint, toPosition: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval {
let toPos = toPosition ?? fromPosition
let fromSize = (layer.presentation() ?? layer).bounds.size
let toSize = size ?? fromSize
let fromTransform = (layer.presentation() ?? layer).transform
let toTransform = transform ?? fromTransform
let realFromPos = CGPoint.zero.transform(fromTransform) + fromPosition
let realToPos = CGPoint.zero.transform(toTransform) + toPos
let realFromSize = fromSize.transform(fromTransform)
let realToSize = toSize.transform(toTransform)
let movePoints = realFromPos.distance(realToPos) + realFromSize.point.distance(realToSize.point)
// duration is 0.2 @ 0 to 0.375 @ 500
return 0.208 + Double(movePoints.clamp(0, 500)) / 3000
}
}
extension UIView {
/// Computes the rotation of the view.
open var motionRotationAngle: CGFloat {
get {
......
......@@ -43,43 +43,45 @@ public class Motion: MotionController {
A boolean indicating if the transition view controller is a
UINavigationController.
*/
fileprivate var isNavigationController = false
internal fileprivate(set) var isNavigationController = false
/**
A boolean indicating if the transition view controller is a
UITabBarController.
*/
fileprivate var isTabBarController = false
internal fileprivate(set) var isTabBarController = false
/**
A boolean indicating if the transition view controller is a
UINavigationController or UITabBarController.
*/
fileprivate var isContainerController: Bool {
internal var isContainerController: Bool {
return isNavigationController || isTabBarController
}
/// A boolean indicating if the toView is at full screen.
fileprivate var isToViewFullScreen: Bool {
return !isContainerController && (.overFullScreen == toViewController!.modalPresentationStyle || .overCurrentContext == toViewController!.modalPresentationStyle)
}
fileprivate var isFromViewFullScreen: Bool {
return !isContainerController && (.overFullScreen == fromViewController!.modalPresentationStyle || .overCurrentContext == fromViewController!.modalPresentationStyle)
}
/// A reference to the fromViewController.view.
fileprivate var fromView: UIView {
internal var fromView: UIView {
return fromViewController!.view
}
/// A reference to the toViewController.view.
fileprivate var toView: UIView {
internal var toView: UIView {
return toViewController!.view
}
/// A reference to the screen snapshot.
fileprivate var screenSnapshot: UIView!
/// A boolean indicating if the fromView is at full screen.
internal var isFromViewFullScreen: Bool {
return !isContainerController && (.overFullScreen == fromViewController!.modalPresentationStyle || .overCurrentContext == fromViewController!.modalPresentationStyle)
}
/// A boolean indicating if the toView is at full screen.
internal var isToViewFullScreen: Bool {
return !isContainerController && (.overFullScreen == toViewController!.modalPresentationStyle || .overCurrentContext == toViewController!.modalPresentationStyle)
}
/**
A reference to a shared Motion instance to control interactive
transitions.
......@@ -212,7 +214,12 @@ extension Motion {
/// Prepares the preprocessors.
fileprivate func preparePreprocessors() {
preprocessors = [DefaultMotionTransitionPreprocessor()]
preprocessors = [
MatchPreprocessor(),
SourcePreprocessor(),
AnimationPreprocessor(motion: self),
DurationPreprocessor(),
]
}
/// Prepares the animators.
......@@ -279,6 +286,11 @@ extension Motion {
processContext()
}
/**
Called when the animation is thought to be completed.
- Parameter isFinished: A boolean value indicatin if the
transition has completed.
*/
fileprivate func completed(isFinished: Bool) {
transitionContext?.completeTransition(!isFinished)
}
......
......@@ -32,16 +32,22 @@ import UIKit
open class MotionContext {
/// A reference to the transition container.
fileprivate var container: UIView
internal fileprivate(set) var container: UIView
/// An index source of identifiers to their corresponding view.
fileprivate var sourceIdentifierToView = [String: UIView]()
internal fileprivate(set) var sourceIdentifierToView = [String: UIView]()
/// An index of destination identifiers to their corresponding view.
fileprivate var destinationIdentifierToView = [String: UIView]()
internal fileprivate(set) var destinationIdentifierToView = [String: UIView]()
/// An index of views to their corresponding snapshot view.
fileprivate var snapshotToView = [UIView: UIView]()
internal fileprivate(set) var snapshotToView = [UIView: UIView]()
/// An index of views to their MotionTransitionState.
internal var viewToMotionTransitionState = [UIView: MotionTransitionState]()
/// An index of views to their alpha value.
internal var viewToAlpha = [UIView: CGFloat]()
/// A reference to the transition from views.
internal var fromViews: [UIView]!
......@@ -83,6 +89,27 @@ extension MotionContext {
extension MotionContext {
/**
Retrieves the transition pair for a given view, if one exists.
- Parameter for view: A UIView.
- Returns: An optional UIView.
*/
internal func transitionPair(for view: UIView) -> UIView? {
guard let identifier = view.motionIdentifier else {
return nil
}
guard let source = sourceIdentifierToView[identifier] else {
return nil
}
guard let destination = destinationIdentifierToView[identifier] else {
return nil
}
return view == source ? destination : view == destination ? source : nil
}
/**
Sets the views that will transition from one state to another.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
......@@ -90,7 +117,114 @@ extension MotionContext {
internal func set(fromViews: [UIView], toViews: [UIView]) {
self.fromViews = fromViews
self.toViews = toViews
prepare(views: fromViews, identifierIndex: &sourceIdentifierToView)
prepare(views: toViews, identifierIndex: &destinationIdentifierToView)
}
internal func hide(view: UIView) {
guard nil == viewToAlpha[view] else {
return
}
guard .none != viewToMotionTransitionState[view]?.motionSnapshot else {
return
}
guard view is UIVisualEffectView else {
view.isHidden = true
viewToAlpha[view] = 1
return
}
viewToAlpha[view] = view.isOpaque ? .infinity : view.alpha
view.alpha = 0
}
internal func show(view: UIView) {
guard let v = viewToAlpha[view] else {
return
}
if view is UIVisualEffectView {
view.isHidden = false
} else if v == .infinity {
view.alpha = 1
view.isOpaque = true
} else {
view.alpha = v
}
viewToAlpha[view] = nil
}
internal func showAll() {
for v in viewToAlpha.keys {
show(view: v)
}
viewToAlpha.removeAll()
}
internal func showSubviews(for view: UIView) {
show(view: view)
for subview in view.subviews {
showSubviews(for: subview)
}
}
internal func removeAllSnapshots() {
for (view, snapshot) in snapshotToView {
guard view != snapshot else {
continue
}
// Do not remove when using .useNoSnapshot.
snapshot.removeFromSuperview()
}
}
internal func removeSnapshots(rootView: UIView) {
if let snapshot = snapshotToView[rootView], snapshot != rootView {
snapshot.removeFromSuperview()
}
for subview in rootView.subviews {
removeSnapshots(rootView: subview)
}
}
internal func snapshots(for view: UIView) -> [UIView] {
var snapshots = [UIView]()
for v in view.flattenedViewHierarchy {
guard let snapshot = snapshotToView[v] else {
continue
}
snapshots.append(snapshot)
}
return snapshots
}
internal func loadViewToAlpha(for view: UIView) {
if let v = view.motionAlpha {
view.alpha = v
view.motionAlpha = nil
}
for subview in view.subviews {
loadViewToAlpha(for: subview)
}
}
internal func storeViewToAlpha(for view: UIView) {
view.motionAlpha = viewToAlpha[view]
for subview in view.subviews {
storeViewToAlpha(for: subview)
}
}
}
......@@ -98,7 +98,7 @@ public class MotionController: NSObject, MotionSubscriber {
public internal(set) var animators = [MotionTransitionAnimator]()
/// A reference to the preprocessors.
public internal(set) var preprocessors = [MotionTransitionPreprocessor]()
public internal(set) var preprocessors = [TransitionPreprocessor]()
/// A boolean indicating if a transition is in progress.
public var isTransitioning: Bool {
......
......@@ -58,5 +58,5 @@ public protocol MotionTransitionAnimator: class {
func clean()
func apply(motionTransitions: [MotionTransitionAnimation], to view: UIView)
func apply(motionTransitions: [MotionTransition], to view: UIView)
}
......@@ -31,12 +31,12 @@
import UIKit
internal class MotionTransitionStateWrapper {
/// A reference to a MotionTransitionAnimationState.
/// A reference to a MotionTransitionState.
internal var state: MotionTransitionState
/**
An initializer that accepts a given MotionTransitionAnimationState.
- Parameter state: A MotionTransitionAnimationState.
An initializer that accepts a given MotionTransitionState.
- Parameter state: A MotionTransitionState.
*/
internal init(state: MotionTransitionState) {
self.state = state
......@@ -50,7 +50,7 @@ public struct MotionTransitionState {
/// A reference to the motion identifier.
public var motionIdentifier: String?
public var startStateIfMatched: [MotionTransitionAnimation]?
public var startStateIfMatched: [MotionTransition]?
public var position: CGPoint?
public var size: CGSize?
......@@ -88,20 +88,20 @@ public struct MotionTransitionState {
public var ignoreSubviewTransitionAnimations: Bool?
public var coordinateSpace: MotionCoordinateSpace?
public var useScaleBasedSizeChange: Bool?
public var snapshotType: MotionSnapshot?
public var motionSnapshot: MotionSnapshot?
public var forceAnimate: Bool = false
public var custom: [String:Any]?
init(transitionAnimations: [MotionTransitionAnimation]) {
init(transitionAnimations: [MotionTransition]) {
append(contentsOf: transitionAnimations)
}
public mutating func append(_ transitionAnimations: MotionTransitionAnimation) {
public mutating func append(_ transitionAnimations: MotionTransition) {
transitionAnimations.apply(&self)
}
public mutating func append(contentsOf transitionAnimations: [MotionTransitionAnimation]) {
public mutating func append(contentsOf transitionAnimations: [MotionTransition]) {
for v in transitionAnimations {
v.apply(&self)
}
......@@ -109,7 +109,7 @@ public struct MotionTransitionState {
}
extension MotionTransitionState: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: MotionTransitionAnimation...) {
public init(arrayLiteral elements: MotionTransition...) {
append(contentsOf: elements)
}
}
......
// The MIT License (MIT)
//
// Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
public class SourcePreprocessor: TransitionPreprocessor {
/// A reference to a MotionContext.
public weak var context: MotionContext!
/**
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
public func process(fromViews: [UIView], toViews: [UIView]) {
prepare(fromViews: fromViews)
prepare(toViews: toViews)
}
}
extension SourcePreprocessor {
/**
Prepares the from views.
- Parameter fromViews: An Array of UIViews.
*/
fileprivate func prepare(fromViews: [UIView]) {
for fv in fromViews {
guard let identifier = context.viewToMotionTransitionState[fv]?.motionIdentifier else {
continue
}
guard let tv = context.destinationIdentifierToView[identifier] else {
continue
}
prepare(view: fv, for: tv)
}
}
/**
Prepares the to views.
- Parameter fromViews: An Array of UIViews.
*/
fileprivate func prepare(toViews: [UIView]) {
for tv in context.toViews {
guard let identifier = context.viewToMotionTransitionState[tv]?.motionIdentifier else {
continue
}
guard let fv = context.sourceIdentifierToView[identifier] else {
continue
}
prepare(view: tv, for: fv)
}
}
fileprivate func prepare(view: UIView, for targetView: UIView) {
guard var state = context.viewToMotionTransitionState[view] else {
return
}
state.coordinateSpace = .global
state.position = context.container.convert(targetView.layer.position, from: targetView.superview!)
state.transform = nil
state.size = nil
state.cornerRadius = nil
if view.bounds.size != targetView.bounds.size {
state.size = targetView.bounds.size
}
if view.layer.cornerRadius != targetView.layer.cornerRadius {
state.cornerRadius = targetView.layer.cornerRadius
}
if view.layer.transform != targetView.layer.transform {
state.transform = targetView.layer.transform
}
if view.layer.shadowColor != targetView.layer.shadowColor {
state.shadowColor = targetView.layer.shadowColor
}
if view.layer.shadowOpacity != targetView.layer.shadowOpacity {
state.shadowOpacity = targetView.layer.shadowOpacity
}
if view.layer.shadowOffset != targetView.layer.shadowOffset {
state.shadowOffset = targetView.layer.shadowOffset
}
if view.layer.shadowRadius != targetView.layer.shadowRadius {
state.shadowRadius = targetView.layer.shadowRadius
}
if view.layer.shadowPath != targetView.layer.shadowPath {
state.shadowPath = targetView.layer.shadowPath
}
if view.layer.contentsRect != targetView.layer.contentsRect {
state.contentsRect = targetView.layer.contentsRect
}
if view.layer.contentsScale != targetView.layer.contentsScale {
state.contentsScale = targetView.layer.contentsScale
}
context.viewToMotionTransitionState[view] = state
}
}
......@@ -30,12 +30,12 @@
import UIKit
public protocol MotionTransitionPreprocessor: class {
public protocol TransitionPreprocessor: class {
/// A reference to a MotionContext.
weak var context: MotionContext! { get set }
/**
Implementation for processors.
Implementation for processor.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
*/
......
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