Compare commits

...

124 Commits

Author SHA1 Message Date
Shin Yamamoto 7d5f03bb6e Release v1.5.1 2019-04-25 16:18:59 +09:00
Shin Yamamoto 680b16aa25 Merge pull request #185 from SCENEE/fix-tap-abort
Fix a touch abort on the interaction interrupted
2019-04-24 14:54:20 +09:00
Shin Yamamoto 2394c03dca Polish a doc comment 2019-04-20 16:00:22 +09:00
Shin Yamamoto c8f211f2bf fix typo 2019-04-19 08:56:54 +09:00
Shin Yamamoto 9076ba8933 Prevent an UIScrollViewDelayedTouchesBeganGestureRecognizer failed
It causes tableView(_:didSelectRowAt:) not being called on first tap
after an nimation. The change hasn't fixed the problem completely, but
it works better.
2019-04-19 08:46:51 +09:00
Shin Yamamoto 08e79bfc5c Merge pull request #191 from SCENEE/add-pages-sample
Add pages sample
2019-04-17 19:22:40 +09:00
Shin Yamamoto e4808516aa Merge pull request #190 from SCENEE/fix-surface-mask
Fix surface mask
2019-04-17 19:21:56 +09:00
Shin Yamamoto 5888104e98 Remove obsolete SWIFT_WHOLE_MODULE_OPTIMIZATION setting
Removing this setting will fix a warning emitted by Xcode to update
project settings.
2019-04-17 09:36:36 +09:00
Shin Yamamoto 2b8d29759a Rename the remaining backgroundView variable 2019-04-16 22:53:06 +09:00
Shin Yamamoto 8e4b56ff17 Fix the doc comment 2019-04-16 07:51:27 +09:00
Shin Yamamoto 23c5761c14 Update README 2019-04-15 21:05:46 +09:00
Shin Yamamoto 835ec0c3a0 Replace 'backgroundView' with 'containerView' 2019-04-15 21:05:16 +09:00
Shin Yamamoto d2dce0b6f8 Add a doc comment 2019-04-15 21:00:19 +09:00
Shin Yamamoto 3f8628af01 Fix typo 2019-04-11 22:52:49 +09:00
Shin Yamamoto 40c6fae07c Add a sample for panels in UIPageViewController 2019-04-11 22:52:29 +09:00
Shin Yamamoto e1185fda93 Merge pull request #188 from SCENEE/allow-subclassing
Allow FloatingPanelController subclassing
2019-04-11 22:31:37 +09:00
Shin Yamamoto 458ed903c5 Update README 2019-04-11 11:28:35 +09:00
Shin Yamamoto 4b640f4f01 Add updateBorder() 2019-04-11 11:25:11 +09:00
Shin Yamamoto 8743c5efd0 Fix the mask bounds of the surface view
- Move `contentView` into `backgroundView` to fix the mask
- Also fix the mask problem on iOS 10-11. See for detail,
https://github.com/SCENEE/FloatingPanel/issues/109.
2019-04-11 11:21:16 +09:00
Shin Yamamoto 9bd9d31d40 Merge pull request #186 from zntfdr/patch-1
Fix FloatingPanelBehavior.swift typos
2019-04-08 21:24:48 +09:00
Federico Zanetello 7e3d720720 Fix FloatingPanelBehavior.swift typos 2019-04-08 08:26:30 +07:00
Shin Yamamoto 7a512191ab Fix a touch abort on the interaction interrupted
Because touch events seem to be cancelled when an animator is released.
2019-04-06 11:25:38 +09:00
Shin Yamamoto af767863bb Merge pull request #177 from SCENEE/support-swift5
[Release v1.5.0] Support both of Swift 5 and 4.2
2019-04-06 10:54:54 +09:00
Shin Yamamoto 1b233f4f87 Update README 2019-04-05 08:21:49 +09:00
Shin Yamamoto 3a840df79e Release v1.5.0 2019-04-04 12:08:27 +09:00
Shin Yamamoto 6851e3b072 Support both of Swift 5 and 4.2
The default Swift version leaves 4.0. Because it avoids build errors
with Carthage on each Xcode version from the source compatibility
between Swift 4.0, 4.2 and 5.0.

With regard to CocoaPods, the pod spec is going to support
`swift_versions` introduced in CocoaPods v1.7.0. For now, a user needs
to override `SWIFT_VERSION` appropriately in Podfile.
2019-04-04 12:08:27 +09:00
Shin Yamamoto 5a2b079872 Merge pull request #183 from SCENEE/release-1.4.1
Release 1.4.1
2019-04-04 09:40:10 +09:00
Shin Yamamoto f683f987d8 Release v1.4.1 2019-04-03 23:14:56 +09:00
Shin Yamamoto 3626621e87 Fix the umbrella header name 2019-04-03 23:14:56 +09:00
Shin Yamamoto cc2d1eb002 Merge pull request #178 from SCENEE/notify-retain-cycle
Notify a retain cycle
2019-04-03 23:13:20 +09:00
Shin Yamamoto 5ba19bcf8b Add an attention comment in Samples app 2019-04-03 21:23:49 +09:00
Shin Yamamoto 8391686e28 Clean up Logger
`.fault` log is removed because the level are intended for capturing
system-level or multi-process errors only in `os_log`.
2019-04-03 21:23:49 +09:00
Shin Yamamoto 38327c917f Add tests to quick-check warnings 2019-04-03 21:23:49 +09:00
Shin Yamamoto fca0f399b2 Merge pull request #181 from SCENEE/fix-loop-crash
Fix an infinite loop crash in iOS 10
2019-04-03 21:17:39 +09:00
Shin Yamamoto e3b7ac0e99 Add 'Test' configuration 2019-04-03 08:21:35 +09:00
Shin Yamamoto 04cd357f68 Print retain cycle warnings 2019-04-03 08:21:35 +09:00
Shin Yamamoto 68f48f714d Fix an infinite loop crash in iOS 10 2019-04-02 21:47:56 +09:00
Shin Yamamoto fa586c494f Merge pull request #180 from SCENEE/fix-scroll-fitting
Fix scroll fitting
2019-04-02 09:43:37 +09:00
Shin Yamamoto b886a0da64 Fix scroll fitting
This is to prevent a surface frame jump on scroll fitting. In fact,
a scroll offset range for fitting should be -10..<0.
2019-03-30 18:52:48 +09:00
Shin Yamamoto 0616aec3d2 Prevent an unexpected layout update
The KVO for `safeAreaInset` can be invoked even when new and old values
are the same value so that the surface of a floating panel sometimes
jumps in dragging by an unexpected layout update called from a
`safeAreaInset` update.
2019-03-30 15:45:41 +09:00
Shin Yamamoto 5df36a6601 Allow FloatingPanelController subclassing 2019-03-29 21:20:02 +09:00
Shin Yamamoto 7d6f295e72 Merge pull request #169 from SCENEE/fix-ambiguous-cornerradius
Open FloatingPanelSurfaceView.backgroundView
2019-03-29 21:05:25 +09:00
Shin Yamamoto 1c952b6dcb Open FloatingPanelSurfaceView.backgroundView
This change lets a user be able to set up a corner radius and shadow of
the surface view, and has the library not update them unnecessarily.

As a result, a user can escape "Ambiguous use of 'cornerRadius'" error
as below, if it's used with a `cornerRadius` property of `UIView`
extension defined by the user.

```swift
extension UIView {
    @objc dynamic var cornerRadius: CGFloat {
        get { return self.layer.cornerRadius }
        set(cornerRadius) {
            self.layer.masksToBounds = true
            self.layer.cornerRadius = cornerRadius
        }
    }
}

...

public extension FloatingPanelSurfaceView {
    @objc public dynamic var fp_cornerRadius: CGFloat {
        get { return backgroundView.layer.cornerRadius }
        set {
             if #available(iOS 11.0, *) {
                 contentView?.layer.masksToBounds = true
                 contentView?.layer.cornerRadius = newValue
                 contentView?.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
             }
             backgroundView.layer.masksToBounds = true
             backgroundView.layer.cornerRadius = newValue
       }
    }
}
```

squash! Open FloatingPanelSurfaceView.backgroundView
2019-03-29 20:34:11 +09:00
Shin Yamamoto 7e7c2a0fd7 Merge pull request #170 from SCENEE/fix-top-dragging-boundary
Fix top dragging boundary
2019-03-27 22:41:56 +09:00
Shin Yamamoto a15444d237 Modify the height calc with a top-most position
According to the previous change, `.half` position meaning is
also changed so that the height calc logic must be modified.

Previously, for example, `.half` position has 2 meaning. One is
a position at half of a screen. Another is a position when a
content is visible half. `.tip` position is also same.

Now `.half`/`.tip` position can display a full content if it's
top most. In the case, the surface height should fit to a height
for `.half`/`.tip` position.
2019-03-26 22:23:27 +09:00
Shin Yamamoto 5b100f3b22 Add FloatingPanelLayout.topMostState
The previous implementation has an implicit pre-condition where .full
position is supposed to be top most. It also means .full position should
be included in a set of the supported positions. But it's not
appropriate because there is a case when a user wants to configure a top
most position of a layout with a bottom inset. This commit lets a panel
work well even if .half/.tip position is top most.

According to the change, .full position means a position where a content
is not just visible fully, but also filled in a full screen almost.
2019-03-26 22:23:16 +09:00
Shin Yamamoto 9fa8a48c56 Fix top panning boundary 2019-03-26 11:41:13 +09:00
Shin Yamamoto cb54a2a7e1 Merge pull request #151 from SCENEE/remove-swapping-scroll-delegate
Remove swapping delegate
2019-03-26 09:55:47 +09:00
Shin Yamamoto ed02713ccc Merge pull request #168 from SCENEE/fix-safeareainsets
Change FloatingPanelLayoutAdapter.safeAreaInsets as a read-only property
2019-03-25 09:50:46 +09:00
Shin Yamamoto 68e3fd2093 ChangeFloatingPanelLayoutAdapter.safeAreaInsets as a read-only property
This solves the problem that a panel's layout and dragging can be
inappropriate. The root cause is that a value of the `safeAreaInsets`
is sometimes different from an actual value referenced from Auto Layout.
It's led by the design that the `safeAreaInsets` is defined as a stored
property. Therefore this commit resolves the issue.
2019-03-23 18:36:26 +09:00
Shin Yamamoto a43f73d7b1 Merge pull request #159 from SCENEE/fix-interruption
Fix interruption
2019-03-20 21:39:20 +09:00
Shin Yamamoto 61e0c4ed0a Merge pull request #165 from SCENEE/fix-ui-extensions
Fix UI extensions
2019-03-20 21:38:28 +09:00
Shin Yamamoto 53e0629b1d Merge pull request #162 from zntfdr/patch-2
Fix typo
2019-03-19 09:31:13 +09:00
Shin Yamamoto 2b1d6a3d8a Fix 'Redundant conformance' error of UIGestureRecognizerState 2019-03-18 10:21:36 +09:00
Federico Zanetello 5ba1fb3d95 Add missing space 2019-03-16 12:42:51 +07:00
Federico Zanetello 1b7c15cdb5 Fix typo 2019-03-16 12:38:54 +07:00
Shin Yamamoto 81fd85e993 Prevent bouncing a scroll content in a panel animating
This issue happens when a panel interacts from tip to half
in 'Show Tab Bar' -> 'Tab 3'.
2019-03-09 10:41:26 +09:00
Shin Yamamoto 8f4c08d5b3 Fix TabBarContentViewController for Tab 3 2019-03-09 10:36:31 +09:00
Shin Yamamoto 61b6429851 Fix the buggy top buffer in scroll tracking
Should not handle a panel gesture even when a zero scroll velocity.
If not, it causes that a scroll view will be handled after a panel
moves a bit in a top buffer. As a result, a panel frame jumps from
a location in a top buffer to topY.
2019-03-09 10:04:18 +09:00
Shin Yamamoto bbc6b39c08 Fix issues on an animation interruption
* Adjust a final position to work scroll tracking well
* Fix issues on an animation cancellation
2019-03-09 10:04:18 +09:00
Shin Yamamoto 3f812f4d6d Merge pull request #156 from SCENEE/fix-layout-assertion
Fix the layout assertion
2019-03-09 10:03:44 +09:00
Shin Yamamoto c9b15e4239 Merge pull request #158 from bryansum/master
Fix retain cycle in FloatingPanel
2019-03-09 09:57:11 +09:00
Bryan Summersett 5fbdb3d481 Make floatingPanel weak 2019-03-08 15:02:33 -08:00
Shin Yamamoto c59e1cd7fc Fix the layout assertion
The assertion didn't allow `.hidden` initial position.
2019-03-08 19:39:10 +09:00
Shin Yamamoto ce891e47da Merge pull request #155 from kfinteractive/master
Adds support for respecting initial safeAreaInsets.
2019-03-08 19:23:58 +09:00
Gunnar Herzog 7160e4a42e Removes unnecessary guard statement. 2019-03-07 08:56:29 +01:00
Gunnar Herzog eba857a285 Adds support for respecting initial safeAreaInsets. 2019-03-07 08:45:05 +01:00
Shin Yamamoto a1dd02c780 Stop scroll deceleration without swapping delegate 2019-03-05 08:31:27 +09:00
Shin Yamamoto 87ff5d629b Merge pull request #143 from SCENEE/release-v1.4.0
Release v1.4.0
2019-02-27 10:00:47 +09:00
Shin Yamamoto 6b75523428 Release v1.4.0 2019-02-27 09:33:14 +09:00
Shin Yamamoto 773434d4f6 Merge pull request #119 from SCENEE/improve-operability
Improve the operability
2019-02-27 09:32:15 +09:00
Shin Yamamoto ad6dcd0314 Clean up sample 2019-02-27 09:04:42 +09:00
Shin Yamamoto b9e29ad87d Fix typo 2019-02-26 10:07:30 +09:00
Shin Yamamoto 32b965ba87 Merge pull request #142 from zntfdr/patch-1
Add README missing character
2019-02-26 09:22:30 +09:00
Federico Zanetello f1b315c9ea Add missing character 2019-02-25 12:50:37 +07:00
Shin Yamamoto 459fc75af3 Tidy up logs 2019-02-25 13:02:49 +09:00
Shin Yamamoto 9b0cd3511f Revamp scroll draw-down action
A scroll view offset often isn't around zero by a user action.
Therefore, rather than checking a offset value, it's working well
to fits a scroll view offset to the surface view bounds by
manipulating their frames a bit temporarily.
2019-02-25 13:02:49 +09:00
Shin Yamamoto af9b988507 Merge pull request #136 from SCENEE/release-v1.3.5
Release v1.3.5
2019-02-23 11:18:21 +09:00
Shin Yamamoto 36f297c35b Allow the grabber area just on touch began 2019-02-23 10:28:34 +09:00
Shin Yamamoto ff959f71a7 Prevent the unexpected scrolling on full state by the second finger 2019-02-23 10:28:34 +09:00
Shin Yamamoto 0a4312ada6 Reduce logger function calls 2019-02-23 10:28:34 +09:00
Shin Yamamoto 5411cdc07a Change time to call floatingPanelWillBeginDecelerating() 2019-02-23 10:28:34 +09:00
Shin Yamamoto a8c6fba3c1 Call floatingPanelDidEndDecelerating(_:) when an animation is interruptted 2019-02-23 10:28:34 +09:00
Shin Yamamoto 11b115b47b Tear down the active interaction in moving programmatically 2019-02-23 10:28:34 +09:00
Shin Yamamoto 22edf5ce46 Prevent unexpected crash
This assertion can be called unexpectedly by the gesture recognizer behavior
of UIScrollView
2019-02-23 10:28:34 +09:00
Shin Yamamoto f43f7df7f3 Prevent calling didMove delegate when it's not moving 2019-02-23 10:28:34 +09:00
Shin Yamamoto 3a2633d818 Must update a panel layout after it becomes a new state
To synchronize layout updates in floatingPanelDidChangePosition(_:) by
a user
2019-02-23 10:28:34 +09:00
Shin Yamamoto 04a62bcf74 Refactor FloatingPanelSurfaceView for AutoLayout backed animation 2019-02-23 10:28:34 +09:00
Shin Yamamoto 6c1320168c Fix a buggy scroll offset on a scroll inset change by a user
* The controller can bother a user who wants to adjust a scroll offset
for a position at `floatingPanelDidChangePosition()` delegate method.
* This prevents interrupting a tracking scroll offset at the end of interaction
2019-02-23 10:28:34 +09:00
Shin Yamamoto 8657c91002 Fix the boundary of the interactive top constraint 2019-02-23 10:28:34 +09:00
Shin Yamamoto bafe492009 Normalize the projected position
It resolves the behavior is determined by the screen size.
2019-02-23 10:28:34 +09:00
Shin Yamamoto c6197ef6a3 Modify the default momentum projection behavior 2019-02-23 10:28:34 +09:00
Shin Yamamoto 1b3f16bcd5 Fix disabling bottom constraints 2019-02-23 10:28:34 +09:00
Shin Yamamoto 28712fdeca Add ThreeTabBarPanelLayout as an advanced layout sample 2019-02-23 10:28:34 +09:00
Shin Yamamoto 0c30b68a9e Update FloatingPanelFullScreenLayout spec
It's useful that all insets(tip, half and full) indicates a inset
from the superview, not the safe area because a user can layout
a floating panel regardless of the safe area or layout guides(iOS10)
whose values depend on iOS system behavior.
2019-02-21 19:19:48 +09:00
Shin Yamamoto 30c4bee432 Remove FloatingPanel.getCurrentY(from:with) 2019-02-21 19:19:48 +09:00
Shin Yamamoto ece9ced085 Configure momentum project with FloatingPanelBehavior
* shouldProjectMomentum(_:for:)
* momentumProjectionRate(_:)
2019-02-21 19:19:48 +09:00
Shin Yamamoto f231105752 Enable to cancel tracking a scroll view 2019-02-21 19:19:48 +09:00
Shin Yamamoto 91dfc1e086 Open the default layout/behavior 2019-02-21 19:19:48 +09:00
Shin Yamamoto b2c59c17aa Modify setBackdropAlpha(of:) as private 2019-02-21 19:19:48 +09:00
Shin Yamamoto 10d1a920f0 Add floatingPanelShouldBeginDragging(_:) delegate method
* Update doc comments
2019-02-21 19:19:48 +09:00
Shin Yamamoto 4cb79a14fc Move the surface view with a top layout constraint 2019-02-21 19:19:48 +09:00
Shin Yamamoto 402b9bd8dc Release v1.3.5 2019-02-21 18:42:02 +09:00
Shin Yamamoto c39cc9d93b Fix a regression of the interaction 2019-02-21 18:42:02 +09:00
Shin Yamamoto aad56ab0a7 Merge pull request #135 from SCENEE/fix-interaction
Fix interaction
2019-02-16 14:51:56 +09:00
Shin Yamamoto fe18e493a9 Fix dragging outside safe area 2019-02-16 10:31:17 +09:00
Shin Yamamoto 5d14166508 Fix an interruptive animator handling
* A velocity parameter passed by an animator had a wrong sign so that an
interruptive animator could be buggy.
* Improve the default pan gesture recognizer to interrupt an animation smoothly.
2019-02-16 10:29:11 +09:00
Shin Yamamoto e1a745e3b5 Clean up 2019-02-15 15:02:10 +09:00
Shin Yamamoto a0cac28ed0 Improve {re}directional position calc 2019-02-15 15:02:10 +09:00
Shin Yamamoto c205dc8672 Improve animator handling 2019-02-15 15:02:10 +09:00
Shin Yamamoto 5c0ed4cf7d Fix the wrong layout update on iOS10
On iOS 10, there is a case when a floating panel is updated by a
different position(the previous position) from the target position in
animating. This is because `FloatingPanelController` calls
`update(safeAreaInsets:)` in `viewDidLayoutSubviews()` unexpectedly.
2019-02-15 10:29:01 +09:00
Shin Yamamoto 780472a17f Stop unexpected scrolling by decelerating on tip and half 2019-02-15 10:15:34 +09:00
Shin Yamamoto 0264db3d54 Should always recognize tap/long press gestures in parallel 2019-02-15 09:55:36 +09:00
Shin Yamamoto c117594669 Merge pull request #132 from SCENEE/release-v1.3.4
Release v1.3.4
2019-02-14 09:45:31 +09:00
Shin Yamamoto 5214bd8936 Release v1.3.4 2019-02-14 09:13:09 +09:00
Shin Yamamoto a1195be08e Merge pull request #134 from g00m/feature/fix_typo_in_readme
Fix typo in `README.md`
2019-02-14 09:11:53 +09:00
Etienne Negro b69d366538 Fix typo in README-md 2019-02-13 14:28:39 +01:00
Shin Yamamoto c9de6f0dc3 Merge pull request #131 from modestman/master
Fix issue with jumping floating panel while dragging
2019-02-12 09:50:10 +09:00
Anton Glezman 21be693a9a Fix issue with jumping floating panel while dragging 2019-02-11 12:28:13 +03:00
Shin Yamamoto 129362fcd0 Merge pull request #129 from SCENEE/fix-typo
Fix typo
2019-02-06 09:40:58 +09:00
Shin Yamamoto 75e2fcc3ce Fix README 2019-02-05 22:46:56 +09:00
Shin Yamamoto 4f56b57b0e Replace FloatingPanel.panGesture with panGestureRecognizer 2019-02-05 22:10:03 +09:00
Shin Yamamoto f9bbdf3427 Update ISSUE_TEMPLATE.md 2019-02-02 18:19:06 +09:00
Shin Yamamoto fcf200e169 Merge pull request #124 from SCENEE/release-1.3.3
Release v1.3.3
2019-02-02 12:19:59 +09:00
22 changed files with 1287 additions and 540 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
> Please fill out this template when filing an issue.
> Please fill out this template appropriately when filing a bug report.
>
> Please remove this line and everything above it before submitting.
+12 -8
View File
@@ -2,7 +2,6 @@ language: swift
branches:
only:
- master
- next
cache:
directories:
- /usr/local/Homebrew
@@ -19,15 +18,20 @@ jobs:
- stage: Build framework(swift 4.1)
osx_image: xcode9.4
script:
- xcodebuild -scheme FloatingPanel clean build
- xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.1 clean build
- stage: Build framework(swift 4.2)
osx_image: xcode10
script:
- xcodebuild -scheme FloatingPanel clean build
- xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.2 clean build
- stage: Build framework(swift 5.0)
osx_image: xcode10.2
script:
- xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.0 clean build
- stage: Carthage
osx_image: xcode10
osx_image: xcode10.2
before_install:
- brew update
- brew outdated carthage || brew upgrade carthage
@@ -35,21 +39,21 @@ jobs:
- carthage build --no-skip-current
- stage: Podspec
osx_image: xcode10
osx_image: xcode10.2
script:
- pod spec lint
- stage: Build maps example
osx_image: xcode10
osx_image: xcode10.2
script:
- xcodebuild -scheme Maps -sdk iphonesimulator clean build
- stage: Build stocks example
osx_image: xcode10
osx_image: xcode10.2
script:
- xcodebuild -scheme Stocks -sdk iphonesimulator clean build
- stage: Build samples example
osx_image: xcode10
osx_image: xcode10.2
script:
- xcodebuild -scheme Samples -sdk iphonesimulator clean build
+2 -2
View File
@@ -312,7 +312,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -331,7 +331,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -499,7 +499,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -518,7 +518,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
+275 -68
View File
@@ -9,7 +9,7 @@
import UIKit
import FloatingPanel
class SampleListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FloatingPanelControllerDelegate, FloatingPanelLayout {
class SampleListViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
enum Menu: Int, CaseIterable {
@@ -19,6 +19,7 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
case showModal
case showFloatingPanelModal
case showTabBar
case showPageView
case showNestedScrollView
case showRemovablePanel
case showIntrinsicView
@@ -31,6 +32,7 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
case .showModal: return "Show Modal"
case .showFloatingPanelModal: return "Show Floating Panel Modal"
case .showTabBar: return "Show Tab Bar"
case .showPageView: return "Show Page View"
case .showNestedScrollView: return "Show Nested ScrollView"
case .showRemovablePanel: return "Show Removable Panel"
case .showIntrinsicView: return "Show Intrinsic View"
@@ -45,6 +47,7 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
case .showModal: return "ModalViewController"
case .showFloatingPanelModal: return nil
case .showTabBar: return "TabBarViewController"
case .showPageView: return nil
case .showNestedScrollView: return "NestedScrollViewController"
case .showRemovablePanel: return "DetailViewController"
case .showIntrinsicView: return "IntrinsicViewController"
@@ -61,6 +64,19 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
var mainPanelObserves: [NSKeyValueObservation] = []
var settingsObserves: [NSKeyValueObservation] = []
lazy var pages: [UIViewController] = {
let page1 = FloatingPanelController(delegate: self)
page1.view.backgroundColor = .blue
page1.show()
let page2 = FloatingPanelController(delegate: self)
page2.view.backgroundColor = .red
page2.show()
let page3 = FloatingPanelController(delegate: self)
page3.view.backgroundColor = .green
page3.show()
return [page1, page2, page3]
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
@@ -159,31 +175,6 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
}
}
// MARK:- TableViewDatasource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if #available(iOS 11.0, *) {
if navigationController?.navigationBar.prefersLargeTitles == true {
return Menu.allCases.count + 30
} else {
return Menu.allCases.count
}
} else {
return Menu.allCases.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if Menu.allCases.count > indexPath.row {
let menu = Menu.allCases[indexPath.row]
cell.textLabel?.text = menu.name
} else {
cell.textLabel?.text = "\(indexPath.row) row"
}
return cell
}
// MARK:- Actions
@IBAction func showDebugMenu(_ sender: UIBarButtonItem) {
guard settingsPanelVC == nil else { return }
@@ -208,9 +199,34 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
// Add FloatingPanel to self.view
settingsPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
}
}
// MARK:- TableViewDelegate
extension SampleListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if #available(iOS 11.0, *) {
if navigationController?.navigationBar.prefersLargeTitles == true {
return Menu.allCases.count + 30
} else {
return Menu.allCases.count
}
} else {
return Menu.allCases.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if Menu.allCases.count > indexPath.row {
let menu = Menu.allCases[indexPath.row]
cell.textLabel?.text = menu.name
} else {
cell.textLabel?.text = "\(indexPath.row) row"
}
return cell
}
}
extension SampleListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard Menu.allCases.count > indexPath.row else { return }
let menu = Menu.allCases[indexPath.row]
@@ -241,6 +257,22 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
case .showModal, .showTabBar:
let modalVC = contentVC
present(modalVC, animated: true, completion: nil)
case .showPageView:
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
let closeButton = UIButton(type: .custom)
pageVC.view.addSubview(closeButton)
closeButton.setTitle("Close", for: .normal)
closeButton.translatesAutoresizingMaskIntoConstraints = false
closeButton.addTarget(self, action: #selector(dismissPresentedVC), for: .touchUpInside)
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: pageVC.layoutGuide.topAnchor, constant: 16.0),
closeButton.leftAnchor.constraint(equalTo: pageVC.view.leftAnchor, constant: 16.0),
])
pageVC.dataSource = self
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
present(pageVC, animated: true, completion: nil)
case .showFloatingPanelModal:
let fpc = FloatingPanelController()
let contentVC = self.storyboard!.instantiateViewController(withIdentifier: "DetailViewController")
@@ -261,6 +293,12 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
}
}
@objc func dismissPresentedVC() {
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
extension SampleListViewController: FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
if vc == settingsPanelVC {
return IntrinsicPanelLayout()
@@ -285,6 +323,9 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
switch currentMenu {
case .showNestedScrollView:
return (vc.contentViewController as? NestedScrollViewController)?.nestedScrollView.gestureRecognizers?.contains(gestureRecognizer) ?? false
case .showPageView:
// Tips: Need to allow recognizing the pan gesture of UIPageViewController simultaneously.
return true
default:
return false
}
@@ -298,7 +339,14 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
break
}
}
}
/**
- Attention: `FloatingPanelLayout` must not be applied by the parent view
controller of a floating panel. But here `SampleListViewController` adopts it
purposely to check if the library prints an appropriate warning.
*/
extension SampleListViewController: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
@@ -313,6 +361,23 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
}
}
extension SampleListViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard
let index = pages.firstIndex(of: viewController),
index + 1 < pages.count
else { return nil }
return pages[index + 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard
let index = pages.firstIndex(of: viewController),
index - 1 >= 0
else { return nil }
return pages[index - 1]
}
}
class IntrinsicPanelLayout: FloatingPanelIntrinsicLayout { }
class RemovablePanelLayout: FloatingPanelIntrinsicLayout {
@@ -726,10 +791,24 @@ class ModalSecondLayout: FloatingPanelLayout {
class TabBarViewController: UITabBarController {}
class TabBarContentViewController: UIViewController, FloatingPanelControllerDelegate {
class TabBarContentViewController: UIViewController {
enum Tab3Mode {
case changeOffset
case changeAutoLayout
var label: String {
switch self {
case .changeAutoLayout: return "Use AutoLayout(OK)"
case .changeOffset: return "Use ContentOffset(NG)"
}
}
}
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
@@ -743,11 +822,47 @@ class TabBarContentViewController: UIViewController, FloatingPanelControllerDele
// Set a content view controller and track the scroll view
let consoleVC = storyboard?.instantiateViewController(withIdentifier: "ConsoleViewController") as! DebugTextViewController
fpc.set(contentViewController: consoleVC)
consoleVC.textView.delegate = self // MUST call it before fpc.track(scrollView:)
fpc.track(scrollView: consoleVC.textView)
self.consoleVC = consoleVC
// Add FloatingPanel to self.view
fpc.addPanel(toParent: self)
if tabBarItem.tag == 2 {
let switcher = UISwitch()
fpc.view.addSubview(switcher)
switcher.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
switcher.bottomAnchor.constraint(equalTo: fpc.surfaceView.topAnchor, constant: -16.0),
switcher.rightAnchor.constraint(equalTo: fpc.surfaceView.rightAnchor, constant: -16.0),
])
switcher.isOn = true
switcher.tintColor = .white
switcher.backgroundColor = .white
switcher.layer.cornerRadius = 16.0
switcher.addTarget(self,
action: #selector(changeTab3Mode(_:)),
for: .valueChanged)
let label = UILabel()
label.text = tab3Mode.label
fpc.view.addSubview(label)
switcherLabel = label
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerYAnchor.constraint(equalTo: switcher.centerYAnchor, constant: 0.0),
label.rightAnchor.constraint(equalTo: switcher.leftAnchor, constant: -16.0),
])
// Turn off the mask instead of content inset change
consoleVC.textView.clipsToBounds = false
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fpc.updateLayout()
}
override func viewWillDisappear(_ animated: Bool) {
@@ -756,6 +871,43 @@ class TabBarContentViewController: UIViewController, FloatingPanelControllerDele
fpc.removePanelFromParent(animated: false)
}
// MARK: - Action
@IBAction func close(sender: UIButton) {
dismiss(animated: true, completion: nil)
}
// MARK: - Private
@objc
private func changeTab3Mode(_ sender: UISwitch) {
if sender.isOn {
tab3Mode = .changeAutoLayout
} else {
tab3Mode = .changeOffset
}
switcherLabel.text = tab3Mode.label
}
}
extension TabBarContentViewController: UITextViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard self.tabBarItem.tag == 2 else { return }
// Reset an invalid content offset by a user after updating the layout
// of `consoleVC.textView`.
// NOTE: FloatingPanel doesn't implicitly reset the offset(i.e.
// Using KVO of `scrollView.contentOffset`). Because it can lead to an
// infinite loop if a user also resets a content offset as below and,
// in the situation, a user has to modify the library.
if fpc.position != .full, fpc.surfaceView.frame.minY < fpc.originYOfSurface(for: .full) {
scrollView.contentOffset = .zero
}
}
}
extension TabBarContentViewController: FloatingPanelControllerDelegate {
// MARK: - FloatingPanel
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
switch self.tabBarItem.tag {
case 0:
@@ -763,7 +915,8 @@ class TabBarContentViewController: UIViewController, FloatingPanelControllerDele
case 1:
return TwoTabBarPanelLayout()
case 2:
return ThreeTabBarPanelLayout()
threeLayout = ThreeTabBarPanelLayout(parent: self)
return threeLayout
default:
return nil
}
@@ -772,51 +925,80 @@ class TabBarContentViewController: UIViewController, FloatingPanelControllerDele
func floatingPanelDidMove(_ vc: FloatingPanelController) {
guard self.tabBarItem.tag == 2 else { return }
/* Solution 1: Manipulate scoll content inset */
/*
guard let scrollView = consoleVC.textView else { return }
var insets = vc.adjustedContentInsets
if vc.surfaceView.frame.minY < vc.layoutInsets.top {
insets.top = vc.layoutInsets.top - vc.surfaceView.frame.minY
} else {
insets.top = 0.0
}
scrollView.contentInset = insets
*/
switch tab3Mode {
case .changeAutoLayout:
/* Good solution: Manipulate top constraint */
assert(consoleVC.textViewTopConstraint != nil)
if vc.surfaceView.frame.minY + threeLayout.topPadding < vc.layoutInsets.top {
consoleVC.textViewTopConstraint?.constant = vc.layoutInsets.top - vc.surfaceView.frame.minY
} else {
consoleVC.textViewTopConstraint?.constant = threeLayout.topPadding
}
case .changeOffset:
/*
Bad solution: Manipulate scroll content inset
// Solution 2: Manipulate top constraint
assert(consoleVC.textViewTopConstraint != nil)
if vc.surfaceView.frame.minY + 17.0 < vc.layoutInsets.top {
consoleVC.textViewTopConstraint?.constant = vc.layoutInsets.top - vc.surfaceView.frame.minY
} else {
consoleVC.textViewTopConstraint?.constant = 17.0
FloatingPanelController keeps a content offset in moving a panel
so that changing content inset or offset causes a buggy behavior.
*/
guard let scrollView = consoleVC.textView else { return }
var insets = vc.adjustedContentInsets
if vc.surfaceView.frame.minY < vc.layoutInsets.top {
insets.top = vc.layoutInsets.top - vc.surfaceView.frame.minY
} else {
insets.top = 0.0
}
scrollView.contentInset = insets
if vc.surfaceView.frame.minY > 0 {
scrollView.contentOffset = CGPoint(x: 0.0,
y: 0.0 - scrollView.contentInset.top)
}
}
consoleVC.view.layoutIfNeeded()
if vc.surfaceView.frame.minY > vc.originYOfSurface(for: .half) {
let progress = (vc.surfaceView.frame.minY - vc.originYOfSurface(for: .half)) / (vc.originYOfSurface(for: .tip) - vc.originYOfSurface(for: .half))
threeLayout.leftConstraint.constant = max(min(progress, 1.0), 0.0) * threeLayout.sideMargin
threeLayout.rightConstraint.constant = -max(min(progress, 1.0), 0.0) * threeLayout.sideMargin
} else {
threeLayout.leftConstraint.constant = 0.0
threeLayout.rightConstraint.constant = 0.0
}
vc.view.layoutIfNeeded() // MUST
}
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
guard self.tabBarItem.tag == 2 else { return }
/* Solution 1: Manipulate scoll content inset */
/*
guard let scrollView = consoleVC.textView else { return }
var insets = vc.adjustedContentInsets
insets.top = (vc.position == .full) ? vc.layoutInsets.top : 0.0
scrollView.contentInset = insets
if scrollView.contentOffset.y - scrollView.contentInset.top < 0.0 {
scrollView.contentOffset = CGPoint(x: 0.0,
y: 0.0 - scrollView.contentInset.top)
switch tab3Mode {
case .changeAutoLayout:
/* Good Solution: Manipulate top constraint */
assert(consoleVC.textViewTopConstraint != nil)
consoleVC.textViewTopConstraint?.constant = (vc.position == .full) ? vc.layoutInsets.top : 17.0
case .changeOffset:
/* Bad Solution: Manipulate scroll content inset */
guard let scrollView = consoleVC.textView else { return }
var insets = vc.adjustedContentInsets
insets.top = (vc.position == .full) ? vc.layoutInsets.top : 0.0
scrollView.contentInset = insets
if scrollView.contentOffset.y - scrollView.contentInset.top < 0.0 {
scrollView.contentOffset = CGPoint(x: 0.0,
y: 0.0 - scrollView.contentInset.top)
}
}
*/
// Solution 2: Manipulate top constraint
assert(consoleVC.textViewTopConstraint != nil)
consoleVC.textViewTopConstraint?.constant = (vc.position == .full) ? vc.layoutInsets.top : 17.0
consoleVC.view.layoutIfNeeded()
}
@IBAction func close(sender: UIButton) {
dismiss(animated: true, completion: nil)
if vc.position == .tip {
threeLayout.leftConstraint.constant = threeLayout.sideMargin
threeLayout.rightConstraint.constant = -threeLayout.sideMargin
} else {
threeLayout.leftConstraint.constant = 0.0
threeLayout.rightConstraint.constant = 0.0
}
// Can call it, but it's not necessary because it will be also called
// by FloatingPanelController after the delegate method
vc.view.layoutIfNeeded()
}
}
@@ -874,22 +1056,47 @@ class TwoTabBarPanelLayout: FloatingPanelLayout {
}
class ThreeTabBarPanelLayout: FloatingPanelFullScreenLayout {
weak var parentVC: UIViewController!
var leftConstraint: NSLayoutConstraint!
var rightConstraint: NSLayoutConstraint!
let topPadding: CGFloat = 17.0
let sideMargin: CGFloat = 16.0
init(parent: UIViewController) {
parentVC = parent
}
var bottomInteractionBuffer: CGFloat = 44.0
var initialPosition: FloatingPanelPosition {
return .half
}
var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .half]
return [.full, .half, .tip]
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 0.0
case .half: return 261.0
case .half: return 261.0 + parentVC.layoutInsets.bottom
case .tip: return 88.0 + parentVC.layoutInsets.bottom
default: return nil
}
}
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.3
}
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
if #available(iOS 11.0, *) {
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
} else {
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0)
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0)
}
return [ leftConstraint, rightConstraint ]
}
}
class SettingsViewController: InspectableViewController {
@@ -900,7 +1107,7 @@ class SettingsViewController: InspectableViewController {
override func viewDidLoad() {
versionLabel.text = "Version: \(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "--")"
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11.0, *) {
@@ -312,7 +312,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -331,7 +331,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
+2 -2
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.3.3"
s.version = "1.5.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.
@@ -14,7 +14,7 @@ The new interface displays the related contents and utilities in parallel as a u
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => "v#{s.version}" }
s.source_files = "Framework/Sources/*.swift"
s.swift_version = "4.0"
s.pod_target_xcconfig = { 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
s.framework = "UIKit"
@@ -11,8 +11,8 @@
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ViewTests.swift */; };
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanelController.h */; settings = {ATTRIBUTES = (Public, ); }; };
545DB9D02151169500CA77B8 /* FloatingPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* FloatingPanelTests.swift */; };
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanel.h */; settings = {ATTRIBUTES = (Public, ); }; };
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DD215118C800CA77B8 /* UIExtensions.swift */; };
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */; };
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */; };
@@ -38,10 +38,10 @@
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9C42151169500CA77B8 /* FloatingPanelController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanelController.h; sourceTree = "<group>"; };
545DB9C42151169500CA77B8 /* FloatingPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanel.h; sourceTree = "<group>"; };
545DB9C52151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FloatingPanelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9CF2151169500CA77B8 /* ViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTests.swift; sourceTree = "<group>"; };
545DB9CF2151169500CA77B8 /* FloatingPanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTests.swift; sourceTree = "<group>"; };
545DB9D12151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DB9DD215118C800CA77B8 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelController.swift; sourceTree = "<group>"; };
@@ -94,7 +94,7 @@
isa = PBXGroup;
children = (
545DB9C52151169500CA77B8 /* Info.plist */,
545DB9C42151169500CA77B8 /* FloatingPanelController.h */,
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */,
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */,
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */,
@@ -113,7 +113,7 @@
545DB9CE2151169500CA77B8 /* Tests */ = {
isa = PBXGroup;
children = (
545DB9CF2151169500CA77B8 /* ViewTests.swift */,
545DB9CF2151169500CA77B8 /* FloatingPanelTests.swift */,
545DB9D12151169500CA77B8 /* Info.plist */,
);
path = Tests;
@@ -126,7 +126,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */,
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -246,7 +246,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */,
545DB9D02151169500CA77B8 /* FloatingPanelTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -473,6 +473,116 @@
};
name = Release;
};
54E79ADF224F6C9800717BC6 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Test;
};
54E79AE0224F6C9800717BC6 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG __FP_LOG";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Test;
};
54E79AE1224F6C9800717BC6 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Test;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -480,6 +590,7 @@
isa = XCConfigurationList;
buildConfigurations = (
545DB9D32151169500CA77B8 /* Debug */,
54E79ADF224F6C9800717BC6 /* Test */,
545DB9D42151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -489,6 +600,7 @@
isa = XCConfigurationList;
buildConfigurations = (
545DB9D62151169500CA77B8 /* Debug */,
54E79AE0224F6C9800717BC6 /* Test */,
545DB9D72151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -498,6 +610,7 @@
isa = XCConfigurationList;
buildConfigurations = (
545DB9D92151169500CA77B8 /* Debug */,
54E79AE1224F6C9800717BC6 /* Test */,
545DB9DA2151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
@@ -23,12 +23,31 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545DB9C92151169500CA77B8"
BuildableName = "FloatingPanelTests.xctest"
BlueprintName = "FloatingPanelTests"
ReferencedContainer = "container:FloatingPanel.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545DB9C02151169500CA77B8"
BuildableName = "FloatingPanel.framework"
BlueprintName = "FloatingPanel"
ReferencedContainer = "container:FloatingPanel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
File diff suppressed because it is too large Load Diff
+42 -4
View File
@@ -6,9 +6,20 @@
import UIKit
public protocol FloatingPanelBehavior {
/// Returns the progress to redirect to the previous position
/// Asks the behavior object if the floating panel should project a momentum of a user interaction to move the proposed position.
///
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next posiiton. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
/// The default implementation of this method returns true. This method is called for a layout to support all positions(tip, half and full).
/// Therefore, `proposedTargetPosition` can only be `FloatingPanelPosition.tip` or `FloatingPanelPosition.full`.
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool
/// Returns a deceleration rate to calculate a target position projected a dragging momentum.
///
/// The default implementation of this method returns the normal deceleration rate of UIScrollView.
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat
/// Returns the progress to redirect to the previous position.
///
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next position. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat
/// Returns a UIViewPropertyAnimator object to project a floating panel to a position on finger up if the user dragged.
@@ -49,10 +60,33 @@ public protocol FloatingPanelBehavior {
}
public extension FloatingPanelBehavior {
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
switch (fpc.position, proposedTargetPosition) {
case (.full, .tip):
return false
case (.tip, .full):
return false
default:
return true
}
}
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat {
#if swift(>=4.2)
return UIScrollView.DecelerationRate.normal.rawValue
#else
return UIScrollViewDecelerationRateNormal
#endif
}
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat {
return 0.5
}
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
return defaultBehavior.interactionAnimator(fpc, to: targetPosition, with: velocity)
}
func addAnimator(_ fpc: FloatingPanelController, to: FloatingPanelPosition) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
}
@@ -82,8 +116,12 @@ public extension FloatingPanelBehavior {
}
}
class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
private let defaultBehavior = FloatingPanelDefaultBehavior()
public class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
public init() { }
public func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
let timing = timeingCurve(with: velocity)
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: timing)
animator.isInterruptible = false
+102 -39
View File
@@ -14,7 +14,10 @@ public protocol FloatingPanelControllerDelegate: class {
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) // changed the settled position in the model layer
func floatingPanelDidMove(_ vc: FloatingPanelController) // any offset changes
/// Asks the delegate if dragging should begin by the pan gesture recognizer.
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool
func floatingPanelDidMove(_ vc: FloatingPanelController) // any surface frame changes in dragging
// called on start of dragging (may require some time and or distance to move)
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController)
@@ -28,7 +31,10 @@ public protocol FloatingPanelControllerDelegate: class {
// called when its views are removed from a parent view controller
func floatingPanelDidEndRemove(_ vc: FloatingPanelController)
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool
/// Asks the delegate if the other gesture recognizer should be allowed to recognize the gesture in parallel.
///
/// By default, any tap and long gesture recognizers are allowed to recognize gestures simultaneously.
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
}
public extension FloatingPanelControllerDelegate {
@@ -39,6 +45,9 @@ public extension FloatingPanelControllerDelegate {
return nil
}
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {}
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool {
return true
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {}
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {}
@@ -48,7 +57,9 @@ public extension FloatingPanelControllerDelegate {
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint) {}
func floatingPanelDidEndRemove(_ vc: FloatingPanelController) {}
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool { return false }
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
@@ -62,7 +73,7 @@ public enum FloatingPanelPosition: Int {
///
/// A container view controller to display a floating panel to present contents in parallel as a user wants.
///
public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
open class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
/// Constants indicating how safe area insets are added to the adjusted content inset.
public enum ContentInsetAdjustmentBehavior: Int {
case always
@@ -93,7 +104,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
// The underlying gesture recognizer for pan gestures
public var panGestureRecognizer: UIPanGestureRecognizer {
return floatingPanel.panGesture
return floatingPanel.panGestureRecognizer
}
/// The current position of the floating panel controller's contents.
@@ -135,6 +146,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
private var _contentViewController: UIViewController?
private var floatingPanel: FloatingPanel!
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
private var safeAreaInsetsObservation: NSKeyValueObservation?
private let modalTransition = FloatingPanelModalTransition()
@@ -169,7 +181,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
// MARK:- Overrides
/// Creates the view that the controller manages.
override public func loadView() {
open override func loadView() {
assert(self.storyboard == nil, "Storyboard isn't supported")
let view = FloatingPanelPassThroughView()
@@ -184,16 +196,18 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
self.view = view as UIView
}
public override func viewDidLayoutSubviews() {
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11.0, *) {}
else {
// Because {top,bottom}LayoutGuide is managed as a view
self.update(safeAreaInsets: layoutInsets)
if preSafeAreaInsets != layoutInsets {
self.update(safeAreaInsets: layoutInsets)
}
}
}
public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if view.translatesAutoresizingMaskIntoConstraints {
@@ -202,7 +216,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
}
}
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
// Change layout for a new trait collection
@@ -212,7 +226,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
floatingPanel.behavior = fetchBehavior(for: newCollection)
}
public override func viewWillDisappear(_ animated: Bool) {
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
safeAreaInsetsObservation = nil
}
@@ -233,15 +247,15 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
}
private func update(safeAreaInsets: UIEdgeInsets) {
// Don't re-layout the surface on SafeArea.Bottom enabled/disabled in interaction progress
guard
floatingPanel.layoutAdapter.safeAreaInsets != safeAreaInsets,
self.floatingPanel.interactionInProgress == false
else { return }
preSafeAreaInsets != safeAreaInsets,
self.floatingPanel.isDecelerating == false
else { return }
log.debug("Update safeAreaInsets", safeAreaInsets)
floatingPanel.layoutAdapter.safeAreaInsets = safeAreaInsets
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
preSafeAreaInsets = safeAreaInsets
setUpLayout()
@@ -257,6 +271,15 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
private func reloadLayout(for traitCollection: UITraitCollection) {
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
floatingPanel.layoutAdapter.prepareLayout(in: self)
if let parent = self.parent {
if let layout = layout as? UIViewController, layout == parent {
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the layout object. Don't let the parent adopt FloatingPanelLayout.")
}
if let behavior = behavior as? UIViewController, behavior == parent {
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the behavior object. Don't let the parent adopt FloatingPanelBehavior.")
}
}
}
private func setUpLayout() {
@@ -284,9 +307,9 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
// 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) { [weak self] (vc, chaneg) in
guard let `self` = self else { return }
self.update(safeAreaInsets: vc.layoutInsets)
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)
}
} else {
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
@@ -328,7 +351,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
parent.view.addSubview(self.view)
}
#if swift(>=4.2)
parent.addChild(self)
#else
parent.addChildViewController(self)
#endif
view.frame = parent.view.bounds // Needed for a correct safe area configuration
view.translatesAutoresizingMaskIntoConstraints = false
@@ -341,7 +368,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
show(animated: animated) { [weak self] in
guard let `self` = self else { return }
#if swift(>=4.2)
self.didMove(toParent: self)
#else
self.didMove(toParentViewController: self)
#endif
}
}
@@ -357,9 +388,20 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
hide(animated: animated) { [weak self] in
guard let `self` = self else { return }
#if swift(>=4.2)
self.willMove(toParent: nil)
#else
self.willMove(toParentViewController: nil)
#endif
self.view.removeFromSuperview()
#if swift(>=4.2)
self.removeFromParent()
#else
self.removeFromParentViewController()
#endif
completion?()
}
}
@@ -374,39 +416,53 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
floatingPanel.move(to: to, animated: animated, completion: completion)
}
/// Sets the view controller responsible for the content portion of the floating panel..
/// Sets the view controller responsible for the content portion of the floating panel.
public func set(contentViewController: UIViewController?) {
if let vc = _contentViewController {
#if swift(>=4.2)
vc.willMove(toParent: nil)
#else
vc.willMove(toParentViewController: nil)
#endif
vc.view.removeFromSuperview()
#if swift(>=4.2)
vc.removeFromParent()
#else
vc.removeFromParentViewController()
if let scrollView = floatingPanel.scrollView,
let delegate = floatingPanel.userScrollViewDelegate,
vc.view.subviews.contains(scrollView) {
scrollView.delegate = delegate
}
#endif
}
if let vc = contentViewController {
#if swift(>=4.2)
addChild(vc)
#else
addChildViewController(vc)
#endif
let surfaceView = floatingPanel.surfaceView
surfaceView.add(contentView: vc.view)
#if swift(>=4.2)
vc.didMove(toParent: self)
#else
vc.didMove(toParentViewController: self)
#endif
}
_contentViewController = contentViewController
}
@available(*, unavailable, renamed: "set(contentViewController:)")
public override func show(_ vc: UIViewController, sender: Any?) {
open override func show(_ vc: UIViewController, sender: Any?) {
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) {
target.show(vc, sender: sender)
}
}
@available(*, unavailable, renamed: "set(contentViewController:)")
public override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
open override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.showDetailViewController(_:sender:)), sender: sender) {
target.showDetailViewController(vc, sender: sender)
}
@@ -416,23 +472,30 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
/// Tracks the specified scroll view to correspond with the scroll.
///
/// - Attention:
/// The specified scroll view must be already assigned to the delegate property because the controller intermediates between the various delegate methods.
///
public func track(scrollView: UIScrollView) {
floatingPanel.scrollView = scrollView
if scrollView.delegate !== floatingPanel {
floatingPanel.userScrollViewDelegate = scrollView.delegate
scrollView.delegate = floatingPanel
/// - Parameters:
/// - scrollView: Specify a scroll view to continuously and seamlessly work in concert with interactions of the surface view or nil to cancel it.
public func track(scrollView: UIScrollView?) {
guard let scrollView = scrollView else {
floatingPanel.scrollView = nil
return
}
floatingPanel.scrollView = scrollView
switch contentInsetAdjustmentBehavior {
case .always:
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
} else {
#if swift(>=4.2)
children.forEach { (vc) in
vc.automaticallyAdjustsScrollViewInsets = false
}
#else
childViewControllers.forEach { (vc) in
vc.automaticallyAdjustsScrollViewInsets = false
}
#endif
}
default:
break
@@ -453,7 +516,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
setUpLayout()
}
/// Returns the y-coordinate of the point at the origin of the surface view
/// Returns the y-coordinate of the point at the origin of the surface view.
public func originYOfSurface(for pos: FloatingPanelPosition) -> CGFloat {
switch pos {
case .full:
@@ -486,10 +549,10 @@ extension FloatingPanelController {
}
public extension UIViewController {
@objc public func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
@objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
}
@objc public func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
@objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Call dismiss(animated:completion:) to a content view controller
if let fpc = parent as? FloatingPanelController {
if fpc.presentingViewController != nil {
+144 -37
View File
@@ -7,7 +7,7 @@ import UIKit
/// FloatingPanelFullScreenLayout
///
/// Use the layout protocol if you want to configure a full inset from Superview.Top, not SafeArea.Top.
/// Use the layout protocol if you configure full, half and tip insets from the superview, not the safe area.
/// It can't be used with FloatingPanelIntrinsicLayout.
public protocol FloatingPanelFullScreenLayout: FloatingPanelLayout { }
@@ -95,6 +95,8 @@ public extension FloatingPanelLayout {
}
public class FloatingPanelDefaultLayout: FloatingPanelLayout {
public init() { }
public var initialPosition: FloatingPanelPosition {
return .half
}
@@ -110,6 +112,8 @@ public class FloatingPanelDefaultLayout: FloatingPanelLayout {
}
public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout {
public init() { }
public var initialPosition: FloatingPanelPosition {
return .tip
}
@@ -138,14 +142,20 @@ class FloatingPanelLayoutAdapter {
}
}
var safeAreaInsets: UIEdgeInsets = .zero
private var safeAreaInsets: UIEdgeInsets {
return vc?.layoutInsets ?? .zero
}
private var initialConst: CGFloat = 0.0
private var fixedConstraints: [NSLayoutConstraint] = []
private var fullConstraints: [NSLayoutConstraint] = []
private var halfConstraints: [NSLayoutConstraint] = []
private var tipConstraints: [NSLayoutConstraint] = []
private var offConstraints: [NSLayoutConstraint] = []
private var heightConstraints: [NSLayoutConstraint] = []
private var interactiveTopConstraint: NSLayoutConstraint?
private var heightConstraint: NSLayoutConstraint?
private var fullInset: CGFloat {
if layout is FloatingPanelIntrinsicLayout {
@@ -170,6 +180,16 @@ class FloatingPanelLayoutAdapter {
return supportedPositions
}
var topMostState: FloatingPanelPosition {
if supportedPositions.contains(.full) {
return .full
}
if supportedPositions.contains(.half) {
return .half
}
return .tip
}
var topY: CGFloat {
if supportedPositions.contains(.full) {
switch layout {
@@ -186,12 +206,20 @@ class FloatingPanelLayoutAdapter {
}
var middleY: CGFloat {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
if layout is FloatingPanelFullScreenLayout {
return surfaceView.superview!.bounds.height - halfInset
} else{
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
}
}
var bottomY: CGFloat {
if supportedPositions.contains(.tip) {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset)
if layout is FloatingPanelFullScreenLayout {
return surfaceView.superview!.bounds.height - tipInset
} else{
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset)
}
} else {
return middleY
}
@@ -201,14 +229,17 @@ class FloatingPanelLayoutAdapter {
return surfaceView.superview!.bounds.height
}
var safeAreaBottomY: CGFloat {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
var topMaxY: CGFloat {
return layout is FloatingPanelFullScreenLayout ? 0.0 : safeAreaInsets.top
}
var topMaxY: CGFloat {
return layout is FloatingPanelFullScreenLayout ? 0.0 : -safeAreaInsets.top
var bottomMaxY: CGFloat {
if layout is FloatingPanelFullScreenLayout{
return surfaceView.superview!.bounds.height - hiddenInset
} else {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
}
}
var bottomMaxY: CGFloat { return safeAreaBottomY }
var adjustedContentInsets: UIEdgeInsets {
return UIEdgeInsets(top: 0.0,
@@ -239,7 +270,11 @@ class FloatingPanelLayoutAdapter {
}
func updateIntrinsicHeight() {
#if swift(>=4.2)
let fittingSize = UIView.layoutFittingCompressedSize
#else
let fittingSize = UILayoutFittingCompressedSize
#endif
var intrinsicHeight = surfaceView.contentView?.systemLayoutSizeFitting(fittingSize).height ?? 0.0
var safeAreaBottom: CGFloat = 0.0
if #available(iOS 11.0, *) {
@@ -275,63 +310,96 @@ class FloatingPanelLayoutAdapter {
fixedConstraints = surfaceConstraints + backdropConstraints
// Flexible surface constarints for full, half, tip and off
// Flexible surface constraints for full, half, tip and off
let topAnchor: NSLayoutYAxisAnchor = {
if layout is FloatingPanelFullScreenLayout {
return vc.view.topAnchor
} else {
return vc.layoutGuide.topAnchor
}
}()
switch layout {
case is FloatingPanelIntrinsicLayout:
// Set up on updateHeight()
break
case is FloatingPanelFullScreenLayout:
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.view.topAnchor,
constant: fullInset),
]
default:
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
surfaceView.topAnchor.constraint(equalTo: topAnchor,
constant: fullInset),
]
}
let bottomAnchor: NSLayoutYAxisAnchor = {
if layout is FloatingPanelFullScreenLayout {
return vc.view.bottomAnchor
} else {
return vc.layoutGuide.bottomAnchor
}
}()
halfConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
surfaceView.topAnchor.constraint(equalTo: bottomAnchor,
constant: -halfInset),
]
tipConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
surfaceView.topAnchor.constraint(equalTo: bottomAnchor,
constant: -tipInset),
]
offConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.view.bottomAnchor,
surfaceView.topAnchor.constraint(equalTo:vc.view.bottomAnchor,
constant: -hiddenInset),
]
}
func startInteraction(at state: FloatingPanelPosition) {
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
let interactiveTopConstraint: NSLayoutConstraint
switch layout {
case is FloatingPanelIntrinsicLayout,
is FloatingPanelFullScreenLayout:
initialConst = surfaceView.frame.minY
interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.view.topAnchor,
constant: initialConst)
default:
initialConst = surfaceView.frame.minY - safeAreaInsets.top
interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
constant: initialConst)
}
NSLayoutConstraint.activate([interactiveTopConstraint])
self.interactiveTopConstraint = interactiveTopConstraint
}
func endInteraction(at state: FloatingPanelPosition) {
// Don't deactivate `interactiveTopConstraint` here because it leads to
// unsatisfiable constraints
}
// The method is separated from prepareLayout(to:) for the rotation support
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
func updateHeight() {
guard let vc = vc else { return }
NSLayoutConstraint.deactivate(heightConstraints)
if let const = self.heightConstraint {
NSLayoutConstraint.deactivate([const])
}
let heightConstraint: NSLayoutConstraint
switch layout {
case is FloatingPanelIntrinsicLayout:
updateIntrinsicHeight()
heightConstraints = [
surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom),
]
case is FloatingPanelFullScreenLayout:
heightConstraints = [
surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
constant: -fullInset),
]
heightConstraint = surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom)
default:
heightConstraints = [
surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
constant: -(safeAreaInsets.top + fullInset)),
]
let const = -(positionY(for: topMostState))
heightConstraint = surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
constant: const)
}
NSLayoutConstraint.activate(heightConstraints)
NSLayoutConstraint.activate([heightConstraint])
self.heightConstraint = heightConstraint
surfaceView.bottomOverflow = vc.view.bounds.height + layout.topInteractionBuffer
@@ -344,6 +412,40 @@ class FloatingPanelLayoutAdapter {
}
}
func updateInteractiveTopConstraint(diff: CGFloat, allowsTopBuffer: Bool) {
defer {
surfaceView.superview!.layoutIfNeeded() // MUST call here to update `surfaceView.frame`
}
let minY: CGFloat = {
var ret: CGFloat = 0.0
switch layout {
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
ret = topY
default:
ret = topY - safeAreaInsets.top
}
if allowsTopBuffer {
ret -= layout.topInteractionBuffer
}
return max(ret, 0.0) // The top boundary is equal to the related topAnchor.
}()
let maxY: CGFloat = {
var ret: CGFloat = 0.0
switch layout {
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
ret = bottomY
default:
ret = bottomY - safeAreaInsets.top
}
ret += layout.bottomInteractionBuffer
return min(ret, bottomMaxY)
}()
let const = initialConst + diff
interactiveTopConstraint?.constant = max(minY, min(maxY, const))
}
func activateLayout(of state: FloatingPanelPosition) {
defer {
surfaceView.superview!.layoutIfNeeded()
@@ -353,6 +455,11 @@ class FloatingPanelLayoutAdapter {
setBackdropAlpha(of: state)
// Must deactivate `interactiveTopConstraint` here
if let interactiveTopConstraint = interactiveTopConstraint {
NSLayoutConstraint.deactivate([interactiveTopConstraint])
self.interactiveTopConstraint = nil
}
NSLayoutConstraint.activate(fixedConstraints)
if supportedPositions.union([.hidden]).contains(state) == false {
@@ -372,7 +479,7 @@ class FloatingPanelLayoutAdapter {
}
}
func setBackdropAlpha(of target: FloatingPanelPosition) {
private func setBackdropAlpha(of target: FloatingPanelPosition) {
if target == .hidden {
self.backdropView.alpha = 0.0
} else {
@@ -383,8 +490,8 @@ class FloatingPanelLayoutAdapter {
private func checkLayoutConsistance() {
// Verify layout configurations
assert(supportedPositions.count > 0)
assert(supportedPositions.contains(layout.initialPosition),
"Does not include an initial potision(\(layout.initialPosition)) in supportedPositions(\(supportedPositions))")
assert(supportedPositions.union([.hidden]).contains(layout.initialPosition),
"Does not include an initial position (\(layout.initialPosition)) in supportedPositions (\(supportedPositions))")
if layout is FloatingPanelIntrinsicLayout {
assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout")
@@ -13,7 +13,7 @@ public class FloatingPanelSurfaceView: UIView {
/// A GrabberHandleView object displayed at the top of the surface view.
///
/// To use a custom grabber handle, hide this and then add the custom one
/// to the surface view at appropirate coordinates.
/// to the surface view at appropriate coordinates.
public var grabberHandle: GrabberHandleView!
/// The height of the grabber bar area
@@ -59,7 +59,19 @@ public class FloatingPanelSurfaceView: UIView {
/// The color of the surface border.
public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
private var backgroundLayer: CAShapeLayer! { didSet { setNeedsLayout() } }
/// The view presents an actual surface shape.
///
/// It renders the background color, border line and top rounded corners,
/// specified by other properties. The reason why they're not be applied to
/// a content view directly is because it avoids any side-effects to the
/// content view.
public var containerView: UIView!
@available(*, unavailable, renamed: "containerView")
public var backgroundView: UIView!
private var containerViewHeightConstraint: NSLayoutConstraint!
private struct Default {
public static let grabberTopPadding: CGFloat = 6.0
@@ -79,9 +91,18 @@ public class FloatingPanelSurfaceView: UIView {
super.backgroundColor = .clear
self.clipsToBounds = false
let backgroundLayer = CAShapeLayer()
layer.insertSublayer(backgroundLayer, at: 0)
self.backgroundLayer = backgroundLayer
let containerView = UIView()
addSubview(containerView)
self.containerView = containerView
containerView.translatesAutoresizingMaskIntoConstraints = false
containerViewHeightConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
containerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
containerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
containerViewHeightConstraint,
])
let grabberHandle = GrabberHandleView()
addSubview(grabberHandle)
@@ -96,29 +117,30 @@ public class FloatingPanelSurfaceView: UIView {
])
}
public override func updateConstraints() {
super.updateConstraints()
containerViewHeightConstraint.constant = bottomOverflow
}
public override func layoutSubviews() {
super.layoutSubviews()
log.debug("SurfaceView frame", frame)
log.debug("surface view frame = \(frame)")
updateLayers()
updateContentViewMask()
updateBorder()
contentView?.layer.borderColor = borderColor?.cgColor
contentView?.layer.borderWidth = borderWidth
contentView?.frame = bounds
}
private func updateLayers() {
log.debug("SurfaceView bounds", bounds)
var rect = bounds
rect.size.height += bottomOverflow // Expand the height for overflow buffer
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
backgroundLayer.path = path.cgPath
backgroundLayer.fillColor = color?.cgColor
containerView.backgroundColor = color
if cornerRadius != 0.0, containerView.layer.cornerRadius != cornerRadius {
containerView.layer.masksToBounds = true
containerView.layer.cornerRadius = cornerRadius
}
if shadowHidden == false {
layer.shadowColor = shadowColor.cgColor
layer.shadowOffset = shadowOffset
@@ -128,26 +150,31 @@ public class FloatingPanelSurfaceView: UIView {
}
private func updateContentViewMask() {
guard
cornerRadius != 0.0,
containerView.layer.cornerRadius != cornerRadius
else { return }
if #available(iOS 11, *) {
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyborad of Example/Maps.
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
let maskLayer = CAShapeLayer()
var rect = bounds
rect.size.height += bottomOverflow
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
maskLayer.path = path.cgPath
contentView?.layer.mask = maskLayer
containerView.layer.masksToBounds = true
containerView.layer.cornerRadius = cornerRadius
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
} else {
// Don't use `contentView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
// Instead, a user can mask the content view manually in an application.
}
}
private func updateBorder() {
containerView.layer.borderColor = borderColor?.cgColor
containerView.layer.borderWidth = borderWidth
}
func add(contentView: UIView) {
insertSubview(contentView, belowSubview: grabberHandle)
containerView.addSubview(contentView)
self.contentView = contentView
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
contentView.translatesAutoresizingMaskIntoConstraints = false
@@ -155,7 +182,7 @@ public class FloatingPanelSurfaceView: UIView {
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
contentView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
contentView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
])
}
}
@@ -24,7 +24,7 @@ class FloatingPanelModalTransition: NSObject, UIViewControllerTransitioningDeleg
class FloatingPanelPresentationController: UIPresentationController {
override func presentationTransitionWillBegin() {
// Must call here even if duplicating on in containerViewWillLayoutSubviews()
// Because it let the floating panel present correclty with the presentation animation
// Because it let the floating panel present correctly with the presentation animation
addFloatingPanel()
}
@@ -52,14 +52,14 @@ class FloatingPanelPresentationController: UIPresentationController {
/*
* Layout the views managed by `FloatingPanelController` here for the
* sake of the presentation and disimissal modally from the controller.
* sake of the presentation and dismissal modally from the controller.
*/
addFloatingPanel()
// Forward touch events to the presenting view controller
(fpc.view as? FloatingPanelPassThroughView)?.eventForwardingView = presentingViewController.view
// Set tap-to-dimiss in the backdrop view
// Set tap-to-dismiss in the backdrop view
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
fpc.backdropView.addGestureRecognizer(tapGesture)
}
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.1.0</string>
<string>1.5.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+17 -27
View File
@@ -14,34 +14,22 @@ struct Logger {
private let osLog: OSLog
private let s = DispatchSemaphore(value: 1)
enum Level: Int, Comparable {
private enum Level: Int, Comparable {
case debug = 0
case info = 1
case warning = 2
case error = 3
case fault = 4
var name: String {
switch self {
case .debug: return "DEBUG"
case .info: return "INFO"
case .warning: return "WARNING"
case .error: return "ERROR"
case .fault: return "FAULT"
}
}
var shortName: String {
var displayName: String {
switch self {
case .debug:
return "D/"
case .info:
return "I/"
case .warning:
return "W/"
return "Warning:"
case .error:
return "E/"
case .fault:
return "F/"
return "Error:"
}
}
@available(iOS 10.0, *)
@@ -49,31 +37,35 @@ struct Logger {
switch self {
case .debug: return .debug
case .info: return .info
case .warning: return .info
case .warning: return .default
case .error: return .error
case .fault: return .fault
}
}
public static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
init() {
fileprivate init() {
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
}
private func log(_ level: Level, _ message: Any, _ arguments: [Any], function: String, line: UInt) {
#if __FP_LOG
_ = s.wait(timeout: .now() + 0.033)
defer { s.signal() }
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
let log = "\(level.shortName) \(message) \(extraMessage) (\(function):\(line))"
let log: String = {
switch level {
case .debug:
return "\(level.displayName) \(message) \(extraMessage) (\(function):\(line))"
default:
return "\(level.displayName) \(message) \(extraMessage)"
}
}()
os_log("%@", log: osLog, type: level.osLogType, log)
#endif
}
private func getPrettyFunction(_ function: String, _ file: String) -> String {
@@ -85,7 +77,9 @@ struct Logger {
}
func debug(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
#if __FP_LOG
self.log(.debug, log, arguments, function: getPrettyFunction(function, file), line: line)
#endif
}
func info(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
@@ -99,8 +93,4 @@ struct Logger {
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.error, log, arguments, function: getPrettyFunction(function, file), line: line)
}
func fault(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.fault, log, arguments, function: getPrettyFunction(function, file), line: line)
}
}
+31 -8
View File
@@ -73,19 +73,35 @@ extension UIView {
}
}
extension UIGestureRecognizerState: CustomDebugStringConvertible {
#if __FP_LOG
#if swift(>=4.2)
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .began: return "Began"
case .changed: return "Changed"
case .failed: return "Failed"
case .cancelled: return "Cancelled"
case .ended: return "Endeded"
case .possible: return "Possible"
case .began: return "began"
case .changed: return "changed"
case .failed: return "failed"
case .cancelled: return "cancelled"
case .ended: return "endeded"
case .possible: return "possible"
}
}
}
#else
extension UIGestureRecognizerState: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .began: return "began"
case .changed: return "changed"
case .failed: return "failed"
case .cancelled: return "cancelled"
case .ended: return "endeded"
case .possible: return "possible"
}
}
}
#endif
#endif
extension UIScrollView {
var contentOffsetZero: CGPoint {
@@ -101,3 +117,10 @@ extension UISpringTimingParameters {
self.init(mass: mass, stiffness: stiffness, damping: damp, initialVelocity: initialVelocity)
}
}
extension CGPoint {
static var nan: CGPoint {
return CGPoint(x: CGFloat.nan,
y: CGFloat.nan)
}
}
+47
View File
@@ -0,0 +1,47 @@
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import XCTest
@testable import FloatingPanel
class ViewTests: XCTestCase {
override func setUp() {}
override func tearDown() {}
func test_WarningRetainCycle() {
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
myVC.loadViewIfNeeded()
// Check if there are memory leak warnings in console logs
}
}
class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
var fpc: FloatingPanelController?
override func viewDidLoad() {
fpc = FloatingPanelController(delegate: self)
fpc?.addPanel(toParent: self)
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
return self
}
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
return self
}
var initialPosition: FloatingPanelPosition {
return .half
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0
case .half: return 262.0
case .tip: return 69.0
case .hidden: return nil
}
}
}
-14
View File
@@ -1,14 +0,0 @@
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import XCTest
@testable import FloatingPanelController
class ViewTests: XCTestCase {
override func setUp() {}
override func tearDown() {}
}
+12 -6
View File
@@ -4,6 +4,7 @@
[![Platform](https://img.shields.io/cocoapods/p/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
[![Swift 4.1](https://img.shields.io/badge/Swift-4.1-orange.svg?style=flat)](https://swift.org/)
[![Swift 4.2](https://img.shields.io/badge/Swift-4.2-orange.svg?style=flat)](https://swift.org/)
[![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat)](https://swift.org/)
# FloatingPanel
@@ -67,7 +68,9 @@ Examples are here.
## Requirements
FloatingPanel is written in Swift. It can be built by Xcode 9.4.1 or later. Compatible with iOS 10.0+.
FloatingPanel is written in Swift 4.0+. It can be built by Xcode 9.4.1 or later. Compatible with iOS 10.0+.
✏️ The default Swift version is 4.0 because it avoids build errors with Carthage on each Xcode version from the source compatibility between Swift 4.0, 4.2 and 5.0.
## Installation
@@ -80,6 +83,8 @@ it, simply add the following line to your Podfile:
pod 'FloatingPanel'
```
✏️ To suppress "Swift Conversion" warnings in Xcode, please set a Swift version to `SWIFT_VERSION` for the project in your Podfile. It will be resolved in CocoaPods v1.7.0.
### Carthage
For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`:
@@ -139,9 +144,9 @@ fpc.isRemovalInteractionEnabled = true // Optional: Let it removable by a swipe-
self.present(fpc, animated: true, completion: nil)
```
You can show a floating panel over UINavigationController from the containnee view controllers as a modality of `.overCurrentContext` style.
You can show a floating panel over UINavigationController from the container view controllers as a modality of `.overCurrentContext` style.
NOTE: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/feat-modality/Framework/Sources/FloatingPanelTransitioning.swift).
✏️ FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Framework/Sources/FloatingPanelTransitioning.swift).
## View hierarchy
@@ -151,8 +156,9 @@ NOTE: FloatingPanelController has the custom presentation controller. If you wou
FloatingPanelController.view (FloatingPanelPassThroughView)
├─ .backdropView (FloatingPanelBackdropView)
└─ .surfaceView (FloatingPanelSurfaceView)
├─ .contentView == FloatingPanelController.contentViewController.view
└─ .grabberHandle (GrabberHandleView)
├─ .containerView (UIView)
└─ .contentView (FloatingPanelController.contentViewController.view)
└─ .grabberHandle (GrabberHandleView)
```
## Usage
@@ -251,7 +257,7 @@ class FloatingPanelLandscapeLayout: FloatingPanelLayout {
public func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
return [
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuid.leftAnchor, constant: 8.0),
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
surfaceView.widthAnchor.constraint(equalToConstant: 291),
]
}