Compare commits

..

17 Commits

Author SHA1 Message Date
Shin Yamamoto 0bb8342873 Release v1.2.2 2018-12-03 09:25:09 +09:00
Shin Yamamoto d4f2a88fdf Merge pull request #71 from SCENEE/fix-bugs
Fix scroll tracking bugs
2018-12-01 15:45:41 +09:00
Shin Yamamoto af45d39841 Fix panning at grabber Area 2018-12-01 12:36:47 +09:00
Shin Yamamoto 66f5b0b210 Fix an invalid content offset on height change 2018-12-01 11:08:33 +09:00
Shin Yamamoto 4a3b79f1b4 Update README 2018-11-29 13:06:27 +09:00
Shin Yamamoto 12a100def8 Merge pull request #68 from SCENEE/release-1.2.1
Release v1.2.1
2018-11-26 13:20:24 +09:00
Shin Yamamoto 47971f607a Release v1.2.1 2018-11-26 09:40:14 +09:00
Shin Yamamoto 03a4d342a3 Merge pull request #66 from Galeas/fix-remove-backdrop-view-on-dismiss
Fix remove backdrop view on dismiss
2018-11-25 11:11:22 +09:00
Evgeniy Branitsky 4f5abfefec Removing backdrop view 2018-11-24 11:21:56 +03:00
Shin Yamamoto e1ee3c06e8 Merge pull request #62 from SCENEE/feat-make-animator-interruptible
Make the animated interaction interruptible
2018-11-22 20:21:11 +09:00
Shin Yamamoto 17ba704472 Make the animated interaction interruptible 2018-11-22 14:26:18 +09:00
Shin Yamamoto e5391fa1f4 Merge pull request #61 from SCENEE/improve-delegate
Improve delegate
2018-11-22 13:54:02 +09:00
Shin Yamamoto c0647017b5 Add FloatingPanelControllerDelegate.floatingPanelDidChangePosition(_:) 2018-11-21 09:10:50 +09:00
Shin Yamamoto 3686bb4b44 Clean up code for scrollGestureRecognizers 2018-11-20 11:08:23 +09:00
Shin Yamamoto 76c8ca4b20 Merge pull request #56 from SCENEE/fix-gesture-handling
Fix the gesture handling
2018-11-20 11:07:14 +09:00
Shin Yamamoto c53e64027b Merge pull request #45 from SCENEE/release-1.2.0
Release v1.2.0
2018-11-17 10:12:10 +09:00
Shin Yamamoto 281504c9c6 Fix the gesture handling
* Fix a detection of a long press gesture in content VC
* Fix a SwipeActionPanGesture is not working in the tracking scroll
    * Update DebugTableViewController to test it
2018-11-17 09:09:07 +09:00
5 changed files with 142 additions and 31 deletions
@@ -409,6 +409,15 @@ class DebugTableViewController: UIViewController, UITableViewDataSource, UITable
cell.textLabel?.text = items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
return [
UITableViewRowAction(style: .destructive, title: "Delete", handler: { (action, path) in
self.items.remove(at: path.row)
tableView.deleteRows(at: [path], with: .automatic)
}),
]
}
}
class DetailViewController: UIViewController {
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.2.0"
s.version = "1.2.2"
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
s.description = <<-DESC
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
+75 -24
View File
@@ -33,7 +33,10 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
unowned let viewcontroller: FloatingPanelController
private(set) var state: FloatingPanelPosition = .tip
private(set) var state: FloatingPanelPosition = .tip {
didSet { viewcontroller.delegate?.floatingPanelDidChangePosition(viewcontroller) }
}
private var isBottomState: Bool {
let remains = layoutAdapter.layout.supportedPositions.filter { $0.rawValue > state.rawValue }
return remains.count == 0
@@ -175,7 +178,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
/* log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */
return otherGestureRecognizer == scrollView?.panGestureRecognizer
// all gestures of the tracking scroll view should be recognized in parallel
// and handle them in self.handle(panGesture:)
return scrollView?.gestureRecognizers?.contains(otherGestureRecognizer) ?? false
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
@@ -183,28 +188,45 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
/* log.debug("shouldBeRequiredToFailBy", otherGestureRecognizer) */
// Do not begin any gestures excluding the tracking scrollView's pan gesture
// until the pan gesture fails
if otherGestureRecognizer == scrollView?.panGestureRecognizer {
// The tracking scroll view's gestures should begin without waiting for the pan gesture failure.
// `scrollView.gestureRecognizers` can contains the following gestures
// * UIScrollViewDelayedTouchesBeganGestureRecognizer
// * UIScrollViewPanGestureRecognizer (scrollView.panGestureRecognizer)
// * _UIDragAutoScrollGestureRecognizer
// * _UISwipeActionPanGestureRecognizer
// * UISwipeDismissalGestureRecognizer
if let scrollView = scrollView,
let scrollGestureRecognizers = scrollView.gestureRecognizers,
scrollGestureRecognizers.contains(otherGestureRecognizer) {
return false
} else {
return true
}
// Long press gesture should begin without waiting for the pan gesture failure.
if otherGestureRecognizer is UILongPressGestureRecognizer {
return false
}
// Do not begin any other gestures until the pan gesture fails.
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer == panGesture else { return false }
/* log.debug("shouldRequireFailureOf", otherGestureRecognizer) */
log.debug("shouldRequireFailureOf", otherGestureRecognizer)
// Do not begin the pan gesture until any other gestures fail except for
// the tracking scrollView's pan gesture and other gestures.
// Should begin the pan gesture without waiting for the tracking scroll view's gestures.
// `scrollView.gestureRecognizers` can contains the following gestures
// * UIScrollViewDelayedTouchesBeganGestureRecognizer
// * UIScrollViewPanGestureRecognizer (scrollView.panGestureRecognizer)
// * _UIDragAutoScrollGestureRecognizer
// * _UISwipeActionPanGestureRecognizer
// * UISwipeDismissalGestureRecognizer
if let scrollView = scrollView {
if scrollView.panGestureRecognizer == otherGestureRecognizer {
return false
}
// For short scroll contents
if scrollView.gestureRecognizers?.contains(otherGestureRecognizer) ?? false {
// On short contents scroll, `_UISwipeActionPanGestureRecognizer` blocks
// the panel's pan gesture if not returns false
if let scrollGestureRecognizers = scrollView.gestureRecognizers,
scrollGestureRecognizers.contains(otherGestureRecognizer) {
return false
}
}
@@ -215,12 +237,22 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
is UIRotationGestureRecognizer,
is UIScreenEdgePanGestureRecognizer,
is UIPinchGestureRecognizer:
// Do not begin the pan gesture until these gestures fail
return true
default:
// Should begin the pan gesture witout waiting tap/long press gestures fail
return false
}
}
var grabberAreaFrame: CGRect {
let grabberAreaFrame = CGRect(x: surfaceView.bounds.origin.x,
y: surfaceView.bounds.origin.y,
width: surfaceView.bounds.width,
height: FloatingPanelSurfaceView.topGrabberBarHeight * 2)
return grabberAreaFrame
}
// MARK: - Gesture handling
private let offsetThreshold: CGFloat = 5.0 // Optimal value from testing
@objc func handle(panGesture: UIPanGestureRecognizer) {
@@ -242,8 +274,14 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
if surfaceView.frame.minY > layoutAdapter.topY {
switch state {
case .full:
// Prevent over scrolling from scroll top in moving the panel from full.
scrollView.contentOffset.y = scrollView.contentOffsetZero.y
let point = panGesture.location(in: surfaceView)
if grabberAreaFrame.contains(point) {
// Preserve the current content offset in moving from full.
scrollView.contentOffset.y = initialScrollOffset.y
} else {
// Prevent over scrolling in moving from full.
scrollView.contentOffset.y = scrollView.contentOffsetZero.y
}
case .half, .tip:
guard scrollView.isDecelerating == false else {
// Don't fix the scroll offset in animating the panel to half and tip.
@@ -275,6 +313,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
return
}
if let animator = self.animator {
animator.stopAnimation(true)
self.animator = nil
}
switch panGesture.state {
case .began:
panningBegan()
@@ -294,17 +337,26 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
}
private func shouldScrollViewHandleTouch(_ scrollView: UIScrollView?, point: CGPoint, velocity: CGPoint) -> Bool {
let grabberBarFrame = CGRect(x: surfaceView.bounds.origin.x,
y: surfaceView.bounds.origin.y,
width: surfaceView.bounds.width,
height: FloatingPanelSurfaceView.topGrabberBarHeight * 2)
// When no scrollView, nothing to handle.
guard let scrollView = scrollView else { return false }
// For _UISwipeActionPanGestureRecognizer
if let scrollGestureRecognizers = scrollView.gestureRecognizers {
for gesture in scrollGestureRecognizers {
guard gesture.state == .began || gesture.state == .changed
else { continue }
if gesture != scrollView.panGestureRecognizer {
return true
}
}
}
guard
let scrollView = scrollView, // When no scrollView, nothing to handle.
state == .full, // When not .full, don't scroll.
interactionInProgress == false, // When interaction already in progress, don't scroll.
scrollView.frame.contains(point), // When point not in scrollView, don't scroll.
!grabberBarFrame.contains(point) // When point within grabber area, don't scroll.
!grabberAreaFrame.contains(point) // When point within grabber area, don't scroll.
else {
return false
}
@@ -442,7 +494,6 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
let targetY = layoutAdapter.positionY(for: targetPosition)
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: max(min(velocity.y/distance, 30.0), -30.0)) : .zero
let animator = behavior.interactionAnimator(self.viewcontroller, to: targetPosition, with: velocityVector)
animator.isInterruptible = false // To prevent a backdrop color's punk
animator.addAnimations { [weak self] in
guard let self = self else { return }
if self.state == targetPosition {
@@ -12,6 +12,8 @@ public protocol FloatingPanelControllerDelegate: class {
// if it returns nil, FloatingPanelController uses the default behavior
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior?
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) // changed the settled position in the model layer
func floatingPanelDidMove(_ vc: FloatingPanelController) // any offset changes
// called on start of dragging (may require some time and or distance to move)
@@ -34,6 +36,7 @@ public extension FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
return nil
}
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {}
func floatingPanelDidMove(_ vc: FloatingPanelController) {}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {}
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {}
@@ -195,7 +198,13 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
}
private func update(safeAreaInsets: UIEdgeInsets) {
// preserve the current content offset
let contentOffset = scrollView?.contentOffset
floatingPanel.safeAreaInsets = safeAreaInsets
scrollView?.contentOffset = contentOffset ?? .zero
switch contentInsetAdjustmentBehavior {
case .always:
scrollView?.contentInset = adjustedContentInsets
@@ -289,6 +298,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
self.willMove(toParent: nil)
self.view.removeFromSuperview()
self.backdropView.removeFromSuperview()
self.removeFromParent()
completion?()
}
+47 -6
View File
@@ -24,14 +24,16 @@ The new interface displays the related contents and utilities in parallel as a u
- [Carthage](#carthage)
- [Getting Started](#getting-started)
- [Usage](#usage)
- [Customize the layout of a floating panel with `FloatingPanelLayout` protocol](#customize-the-layout-of-a-floating-panel-with--floatingpanellayout-protocol)
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
- [Change the initial position and height](#change-the-initial-position-and-height)
- [Support your landscape layout](#support-your-landscape-layout)
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
- [Add tap gestures to the surface or backdrop views](#add-tap-gestures-to-the-surface-or-backdrop-views)
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
- [Move a position with an animation](#move-a-position-with-an-animation)
- [Make your contents correspond with a floating panel behavior](#make-your-contents-correspond-with-a-floating-panel-behavior)
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
- [Notes](#notes)
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
- [FloatingPanelSurfaceView's issue on iOS 10](#floatingpanelsurfaceviews-issue-on-ios-10)
@@ -119,7 +121,7 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
## Usage
### Customize the layout of a floating panel with `FloatingPanelLayout` protocol
### Customize the layout with `FloatingPanelLayout` protocol
#### Change the initial position and height
@@ -211,6 +213,45 @@ class FloatingPanelStocksBehavior: FloatingPanelBehavior {
}
```
### Use a custom grabber handle
```swift
class ViewController: UIViewController {
...
override func viewDidLoad() {
...
let myGrabberHandleView = MyGrabberHandleView()
fpc.surfaceView.grabberHandle.isHidden = true
fpc.surfaceView.addSubview(myGrabberHandleView)
}
...
}
```
### Add tap gestures to the surface or backdrop views
```swift
class ViewController: UIViewController, FloatingPanelControllerDelegate {
...
override func viewDidLoad() {
...
surfaceTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleSurface(tapGesture:)))
fpc.surfaceView.addGestureRecognizer(surfaceTapGesture)
backdropTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
fpc.backdropView.addGestureRecognizer(backdropTapGesture)
surfaceTapGesture.isEnabled = (fpc.position == .tip)
...
}
...
// Enable `surfaceTapGesture` only at `tip` position
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
surfaceTapGesture.isEnabled = (vc.position == .tip)
}
}
```
### Create an additional floating panel for a detail
```swift
@@ -257,7 +298,7 @@ In the following example, I move a floating panel to full or half position while
}
```
### Make your contents correspond with a floating panel behavior
### Work your contents together with a floating panel behavior
```swift
class ViewController: UIViewController, FloatingPanelControllerDelegate {
@@ -311,9 +352,9 @@ class ViewController: UIViewController {
A `FloatingPanelController` object proxies an action for `show(_:sender)` to the master VC. That's why the master VC can handle a destination view controller of a 'Show' or 'Show Detail' segue and you can hook `show(_:sender)` to show a secondally floating panel set the destination view controller to the content.
It's a greate way to decouple between a floating panel and the content VC.
It's a great way to decouple between a floating panel and the content VC.
### FloatingPanelSurfaceView's issue on iOS 10
### FloatingPanelSurfaceView's issue on iOS 10
* On iOS 10, `FloatingPanelSurfaceView.cornerRadius` isn't not automatically masked with the top rounded corners because of UIVisualEffectView issue. See https://forums.developer.apple.com/thread/50854.
So you need to draw top rounding corners of your content. Here is an example in Examples/Maps.