Compare commits

...

15 Commits

Author SHA1 Message Date
Shin Yamamoto 4edaad2cf4 Release 1.7.1 2019-11-21 21:17:11 +09:00
Shin Yamamoto 92fc0621e2 Merge pull request #292 from TadeasKriz/patch-1
Improve manual `show` and `hide` example.
2019-11-21 21:16:32 +09:00
Tadeas Kriz e9f4392c48 Improve manual show and hide example. 2019-11-20 15:56:17 +01:00
Shin Yamamoto 4df40becaf Merge pull request #290 from SCENEE/fix/addpanel
Pass parent to didMove(toParent:)
2019-11-20 10:36:22 +09:00
Shin Yamamoto ba11e7c7d7 Pass parent to didMove(toParent:)
4ad7f11 commit causes the wrong parameter.
2019-11-20 09:51:47 +09:00
Shin Yamamoto ae671f22c6 Merge pull request #288 from SCENEE/fix-swiftinterface-error
Rename the internal FloatingPanel object for .swiftinterface issue
2019-11-19 23:01:36 +09:00
Shin Yamamoto f22f58212b Clean up lines with only white spaces 2019-11-19 18:37:52 +09:00
Shin Yamamoto 54ff1c360d Rename 'FloatingPanel' type for '.swiftinterface' issue
See also https://forums.swift.org/t/frameworkname-is-not-a-member-type-of-frameworkname-errors-inside-swiftinterface/28962
2019-11-19 18:37:44 +09:00
dmytrofrolov1 772d6c3ef3 Improve the surface position evaluation and top scroll bouncing of content
* Evaluate the surface position approximately by 1px with a display scale
* Allow a top scroll bouncing of content without closing floating panel when a user scrolls it a lot
2019-11-19 18:29:20 +09:00
Shin Yamamoto a94c3b3c26 Merge pull request #287 from SCENEE/fix-module-stability
Enable the swift module interfaces
2019-11-15 22:07:01 +09:00
Shin Yamamoto d0ffc4ceb1 Enable the swift module interfaces 2019-11-15 14:32:33 +09:00
Shin Yamamoto 597ce487aa Merge pull request #275 from peka2/patch-1
Update README
2019-10-09 00:32:57 +09:00
peka2 87eb8d94fd Update README 2019-10-08 18:58:01 +09:00
Shin Yamamoto 4944fc516a Update README 2019-10-05 14:09:41 +09:00
Shin Yamamoto 7537384339 Merge pull request #273 from SCENEE/release-1.7.0
Release 1.7.0
2019-10-05 14:07:40 +09:00
10 changed files with 93 additions and 52 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.7.0"
s.version = "1.7.1"
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.
@@ -26,7 +26,7 @@
54CDC5D3215B6D5A007D205C /* FloatingPanelSurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */; };
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */; };
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */; };
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */; };
54CFBFC5215CD09C006B5735 /* FloatingPanelCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* FloatingPanelCore.swift */; };
54E740CD218AFD67005C1A34 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E740CC218AFD67005C1A34 /* AppDelegate.swift */; };
/* End PBXBuildFile section */
@@ -70,7 +70,7 @@
54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelSurfaceView.swift; sourceTree = "<group>"; };
54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBackdropView.swift; sourceTree = "<group>"; };
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelLayout.swift; sourceTree = "<group>"; };
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanel.swift; sourceTree = "<group>"; };
54CFBFC4215CD09C006B5735 /* FloatingPanelCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelCore.swift; sourceTree = "<group>"; };
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FloatingPanelTesting.app; sourceTree = BUILT_PRODUCTS_DIR; };
54E740CC218AFD67005C1A34 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
54E740D8218AFD6A005C1A34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -129,7 +129,7 @@
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */,
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */,
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */,
54CFBFC4215CD09C006B5735 /* FloatingPanelCore.swift */,
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */,
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */,
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */,
@@ -311,7 +311,7 @@
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */,
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */,
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */,
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */,
54CFBFC5215CD09C006B5735 /* FloatingPanelCore.swift in Sources */,
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */,
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */,
@@ -484,6 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
@@ -514,6 +515,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
@@ -197,7 +197,7 @@ open class FloatingPanelController: UIViewController {
private var _contentViewController: UIViewController?
private(set) var floatingPanel: FloatingPanel!
private(set) var floatingPanel: FloatingPanelCore!
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
private var safeAreaInsetsObservation: NSKeyValueObservation?
private let modalTransition = FloatingPanelModalTransition()
@@ -220,7 +220,7 @@ open class FloatingPanelController: UIViewController {
modalPresentationStyle = .custom
transitioningDelegate = modalTransition
floatingPanel = FloatingPanel(self,
floatingPanel = FloatingPanelCore(self,
layout: fetchLayout(for: self.traitCollection),
behavior: fetchBehavior(for: self.traitCollection))
}
@@ -229,7 +229,7 @@ open class FloatingPanelController: UIViewController {
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
floatingPanel.behavior = fetchBehavior(for: self.traitCollection)
}
// MARK:- Overrides
/// Creates the view that the controller manages.
@@ -425,9 +425,9 @@ open class FloatingPanelController: UIViewController {
show(animated: animated) { [weak self] in
guard let `self` = self else { return }
#if swift(>=4.2)
self.didMove(toParent: self)
self.didMove(toParent: parent)
#else
self.didMove(toParentViewController: self)
self.didMove(toParentViewController: parent)
#endif
}
}
@@ -8,7 +8,7 @@ import UIKit.UIGestureRecognizerSubclass // For Xcode 9.4.1
///
/// FloatingPanel presentation model
///
class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
class FloatingPanelCore: NSObject, UIGestureRecognizerDelegate {
// MUST be a weak reference to prevent UI freeze on the presentation modally
weak var viewcontroller: FloatingPanelController?
@@ -81,7 +81,6 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
super.init()
panGestureRecognizer.floatingPanel = self
surfaceView.addGestureRecognizer(panGestureRecognizer)
panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
panGestureRecognizer.delegate = self
@@ -203,7 +202,10 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
default:
// Should recognize tap/long press gestures in parallel when the surface view is at an anchor position.
let surfaceFrame = surfaceView.layer.presentation()?.frame ?? surfaceView.frame
return surfaceFrame.minY == layoutAdapter.positionY(for: state)
let surfaceY = surfaceFrame.minY
let adapterY = layoutAdapter.positionY(for: state)
return abs(surfaceY - adapterY) < (1.0 / surfaceView.traitCollection.displayScale)
}
}
@@ -230,7 +232,13 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
// the panel's pan gesture if not returns false
if let scrollGestureRecognizers = scrollView.gestureRecognizers,
scrollGestureRecognizers.contains(otherGestureRecognizer) {
return false
switch otherGestureRecognizer {
case scrollView.panGestureRecognizer:
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
return allowScrollPanGesture(at: CGPoint(x: 0.0, y: offset))
default:
return false
}
}
}
@@ -279,7 +287,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
let location = panGesture.location(in: surfaceView)
let belowTop = surfaceView.presentationFrame.minY > layoutAdapter.topY
let surfaceMinY = surfaceView.presentationFrame.minY
let adapterTopY = layoutAdapter.topY
let belowTop = surfaceMinY > (adapterTopY + (1.0 / surfaceView.traitCollection.displayScale))
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
log.debug("scroll gesture(\(state):\(panGesture.state)) --",
@@ -330,11 +340,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
} else {
if state == layoutAdapter.topMostState {
// Hide a scroll indicator just before starting an interaction by swiping a panel down.
if offset < 0, velocity.y > 0 {
if velocity.y > 0, !allowScrollPanGesture(at: CGPoint(x: 0.0, y: offset)) {
lockScrollView()
}
// Show a scroll indicator when an animation is interrupted at the top and content is scrolled up
if offset > 0, velocity.y < 0 {
if velocity.y < 0, allowScrollPanGesture(at: CGPoint(x: 0.0, y: offset)) {
unlockScrollView()
}
@@ -565,7 +575,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
return
}
stopScrollDeceleration = (surfaceView.frame.minY > layoutAdapter.topY) // Projecting the dragging to the scroll dragging or not
stopScrollDeceleration = surfaceView.frame.minY > (layoutAdapter.topY + (1.0 / surfaceView.traitCollection.displayScale)) // Projecting the dragging to the scroll dragging or not
if stopScrollDeceleration {
DispatchQueue.main.async { [weak self] in
guard let `self` = self else { return }
@@ -870,10 +880,17 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate {
// Must use setContentOffset(_:animated) to force-stop deceleration
scrollView?.setContentOffset(contentOffset, animated: false)
}
private func allowScrollPanGesture(at contentOffset: CGPoint) -> Bool {
if state == layoutAdapter.topMostState {
return contentOffset.y <= -30.0 || contentOffset.y > 0
}
return false
}
}
class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
fileprivate weak var floatingPanel: FloatingPanel?
fileprivate weak var floatingPanel: FloatingPanelCore?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if floatingPanel?.animator != nil {
@@ -885,7 +902,7 @@ class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
return super.delegate
}
set {
guard newValue is FloatingPanel else {
guard newValue is FloatingPanelCore else {
let exception = NSException(name: .invalidArgumentException,
reason: "FloatingPanelController's built-in pan gesture recognizer must have its controller as its delegate.",
userInfo: nil)
@@ -94,6 +94,7 @@ public protocol FloatingPanelLayout: class {
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat
var positionReference: FloatingPanelLayoutReference { get }
}
public extension FloatingPanelLayout {
@@ -34,7 +34,7 @@ public class FloatingPanelSurfaceView: UIView {
/// A root view of a content view controller
public weak var contentView: UIView!
/// The content insets specifying the insets around the content view.
public var contentInsets: UIEdgeInsets = .zero {
didSet {
@@ -101,7 +101,7 @@ public class FloatingPanelSurfaceView: UIView {
private lazy var containerViewHeightConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
private lazy var containerViewLeftConstraint = containerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0)
private lazy var containerViewRightConstraint = containerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0)
/// The content view top constraint
private var contentViewTopConstraint: NSLayoutConstraint?
/// The content view left constraint
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.7.0</string>
<string>1.7.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
@@ -238,7 +238,7 @@ class FloatingPanelLayoutTests: XCTestCase {
}
private typealias LayoutSegmentTestParameter = (UInt, pos: CGFloat, forwardY: Bool, lower: FloatingPanelPosition?, upper: FloatingPanelPosition?)
private func assertLayoutSegment(_ floatingPanel: FloatingPanel, with params: [LayoutSegmentTestParameter]) {
private func assertLayoutSegment(_ floatingPanel: FloatingPanelCore, with params: [LayoutSegmentTestParameter]) {
params.forEach { (line, pos, forwardY, lowr, upper) in
let segument = floatingPanel.layoutAdapter.segument(at: pos, forward: forwardY)
XCTAssertEqual(segument.lower, lowr, line: line)
+1 -1
View File
@@ -523,7 +523,7 @@ private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
}
private typealias TestParameter = (UInt, CGFloat,CGPoint, FloatingPanelPosition)
private func assertTargetPosition(_ floatingPanel: FloatingPanel, with params: [TestParameter]) {
private func assertTargetPosition(_ floatingPanel: FloatingPanelCore, with params: [TestParameter]) {
params.forEach { (line, pos, velocity, result) in
floatingPanel.surfaceView.frame.origin.y = pos
XCTAssertEqual(floatingPanel.targetPosition(from: pos, with: velocity), result, line: line)
+48 -27
View File
@@ -181,41 +181,60 @@ FloatingPanelController.view (FloatingPanelPassThroughView)
### Show/Hide a floating panel in a view with your view hierarchy
If you need more control over showing and hiding the floating panel, you can forgo the `addPanel` and `removePanelFromParent` methods. These methods are a convenience wrapper for **FloatingPanel**'s `show` and `hide` methods along with some required setup.
There are two ways to work with the `FloatingPanelController`:
1. Add it to the hierarchy once and then call `show` and `hide` methods to make it appear/disappear.
2. Add it to the hierarchy when needed and remove afterwards.
The following example shows how to add the controller to your `UIViewController` and how to remove it. Make sure that you never add the same `FloatingPanelController` to the hierarchy before removing it.
**NOTE**: `self.` prefix is not required, nor recommended. It's used here to make it clearer where do the functions used come from. `self` is an instance of a custom UIViewController in your code.
```swift
// Add the controller and the managed views to a view controller.
// From the second time, just call `show(animated:completion)`.
view.addSubview(fpc.view)
// Add the floating panel view to the controller's view on top of other views.
self.view.addSubview(fpc.view)
// REQUIRED. It makes the floating panel view have the same size as the controller's view.
fpc.view.frame = self.view.bounds
fpc.view.frame = view.bounds // MUST
// In addition, Auto Layout constraints are highly recommended.
// Because it makes the layout more robust on trait collection change.
//
// fpc.view.translatesAutoresizingMaskIntoConstraints = false
// NSLayoutConstraint.activate([...])
//
// Constraint the fpc.view to all four edges of your controller's view.
// It makes the layout more robust on trait collection change.
fpc.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
fpc.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0),
fpc.view.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0.0),
fpc.view.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0.0),
fpc.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0),
])
parent.addChild(fpc)
// Add the floating panel controller to the controller hierarchy.
self.addChild(fpc)
// Show a floating panel to the initial position defined in your `FloatingPanelLayout` object.
// Show the floating panel at the initial position defined in your `FloatingPanelLayout` object.
fpc.show(animated: true) {
// Only for the first time
self.didMove(toParent: self)
}
...
// Hide it
fpc.hide(animated: true) {
// Remove it if needed
self.willMove(toParent: nil)
self.view.removeFromSuperview()
self.removeFromParent()
// Inform the floating panel controller that the transition to the controller hierarchy has completed.
fpc.didMove(toParent: self)
}
```
NOTE: `FloatingPanelController` wraps `show`/`hide` with `addPanel`/`removePanelFromParent` for easy-to-use. But `show`/`hide` are more convenience for your app.
After you add the `FloatingPanelController` as seen above, you can call `fpc.show(animated: true) { }` to show the panel and `fpc.hide(animated: true) { }` to hide it.
To remove the `FloatingPanelController` from the hierarchy, follow the example below.
```swift
// Inform the panel controller that it will be removed from the hierarchy.
fpc.willMove(toParent: nil)
// Hide the floating panel.
fpc.hide(animated: true) {
// Remove the floating panel view from your controller's view.
fpc.view.removeFromSuperview()
// Remove the floating panel controller from the controller hierarchy.
fpc.removeFromParent()
}
```
### Scale the content view when the surface position changes
@@ -227,6 +246,8 @@ fpc.contentMode = .fitToBounds
Otherwise, `FloatingPanelController` fixes the content by the height of the top most position.
✏️ In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
### Customize the layout with `FloatingPanelLayout` protocol
#### Change the initial position and height
@@ -378,7 +399,7 @@ This allows full projectional panel behavior. For example, a user can swipe up a
```swift
class FloatingPanelBehavior: FloatingPanelBehavior {
...
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
return true
}
}