Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
Motion
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Dmitriy Stepanets
Motion
Commits
3e422773
Unverified
Commit
3e422773
authored
Jun 08, 2017
by
Daniel Dahan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
second round reworking the MotionController
parent
fcf3d822
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
331 additions
and
248 deletions
+331
-248
Sources/Debug Plugin/MotionDebugPlugin.swift
+1
-1
Sources/Motion.swift
+12
-12
Sources/MotionController.swift
+316
-233
Sources/MotionIndependentController.swift
+2
-2
No files found.
Sources/Debug Plugin/MotionDebugPlugin.swift
View file @
3e422773
...
...
@@ -91,7 +91,7 @@ extension MotionDebugPlugin:MotionDebugViewDelegate {
public
func
onProcessSliderChanged
(
progress
:
Float
)
{
let
seekValue
=
Motion
.
shared
.
isPresenting
?
progress
:
1.0
-
progress
Motion
.
shared
.
update
(
progress
:
Double
(
seekValue
))
Motion
.
shared
.
update
(
elapsedTime
:
Double
(
seekValue
))
}
func
onPerspectiveChanged
(
translation
:
CGPoint
,
rotation
:
CGFloat
,
scale
:
CGFloat
)
{
...
...
Sources/Motion.swift
View file @
3e422773
...
...
@@ -166,12 +166,12 @@ fileprivate extension Motion {
prepareViewControllers
()
prepareSnapshotView
()
prepare
For
Transition
()
prepareTransition
()
prepareContext
()
prepareToView
()
prepareViewHierarchy
()
processContext
()
prepare
ForAnimation
()
prepare
TransitionPairs
()
processForAnimation
()
}
}
...
...
@@ -188,7 +188,7 @@ internal extension Motion {
fullScreenSnapshot
?
.
removeFromSuperview
()
}
override
func
complete
(
finished
:
Bool
)
{
override
func
complete
(
isFinished
finished
:
Bool
)
{
guard
isTransitioning
else
{
return
}
...
...
@@ -220,13 +220,13 @@ internal extension Motion {
}
// move fromView & toView back from our container back to the one supplied by UIKit
if
(
toOverFullScreen
&&
f
inished
)
||
(
fromOverFullScreen
&&
!
finished
)
{
transitionContainer
.
addSubview
(
f
inished
?
fromView
:
toView
)
if
(
toOverFullScreen
&&
isF
inished
)
||
(
fromOverFullScreen
&&
!
finished
)
{
transitionContainer
.
addSubview
(
isF
inished
?
fromView
:
toView
)
}
transitionContainer
.
addSubview
(
f
inished
?
toView
:
fromView
)
transitionContainer
.
addSubview
(
isF
inished
?
toView
:
fromView
)
if
isPresenting
!=
f
inished
,
!
isContainerController
{
if
isPresenting
!=
isF
inished
,
!
isContainerController
{
// only happens when present a .overFullScreen VC
// bug: http://openradar.appspot.com/radar?id=5320103646199808
UIApplication
.
shared
.
keyWindow
!.
addSubview
(
isPresenting
?
fromView
:
toView
)
...
...
@@ -249,7 +249,7 @@ internal extension Motion {
insertToViewFirst
=
false
defaultAnimation
=
.
auto
super
.
complete
(
finished
:
f
inished
)
super
.
complete
(
isFinished
:
isF
inished
)
if
finished
{
if
let
fvc
=
fvc
,
let
tvc
=
tvc
{
...
...
@@ -342,13 +342,13 @@ fileprivate extension Motion {
}
internal
extension
Motion
{
override
func
prepare
For
Transition
()
{
super
.
prepare
For
Transition
()
override
func
prepareTransition
()
{
super
.
prepareTransition
()
insert
(
preprocessor
:
DefaultAnimationPreprocessor
(
motion
:
self
),
before
:
DurationPreprocessor
.
self
)
}
override
func
prepare
ForAnimation
()
{
super
.
prepare
ForAnimation
()
override
func
prepare
TransitionPairs
()
{
super
.
prepare
TransitionPairs
()
context
.
hide
(
view
:
toView
)
}
}
...
...
Sources/MotionController.swift
View file @
3e422773
...
...
@@ -107,7 +107,7 @@ public class MotionController: NSObject {
internal
var
isFinished
=
true
/// An Array of MotionPreprocessors used during a transition.
internal
var
processors
:
[
MotionPreprocessor
]
!
internal
var
pr
epr
ocessors
:
[
MotionPreprocessor
]
!
/// An Array of MotionAnimators used during a transition.
internal
var
animators
:
[
MotionAnimator
]
!
...
...
@@ -125,6 +125,25 @@ public class MotionController: NSObject {
internal
override
init
()
{}
}
public
extension
MotionController
{
/**
Receive callbacks on each animation frame.
Observers will be cleaned when a transition completes.
- Parameter observer: A MotionTransitionObserver.
*/
func
addTransitionObserver
(
observer
:
MotionTransitionObserver
)
{
defer
{
transitionObservers
?
.
append
(
observer
)
}
guard
nil
==
transitionObservers
else
{
return
}
transitionObservers
=
[]
}
}
fileprivate
extension
MotionController
{
/// Updates the transition observers.
func
updateTransitionObservers
()
{
...
...
@@ -155,6 +174,10 @@ fileprivate extension MotionController {
}
fileprivate
extension
MotionController
{
/**
Handler for the DisplayLink updates.
- Parameter _ link: CADisplayLink.
*/
@objc
func
handleDisplayLink
(
_
link
:
CADisplayLink
)
{
guard
isTransitioning
else
{
...
...
@@ -180,7 +203,7 @@ fileprivate extension MotionController {
}
else
{
var
eTime
=
cTime
/
totalDuration
if
!
isFinished
{
eTime
=
1
-
eTime
}
...
...
@@ -191,269 +214,329 @@ fileprivate extension MotionController {
}
public
extension
MotionController
{
// MARK: Interactive Transition
/**
Update the progress for the interactive transition.
- Parameters:
- progress: the current progress, must be between -1...1
*/
public
func
update
(
progress
:
Double
)
{
guard
isTransitioning
else
{
return
}
self
.
beginTime
=
nil
self
.
elapsedTime
=
max
(
-
1
,
min
(
1
,
progress
))
}
/**
Finish the interactive transition.
Will stop the interactive transition and animate from the
current state to the **end** state
*/
public
func
end
(
animate
:
Bool
=
true
)
{
guard
isTransitioning
else
{
return
}
if
!
animate
{
self
.
complete
(
isFinished
:
true
)
return
/**
Updates the elapsed time for the interactive transition.
- Parameter elapsedTime t: the current progress, must be between -1...1.
*/
public
func
update
(
elapsedTime
t
:
TimeInterval
)
{
guard
isTransitioning
else
{
return
}
beginTime
=
nil
elapsedTime
=
max
(
-
1
,
min
(
1
,
t
))
}
var
maxTime
:
TimeInterval
=
0
for
animator
in
self
.
animators
{
maxTime
=
max
(
maxTime
,
animator
.
resume
(
at
:
self
.
elapsedTime
*
self
.
totalDuration
,
isReversed
:
false
))
/**
Finish the interactive transition.
Will stop the interactive transition and animate from the
current state to the **end** state
- Parameter isAnimated: A boolean indicating if the completion is animated.
*/
public
func
end
(
isAnimated
:
Bool
=
true
)
{
guard
isTransitioning
else
{
return
}
guard
isAnimated
else
{
complete
(
isFinished
:
true
)
return
}
var
v
:
TimeInterval
=
0
for
a
in
animators
{
v
=
max
(
v
,
a
.
resume
(
at
:
elapsedTime
*
totalDuration
,
isReversed
:
false
))
}
complete
(
after
:
v
,
isFinished
:
true
)
}
self
.
complete
(
after
:
maxTime
,
isFinished
:
true
)
}
/**
Cancel the interactive transition.
Will stop the interactive transition and animate from the
current state to the **begining** state
*/
public
func
cancel
(
animate
:
Bool
=
true
)
{
guard
isTransitioning
else
{
return
}
if
!
animate
{
self
.
complete
(
isFinished
:
false
)
return
}
var
maxTime
:
TimeInterval
=
0
for
animator
in
self
.
animators
{
var
adjustedProgress
=
self
.
elapsedTime
if
adjustedProgress
<
0
{
adjustedProgress
=
-
adjustedProgress
}
maxTime
=
max
(
maxTime
,
animator
.
resume
(
at
:
adjustedProgress
*
self
.
totalDuration
,
isReversed
:
true
))
/**
Cancel the interactive transition.
Will stop the interactive transition and animate from the
current state to the **begining** state
- Parameter isAnimated: A boolean indicating if the completion is animated.
*/
public
func
cancel
(
isAnimated
:
Bool
=
true
)
{
guard
isTransitioning
else
{
return
}
guard
isAnimated
else
{
complete
(
isFinished
:
false
)
return
}
var
v
:
TimeInterval
=
0
for
a
in
animators
{
var
t
=
elapsedTime
if
t
<
0
{
t
=
-
t
}
v
=
max
(
v
,
a
.
resume
(
at
:
t
*
totalDuration
,
isReversed
:
true
))
}
complete
(
after
:
v
,
isFinished
:
false
)
}
self
.
complete
(
after
:
maxTime
,
isFinished
:
false
)
}
/**
Override modifiers during an interactive animation.
For example:
Motion.shared.apply([.position(x:50, y:50)], to:view)
will set the view's position to 50, 50
- Parameters:
- modifiers: the modifiers to override
- view: the view to override to
*/
public
func
apply
(
transitions
:
[
MotionTransition
],
to
view
:
UIView
)
{
guard
isTransitioning
else
{
return
}
let
targetState
=
MotionTargetState
(
transitions
:
transitions
)
if
let
otherView
=
self
.
context
.
pairedView
(
for
:
view
)
{
for
animator
in
self
.
animators
{
animator
.
apply
(
state
:
targetState
,
to
:
otherView
)
}
}
for
animator
in
self
.
animators
{
animator
.
apply
(
state
:
targetState
,
to
:
view
)
}
}
}
/**
Override transition animations during an interactive animation.
public
extension
MotionController
{
// MARK: Observe Progress
For example:
/**
Receive callbacks on each animation frame.
Observers will be cleaned when transition completes
Motion.shared.apply([.position(x:50, y:50)], to: view)
- Parameters:
- observer: the observer
*/
func
observeForProgressUpdate
(
observer
:
MotionTransitionObserver
)
{
if
transitionObservers
==
nil
{
transitionObservers
=
[]
will set the view's position to 50, 50
- Parameter transitions: An Array of MotionTransitions.
- Parameter to view: A UIView.
*/
public
func
apply
(
transitions
:
[
MotionTransition
],
to
view
:
UIView
)
{
guard
isTransitioning
else
{
return
}
let
s
=
MotionTargetState
(
transitions
:
transitions
)
let
v
=
context
.
pairedView
(
for
:
view
)
??
view
for
a
in
animators
{
a
.
apply
(
state
:
s
,
to
:
v
)
}
}
transitionObservers
!.
append
(
observer
)
}
}
// internal methods for transition
internal
extension
MotionController
{
/// Load plugins, processors, animators, container, & context
/// must have transitionContainer set already
/// subclass should call context.set(fromViews:toViews) after inserting fromViews & toViews into the container
func
prepareForTransition
()
{
guard
isTransitioning
else
{
fatalError
()
}
plugins
=
Motion
.
enabledPlugins
.
map
({
return
$0
.
init
()
})
processors
=
[
IgnoreSubviewModifiersPreprocessor
(),
MatchPreprocessor
(),
SourcePreprocessor
(),
CascadePreprocessor
(),
DurationPreprocessor
()
]
animators
=
[
MotionDefaultAnimator
<
MotionCoreAnimationViewContext
>
()
]
if
#available(iOS 10, tvOS 10, *)
{
animators
.
append
(
MotionDefaultAnimator
<
MotionViewPropertyViewContext
>
())
/**
Load plugins, processors, animators, container, & context
The transitionContainer must already be set.
Subclasses should call context.set(fromViews: toViews) after
inserting fromViews & toViews into the container
*/
func
prepareTransition
()
{
guard
isTransitioning
else
{
fatalError
()
}
prepareTransitionContainer
()
prepareContext
()
preparePreprocessors
()
prepareAnimators
()
preparePlugins
()
}
// There is no covariant in Swift, so we need to add plugins one by one.
for
plugin
in
plugins
{
processors
.
append
(
plugin
)
animators
.
append
(
plugin
)
/// Prepares the transition from-view & to-view pairs.
func
prepareTransitionPairs
()
{
guard
isTransitioning
else
{
fatalError
()
}
transitionPairs
=
[([
UIView
],
[
UIView
])]()
for
a
in
animators
{
let
fv
=
context
.
fromViews
.
filter
{
(
view
:
UIView
)
->
Bool
in
return
a
.
canAnimate
(
view
:
view
,
isAppearing
:
false
)
}
let
tv
=
context
.
toViews
.
filter
{
(
view
:
UIView
)
->
Bool
in
return
a
.
canAnimate
(
view
:
view
,
isAppearing
:
true
)
}
transitionPairs
.
append
((
fv
,
tv
))
}
}
}
transitionContainer
.
isUserInteractionEnabled
=
false
// a view to hold all the animating views
container
=
UIView
(
frame
:
transitionContainer
.
bounds
)
transitionContainer
.
addSubview
(
container
)
context
=
MotionContext
(
container
:
container
)
for
processor
in
processors
{
processor
.
context
=
context
}
for
animator
in
animators
{
animator
.
context
=
context
internal
extension
MotionController
{
func
processContext
()
{
guard
isTransitioning
else
{
fatalError
()
}
for
v
in
preprocessors
{
v
.
process
(
fromViews
:
context
.
fromViews
,
toViews
:
context
.
toViews
)
}
}
}
/// Actually animate the views
/// subclass should call `prepareTransition` & `prepareTransitionPairs` before calling `animate`
func
animate
()
{
guard
isTransitioning
else
{
fatalError
()
}
for
(
currentFromViews
,
currentToViews
)
in
transitionPairs
{
// auto hide all animated views
for
view
in
currentFromViews
{
context
.
hide
(
view
:
view
)
}
for
view
in
currentToViews
{
context
.
hide
(
view
:
view
)
}
}
var
totalDuration
:
TimeInterval
=
0
var
animatorWantsInteractive
=
false
for
(
i
,
animator
)
in
animators
.
enumerated
()
{
let
duration
=
animator
.
animate
(
fromViews
:
transitionPairs
[
i
]
.
0
,
toViews
:
transitionPairs
[
i
]
.
1
)
if
duration
==
.
infinity
{
animatorWantsInteractive
=
true
}
else
{
totalDuration
=
max
(
totalDuration
,
duration
)
}
}
self
.
totalDuration
=
totalDuration
if
animatorWantsInteractive
{
update
(
elapsedTime
:
0
)
}
else
{
complete
(
after
:
totalDuration
,
isFinished
:
true
)
}
}
func
processContext
()
{
guard
isTransitioning
else
{
fatalError
()
}
for
processor
in
processors
{
processor
.
process
(
fromViews
:
context
.
fromViews
,
toViews
:
context
.
toViews
)
func
complete
(
after
:
TimeInterval
,
isFinished
:
Bool
)
{
guard
isTransitioning
else
{
fatalError
()
}
if
after
<=
0.001
{
complete
(
isFinished
:
isFinished
)
return
}
let
v
=
(
isFinished
?
elapsedTime
:
1
-
elapsedTime
)
*
totalDuration
self
.
isFinished
=
isFinished
self
.
currentAnimationDuration
=
after
+
v
self
.
beginTime
=
CACurrentMediaTime
()
-
v
}
}
func
prepareForAnimation
()
{
guard
isTransitioning
else
{
fatalError
()
}
transitionPairs
=
[([
UIView
],
[
UIView
])]()
for
animator
in
animators
{
let
currentFromViews
=
context
.
fromViews
.
filter
{
(
view
:
UIView
)
->
Bool
in
return
animator
.
canAnimate
(
view
:
view
,
isAppearing
:
false
)
}
let
currentToViews
=
context
.
toViews
.
filter
{
(
view
:
UIView
)
->
Bool
in
return
animator
.
canAnimate
(
view
:
view
,
isAppearing
:
true
)
}
transitionPairs
.
append
((
currentFromViews
,
currentToViews
))
func
complete
(
isFinished
:
Bool
)
{
guard
isTransitioning
else
{
fatalError
()
}
for
animator
in
animators
{
animator
.
clean
()
}
transitionContainer
!.
isUserInteractionEnabled
=
true
let
completion
=
completionCallback
transitionPairs
=
nil
transitionObservers
=
nil
transitionContainer
=
nil
completionCallback
=
nil
container
=
nil
preprocessors
=
nil
animators
=
nil
plugins
=
nil
context
=
nil
beginTime
=
nil
elapsedTime
=
0
totalDuration
=
0
completion
?(
isFinished
)
}
}
}
/// Actually animate the views
/// subclass should call `prepareForTransition` & `prepareForAnimation` before calling `animate`
func
animate
()
{
guard
isTransitioning
else
{
fatalError
()
}
for
(
currentFromViews
,
currentToViews
)
in
transitionPairs
{
// auto hide all animated views
for
view
in
currentFromViews
{
context
.
hide
(
view
:
view
)
}
for
view
in
currentToViews
{
context
.
hide
(
view
:
view
)
}
fileprivate
extension
MotionController
{
/// Prepares the transition container.
func
prepareTransitionContainer
()
{
transitionContainer
.
isUserInteractionEnabled
=
false
// a view to hold all the animating views
container
=
UIView
(
frame
:
transitionContainer
.
bounds
)
transitionContainer
.
addSubview
(
container
)
}
var
totalDuration
:
TimeInterval
=
0
var
animatorWantsInteractive
=
false
for
(
i
,
animator
)
in
animators
.
enumerated
()
{
let
duration
=
animator
.
animate
(
fromViews
:
transitionPairs
[
i
]
.
0
,
toViews
:
transitionPairs
[
i
]
.
1
)
if
duration
==
.
infinity
{
animatorWantsInteractive
=
true
}
else
{
totalDuration
=
max
(
totalDuration
,
duration
)
}
/// Prepares the context.
func
prepareContext
()
{
context
=
MotionContext
(
container
:
container
)
}
self
.
totalDuration
=
totalDuration
if
animatorWantsInteractive
{
update
(
progress
:
0
)
}
else
{
complete
(
after
:
totalDuration
,
isFinished
:
true
)
/// Prepares the preprocessors.
func
preparePreprocessors
()
{
preprocessors
=
[
IgnoreSubviewModifiersPreprocessor
(),
MatchPreprocessor
(),
SourcePreprocessor
(),
CascadePreprocessor
(),
DurationPreprocessor
()
]
for
v
in
preprocessors
{
v
.
context
=
context
}
}
}
func
complete
(
after
:
TimeInterval
,
isFinished
:
Bool
)
{
guard
isTransitioning
else
{
fatalError
()
}
if
after
<=
0.001
{
complete
(
isFinished
:
isFinished
)
return
/// Prepares the animators.
func
prepareAnimators
()
{
animators
=
[
MotionDefaultAnimator
<
MotionCoreAnimationViewContext
>
()
]
if
#available(iOS 10, tvOS 10, *)
{
animators
.
append
(
MotionDefaultAnimator
<
MotionViewPropertyViewContext
>
())
}
for
v
in
animators
{
v
.
context
=
context
}
}
let
v
=
(
isFinished
?
elapsedTime
:
1
-
elapsedTime
)
*
totalDuration
self
.
isFinished
=
isFinished
self
.
currentAnimationDuration
=
after
+
v
self
.
beginTime
=
CACurrentMediaTime
()
-
v
}
func
complete
(
isFinished
:
Bool
)
{
guard
isTransitioning
else
{
fatalError
()
}
for
animator
in
animators
{
animator
.
clean
()
/// Prepares the plugins.
func
preparePlugins
()
{
plugins
=
Motion
.
enabledPlugins
.
map
({
return
$0
.
init
()
})
for
plugin
in
plugins
{
preprocessors
.
append
(
plugin
)
animators
.
append
(
plugin
)
}
}
transitionContainer
!.
isUserInteractionEnabled
=
true
let
completion
=
completionCallback
transitionPairs
=
nil
transitionObservers
=
nil
transitionContainer
=
nil
completionCallback
=
nil
container
=
nil
processors
=
nil
animators
=
nil
plugins
=
nil
context
=
nil
beginTime
=
nil
elapsedTime
=
0
totalDuration
=
0
completion
?(
isFinished
)
}
}
// MARK: Plugin Support
internal
extension
MotionController
{
static
func
isEnabled
(
plugin
:
MotionPlugin
.
Type
)
->
Bool
{
return
enabledPlugins
.
index
(
where
:
{
return
$0
==
plugin
})
!=
nil
}
static
func
enable
(
plugin
:
MotionPlugin
.
Type
)
{
disable
(
plugin
:
plugin
)
enabledPlugins
.
append
(
plugin
)
}
/**
Checks if a given plugin is enabled.
- Parameter plugin: A MotionPlugin.Type.
- Returns: A boolean indicating if the plugin is enabled or not.
*/
static
func
isEnabled
(
plugin
:
MotionPlugin
.
Type
)
->
Bool
{
return
nil
!=
enabledPlugins
.
index
(
where
:
{
return
$0
==
plugin
})
}
/**
Enables a given plugin.
- Parameter plugin: A MotionPlugin.Type.
*/
static
func
enable
(
plugin
:
MotionPlugin
.
Type
)
{
disable
(
plugin
:
plugin
)
enabledPlugins
.
append
(
plugin
)
}
static
func
disable
(
plugin
:
MotionPlugin
.
Type
)
{
if
let
index
=
enabledPlugins
.
index
(
where
:
{
return
$0
==
plugin
})
{
enabledPlugins
.
remove
(
at
:
index
)
/**
Disables a given plugin.
- Parameter plugin: A MotionPlugin.Type.
*/
static
func
disable
(
plugin
:
MotionPlugin
.
Type
)
{
guard
let
index
=
enabledPlugins
.
index
(
where
:
{
return
$0
==
plugin
})
else
{
return
}
enabledPlugins
.
remove
(
at
:
index
)
}
}
}
internal
extension
MotionController
{
// should call this after `prepareForTransition` & before `processContext`
func
insert
<
T
>
(
preprocessor
:
MotionPreprocessor
,
before
:
T
.
Type
)
{
let
processorIndex
=
processors
.
index
{
$0
is
T
}
??
processors
.
count
preprocessor
.
context
=
context
processors
.
insert
(
preprocessor
,
at
:
processorIndex
)
}
// should call this after `prepareTransitionPairs` & before `processContext`
func
insert
<
T
>
(
preprocessor
:
MotionPreprocessor
,
before
:
T
.
Type
)
{
let
v
=
preprocessors
.
index
{
$0
is
T
}
??
preprocessors
.
count
preprocessor
.
context
=
context
preprocessors
.
insert
(
preprocessor
,
at
:
v
)
}
}
Sources/MotionIndependentController.swift
View file @
3e422773
...
...
@@ -37,11 +37,11 @@ public class MotionIndependentController: MotionController {
transitionContainer
=
rootView
completionCallback
=
completion
prepare
For
Transition
()
prepareTransition
()
context
.
defaultCoordinateSpace
=
.
sameParent
context
.
set
(
fromViews
:
fromViews
,
toViews
:
toViews
)
processContext
()
prepare
ForAnimation
()
prepare
TransitionPairs
()
animate
()
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment