Compare commits

..

26 Commits

Author SHA1 Message Date
Shin Yamamoto ca7596e1ca Version 1.7.6 2020-09-19 11:09:30 +09:00
Shin Yamamoto 007f9af3eb Enable the removal interaction at any positions upon the conditions (#335)
If a library consumer allows a panel projectable movement with the
FloatingPanelBehavior object, the panel is able to invoke the removal
interaction when the next moving position projected the momentum is
hidden.
2020-09-03 23:07:33 +09:00
Michal Raška da4e1d26d3 Fix quick pull down (#385) 2020-09-03 21:41:16 +09:00
Shin Yamamoto 2ce1375ce7 Fix an issue where keyboard opens above image picker (#381)
This issue was reported in
https://github.com/SCENEE/FloatingPanel/issues/369.

The cause of that is the containerViewWillLayoutSubviews() of
FloatingPanelPresentationController is called in presenting a
UIImagePickerViewController. As a result, a FloatingPanelController
view is added unnecessarily.

I don't know the reason why the method is called, but this patch
resolves the issue.

By the way, the issue doesn't happen when a FloatingPanelController
shows as a child view controller
2020-09-03 20:42:21 +09:00
Greg Hazel 28c384aa0d use 'prominent' blur effect (#379)
'prominent' blue effect is adaptive to dark mode.
2020-08-10 12:36:47 +09:00
Christopher Truman 9c71a47d9b Small typo fixes (#378) 2020-08-10 09:55:10 +09:00
Leko Murphy 8903e4e610 don't remove panel on view disappearance (#367) 2020-07-11 09:46:41 +09:00
Shin Yamamoto 5634de2eee Merge pull request #371 from knchst/modify/readme
Modify README.md sample code.
2020-07-11 09:42:16 +09:00
Kenichi Saito 1957ae3919 Modify README 2020-07-09 19:59:20 +09:00
Shin Yamamoto a4f8c0528c Merge pull request #356 from SCENEE/release-1.7.5
Release 1.7.5
2020-06-04 08:30:31 +09:00
Shin Yamamoto e4548b26bd Release 1.7.5 2020-06-03 22:33:02 +09:00
Shin Yamamoto d540b1ddde Fix the panel behavior in a sheet modal (#358)
* Add "Show Panel in Sheet Modal" sample
* Fix the behavior in a sheet modal
2020-06-03 22:31:30 +09:00
Shin Yamamoto aaeb752911 No need to recognize both of the pan gesture and dismiss gesuter of sheet modal 2020-05-30 10:11:12 +09:00
Grigory 966caad519 Fix the constraints break on fitToBounds mode (#359) 2020-05-30 09:44:58 +09:00
Shin Yamamoto a62c3a23dc Fix some view controllers in Samples.app for a sheet modal 2020-05-23 09:07:01 +09:00
Shin Yamamoto 7bbc3d5910 fix the behavior in a sheet modal 2020-05-23 08:12:01 +09:00
Shin Yamamoto e2afb1e22f Add "Show Panel in Sheet Modal" sample 2020-05-23 08:11:30 +09:00
Shin Yamamoto 8cca1178fd fix {top,bottom} constant's boundary in updating panel interactively (#352)
Because {top,bottom}Y can be {less,more} than the SafeArea/Superview bounds,
if a minus value is set to the inset for {top,bottom} most position.
2020-05-21 08:59:52 +09:00
Shin Yamamoto a09a0e9e32 fix the animation velocity's sign (#354)
Using a directional distance to calculate an animation velocity fixes an
issue where a panel's animation was wrong when a user swipes up a panel
at the top most position.
2020-05-16 09:18:59 +09:00
Shin Yamamoto 43c76faa20 fix invalid safearea insets in a table view with static cells (#353)
See also https://github.com/SCENEE/FloatingPanel/issues/330
2020-05-16 09:17:34 +09:00
Shin Yamamoto 5787a350ab fix the memory leak of FloatingPanelController object (#350) 2020-05-15 07:41:18 +09:00
Shin Yamamoto 9abb80de64 Fix an invalid indicator insets of the tracking scroll view (#346) 2020-04-29 14:22:34 +09:00
Shin Yamamoto 7d90458d99 Support the initial hidden position not including the supported positions (#345) 2020-04-29 14:20:49 +09:00
Shin Yamamoto 5d5f14acd8 fix a build error on Xcode 11.4 (#337) 2020-03-30 17:52:00 +09:00
Shin Yamamoto bed519f0c0 Merge pull request #326 from SCENEE/release-1.7.4
Release 1.7.4
2020-02-29 18:52:15 +09:00
Shin Yamamoto 7c47e2e20e Release 1.7.4 2020-02-29 13:25:11 +09:00
12 changed files with 135 additions and 61 deletions
@@ -27,7 +27,7 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<blurEffect style="light"/>
<blurEffect style="prominent"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -232,7 +232,7 @@
<constraint firstItem="Zcj-SE-gb8" firstAttribute="leading" secondItem="ED1-gT-FBj" secondAttribute="leading" id="wMb-L2-Z0W"/>
</constraints>
</view>
<blurEffect style="extraLight"/>
<blurEffect style="prominent"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+3 -3
View File
@@ -274,12 +274,12 @@ class SearchHeaderView: UIView {
extension UISearchBar {
func setSearchText(fontSize: CGFloat) {
#if swift(>=5.1) // Xcode 11 or later
if #available(iOS 13, *) {
let font = searchTextField.font
searchTextField.font = font?.withSize(fontSize)
#else
} else {
let textField = value(forKey: "_searchField") as! UITextField
textField.font = textField.font?.withSize(fontSize)
#endif
}
}
}
+34 -18
View File
@@ -19,6 +19,7 @@ class SampleListViewController: UIViewController {
case showModal
case showPanelModal
case showMultiPanelModal
case showPanelInSheetModal
case showTabBar
case showPageView
case showPageContentView
@@ -37,6 +38,7 @@ class SampleListViewController: UIViewController {
case .showModal: return "Show Modal"
case .showPanelModal: return "Show Panel Modal"
case .showMultiPanelModal: return "Show Multi Panel Modal"
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
case .showTabBar: return "Show Tab Bar"
case .showPageView: return "Show Page View"
case .showPageContentView: return "Show Page Content View"
@@ -56,6 +58,7 @@ class SampleListViewController: UIViewController {
case .showDetail: return "DetailViewController"
case .showModal: return "ModalViewController"
case .showMultiPanelModal: return nil
case .showPanelInSheetModal: return nil
case .showPanelModal: return nil
case .showTabBar: return "TabBarViewController"
case .showPageView: return nil
@@ -350,6 +353,20 @@ extension SampleListViewController: UITableViewDelegate {
let fpc = MultiPanelController()
self.present(fpc, animated: true, completion: nil)
case .showPanelInSheetModal:
let fpc = FloatingPanelController()
let contentVC = UIViewController()
fpc.set(contentViewController: contentVC)
fpc.delegate = self
fpc.surfaceView.cornerRadius = 38.5
fpc.surfaceView.shadowHidden = false
fpc.isRemovalInteractionEnabled = true
let mvc = UIViewController()
mvc.view.backgroundColor = UIColor(displayP3Red: 2/255, green: 184/255, blue: 117/255, alpha: 1.0)
fpc.addPanel(toParent: mvc)
self.present(mvc, animated: true, completion: nil)
case .showContentInset:
let contentViewController = UIViewController()
contentViewController.view.backgroundColor = .green
@@ -865,8 +882,7 @@ class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
var isNewlayout: Bool = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
override func viewDidLoad() {
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
@@ -886,8 +902,8 @@ class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
fpc.addPanel(toParent: self, belowView: safeAreaView)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Remove FloatingPanel from a view
fpc.removePanelFromParent(animated: false)
}
@@ -948,20 +964,15 @@ class TabBarContentViewController: UIViewController {
}
}
}
var fpc: FloatingPanelController!
lazy var fpc = FloatingPanelController()
var consoleVC: DebugTextViewController!
var threeLayout: ThreeTabBarPanelLayout!
var tab3Mode: Tab3Mode = .changeAutoLayout
var switcherLabel: UILabel!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Initialize FloatingPanelController
fpc = FloatingPanelController()
override func viewDidLoad() {
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.cornerRadius = 6.0
fpc.surfaceView.shadowHidden = false
@@ -1011,12 +1022,6 @@ class TabBarContentViewController: UIViewController {
fpc.updateLayout()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove FloatingPanel from a view
fpc.removePanelFromParent(animated: false)
}
// MARK: - Action
@IBAction func close(sender: UIButton) {
@@ -1350,15 +1355,26 @@ final class MultiPanelController: FloatingPanelController, FloatingPanelControll
private final class FirstViewLayout: FloatingPanelLayout {
let initialPosition: FloatingPanelPosition = .full
let supportedPositions: Set<FloatingPanelPosition> = [.full]
let supportedPositions: Set<FloatingPanelPosition> = [.full, .half]
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 40.0
case .half: return 200.0
default: return nil
}
}
}
private final class FirstViewBehavior: FloatingPanelBehavior {
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
return true
}
}
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
return FirstViewBehavior()
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
return FirstViewLayout()
}
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.7.3"
s.version = "1.7.6"
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.
@@ -371,7 +371,6 @@ open class FloatingPanelController: UIViewController {
switch contentInsetAdjustmentBehavior {
case .always:
scrollView?.contentInset = adjustedContentInsets
scrollView?.scrollIndicatorInsets = adjustedContentInsets
default:
break
}
@@ -422,9 +421,11 @@ open class FloatingPanelController: UIViewController {
// inset's update expectedly.
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
// That's why it needs the observation to keep `adjustedContentInsets` correct.
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (vc, change) in
guard change.oldValue != change.newValue else { return }
self?.update(safeAreaInsets: vc.layoutInsets)
safeAreaInsetsObservation = self.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
// Use `self.view.safeAreaInsets` becauese `change.newValue` can be nil in particular case when
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
guard let `self` = self, change.oldValue != self.view.safeAreaInsets else { return }
self.update(safeAreaInsets: self.view.safeAreaInsets)
}
} else {
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
@@ -669,7 +670,8 @@ public extension UIViewController {
}
// Call dismiss(animated:completion:) to FloatingPanelController directly
if let fpc = self as? FloatingPanelController {
if fpc.presentingViewController != nil {
// When a panel is presented modally and it's not a child view controller of the presented view controller.
if fpc.presentingViewController != nil, fpc.parent == nil {
self.fp_original_dismiss(animated: flag, completion: completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
+28 -10
View File
@@ -97,7 +97,7 @@ class FloatingPanelCore: NSObject, UIGestureRecognizerDelegate {
}
private func move(from: FloatingPanelPosition, to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
assert(layoutAdapter.isValid(to), "Can't move to '\(to)' position because it's not valid in the layout")
assert(layoutAdapter.validPositions.contains(to), "Can't move to '\(to)' position because it's not valid in the layout")
guard let vc = viewcontroller else {
completion?()
return
@@ -221,6 +221,11 @@ class FloatingPanelCore: NSObject, UIGestureRecognizerDelegate {
return true
}
}
if #available(iOS 11.0, *),
otherGestureRecognizer.name == "_UISheetInteractionBackgroundDismissRecognizer" {
// The dismiss gesture of a sheet modal should not begin until the pan gesture fails.
return true
}
return false
}
@@ -271,6 +276,11 @@ class FloatingPanelCore: NSObject, UIGestureRecognizerDelegate {
is UIRotationGestureRecognizer,
is UIScreenEdgePanGestureRecognizer,
is UIPinchGestureRecognizer:
if #available(iOS 11.0, *),
otherGestureRecognizer.name == "_UISheetInteractionBackgroundDismissRecognizer" {
// Should begin the pan gesture without waiting the dismiss gesture of a sheet modal.
return false
}
// Do not begin the pan gesture until these gestures fail
return true
default:
@@ -607,16 +617,24 @@ class FloatingPanelCore: NSObject, UIGestureRecognizerDelegate {
endInteraction(for: targetPosition)
if isRemovalInteractionEnabled, isBottomState {
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: min(velocity.y/distance, behavior.removalVelocity)) : .zero
if isRemovalInteractionEnabled {
let velocityVector: CGVector
if distance == 0 {
velocityVector = .zero
} else {
let dy = min(velocity.y / abs(distance), behavior.removalVelocity)
velocityVector = CGVector(dx: 0, dy: dy)
}
// `velocityVector` will be replaced by just a velocity(not vector) when FloatingPanelRemovalInteraction will be added.
if shouldStartRemovalAnimation(with: velocityVector), let vc = viewcontroller {
vc.delegate?.floatingPanelDidEndDraggingToRemove(vc, withVelocity: velocity)
let animationVector = CGVector(dx: abs(velocityVector.dx), dy: abs(velocityVector.dy))
startRemovalAnimation(vc, with: animationVector) { [weak self] in
self?.finishRemovalAnimation()
if behavior.shouldProjectMomentum(vc, for: targetPosition) || isBottomState {
vc.delegate?.floatingPanelDidEndDraggingToRemove(vc, withVelocity: velocity)
let animationVector = CGVector(dx: abs(velocityVector.dx), dy: abs(velocityVector.dy))
startRemovalAnimation(vc, with: animationVector) { [weak self] in
self?.finishRemovalAnimation()
}
return
}
return
}
}
@@ -755,7 +773,7 @@ class FloatingPanelCore: NSObject, UIGestureRecognizerDelegate {
vc.delegate?.floatingPanelWillBeginDecelerating(vc)
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: abs(velocity.y)/distance) : .zero
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: velocity.y / distance) : .zero
let animator = behavior.interactionAnimator(vc, to: targetPosition, with: velocityVector)
animator.addAnimations { [weak self] in
guard let `self` = self, let vc = self.viewcontroller else { return }
@@ -809,7 +827,7 @@ class FloatingPanelCore: NSObject, UIGestureRecognizerDelegate {
private func distance(to targetPosition: FloatingPanelPosition) -> CGFloat {
let currentY = surfaceView.frame.minY
let targetY = layoutAdapter.positionY(for: targetPosition)
return CGFloat(abs(currentY - targetY))
return CGFloat(targetY - currentY)
}
// Distance travelled after decelerating to zero velocity at a constant rate.
+11 -11
View File
@@ -211,6 +211,10 @@ class FloatingPanelLayoutAdapter {
return layout.supportedPositions
}
var validPositions: Set<FloatingPanelPosition> {
return supportedPositions.union([.hidden])
}
var topMostState: FloatingPanelPosition {
return supportedPositions.sorted(by: { $0.rawValue < $1.rawValue }).first ?? .hidden
}
@@ -398,8 +402,8 @@ class FloatingPanelLayoutAdapter {
// unsatisfiable constraints
if self.interactiveTopConstraint == nil {
// Actiavate `interactiveTopConstraint` for `fitToBounds` mode.
// It goes throught this path when the pan gesture state jumps
// Activate `interactiveTopConstraint` for `fitToBounds` mode.
// It goes through this path when the pan gesture state jumps
// from .begin to .end.
startInteraction(at: state)
}
@@ -453,7 +457,7 @@ class FloatingPanelLayoutAdapter {
case .fromSuperview:
ret = topY
}
return max(ret, 0.0) // The top boundary is equal to the related topAnchor.
return ret
}()
let bottomMostConst: CGFloat = {
var ret: CGFloat = 0.0
@@ -464,7 +468,7 @@ class FloatingPanelLayoutAdapter {
case .fromSuperview:
ret = _bottomY
}
return min(ret, surfaceView.superview!.bounds.height)
return ret
}()
let minConst = allowsTopBuffer ? topMostConst - layout.topInteractionBuffer : topMostConst
let maxConst = bottomMostConst + layout.bottomInteractionBuffer
@@ -516,7 +520,7 @@ class FloatingPanelLayoutAdapter {
setBackdropAlpha(of: state)
if isValid(state) == false {
if validPositions.contains(state) == false {
state = layout.initialPosition
}
@@ -538,10 +542,6 @@ class FloatingPanelLayoutAdapter {
activateInteractiveLayout(of: state)
}
func isValid(_ state: FloatingPanelPosition) -> Bool {
return supportedPositions.union([.hidden]).contains(state)
}
private func layoutSurfaceIfNeeded() {
#if !TEST
guard surfaceView.window != nil else { return }
@@ -560,8 +560,8 @@ class FloatingPanelLayoutAdapter {
private func checkLayoutConsistance() {
// Verify layout configurations
assert(supportedPositions.count > 0)
assert(supportedPositions.contains(layout.initialPosition),
"Does not include an initial position (\(layout.initialPosition)) in supportedPositions (\(supportedPositions))")
assert(validPositions.contains(layout.initialPosition),
"Does not include an initial position (\(layout.initialPosition)) in (\(validPositions))")
if layout is FloatingPanelIntrinsicLayout {
assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout")
@@ -228,6 +228,7 @@ public class FloatingPanelSurfaceView: UIView {
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: containerMargins.right + contentInsets.right)
let heightPadding = containerMargins.top + containerMargins.bottom + contentInsets.top + contentInsets.bottom
let heightConstraint = contentView.heightAnchor.constraint(equalTo: heightAnchor, constant: -heightPadding)
heightConstraint.priority = UILayoutPriority(999)
NSLayoutConstraint.activate([
topConstraint,
leftConstraint,
@@ -47,8 +47,15 @@ class FloatingPanelPresentationController: UIPresentationController {
override func containerViewWillLayoutSubviews() {
guard
let fpc = presentedViewController as? FloatingPanelController
else { fatalError() }
let fpc = presentedViewController as? FloatingPanelController,
/**
This condition fixes https://github.com/SCENEE/FloatingPanel/issues/369.
The issue is that this method is called in presenting a
UIImagePickerViewController and then a FloatingPanelController
view is added unnecessarily.
*/
fpc.presentedViewController == nil
else { return }
/*
* Layout the views managed by `FloatingPanelController` here for the
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.7.3</string>
<string>1.7.6</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
@@ -195,6 +195,43 @@ class FloatingPanelLayoutTests: XCTestCase {
fpc.floatingPanel.layoutAdapter.endInteraction(at: fpc.position)
}
func test_updateInteractiveTopConstraintWithMinusInsets() {
class FloatingPanelLayoutMinusInsets: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .full
let supportedPositions: Set<FloatingPanelPosition> = [.tip, .full]
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full, .tip: return -200
default: return nil
}
}
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayoutMinusInsets()
fpc.delegate = delegate
fpc.showForTest()
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.position)
let fullPos = fpc.originYOfSurface(for: .full)
let tipPos = fpc.originYOfSurface(for: .tip)
let current = fpc.surfaceView.frame.minY
var next: CGFloat
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: false, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, current)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, fullPos - fpc.layout.topInteractionBuffer)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: tipPos - fullPos + 100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, tipPos + fpc.layout.bottomInteractionBuffer)
fpc.floatingPanel.layoutAdapter.endInteraction(at: fpc.position)
}
func test_positionReference() {
fpc = CustomSafeAreaFloatingPanelController()
fpc.loadViewIfNeeded()
+1 -8
View File
@@ -138,13 +138,6 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
// Add and show the views managed by the `FloatingPanelController` object to self.view.
fpc.addPanel(toParent: self)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove the views managed by the `FloatingPanelController` object from self.view.
fpc.removePanelFromParent()
}
}
```
@@ -447,7 +440,7 @@ The feature can be used for these 2 kind panels
You can disable the pan gesture recognizer directly
```swift
fpc.panGestureRecognizer.isEnable = false
fpc.panGestureRecognizer.isEnabled = false
```
Or use this `FloatingPanelControllerDelegate` method.