Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
Material
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
Material
Commits
0cddaeb7
Unverified
Commit
0cddaeb7
authored
Sep 27, 2018
by
Daniel Jonathan
Committed by
GitHub
Sep 27, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1165 from OrkhanAlikhanov/interactive-swipe
[WIP] Adding interactive swipe
parents
cbd13228
fb734416
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
290 additions
and
122 deletions
+290
-122
Sources/iOS/BottomNavigationController.swift
+116
-51
Sources/iOS/TabBar.swift
+51
-0
Sources/iOS/TabsController.swift
+110
-62
Sources/iOS/TransitionController.swift
+13
-9
No files found.
Sources/iOS/BottomNavigationController.swift
View file @
0cddaeb7
...
...
@@ -29,6 +29,7 @@
*/
import
UIKit
import
Motion
extension
UIViewController
{
/**
...
...
@@ -51,19 +52,31 @@ private class MaterialTabBar: UITabBar {
}
open
class
BottomNavigationController
:
UITabBarController
{
/// A Boolean that
indicates if the swipe feature is enabled.
.
open
var
isSwipeEnabled
=
fals
e
{
/// A Boolean that
controls if the swipe feature is enabled
.
open
var
isSwipeEnabled
=
tru
e
{
didSet
{
guard
isSwipeEnabled
else
{
removeSwipeGesture
Recognizers
()
removeSwipeGesture
()
return
}
prepareSwipeGesture
Recognizers
()
prepareSwipeGesture
()
}
}
/**
A UIPanGestureRecognizer property internally used for the interactive
swipe.
*/
public
private(set)
var
interactiveSwipeGesture
:
UIPanGestureRecognizer
?
/**
A private integer for storing index of current view controller
during interactive transition.
*/
private
var
currentIndex
=
-
1
/**
An initializer that initializes the object with a NSCoder object.
- Parameter aDecoder: A NSCoder instance.
*/
...
...
@@ -151,6 +164,97 @@ open class BottomNavigationController: UITabBarController {
view
.
contentScaleFactor
=
Screen
.
scale
prepareTabBar
()
isSwipeEnabled
=
true
isMotionEnabled
=
true
}
}
private
extension
BottomNavigationController
{
/**
A target method contolling interactive swipe transition based on
gesture recognizer.
- Parameter _ gesture: A UIPanGestureRecognizer.
*/
@objc
func
handleTransitionPan
(
_
gesture
:
UIPanGestureRecognizer
)
{
guard
selectedIndex
!=
NSNotFound
else
{
return
}
let
translationX
=
gesture
.
translation
(
in
:
nil
)
.
x
let
velocityX
=
gesture
.
velocity
(
in
:
nil
)
.
x
switch
gesture
.
state
{
case
.
began
,
.
changed
:
let
isSlidingLeft
=
currentIndex
==
-
1
?
velocityX
<
0
:
translationX
<
0
if
currentIndex
==
-
1
{
currentIndex
=
selectedIndex
}
let
nextIndex
=
currentIndex
+
(
isSlidingLeft
?
1
:
-
1
)
if
selectedIndex
!=
nextIndex
{
/// 5 point threshold
guard
abs
(
translationX
)
>
5
else
{
return
}
if
currentIndex
!=
selectedIndex
{
MotionTransition
.
shared
.
cancel
(
isAnimated
:
false
)
}
guard
canSelect
(
at
:
nextIndex
)
else
{
return
}
selectedIndex
=
nextIndex
MotionTransition
.
shared
.
setCompletionCallbackForNextTransition
{
[
weak
self
]
isFinishing
in
guard
let
`
self
`
=
self
,
isFinishing
else
{
return
}
self
.
delegate
?
.
tabBarController
?(
self
,
didSelect
:
self
.
viewControllers
!
[
nextIndex
])
}
}
else
{
let
progress
=
abs
(
translationX
/
view
.
bounds
.
width
)
MotionTransition
.
shared
.
update
(
Double
(
progress
))
}
default
:
let
progress
=
(
translationX
+
velocityX
)
/
view
.
bounds
.
width
let
isUserHandDirectionLeft
=
progress
<
0
let
isTargetHandDirectionLeft
=
selectedIndex
>
currentIndex
if
isUserHandDirectionLeft
==
isTargetHandDirectionLeft
&&
abs
(
progress
)
>
0.5
{
MotionTransition
.
shared
.
finish
()
}
else
{
MotionTransition
.
shared
.
cancel
()
}
currentIndex
=
-
1
}
}
/// Prepares interactiveSwipeGesture.
func
prepareSwipeGesture
()
{
guard
nil
==
interactiveSwipeGesture
else
{
return
}
interactiveSwipeGesture
=
UIPanGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleTransitionPan
)
)
view
.
addGestureRecognizer
(
interactiveSwipeGesture
!
)
}
/// Removes interactiveSwipeGesture.
func
removeSwipeGesture
()
{
guard
let
v
=
interactiveSwipeGesture
else
{
return
}
view
.
removeGestureRecognizer
(
v
)
interactiveSwipeGesture
=
nil
}
}
...
...
@@ -167,29 +271,29 @@ private extension BottomNavigationController {
private
extension
BottomNavigationController
{
/**
Selects a view controller at a given index
.
Checks if the view controller at a given index can be selected
.
- Parameter at index: An Int.
*/
func
select
(
at
index
:
Int
)
{
func
canSelect
(
at
index
:
Int
)
->
Bool
{
guard
index
!=
selectedIndex
else
{
return
return
false
}
let
lastTabIndex
=
(
tabBar
.
items
?
.
count
??
1
)
-
1
guard
(
0
...
lastTabIndex
)
.
contains
(
index
)
else
{
return
return
false
}
guard
!
(
index
==
lastTabIndex
&&
tabBar
.
items
?
.
last
==
moreNavigationController
.
tabBarItem
)
else
{
return
return
false
}
let
vc
=
viewControllers
!
[
index
]
guard
delegate
?
.
tabBarController
?(
self
,
shouldSelect
:
vc
)
!=
false
else
{
return
return
false
}
selectedIndex
=
index
delegate
?
.
tabBarController
?(
self
,
didSelect
:
vc
)
return
true
}
}
...
...
@@ -206,43 +310,4 @@ private extension BottomNavigationController {
tabBar
.
backgroundImage
=
image
tabBar
.
backgroundColor
=
.
white
}
/// Prepare the UISwipeGestureRecognizers.
func
prepareSwipeGestureRecognizers
()
{
removeSwipeGestureRecognizers
()
let
right
=
UISwipeGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleSwipeGesture(_:)
)
)
right
.
direction
=
.
right
view
.
addGestureRecognizer
(
right
)
let
left
=
UISwipeGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleSwipeGesture(_:)
)
)
left
.
direction
=
.
left
view
.
addGestureRecognizer
(
left
)
}
/// Remove the UISwipeGestureRecognizers.
func
removeSwipeGestureRecognizers
()
{
view
.
gestureRecognizers
?
.
compactMap
{
$0
as?
UISwipeGestureRecognizer
}
.
filter
{
.
left
==
$0
.
direction
||
.
right
==
$0
.
direction
}
.
forEach
{
view
.
removeGestureRecognizer
(
$0
)
}
}
}
private
extension
BottomNavigationController
{
/**
A UISwipeGestureRecognizer that handles swipes.
- Parameter _ gesture: A UISwipeGestureRecognizer.
*/
@objc
func
handleSwipeGesture
(
_
gesture
:
UISwipeGestureRecognizer
)
{
guard
selectedIndex
!=
NSNotFound
else
{
return
}
select
(
at
:
.
right
==
gesture
.
direction
?
selectedIndex
-
1
:
selectedIndex
+
1
)
}
}
Sources/iOS/TabBar.swift
View file @
0cddaeb7
...
...
@@ -29,6 +29,7 @@
*/
import
UIKit
import
Motion
open
class
TabItem
:
FlatButton
{
/// A dictionary of TabItemStates to UIColors for states.
...
...
@@ -278,6 +279,7 @@ open class TabBar: Bar {
didSet
{
oldValue
?
.
isSelected
=
false
selectedTabItem
?
.
isSelected
=
true
updateScrollView
()
}
}
...
...
@@ -574,6 +576,55 @@ extension TabBar {
}
}
internal
extension
TabBar
{
/**
Starts line transition for the index with the given duration.
- Parameter for index: An Int.
- Parameter duration: A TimeInterval.
*/
func
startLineTransition
(
for
index
:
Int
,
duration
:
TimeInterval
=
0.35
)
{
guard
let
s
=
selectedTabItem
,
let
currentIndex
=
tabItems
.
firstIndex
(
of
:
s
)
else
{
return
}
guard
currentIndex
!=
index
else
{
return
}
let
targetFrame
=
lineFrame
(
for
:
tabItems
[
index
],
forMotion
:
true
)
line
.
transition
(
.
size
(
targetFrame
.
size
),
.
position
(
targetFrame
.
origin
),
.
duration
(
duration
))
line
.
motionViewTransition
.
start
()
}
/**
Updates line transition to the given progress value.
- Parameter _ progress: A CGFloat.
*/
func
updateLineTransition
(
_
progress
:
CGFloat
)
{
line
.
motionViewTransition
.
update
(
progress
)
}
/**
Finishes line transition.
- Parameter isAnimated: A Boolean indicating if the change should be animated.
*/
func
finishLineTransition
(
isAnimated
:
Bool
=
true
)
{
line
.
motionViewTransition
.
finish
(
isAnimated
:
isAnimated
)
}
/**
Cancels line transition.
- Parameter isAnimated: A Boolean indicating if the change should be animated.
*/
func
cancelLineTransition
(
isAnimated
:
Bool
=
true
)
{
line
.
motionViewTransition
.
cancel
(
isAnimated
:
isAnimated
)
}
}
fileprivate
extension
TabBar
{
/**
Removes the tabItem animation handler.
...
...
Sources/iOS/TabsController.swift
View file @
0cddaeb7
...
...
@@ -92,6 +92,24 @@ public protocol TabsControllerDelegate {
*/
@objc
optional
func
tabsController
(
tabsController
:
TabsController
,
didSelect
viewController
:
UIViewController
)
/**
A delegation method that is executed when the interactive transition to view controller
will be cancelled.
- Parameter tabsController: A TabsController.
- Parameter viewController: A UIViewController.
*/
@objc
optional
func
tabsController
(
tabsController
:
TabsController
,
willCancelSelecting
viewController
:
UIViewController
)
/**
A delegation method that is executed when the interactive transition to view controller
has been cancelled.
- Parameter tabsController: A TabsController.
- Parameter viewController: A UIViewController.
*/
@objc
optional
func
tabsController
(
tabsController
:
TabsController
,
didCancelSelecting
viewController
:
UIViewController
)
}
open
class
TabsController
:
TransitionController
{
...
...
@@ -110,9 +128,8 @@ open class TabsController: TransitionController {
@IBInspectable
public
let
tabBar
=
TabBar
()
/// A Boolean that indicates if the swipe feature is enabled..
open
var
isSwipeEnabled
=
false
{
/// A Boolean that controls if the swipe feature is enabled.
open
var
isSwipeEnabled
=
true
{
didSet
{
guard
isSwipeEnabled
else
{
removeSwipeGesture
()
...
...
@@ -149,6 +166,18 @@ open class TabsController: TransitionController {
}
/**
A UIPanGestureRecognizer property internally used for the interactive
swipe.
*/
public
private(set)
var
interactiveSwipeGesture
:
UIPanGestureRecognizer
?
/**
A private integer for storing index of target view controller
during interactive transition.
*/
private
var
targetIndex
=
-
1
/**
An initializer that initializes the object with a NSCoder object.
- Parameter aDecoder: A NSCoder instance.
*/
...
...
@@ -212,36 +241,25 @@ fileprivate extension TabsController {
return
}
var
isAuto
=
false
switch
motionTransitionType
{
case
.
auto
:
switch
viewController
.
motionTransitionType
{
case
.
auto
:
isAuto
=
true
if
case
.
auto
=
motionTransitionType
,
case
.
auto
=
viewController
.
motionTransitionType
{
MotionTransition
.
shared
.
setAnimationForNextTransition
(
fvcIndex
<
tvcIndex
?
.
slide
(
direction
:
.
left
)
:
.
slide
(
direction
:
.
right
))
default
:
break
}
default
:
break
}
if
isTriggeredByUserInteraction
{
delegate
?
.
tabsController
?(
tabsController
:
self
,
willSelect
:
viewController
)
}
super
.
transition
(
to
:
viewController
)
{
[
weak
self
,
viewController
=
viewController
,
completion
=
completion
]
(
isFinishing
)
in
super
.
transition
(
to
:
viewController
)
{
[
weak
self
]
(
isFinishing
)
in
guard
let
`
self
`
=
self
else
{
return
}
if
isAuto
{
MotionTransition
.
shared
.
setAnimationForNextTransition
(
.
auto
)
}
completion
?(
isFinishing
)
if
isTriggeredByUserInteraction
{
if
isTriggeredByUserInteraction
&&
isFinishing
{
self
.
delegate
?
.
tabsController
?(
tabsController
:
self
,
didSelect
:
viewController
)
}
else
{
self
.
delegate
?
.
tabsController
?(
tabsController
:
self
,
didCancelSelecting
:
viewController
)
}
}
}
...
...
@@ -279,38 +297,91 @@ fileprivate extension TabsController {
tabBar
.
tabItems
=
tabItems
tabBar
.
selectedTabItem
=
tabItems
[
selectedIndex
]
}
}
/// Prepare Swipe Gesture.
func
prepareSwipeGesture
()
{
removeSwipeGesture
()
private
extension
TabsController
{
/**
A target method contolling interactive swipe transition based on
gesture recognizer.
- Parameter _ gesture: A UIPanGestureRecognizer.
*/
@objc
func
handleTransitionPan
(
_
gesture
:
UIPanGestureRecognizer
)
{
let
translationX
=
gesture
.
translation
(
in
:
nil
)
.
x
let
velocityX
=
gesture
.
velocity
(
in
:
nil
)
.
x
let
swipeRight
=
UISwipeGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleSwipeGesture(gesture:)
)
)
swipeRight
.
direction
=
.
right
view
.
addGestureRecognizer
(
swipeRight
)
switch
gesture
.
state
{
case
.
began
,
.
changed
:
let
isSlidingLeft
=
targetIndex
==
-
1
?
velocityX
<
0
:
translationX
<
0
let
nextIndex
=
selectedIndex
+
(
isSlidingLeft
?
1
:
-
1
)
let
swipeLeft
=
UISwipeGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleSwipeGesture(gesture:)
)
)
swipeLeft
.
direction
=
.
left
view
.
addGestureRecognizer
(
swipeLeft
)
guard
nextIndex
>=
0
,
nextIndex
<
viewControllers
.
count
else
{
return
}
}
fileprivate
extension
TabsController
{
/// Remove Swipe Gesture.
func
removeSwipeGesture
()
{
guard
let
v
=
view
.
gestureRecognizers
else
{
if
targetIndex
!=
nextIndex
{
/// 5 point threshold
guard
abs
(
translationX
)
>
5
else
{
return
}
for
gesture
in
v
{
guard
let
recognizer
=
gesture
as?
UISwipeGestureRecognizer
else
{
continue
if
targetIndex
!=
-
1
{
delegate
?
.
tabsController
?(
tabsController
:
self
,
willCancelSelecting
:
viewControllers
[
targetIndex
])
tabBar
.
cancelLineTransition
(
isAnimated
:
false
)
MotionTransition
.
shared
.
cancel
(
isAnimated
:
false
)
}
if
.
left
==
recognizer
.
direction
||
.
right
==
recognizer
.
direction
{
view
.
removeGestureRecognizer
(
recognizer
)
if
internalSelect
(
at
:
nextIndex
,
isTriggeredByUserInteraction
:
true
,
selectTabItem
:
false
)
{
tabBar
.
startLineTransition
(
for
:
nextIndex
,
duration
:
0.35
)
targetIndex
=
nextIndex
}
}
else
{
let
progress
=
abs
(
translationX
/
view
.
bounds
.
width
)
tabBar
.
updateLineTransition
(
progress
)
MotionTransition
.
shared
.
update
(
Double
(
progress
))
}
default
:
guard
targetIndex
!=
-
1
else
{
return
}
let
progress
=
(
translationX
+
velocityX
)
/
view
.
bounds
.
width
let
isUserHandDirectionLeft
=
progress
<
0
let
isTargetHandDirectionLeft
=
targetIndex
>
selectedIndex
if
isUserHandDirectionLeft
==
isTargetHandDirectionLeft
&&
abs
(
progress
)
>
0.5
{
tabBar
.
finishLineTransition
()
MotionTransition
.
shared
.
finish
()
}
else
{
tabBar
.
cancelLineTransition
()
MotionTransition
.
shared
.
cancel
()
delegate
?
.
tabsController
?(
tabsController
:
self
,
willCancelSelecting
:
viewControllers
[
targetIndex
])
}
targetIndex
=
-
1
}
}
/// Prepares interactiveSwipeGesture.
func
prepareSwipeGesture
()
{
guard
nil
==
interactiveSwipeGesture
else
{
return
}
interactiveSwipeGesture
=
UIPanGestureRecognizer
(
target
:
self
,
action
:
#selector(
handleTransitionPan
)
)
container
.
addGestureRecognizer
(
interactiveSwipeGesture
!
)
}
/// Removes interactiveSwipeGesture.
func
removeSwipeGesture
()
{
guard
let
v
=
interactiveSwipeGesture
else
{
return
}
container
.
removeGestureRecognizer
(
v
)
interactiveSwipeGesture
=
nil
}
}
fileprivate
extension
TabsController
{
...
...
@@ -363,30 +434,6 @@ fileprivate extension TabsController {
}
}
fileprivate
extension
TabsController
{
/**
Handles the swipe gesture.
- Parameter gesture: A UIGestureRecognizer.
*/
@objc
func
handleSwipeGesture
(
gesture
:
UIGestureRecognizer
)
{
if
let
swipeGesture
=
gesture
as?
UISwipeGestureRecognizer
{
switch
swipeGesture
.
direction
{
case
.
right
:
guard
(
selectedIndex
-
1
)
>=
0
else
{
return
}
internalSelect
(
at
:
selectedIndex
-
1
,
isTriggeredByUserInteraction
:
true
,
selectTabItem
:
true
)
case
.
left
:
guard
(
selectedIndex
+
1
)
<
viewControllers
.
count
else
{
return
}
internalSelect
(
at
:
selectedIndex
+
1
,
isTriggeredByUserInteraction
:
true
,
selectTabItem
:
true
)
default
:
break
}
}
}
}
extension
TabsController
{
/**
Transitions to the view controller that is at the given index.
...
...
@@ -425,6 +472,7 @@ extension TabsController {
}
self
?
.
selectedIndex
=
index
self
?
.
tabBar
.
selectedTabItem
=
self
?
.
tabBar
.
tabItems
[
index
]
}
return
true
...
...
Sources/iOS/TransitionController.swift
View file @
0cddaeb7
...
...
@@ -131,26 +131,30 @@ open class TransitionController: ViewController {
open
func
transition
(
to
viewController
:
UIViewController
,
completion
:
((
Bool
)
->
Void
)?
=
nil
)
{
prepare
(
viewController
:
viewController
,
in
:
container
)
switch
motionTransitionType
{
case
.
auto
:
break
default
:
switch
viewController
.
motionTransitionType
{
case
.
auto
:
if
case
.
auto
=
viewController
.
motionTransitionType
{
viewController
.
motionTransitionType
=
motionTransitionType
default
:
break
}
}
view
.
isUserInteractionEnabled
=
false
MotionTransition
.
shared
.
transition
(
from
:
rootViewController
,
to
:
viewController
,
in
:
container
)
{
[
weak
self
,
viewController
=
viewController
,
completion
=
completion
]
(
isFinishing
)
in
MotionTransition
.
shared
.
transition
(
from
:
rootViewController
,
to
:
viewController
,
in
:
container
)
{
[
weak
self
]
isFinishing
in
guard
let
s
=
self
else
{
return
}
s
.
rootViewController
=
viewController
defer
{
s
.
view
.
isUserInteractionEnabled
=
true
completion
?(
isFinishing
)
}
guard
isFinishing
else
{
s
.
removeViewController
(
viewController
:
viewController
)
s
.
removeViewController
(
viewController
:
s
.
rootViewController
)
s
.
prepare
(
viewController
:
s
.
rootViewController
,
in
:
s
.
container
)
return
}
s
.
rootViewController
=
viewController
}
}
open
override
func
prepare
()
{
...
...
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