Compare commits

...

134 Commits

Author SHA1 Message Date
Shin Yamamoto b6e8928b1a Version 2.8.6 2024-09-07 10:12:44 +09:00
Shin Yamamoto 3a3d53424c Fix doc comments' errors (#644)
* Fix doc comment errors in ObjC APIs
* Improve doc comments in LayoutAnchoring.swift
2024-09-06 13:49:56 +09:00
Shin Yamamoto 71f419a3cd Version 2.8.5 2024-08-05 22:07:48 +09:00
Ivan Levin 0e27410460 Replace fatal errors in transitionDuration delegate methods (#642)
This PR modifies the transitionDuration(using:) method in FloatingPanelController to return 0.0 instead of calling fatalError when the FloatingPanelController instance is not found. This change is crucial for several reasons:

1. Avoiding Crashes in Production: Using fatalError in production can lead to unexpected crashes, which are detrimental to user experience. It's safer to return a default value like 0.0 and handle the scenario gracefully.

2. Improved Stability: By returning 0.0, the application can continue running, allowing for better stability and user satisfaction. This approach also aligns with the principle of fail-safety, where the system continues operating under error conditions.

3. Real-World Case: In our large-scale project, we encountered a crash at this specific point due to the fatalError. Given the size and complexity of our application, it has been challenging to pinpoint the exact cause. Switching to a return value of 0.0 would significantly help us mitigate the issue and maintain app stability while we investigate further.

4. Maintainability: Returning a default value makes the codebase more maintainable and easier to debug, as it avoids abrupt termination and allows for logging or other error handling mechanisms.
2024-08-05 22:06:43 +09:00
Shin Yamamoto 29185a47bd Version 2.8.4 2024-07-06 15:59:52 +09:00
Shin Yamamoto 6821b26706 Fix an inappropriate condition to determine scrolling content or not (#633)
Removed the `pre > .zero` condition from `FloatingPanelLayoutAnchor` as it was not appropriate for zero or negative `absoluteInset` values. 

Added documentation for `shouldScrollingContentInMoving(from:to:)` to prevent similar mistakes in the future.
2024-07-04 10:08:17 +09:00
Shin Yamamoto 5bdbe0f0ea test: increase timeout durations 2024-07-03 22:55:32 +09:00
Shin Yamamoto afe2a9bced ci: remove macos-11 runner
The macOS 11 runner image was removed on 6/28/2024.
https://github.blog/changelog/2024-05-20-actions-upcoming-changes-to-github-hosted-macos-runners/

Therefore Carthage support was removed from README since the 'carthage' job can
no longer be run and then 'carthage' build can not be checked.
2024-07-03 22:13:37 +09:00
Shin Yamamoto c0d88af234 ci: update actions/checkout to v4 2024-07-02 21:09:56 +09:00
Shin Yamamoto 3b4c1bd51c test: increase timeout durations 2024-07-02 20:48:44 +09:00
Shin Yamamoto 3c9f556533 ci: remove test-ios13_7-iPhone_11_Pro job
Because all Intel-based macOS resource were deprecated on June 28, 2024.
2024-07-02 15:31:26 +09:00
Shin Yamamoto 7f1a74825d Remove an unused variable 2024-07-02 00:01:01 +09:00
Shin Yamamoto 22d46c5260 Version 2.8.3 2024-06-12 09:21:33 +09:00
Will Bishop 466aaf21dd Fix a compilation error in Xcode 16 (#636)
This patch fixed the error at SurfaceView#L63 in Xcode 16 (#635):

> 'CALayerCornerCurve' is only available in application extensions for iOS 13.0 or newer.
2024-06-12 09:19:04 +09:00
Shin Yamamoto 56e71ac580 Fix a broken panel layout with a compositional collection view (#634)
* Fix #628 
* Add a new sample to test UICollectionView using a compositional layout
2024-06-08 16:23:29 +09:00
Shin Yamamoto f45b6aaa3f ci: use Xcode 15.4 (#631) 2024-05-18 11:49:18 +09:00
Shin Yamamoto e473c3c440 Fix the scroll tracking of WKWebView on iOS 17.4 (#630)
* Fix a scroll tracking issue of WKWebView on iOS 17.4
* Add 'Scroll tracking(List CollectionView)' use case in Samples app
2024-05-18 10:29:12 +09:00
Shin Yamamoto 8f2be39bf4 Version 2.8.2 2024-02-18 15:25:08 +09:00
Shin Yamamoto 92c10830ff ci: use Xcode 15.2 on the GitHub Actions (#619)
- Replaced 'runsOn' with 'runs-on'.
- Supported build jobs for Xcode 15.2.
- Didn't use `macos-11` as possible which was deprecated.
2024-02-18 14:46:32 +09:00
Shin Yamamoto dcb89f58c3 Add CoreTest.test_handleGesture_endWithoutAttraction() 2024-02-17 09:04:09 +09:00
Shin Yamamoto d39c4b54d1 Enable to define and use a subclass object of BackdropView (#617)
* Enable to create a subclass of BackdropView
* Add a custom backdrop sample in the Samples example
2024-02-16 22:07:14 +09:00
Ortwin Gentz, FutureTap 504182ceae Fix scroll locking behavior (#615)
use a separate `scrollLocked` var instead of abusing the scrollView's properties to store the locked state;
fixes an issue where the scroll indicators were no longer visible because lockScrollView() was executed twice before unlockScrollView() was called (due to the user changing `showsVerticalScrollIndicator` mid-animation);
2024-02-16 22:04:10 +09:00
Shin Yamamoto bc1cfe444b Fix a bug state was not changed property after v2.8.1
The state was not changed after moving a panel without attractive
interaction. For example, a panel is moved from half to full and
the scroll content continues to scroll with its deceleration
animation. We can test it on 'Show tracking(TextView)' in Samples app.

dbef6a6 commit causes this issue.
2024-02-03 13:36:36 +09:00
Shin Yamamoto 0e0f773df7 Possible fix for #586 2024-02-03 10:27:21 +09:00
Shin Yamamoto be2be99537 Fix a typo 2023-12-02 09:03:31 +09:00
Shin Yamamoto afcf1ced36 ci: support xcode 15.0.1 2023-11-18 11:51:15 +09:00
Shin Yamamoto 5b33d3d5ff Version 2.8.1 2023-11-04 21:17:09 +09:00
Shin Yamamoto dbef6a691a Fix an invalid behavior after switching to a new layout object (#611)
* Added a test for the use case, ControllerTests.test_switching_layout()
2023-11-04 13:25:25 +09:00
Shin Yamamoto dd238884bf Version 2.8.0 2023-10-08 14:51:26 +09:00
Shin Yamamoto 046ed3df5b Modify the doc comment of 'floatingPanel(_:shouldAllowToScroll:in:)' 2023-10-08 14:51:26 +09:00
Shin Yamamoto 4996ce1a84 Remove unnecessary notes 2023-10-08 14:51:26 +09:00
Shin Yamamoto b8f7ff825d Revise the doc comment of shouldProjectMomentum delegate method 2023-10-08 14:51:26 +09:00
Shin Yamamoto 6fb9a9b3a2 Add a new section for the new API 2023-09-20 08:58:02 +09:00
Shin Yamamoto 34ebb3bf19 Use alerts syntax in the README 2023-09-19 15:18:10 +09:00
Shin Yamamoto dbf665526d ci: disable parallel testing to improve stability 2023-09-19 09:43:04 +09:00
Shin Yamamoto 6c7f529eff Add 'state' parameter into 'floatingPanel(_:shouldAllowToScroll:)'
This is because the `state` argument of `Core.isScrollable(state:)` is
not always equal to `FloatingPanelController.state` property. Therefore,
the API should pass the `state` property of `Core.isScrollable(state:)`.
2023-09-19 09:41:07 +09:00
Shin Yamamoto c414d3a2a6 Fix errors of offset value from a state position
Sometimes an offset value has an error less than 1 pt, for example,
-0.666... 0.333... etc. This is due to `surfaceView.presentationFrame`.
2023-09-13 22:04:18 +09:00
Shin Yamamoto 8eba647d75 Fix a panel not moving when picked up in certain area
On `DebugTableViewController` in the Samples app, the panel does not move
when a finger move up from the menu area to the grabber area.
`scrollViewFrame` is not necessary due to the same condition is already
checked at Core.swift:L578.
2023-09-13 22:04:18 +09:00
Shin Yamamoto c3568067b7 Reverse 'LayoutAdapter.offsetFromMostExpandedAnchor' direction 2023-09-13 22:04:18 +09:00
Shin Yamamoto 72580f089d Fix scroll offset reset when moving in grabber area
In addition, `scrollFrame` conversion is removed because it's not really
necessary.
2023-09-13 22:04:16 +09:00
Shin Yamamoto c508ec892d Return false in shouldScrollViewHandleTouch if initial touch is outside scroll view 2023-09-13 22:03:29 +09:00
Shin Yamamoto ff2d4a48f1 Enable content scrolling in non-expanded states (#455)
The new `floatingPanel(_:shouldAllowToScroll)` delegate method allows the
library user to determine whether the content scrolls or not in certain
state. `Core.isScrollable(state:)` and `LayoutAdpter.offset(from:)` are
added for this feature.
2023-09-13 20:32:48 +09:00
Shin Yamamoto 62364eb6d5 Update the minimum deployment target to 11.0 2023-09-06 21:57:23 +09:00
Shin Yamamoto ce5469a69d Fix a compile error on swift 5.4 or earlier
The error detail is here:

> Extensions.swift:11:18: error: cannot convert value of type 'CGFloat' to
> expected argument type 'Double'
>
>         let v = (self * p).rounded(.towardZero) / p
>                  ^
>                  Double( )
2023-09-03 23:25:50 +09:00
Shin Yamamoto 93c31fd71d Fix CGFloat.rounded(by displayScale) for a floating point error
Where a value is -0.16666666666674246, -0.0 is the rounded value to be
expected. However the current implementation returned -0.3333333333333.
This is because the floating point error. So this patch truncates it.
2023-09-01 22:34:48 +09:00
Shin Yamamoto 461f637818 Add isLooselyLocked flag 2023-09-01 22:34:48 +09:00
Shin Yamamoto 6b3b18b8ed Care left/right positioned panels on the scroll indicator lock/unlock 2023-09-01 22:34:48 +09:00
Shin Yamamoto b5ca468397 Fix the hidden scroll indicator in Maps example
In Maps example, the scroll indicator of the table view doesn't show
even if `UIScrollView.showsVerticalScrollIndicator` is set to `true`.
This is due to the occurrence of two loose scroll locks before the
scroll content is displayed.
2023-09-01 22:34:45 +09:00
Shin Yamamoto 80956bfac6 Display the scroll indicator of table view in Maps example
The top inset of its scroll indicator has an unexpected top margin.
`UITableView` seems to append a top inset in addition to its scroll
insets. However, it's important to display the indicator for the library
testing. This allows us to verify whether the scroll indicator shows and
hides as expected.
2023-09-01 22:32:12 +09:00
Shin Yamamoto 5d382c440f Rename outdated compilation condition name to latest 2023-09-01 21:13:06 +09:00
Shin Yamamoto d28c939a4c Fix active compilation conditinos in unit testing 2023-09-01 21:11:51 +09:00
Shin Yamamoto 8bd02145cf Fix typo 2023-09-01 21:11:51 +09:00
Shin Yamamoto debeca1fb2 Version 2.7.0 2023-08-21 19:28:57 +09:00
Shin Yamamoto 9da54f9fc1 Call floatingPanelWillRemove when a panel removes from a window 2023-08-20 22:23:41 +09:00
Shin Yamamoto a13d053867 Update the doc comment of floatingPanelDidEndDragging method 2023-08-20 22:23:41 +09:00
Shin Yamamoto eda7201fe8 Remove unnecessary code 2023-08-18 12:09:34 +09:00
Shin Yamamoto b1edef49a4 ci: update the push trigger of github actions 2023-08-17 09:02:35 +09:00
Shin Yamamoto a2db94a8c4 Fix invalid scroll offsets after moving between states
Sometimes, the content offset of the tracking scroll view would become
less than the content inset (e.g. when a panel moves from half to full
state displaying content with a top bar similar to 'Show Navigation
Controller' in the Samples app). This resulted in the content getting
fixed in an unintended position.

For this issue, this commit completely changes the scroll offset pinning
logic from one shot pinning by DispatchQueue at Core:L43-L56

That old logic was added when the UIViewPropertyAnimator was used to move
the panel. But now, the custom animator using CADisplayLink allows
fine-grained control of panel movement and then the scroll offset is
able to be pinned during its panel transitions.
2023-08-17 09:02:35 +09:00
Shin Yamamoto 57495cff84 Fix log prints 2023-08-14 22:32:14 +09:00
Shin Yamamoto eff5cde844 Remove libswiftCoreGraphics.tbd to fix a crash of SamplesObjC.app
Sometimes SamplesObjC app crashes by the following error:
> dyld[21380]: Library not loaded: @rpath/libswiftCoreGraphics.dylib
>   Reason: tried: '/usr/lib/system/introspection/libswiftCoreGraphics.dylib'
>   (no such file, not in dyld cache),
2023-08-14 22:32:14 +09:00
Shin Yamamoto c365eadf1e Retain scroll view position while moving between states (#587)
Previously, the panel might not consistently keep its scroll content
offset when moving from its most expanded state to another.

Changes made in this commit:

* Keep the content offset of tracking scroll view in the following cases.
  A panel is moved...
  1. Outside of the tracking scroll view.
  2. Inside of a navigation bar/toolbar over the tracking scroll view.

* Stopped the scroll offset reset of the `stopScrollDeceleration` flag
  in the  `panningEnd` method when the panel transitions from its most
  expanded state because there is no issue without the reset.
2023-08-14 22:28:19 +09:00
Shin Yamamoto 5d02681b05 Add 'Expand top margin' switcher in DebugTextViewController 2023-08-11 16:46:42 +09:00
Shin Yamamoto 6e17ff734a Modify shouldProjectMomentum(_:{proposedTargetPosition => proposedState}:) 2023-08-11 16:45:23 +09:00
Shin Yamamoto 02ed923e7b Replace 'Position' words 2023-08-11 16:45:23 +09:00
Shin Yamamoto 421335d98c Make the 'Show Panel over Window' panel removable 2023-08-11 16:09:47 +09:00
Shin Yamamoto 2618f49556 Add GestureTests 2023-08-11 15:01:25 +09:00
Shin Yamamoto 328116600f Update the minimum deployment target to iOS 11.0 on the Unit test target 2023-08-11 15:01:25 +09:00
Shin Yamamoto a10b1426cd Allow a 'delegateProxy' object to access the default implementations 2023-08-11 15:01:25 +09:00
Shin Yamamoto 5468856a93 Set isAttracting to true while moveAnimator is active
And make it calls the related delegate methods
2023-08-11 15:01:24 +09:00
Shin Yamamoto 8f3a7de321 Call the 'floatingPanelDidEndDragging' method after 'state' property changes
This change allows the library user to get the correct state in
'floatingPanelDidEndDragging' method while `attract` is false.
2023-08-11 15:01:01 +09:00
Shin Yamamoto 2de1fb9ac8 Call the 'floatingPanelDidMove' delegate at the end of a move interaction 2023-08-11 15:01:01 +09:00
Shin Yamamoto 27a2d81a71 Version 2.6.6 2023-08-11 14:17:09 +09:00
Shin Yamamoto 85ed3a6ce3 Fix scroll tracking issues of the scroll view with a positive scroll inset
These issues arose in 'Show Navigation Controller' sample of Samples app.

1. The scrollView's contentOffset always becomes (0, 0) instead of (0, -44),
   which is normal if there is a UINavigationBar.

2. The scrollView's contentOffset sometimes becomes (0, 0) after moving
   a panel quickly like picking.

Case 1 is caused by 7511ce5 commit.
Case 2 is caused by a workaround added at 81fd85e commit.

I tested this library from iOS 11 to iOS 16, and then I confirmed this
workaround doesn't need anymore.

Related to #602, #603.
2023-08-11 13:52:54 +09:00
Shin Yamamoto b34f1093de Version 2.6.5 2023-07-26 18:29:23 +09:00
Shin Yamamoto de1dbe70de Fix a method name for the method convention 2023-07-25 22:47:20 +09:00
Shin Yamamoto c593c646ca Fix a scroll tracking problem caused by a floating point error in LayoutAdapter.offsetFromMostExpandedAnchor
This problem arose after 6611ec8 commit. The root cause is linked to
this condition: `0 == layoutAdapter.offsetFromMostExpandedAnchor` at
line 589 in the method `Core.shouldScrollViewHandleTouch(_:point:velocity:)`.

If the value of `layoutAdapter.offsetFromMostExpandedAnchor` has a
floating point error, the condition evaluates to false. As a result,
the panel moves even when the tracking scroll view is intended to
scroll.

This problem may not occur if there is no floating point error.
2023-07-25 22:13:30 +09:00
Shin Yamamoto 0cb5307a61 Version 2.6.4 2023-07-22 10:18:18 +09:00
Shin Yamamoto 8ab3b7986c Fix test failures in ControllerTests 2023-07-20 22:42:58 +09:00
Shin Yamamoto bbdf3e7c6f Update the logging impl using unified logging system for Xcode 15
The Logging API document says that `os_log` is one of the legacy logging
symbols. However,  this library needs to be supported below iOS 14 so
`log(level:_:)` cannot be used.
2023-07-19 22:16:43 +09:00
Shin Yamamoto bdb756b665 ci: update the test matrix for iOS 16 2023-07-19 16:20:12 +09:00
Shin Yamamoto 6611ec83a2 Fix an issue where a tracking scroll content stops and the panel doesn't move (#530) 2023-07-19 16:18:41 +09:00
Shin Yamamoto a917d6a626 Version 2.6.3 2023-07-01 13:54:25 +09:00
Sören Gade 7511ce577d Fix scrollview content staying non-interactive after slowly swiping down (#597)
This was noticed when updating contained SwiftUI views rapidly.

This change is what fixed a certain bug the scroll content can be locked at a negative offset. It was most obvious when the whitespace was large, hence the offset was something around -100.0. At the same time, the contents (like buttons) were non-interactive.

Co-authored-by: Sören Gade <soeren.gade@lichtblick.de>
2023-07-01 11:52:38 +09:00
Shin Yamamoto e7d0a72440 Fix an issue where dismissalTapGestureRecognizer doesn't work in one case (#590)
`dismissalTapGestureRecognizer` didn't work when the panel is added into UIWindow directly as its subview. This PR fixes this issue and also adds the use case in Samples.app.
2023-07-01 11:49:36 +09:00
Shin Yamamoto 44923ef66e [Revised] Fix an issue scrolling jumps with a small scroll view content (#524)
This is the revised version of commit 448fc5c.

Commit 448fc5c has a critical regression in scroll tracking that can cause the
scroll content to bounce after moving a panel, for example, pulling down it from
full to half state.

By re-investigating #524, I found that this problem only occurred with the
`fitToBounds` content mode and a small scroll view content.

Therefore I fixed it in the more specific way.
2023-07-01 11:46:23 +09:00
Shin Yamamoto 2760bc7298 Modify a private typealias name 2023-06-21 21:04:28 +09:00
Shin Yamamoto d428e96b03 Add the SPI manifest YAML file 2023-06-12 20:32:04 +09:00
Shin Yamamoto 67495961e5 Remove unnecessary prints in unit testing 2023-06-11 21:59:29 +09:00
Shin Yamamoto 7b88703e43 ci: add Xcode 14.3 / Swift 5.8 build 2023-06-10 10:01:46 +09:00
Shin Yamamoto c40e66ef3d Fix a trivial bug on Samples app 2023-06-06 11:35:49 +09:00
Shin Yamamoto 53fb131d0e Update the swizzling way in FloatingPanelController 2023-06-03 12:26:57 +09:00
Shin Yamamoto 37969f6cb3 Merge pull request #588 from scenee/release/2.6.2
Release v2.6.2
2023-06-03 10:40:57 +09:00
Shin Yamamoto a0ba3af012 Version 2.6.2 2023-06-03 10:23:03 +09:00
Shin Yamamoto 6082dba6a7 ci: add circleci config for testing on ios13/14 2023-06-03 10:12:13 +09:00
Shin Yamamoto 43327e8123 ci: add timeout-minutes 2023-06-03 10:12:13 +09:00
Shin Yamamoto 0a5bc19d0f Remove the host app for the unit testing 2023-06-03 10:12:13 +09:00
Shin Yamamoto dd52e1eaee Revise an illegible variable name 2023-04-16 10:14:54 +09:00
Shin Yamamoto 22aa055843 Revise the short descriptions 2023-04-16 10:09:39 +09:00
Shin Yamamoto 448fc5cbb4 Stop changing UIScrollView.bounces when locking or unlocking a scroll view (#584)
UIScrollView would unexpectedly change its scroll offset after updating the bounces property when dealing with small scrollable content. This fixes issue #524, "Scrolling jumps when tableView content is small".
2023-04-08 10:30:11 +09:00
Shin Yamamoto b4fe3b408c Remove unused testing targets in the Samples project 2023-04-08 10:11:41 +09:00
Shin Yamamoto 6ecc7924ba Update badges in README 2023-04-06 21:50:49 +09:00
Shin Yamamoto 0b82902233 Update README for the API documentation on GitHub Pages 2023-03-25 11:57:24 +09:00
Shin Yamamoto 597dbd4145 Revise some sentences in README 2023-03-25 11:04:55 +09:00
Shin Yamamoto 1ee949ff1b Merge pull request #583 from scenee/release/2.6.1
Release 2.6.1
2023-03-04 10:46:11 +09:00
Shin Yamamoto 2a29cb5b3e Version 2.6.1 2023-03-04 09:43:23 +09:00
Shin Yamamoto 208ab665da ci: add Maps-SwiftUI example build 2023-03-04 09:42:40 +09:00
Shin Yamamoto 3e20314cfa Update DebugTableViewController with followScrollViewBouncing() API 2023-03-02 21:53:24 +09:00
Shin Yamamoto 5b8e9a54d9 Update trackingScrollViewDidScroll() with doc comments 2023-03-02 21:53:24 +09:00
Vlad d3c30b35d9 track scroll view bouncing (#525) 2023-02-25 11:11:50 +09:00
Shin Yamamoto 8bb3795931 Take care of an invalid bounding rectangle of PassthroughView in a screen rotation 2023-02-25 10:51:19 +09:00
Shin Yamamoto 9383cd001d ci: replace iOS 15 envs on testing
Because testing on iOS 15.4 is buggy.
2023-02-18 12:15:26 +09:00
Shin Yamamoto 6d5f770066 ci: replace macos-10.15 with macos-11
macos-10.15 will be completely removed by 2023-03-31.
https://github.com/actions/runner-images/issues/5583#issuecomment-1426004850
2023-02-18 11:54:19 +09:00
Shin Yamamoto ce2cafed5b Workaround: fix a laggy animation in Samples app
The AutoLayout rendering engine has been changed since iOS 16.
As a result, "Show Detail Panel" in Samples.apps has a laggy animation
when pulling it down to the bottom of the screen. The issue hadn't
occurred until iOS 15. "On Safe Area View" causes this issue. I could
fix it by removing the constraint of its view to the top of the safe area.

As a workaround, I've replaced its top constraint with one of a
constraint to the top of the superview.
2023-02-18 11:46:41 +09:00
Shin Yamamoto 68352218ac ci: upgrade actions/checkout to suppress Node.js 12 warnings 2023-02-18 11:10:35 +09:00
Shin Yamamoto e3736e4214 Merge pull request #582 from scenee/release/2.6.0
Release 2.6.0
2023-02-18 11:06:19 +09:00
Shin Yamamoto d6bbf92339 Version 2.6.0 2023-02-18 10:27:53 +09:00
Shin Yamamoto 0e833aee3c Fix SettingsViewController in Samples app 2023-02-17 09:51:22 +09:00
Shin Yamamoto 5949bc88ea Fix typo 2023-02-06 20:46:25 +09:00
Shin Yamamoto 30bd261a10 Update the swift version in the pod spec 2023-01-30 20:47:47 +09:00
Shin Yamamoto f92279484f Fix the buggy scroll tracking
This commit sets the initial scroll offset to the pinning offset.
The previous implementation, which set it to the current content offset,
leads various scroll tracking bugs. This reproduction is one of issues.

Using 'Scroll tracking(UITableView)' in Samples app.
1. Bounce the scroll content at the top most anchor.
2. Pull down the panel in bouncing at a minus content offset. (the
   scroll content stops at the minus offset.)
3. Pull up it

The previous implementation was implemented for #526/#527. But now the
issue hasn't been reproduced in v2.5.6.
2023-01-21 11:21:34 +09:00
Shin Yamamoto 2205d1186d Enable to restrict the content size in FloatingPanelAdaptiveLayoutAnchor (#518)
* Introduce FloatingPanelLayoutContentBoundingGuide property for FloatingPanelAdaptiveLayoutAnchor
* Revise doc comments
* Clean up code
* Update the minimum deployment target of Samples app to iOS 11
2023-01-15 10:30:25 +09:00
Shin Yamamoto cd0948a9a4 Update a doc comment 2023-01-15 08:52:29 +09:00
Shin Yamamoto 6589aff4ac ci: add xcode 14.1 env (#577) 2023-01-13 09:12:17 +09:00
Shin Yamamoto d34c16b1a5 Refactor BackdropView.dismissalTapGestureRecognizer
* Added the interface and implementation comment
* Changed the property as non-optional
2023-01-07 09:14:04 +09:00
Shin Yamamoto 05813253f9 Merge pull request #571 from scenee/release/2.5.5
Release 2.5.5
2022-10-17 20:26:36 +09:00
Shin Yamamoto 4ab7d26030 Version 2.5.5 2022-10-15 19:39:31 +09:00
Shin Yamamoto 162545d7e9 Add test_initial_surface_position() 2022-10-15 09:30:44 +09:00
Shin Yamamoto 4904ea19cb Fix the stiff animation of "show detail panel" panel.
When a panel moves to tip position, the moving animation has been stiff.
This issue has happened since Xcode 14.
2022-10-15 09:19:01 +09:00
Shin Yamamoto b941f91556 Fix the backdrop alpha when the view size or its size class changes (#573)
This fixes #572 to change the backdrop alpha when the view size or 
its size class changes.

The main change is that `true` is passed as a
`forceLayout` parameter into `viewWillTransition(to:with:)` callbacks.

Because it's necessary for the backdrop alpha's update when the view
size or its size class changes.

This also fixes a regression at `9c45c31` commit.

```diff
-        layoutAdapter.activateLayout(for: state, forceLayout: true)
+        layoutAdapter.activateLayout(for: state, forceLayout: forceLayout)
```

The behavior before the above change indicates that the method has
worked well even when `forceLayout` is set to `true` in their callbacks.

Additional improvements: 

* Format `activateLayout(forceLayout:contentInsetAdjustmentBehavior:)`
* Add `_floor` function for `test_updateBackdropAlpha()`
2022-10-15 09:06:14 +09:00
Shin Yamamoto f917316135 Remove the libswiftCoreGraphics.dylib workaround from Maps-SwiftUI.app
Because the deployment target is above iOS 14.
2022-10-10 11:15:58 +09:00
ll_shioi_atsumori 6235a19588 workaround: add libswiftCoreGraphics.tbd to some sample apps (#567)
This fixed the following error occurs and crashes when running an app built with xcode14 on iOS11.
```
dyld: Library not loaded: /usr/lib/swift/libswiftCoreGraphics.dylib
  Referenced from: /private/var/containers/Bundle/Application/0B28F8D6-D8CE-400B-98B7-052EAD3FB923/xxxxxx.app/Frameworks/FloatingPanel.framework/FloatingPanel
  Reason: image not found
```

These are the related forum threads.
- https://developer.apple.com/forums/thread/714629
- https://developer.apple.com/forums/thread/714795

Co-authored-by: atsunori.shioi <astunori.shioi@play.jp>
2022-10-10 11:01:11 +09:00
Shin Yamamoto c47fc3d1d5 Better to declare FloatingPanelLayout.anchors as constants if possible 2022-10-05 22:03:59 +09:00
Shin Yamamoto e6d285c6df Fix the initial position value of the surface view
'Show Top Positioned Panel' in Samples app was broken.
2022-10-05 22:03:50 +09:00
Shin Yamamoto a26a6beab2 Merge pull request #565 from scenee/release/2.5.4
Release 2.5.4
2022-09-11 08:44:13 +09:00
59 changed files with 2621 additions and 1827 deletions
+14
View File
@@ -0,0 +1,14 @@
version: 2.1
jobs:
test-ios14_5-iPhone_12_Pro:
macos:
xcode: 13.4.1
steps:
- checkout
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.5,name=iPhone 12 Pro'
workflows:
test:
jobs:
- test-ios14_5-iPhone_12_Pro
+101 -58
View File
@@ -4,117 +4,160 @@ on:
push:
branches:
- master
- next
pull_request:
branches:
- '*'
jobs:
build:
runs-on: ${{ matrix.runsOn }}
runs-on: ${{ matrix.runs-on }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- swift: "5.10"
xcode: "15.4"
runs-on: macos-14
- swift: "5.9"
xcode: "15.2"
runs-on: macos-13
- swift: "5.8"
xcode: "14.3.1"
runs-on: macos-13
- swift: "5.7"
xcode: "14.1"
runs-on: macos-12
- swift: "5.6"
xcode: "13.3.1"
runsOn: macos-12
xcode: "13.4.1"
runs-on: macos-12
- swift: "5.5"
xcode: "13.2.1"
runsOn: macos-11
- swift: "5.4"
xcode: "12.5.1"
runsOn: macos-11
- swift: "5.3"
xcode: "12.4"
runsOn: macos-10.15
- swift: "5.2"
xcode: "11.7"
runsOn: macos-10.15
- swift: "5.1"
xcode: "11.3.1"
runsOn: macos-10.15
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Building in Swift ${{ matrix.swift }}
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
test:
runs-on: ${{ matrix.runsOn }}
runs-on: ${{ matrix.runs-on }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- os: "15.4"
xcode: "13.3.1"
- os: "17.5"
xcode: "15.4"
sim: "iPhone 15 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-14
- os: "16.4"
xcode: "14.3.1"
sim: "iPhone 14 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-13
- os: "15.5"
xcode: "13.4.1"
sim: "iPhone 13 Pro"
runsOn: macos-12
- os: "15.2"
xcode: "13.2.1"
sim: "iPhone 13 Pro"
runsOn: macos-11
- os: "14.5"
xcode: "12.5.1"
sim: "iPhone 12 Pro"
runsOn: macos-11
- os: "14.4"
xcode: "12.4"
sim: "iPhone 12 Pro"
runsOn: macos-10.15
- os: "13.7"
xcode: "11.7"
sim: "iPhone 11 Pro"
runsOn: macos-10.15
parallel: NO # Stop random test job failures
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Testing in iOS ${{ matrix.os }}
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}'
run: |
xcodebuild clean test \
-workspace FloatingPanel.xcworkspace \
-scheme FloatingPanel \
-destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \
-parallel-testing-enabled '${{ matrix.parallel }}'
timeout-minutes: 20
example:
runs-on: macos-12
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_13.3.1.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- example: "Maps"
- example: "Maps-SwiftUI"
- example: "Stocks"
- example: "Samples"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Building ${{ matrix.example }}
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
run: |
xcodebuild clean build \
-workspace FloatingPanel.xcworkspace \
-scheme ${{ matrix.example }} \
-sdk iphonesimulator
swiftpm:
runs-on: macos-12
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_13.3.1.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
strategy:
fail-fast: false
matrix:
platform: [iphoneos, iphonesimulator]
arch: [x86_64, arm64]
exclude:
- platform: iphoneos
arch: x86_64
include:
# 17.2
- platform: iphoneos
sys: "ios17.2"
- platform: iphonesimulator
sys: "ios17.2-simulator"
steps:
- uses: actions/checkout@v4
- name: "Swift Package Manager build"
run: |
xcrun swift build \
--sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \
-Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}"
swiftpm_old:
runs-on: ${{ matrix.runs-on }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- target: "arm64-apple-ios15.4-simulator"
- target: "x86_64-apple-ios15.4-simulator"
# 16.4
- target: "x86_64-apple-ios16.4-simulator"
xcode: "14.3.1"
runs-on: macos-13
- target: "arm64-apple-ios16.4-simulator"
xcode: "14.3.1"
runs-on: macos-13
# 15.7
- target: "x86_64-apple-ios15.7-simulator"
xcode: "14.1"
runs-on: macos-12
- target: "arm64-apple-ios15.7-simulator"
xcode: "14.1"
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: "Swift Package Manager build"
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
carthage:
runs-on: macos-11
steps:
- uses: actions/checkout@v2
- name: "Carthage build"
run: carthage build --use-xcframeworks --no-skip-current
run: |
swift build \
-Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" \
-Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
cocoapods:
runs-on: macos-12
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: "CocoaPods: pod lib lint"
run: pod lib lint --allow-warnings
- name: "CocoaPods: pod spec lint"
+5
View File
@@ -0,0 +1,5 @@
version: 1
builder:
configs:
- documentation_targets: [FloatingPanel]
platform: ios
@@ -18,6 +18,7 @@
54B51134216C3D860033A6F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */; };
54E26CB624A989090066C720 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E26CB524A989090066C720 /* Utils.swift */; };
54E26CB824A98E310066C720 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E26CB724A98E310066C720 /* DetailViewController.swift */; };
5D82A6A728D18422006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6A628D1841E006A44BA /* libswiftCoreGraphics.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -47,6 +48,7 @@
54B51135216C3D860033A6F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54E26CB524A989090066C720 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
54E26CB724A98E310066C720 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
5D82A6A628D1841E006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -55,6 +57,7 @@
buildActionMask = 2147483647;
files = (
543844BD23D2BE2000D5EDE4 /* MapKit.framework in Frameworks */,
5D82A6A728D18422006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
549D23D2233C77D5008EF4D7 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -65,6 +68,7 @@
543844BB23D2BE1F00D5EDE4 /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6A628D1841E006A44BA /* libswiftCoreGraphics.tbd */,
543844BC23D2BE2000D5EDE4 /* MapKit.framework */,
);
name = Frameworks;
@@ -336,6 +340,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -355,6 +363,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
+33 -31
View File
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -11,16 +13,16 @@
<objects>
<viewController id="BYZ-38-t0r" customClass="MainViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="5Jw-n2-Cpw">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
</mapView>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d9i-3g-8Ja">
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="59"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="lMa-xa-AVV">
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="59"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<blurEffect style="prominent"/>
@@ -52,31 +54,31 @@
<objects>
<viewController storyboardIdentifier="SearchViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0S1-Lk-JgE" customClass="SearchViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ncl-E9-yRn">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ye3-uU-bq3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="ED1-gT-FBj">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<searchBar contentMode="redraw" searchBarStyle="minimal" translatesAutoresizingMaskIntoConstraints="NO" id="Zcj-SE-gb8">
<rect key="frame" x="0.0" y="6" width="600" height="51"/>
<rect key="frame" x="0.0" y="6" width="393" height="56"/>
<textInputTraits key="textInputTraits"/>
</searchBar>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="D7r-re-InH">
<rect key="frame" x="0.0" y="61" width="600" height="539"/>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="D7r-re-InH">
<rect key="frame" x="0.0" y="66" width="393" height="786"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<view key="tableHeaderView" contentMode="scaleToFill" id="u28-LY-hIh" customClass="SearchHeaderView" customModule="Maps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="600" height="116"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="116"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" baselineRelativeArrangement="YES" translatesAutoresizingMaskIntoConstraints="NO" id="era-8w-yA1">
<rect key="frame" x="24" y="10.5" width="552" height="97.5"/>
<rect key="frame" x="24" y="10.666666666666664" width="345" height="97.333333333333343"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="auI-1v-Yfk">
<rect key="frame" x="0.0" y="0.0" width="60" height="97.5"/>
<rect key="frame" x="0.0" y="0.0" width="60" height="97.333333333333329"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="food" translatesAutoresizingMaskIntoConstraints="NO" id="ErN-bC-qTx">
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
@@ -86,7 +88,7 @@
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Food &amp; Drinks" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nx2-fW-xAm">
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@@ -94,7 +96,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="0vd-sD-XKv">
<rect key="frame" x="164" y="0.0" width="60" height="97.5"/>
<rect key="frame" x="95" y="0.0" width="60" height="97.333333333333329"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="shopping" translatesAutoresizingMaskIntoConstraints="NO" id="xcm-St-HAo">
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
@@ -104,7 +106,7 @@
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="H7q-q2-ga5">
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
<string key="text">Shopping
</string>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
@@ -114,7 +116,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Jd8-YL-b5s">
<rect key="frame" x="328" y="0.0" width="60" height="97.5"/>
<rect key="frame" x="190" y="0.0" width="60" height="97.333333333333329"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="fun" translatesAutoresizingMaskIntoConstraints="NO" id="bMJ-Jn-Gi8">
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
@@ -124,7 +126,7 @@
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kKh-45-FZ2">
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
<string key="text">Fun
</string>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
@@ -134,7 +136,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="dTL-e1-Arz">
<rect key="frame" x="492" y="0.0" width="60" height="97.5"/>
<rect key="frame" x="285" y="0.0" width="60" height="97.333333333333329"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="travel" translatesAutoresizingMaskIntoConstraints="NO" id="8h3-fo-pC3">
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
@@ -144,7 +146,7 @@
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WBT-Vj-7QA">
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
<string key="text">Travel
</string>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
@@ -166,10 +168,10 @@
</view>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="Cell" rowHeight="70" id="LzC-B9-Adb" customClass="SearchCell" customModule="Maps" customModuleProvider="target">
<rect key="frame" x="0.0" y="160.5" width="600" height="70"/>
<rect key="frame" x="0.0" y="166" width="393" height="70"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LzC-B9-Adb" id="evr-60-laS">
<rect key="frame" x="0.0" y="0.0" width="600" height="70"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="70"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="like" translatesAutoresizingMaskIntoConstraints="NO" id="GEk-yE-lLq">
@@ -181,16 +183,16 @@
</constraints>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="Gfl-Oy-rsy">
<rect key="frame" x="57" y="12" width="528" height="46"/>
<rect key="frame" x="57" y="12" width="321" height="46"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Favorites" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Spf-8L-Ne6">
<rect key="frame" x="0.0" y="0.0" width="528" height="22"/>
<rect key="frame" x="0.0" y="0.0" width="321" height="22"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0 Places" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gyo-3V-7U8">
<rect key="frame" x="0.0" y="24" width="528" height="22"/>
<rect key="frame" x="0.0" y="24" width="321" height="22"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" red="0.57647058819999997" green="0.57647058819999997" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
@@ -255,18 +257,18 @@
<objects>
<viewController storyboardIdentifier="DetailViewController" id="Tp2-MF-IFz" customClass="DetailViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="FmO-AT-4Y7">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c3d-2e-0b1">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="-1" y="-1" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="9fL-a5-0LS">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="kP7-56-wlG">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView>
</subviews>
+31 -40
View File
@@ -156,6 +156,14 @@ class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGes
self.owner = owner
}
func floatingPanel(
_ fpc: FloatingPanelController,
shouldAllowToScroll scrollView: UIScrollView,
in state: FloatingPanelState
) -> Bool {
return state == .full || state == .half
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
switch newCollection.verticalSizeClass {
case .compact:
@@ -210,24 +218,15 @@ class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGes
class SearchPanelLandscapeLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .tip
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
]
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
if #available(iOS 11.0, *) {
return [
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
surfaceView.widthAnchor.constraint(equalToConstant: 291),
]
} else {
return [
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0),
surfaceView.widthAnchor.constraint(equalToConstant: 291),
]
}
return [
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
surfaceView.widthAnchor.constraint(equalToConstant: 291),
]
}
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
@@ -244,11 +243,9 @@ class DetailPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGes
class DetailPanelPhoneLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
]
let initialState: FloatingPanelState = .full
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
@@ -297,13 +294,11 @@ class SearchPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestu
class SearchPanelPadLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .top
let initialState: FloatingPanelState = .tip
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.tip: FloatingPanelLayoutAnchor(absoluteInset: 80.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 200.0, edge: .top, referenceGuide: .superview),
.full: FloatingPanelLayoutAnchor(absoluteInset: 60.0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.tip: FloatingPanelLayoutAnchor(absoluteInset: 80.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 200.0, edge: .top, referenceGuide: .superview),
.full: FloatingPanelLayoutAnchor(absoluteInset: 60.0, edge: .bottom, referenceGuide: .superview),
]
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
}
@@ -325,7 +320,7 @@ class SearchPaneliPadBehavior: FloatingPanelBehavior {
var momentumProjectionRate: CGFloat {
return UIScrollView.DecelerationRate.fast.rawValue
}
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
return true
}
}
@@ -357,11 +352,9 @@ class DetailPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestu
class DetailPanelPadLeftLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .left
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 375, edge: .left, referenceGuide: .superview)
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 375, edge: .left, referenceGuide: .superview)
]
let initialState: FloatingPanelState = .full
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
@@ -370,11 +363,9 @@ class DetailPanelPadLeftLayout: FloatingPanelLayout {
class DetailPanelPadRightLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .right
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 375, edge: .right, referenceGuide: .superview)
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 375, edge: .right, referenceGuide: .superview)
]
let initialState: FloatingPanelState = .full
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
+4 -30
View File
@@ -81,9 +81,6 @@ class SearchViewController: UIViewController, UITableViewDataSource {
var items: [LocationItem] = []
// For iOS 10 only
private lazy var shadowLayer: CAShapeLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
@@ -92,29 +89,6 @@ class SearchViewController: UIViewController, UITableViewDataSource {
hideHeader(animated: false)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11, *) {
} else {
// Exmaple: Add rounding corners on iOS 10
visualEffectView.layer.cornerRadius = 9.0
visualEffectView.clipsToBounds = true
// Exmaple: Add shadow manually on iOS 10
view.layer.insertSublayer(shadowLayer, at: 0)
let rect = visualEffectView.frame
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 9.0, height: 9.0))
shadowLayer.frame = visualEffectView.frame
shadowLayer.shadowPath = path.cgPath
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0)
shadowLayer.shadowOpacity = 0.2
shadowLayer.shadowRadius = 3.0
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
@@ -130,16 +104,16 @@ class SearchViewController: UIViewController, UITableViewDataSource {
}
func showHeader(animated: Bool) {
changeHeader(height: 116.0, aniamted: animated)
changeHeader(height: 116.0, animated: animated)
}
func hideHeader(animated: Bool) {
changeHeader(height: 0.0, aniamted: animated)
changeHeader(height: 0.0, animated: animated)
}
private func changeHeader(height: CGFloat, aniamted: Bool) {
private func changeHeader(height: CGFloat, animated: Bool) {
guard let headerView = tableView.tableHeaderView, headerView.bounds.height != height else { return }
if aniamted == false {
if animated == false {
updateHeader(height: height)
return
}
@@ -18,40 +18,26 @@
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E24925FC53C100A26F43 /* DebugTextViewController.swift */; };
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E25125FC541700A26F43 /* NestedScrollViewController.swift */; };
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54496C58263A7E5A0031E0C8 /* UseCaseController.swift */; };
544BC56826CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */; };
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9ED21511E6300CA77B8 /* AppDelegate.swift */; };
545DB9F021511E6300CA77B8 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9EF21511E6300CA77B8 /* MainViewController.swift */; };
545DB9F321511E6300CA77B8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F121511E6300CA77B8 /* Main.storyboard */; };
545DB9F521511E6400CA77B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F421511E6400CA77B8 /* Assets.xcassets */; };
545DB9F821511E6400CA77B8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F621511E6400CA77B8 /* LaunchScreen.storyboard */; };
545DBA0321511E6400CA77B8 /* SampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0221511E6400CA77B8 /* SampleTests.swift */; };
545DBA0E21511E6400CA77B8 /* SampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0D21511E6400CA77B8 /* SampleUITests.swift */; };
546341A125C6415100CA0596 /* UseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341A025C6415100CA0596 /* UseCase.swift */; };
546341AC25C6426500CA0596 /* CustomState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341AB25C6426500CA0596 /* CustomState.swift */; };
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; };
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* Extensions.swift */; };
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */; };
54E58CB72BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */; };
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD35A263A75EB006A36EA /* PanelLayouts.swift */; };
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD364263A765F006A36EA /* PagePanelController.swift */; };
54F185822BF4AD0000916F57 /* DebugListCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */; };
54F185842BF4B82E00916F57 /* UnavailableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F185832BF4B82E00916F57 /* UnavailableViewController.swift */; };
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
545DB9FF21511E6400CA77B8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 545DB9E221511E6300CA77B8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 545DB9E921511E6300CA77B8;
remoteInfo = FloatingModalSample;
};
545DBA0A21511E6400CA77B8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 545DB9E221511E6300CA77B8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 545DB9E921511E6300CA77B8;
remoteInfo = FloatingModalSample;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
549D23CD233C7779008EF4D7 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
@@ -78,6 +64,7 @@
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTextViewController.swift; sourceTree = "<group>"; };
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedScrollViewController.swift; sourceTree = "<group>"; };
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCaseController.swift; sourceTree = "<group>"; };
544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewControllerForAdaptiveLayout.swift; sourceTree = "<group>"; };
545DB9EA21511E6300CA77B8 /* Samples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Samples.app; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
545DB9EF21511E6300CA77B8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
@@ -85,19 +72,17 @@
545DB9F421511E6400CA77B8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
545DB9F721511E6400CA77B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
545DB9F921511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DB9FE21511E6400CA77B8 /* SamplesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SamplesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
545DBA0221511E6400CA77B8 /* SampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleTests.swift; sourceTree = "<group>"; };
545DBA0421511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
545DBA0D21511E6400CA77B8 /* SampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleUITests.swift; sourceTree = "<group>"; };
545DBA0F21511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
546341A025C6415100CA0596 /* UseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCase.swift; sourceTree = "<group>"; };
546341AB25C6426500CA0596 /* CustomState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomState.swift; sourceTree = "<group>"; };
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54B51115216AFE5F0033A6F3 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupplementaryViews.swift; sourceTree = "<group>"; };
54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewControllerForAdaptiveLayout.swift; sourceTree = "<group>"; };
54EAD35A263A75EB006A36EA /* PanelLayouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelLayouts.swift; sourceTree = "<group>"; };
54EAD364263A765F006A36EA /* PagePanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagePanelController.swift; sourceTree = "<group>"; };
54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugListCollectionViewController.swift; sourceTree = "<group>"; };
54F185832BF4B82E00916F57 /* UnavailableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnavailableViewController.swift; sourceTree = "<group>"; };
5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -105,40 +90,29 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9FB21511E6400CA77B8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
545DBA0621511E6400CA77B8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
5442E22225FC519700A26F43 /* ContentViewControllers */ = {
isa = PBXGroup;
children = (
54E58CB52BF8A88600408EA9 /* AdaptiveLayout */,
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */,
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */,
54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */,
5442E23325FC528400A26F43 /* DetailViewController.swift */,
5442E24325FC538200A26F43 /* InspectorViewController.swift */,
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */,
5442E23925FC52CD00A26F43 /* ModalViewController.swift */,
5442E22725FC51E200A26F43 /* MultiPanelController.swift */,
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */,
5442E22F25FC525200A26F43 /* TabBarViewController.swift */,
54F185832BF4B82E00916F57 /* UnavailableViewController.swift */,
);
path = ContentViewControllers;
sourceTree = "<group>";
@@ -148,9 +122,8 @@
children = (
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */,
545DB9EC21511E6300CA77B8 /* Sources */,
545DBA0121511E6400CA77B8 /* Tests */,
545DBA0C21511E6400CA77B8 /* UITests */,
545DB9EB21511E6300CA77B8 /* Products */,
5D82A6AB28D18438006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -158,8 +131,6 @@
isa = PBXGroup;
children = (
545DB9EA21511E6300CA77B8 /* Samples.app */,
545DB9FE21511E6400CA77B8 /* SamplesTests.xctest */,
545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -183,24 +154,6 @@
path = Sources;
sourceTree = "<group>";
};
545DBA0121511E6400CA77B8 /* Tests */ = {
isa = PBXGroup;
children = (
545DBA0221511E6400CA77B8 /* SampleTests.swift */,
545DBA0421511E6400CA77B8 /* Info.plist */,
);
path = Tests;
sourceTree = "<group>";
};
545DBA0C21511E6400CA77B8 /* UITests */ = {
isa = PBXGroup;
children = (
545DBA0D21511E6400CA77B8 /* SampleUITests.swift */,
545DBA0F21511E6400CA77B8 /* Info.plist */,
);
path = UITests;
sourceTree = "<group>";
};
546341AA25C6421000CA0596 /* UseCases */ = {
isa = PBXGroup;
children = (
@@ -211,6 +164,24 @@
path = UseCases;
sourceTree = "<group>";
};
54E58CB52BF8A88600408EA9 /* AdaptiveLayout */ = {
isa = PBXGroup;
children = (
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */,
54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */,
);
path = AdaptiveLayout;
sourceTree = "<group>";
};
5D82A6AB28D18438006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -232,42 +203,6 @@
productReference = 545DB9EA21511E6300CA77B8 /* Samples.app */;
productType = "com.apple.product-type.application";
};
545DB9FD21511E6400CA77B8 /* SamplesTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 545DBA1521511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesTests" */;
buildPhases = (
545DB9FA21511E6400CA77B8 /* Sources */,
545DB9FB21511E6400CA77B8 /* Frameworks */,
545DB9FC21511E6400CA77B8 /* Resources */,
);
buildRules = (
);
dependencies = (
545DBA0021511E6400CA77B8 /* PBXTargetDependency */,
);
name = SamplesTests;
productName = FloatingModalSampleTests;
productReference = 545DB9FE21511E6400CA77B8 /* SamplesTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
545DBA0821511E6400CA77B8 /* SamplesUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 545DBA1821511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesUITests" */;
buildPhases = (
545DBA0521511E6400CA77B8 /* Sources */,
545DBA0621511E6400CA77B8 /* Frameworks */,
545DBA0721511E6400CA77B8 /* Resources */,
);
buildRules = (
);
dependencies = (
545DBA0B21511E6400CA77B8 /* PBXTargetDependency */,
);
name = SamplesUITests;
productName = FloatingModalSampleUITests;
productReference = 545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -281,14 +216,6 @@
545DB9E921511E6300CA77B8 = {
CreatedOnToolsVersion = 10.0;
};
545DB9FD21511E6400CA77B8 = {
CreatedOnToolsVersion = 10.0;
TestTargetID = 545DB9E921511E6300CA77B8;
};
545DBA0821511E6400CA77B8 = {
CreatedOnToolsVersion = 10.0;
TestTargetID = 545DB9E921511E6300CA77B8;
};
};
};
buildConfigurationList = 545DB9E521511E6300CA77B8 /* Build configuration list for PBXProject "Samples" */;
@@ -305,8 +232,6 @@
projectRoot = "";
targets = (
545DB9E921511E6300CA77B8 /* Samples */,
545DB9FD21511E6400CA77B8 /* SamplesTests */,
545DBA0821511E6400CA77B8 /* SamplesUITests */,
);
};
/* End PBXProject section */
@@ -322,20 +247,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9FC21511E6400CA77B8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
545DBA0721511E6400CA77B8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -344,14 +255,17 @@
buildActionMask = 2147483647;
files = (
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */,
54E58CB72BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift in Sources */,
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */,
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */,
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
544BC56826CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift in Sources */,
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */,
546341AC25C6426500CA0596 /* CustomState.swift in Sources */,
5442E23A25FC52CD00A26F43 /* ModalViewController.swift in Sources */,
5442E22425FC51AF00A26F43 /* ImageViewController.swift in Sources */,
5442E24025FC533800A26F43 /* DebugTableViewController.swift in Sources */,
54F185822BF4AD0000916F57 /* DebugListCollectionViewController.swift in Sources */,
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */,
5442E22825FC51E200A26F43 /* MultiPanelController.swift in Sources */,
546341A125C6415100CA0596 /* UseCase.swift in Sources */,
@@ -362,40 +276,12 @@
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */,
5442E24425FC538200A26F43 /* InspectorViewController.swift in Sources */,
5442E22C25FC521F00A26F43 /* SettingsViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9FA21511E6400CA77B8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
545DBA0321511E6400CA77B8 /* SampleTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
545DBA0521511E6400CA77B8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
545DBA0E21511E6400CA77B8 /* SampleUITests.swift in Sources */,
54F185842BF4B82E00916F57 /* UnavailableViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
545DBA0021511E6400CA77B8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 545DB9E921511E6300CA77B8 /* Samples */;
targetProxy = 545DB9FF21511E6400CA77B8 /* PBXContainerItemProxy */;
};
545DBA0B21511E6400CA77B8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 545DB9E921511E6300CA77B8 /* Samples */;
targetProxy = 545DBA0A21511E6400CA77B8 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
545DB9F121511E6300CA77B8 /* Main.storyboard */ = {
isa = PBXVariantGroup;
@@ -541,11 +427,15 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Sources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -560,11 +450,15 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Sources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -572,90 +466,6 @@
};
name = Release;
};
545DBA1621511E6400CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Samples.app/Samples";
};
name = Debug;
};
545DBA1721511E6400CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Samples.app/Samples";
};
name = Release;
};
545DBA1921511E6400CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = J3D7L9FHSS;
INFOPLIST_FILE = UITests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = FloatingModalSample;
};
name = Debug;
};
545DBA1A21511E6400CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = J3D7L9FHSS;
INFOPLIST_FILE = UITests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalSampleUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = FloatingModalSample;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -677,24 +487,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
545DBA1521511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DBA1621511E6400CA77B8 /* Debug */,
545DBA1721511E6400CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
545DBA1821511E6400CA77B8 /* Build configuration list for PBXNativeTarget "SamplesUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DBA1921511E6400CA77B8 /* Debug */,
545DBA1A21511E6400CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 545DB9E221511E6300CA77B8 /* Project object */;
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -15,7 +15,7 @@
<objects>
<navigationController storyboardIdentifier="RootNavigationController" id="RoN-h0-uBD" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="hNW-5m-Omi">
<rect key="frame" x="0.0" y="44" width="375" height="96"/>
<rect key="frame" x="0.0" y="48" width="414" height="96"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@@ -31,22 +31,22 @@
<objects>
<viewController id="jF4-A0-Eq6" customClass="MainViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Smh-Bd-AAc">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7IS-PU-x0P">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="M0G-C8-hAO" style="IBUITableViewCellStyleDefault" id="ySY-oA-g81">
<rect key="frame" x="0.0" y="28" width="600" height="43.5"/>
<rect key="frame" x="0.0" y="50" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ySY-oA-g81" id="sXB-nH-2g2">
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="M0G-C8-hAO">
<rect key="frame" x="16" y="0.0" width="568" height="43.5"/>
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@@ -87,7 +87,7 @@
<objects>
<viewController storyboardIdentifier="SettingsViewController" id="C1X-9Z-TyQ" customClass="SettingsViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="af9-Zr-Ppc">
<rect key="frame" x="0.0" y="0.0" width="375" height="197.33000000000001"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="197.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="n93-ZL-fmC">
@@ -125,7 +125,7 @@
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="js8-Qv-lUC">
<rect key="frame" x="262" y="0.5" width="51" height="31"/>
<connections>
<action selector="toggleLargeTitle:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="FJS-Ty-mCY"/>
<action selector="toggleLargeTitle:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="CxM-wn-r09"/>
</connections>
</switch>
</subviews>
@@ -142,7 +142,7 @@
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="s6b-j9-8Kw">
<rect key="frame" x="262" y="0.5" width="51" height="31"/>
<connections>
<action selector="toggleTranslucent:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="nL4-3L-9hh"/>
<action selector="toggleTranslucent:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="w7r-AV-RqX"/>
</connections>
</switch>
</subviews>
@@ -163,8 +163,8 @@
</view>
<size key="freeformSize" width="375" height="197.33000000000001"/>
<connections>
<outlet property="largeTitlesSwicth" destination="js8-Qv-lUC" id="FOm-6k-ffi"/>
<outlet property="translucentSwicth" destination="s6b-j9-8Kw" id="jmf-WH-bzZ"/>
<outlet property="largeTitlesSwitch" destination="js8-Qv-lUC" id="szl-pU-uRE"/>
<outlet property="translucentSwitch" destination="s6b-j9-8Kw" id="8yK-Du-jkM"/>
<outlet property="versionLabel" destination="WmC-Tq-NDN" id="Woh-kK-U0m"/>
</connections>
</viewController>
@@ -177,11 +177,11 @@
<objects>
<viewController id="RpE-lI-27a" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="JER-jz-KSq">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
<rect key="frame" x="20" y="48" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="RpE-lI-27a" eventType="touchUpInside" id="hj3-Xv-6Gq"/>
@@ -208,11 +208,11 @@
<objects>
<viewController id="pOk-Zm-vD9" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="85d-ub-G8k">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NbG-e8-HdI">
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
<rect key="frame" x="20" y="48" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="pOk-Zm-vD9" eventType="touchUpInside" id="111-PD-Pop"/>
@@ -239,11 +239,11 @@
<objects>
<viewController id="lto-Zc-Vtp" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="ji9-Ez-N7i">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
<rect key="frame" x="20" y="48" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="YL4-GP-ZEZ"/>
@@ -383,11 +383,11 @@
<objects>
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="768"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vut-mK-Y4t" customClass="SafeAreaView" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="0.0" y="758" width="375" height="0.0"/>
<rect key="frame" x="0.0" y="768" width="375" height="0.0"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
@@ -598,11 +598,11 @@
</constraints>
</view>
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="744"/>
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="319" y="0.0" width="44" height="44"/>
<rect key="frame" x="319" y="48" width="44" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="0jg-5D-A1F"/>
<constraint firstAttribute="width" constant="44" id="1Cq-PA-wgW"/>
@@ -612,7 +612,7 @@
</connections>
</button>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="qux-uG-4o2">
<rect key="frame" x="8" y="8" width="148" height="31"/>
<rect key="frame" x="8" y="56" width="148" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="fitToBounds" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7lq-d3-PKi">
<rect key="frame" x="0.0" y="5.5" width="91" height="20.5"/>
@@ -660,19 +660,19 @@
<color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="EQy-cr-F2Y"/>
<constraint firstItem="Kva-Z7-0qY" firstAttribute="top" secondItem="g7l-kO-y7q" secondAttribute="top" id="A4b-Le-m4I"/>
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" priority="750" id="EQy-cr-F2Y"/>
<constraint firstItem="tP3-oJ-4EB" firstAttribute="centerX" secondItem="aOK-7l-cA6" secondAttribute="centerX" id="EsD-Vf-dNZ"/>
<constraint firstItem="Kva-Z7-0qY" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="Fff-HL-4mo"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="bottom" secondItem="g7l-kO-y7q" secondAttribute="bottom" id="JOL-wC-w74"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="bottom" secondItem="g7l-kO-y7q" secondAttribute="bottom" priority="750" id="JOL-wC-w74"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="RiJ-Hb-OOZ"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="Sof-yL-mwK"/>
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="g7l-kO-y7q" secondAttribute="top" constant="88" id="Zhb-Ss-epe"/>
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="g7l-kO-y7q" secondAttribute="top" priority="750" constant="88" id="Zhb-Ss-epe"/>
<constraint firstItem="Kva-Z7-0qY" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="kkp-Yo-FQW"/>
<constraint firstItem="aOK-7l-cA6" firstAttribute="trailing" secondItem="noi-1a-5bZ" secondAttribute="trailing" constant="12" id="lv9-Nf-HNB"/>
<constraint firstItem="qux-uG-4o2" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" constant="8" id="naa-cf-ZIc"/>
<constraint firstItem="Kva-Z7-0qY" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="oVC-i1-TwS"/>
<constraint firstItem="aOK-7l-cA6" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" id="rW2-mF-5DR"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="top" relation="greaterThanOrEqual" secondItem="tP3-oJ-4EB" secondAttribute="bottom" constant="88" id="vKQ-h9-uKt"/>
<constraint firstItem="aOK-7l-cA6" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" priority="750" id="rW2-mF-5DR"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="top" relation="greaterThanOrEqual" secondItem="tP3-oJ-4EB" secondAttribute="bottom" priority="750" constant="88" id="vKQ-h9-uKt"/>
<constraint firstItem="qux-uG-4o2" firstAttribute="leading" secondItem="g7l-kO-y7q" secondAttribute="leading" constant="8" id="zXb-R9-bMO"/>
</constraints>
<connections>
@@ -761,14 +761,33 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
<fontDescription key="fontDescription" name="CourierNewPSMT" family="Courier New" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wUo-kb-NIn">
<rect key="frame" x="159" y="16" width="200" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Expand top margin" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OC3-od-ldC">
<rect key="frame" x="0.0" y="5.5" width="143" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="XFC-sq-pWj">
<rect key="frame" x="151" y="0.0" width="51" height="31"/>
<connections>
<action selector="toggleTopMargin:" destination="tvD-nO-QUb" eventType="valueChanged" id="XWo-eX-0Jn"/>
</connections>
</switch>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
<constraint firstItem="5ET-zC-lCb" firstAttribute="trailing" secondItem="wUo-kb-NIn" secondAttribute="trailing" constant="16" id="CtG-H5-tAI"/>
<constraint firstAttribute="bottom" secondItem="rN1-HL-YHv" secondAttribute="bottom" id="efD-U5-Tet"/>
<constraint firstItem="rN1-HL-YHv" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="17" id="fiO-LL-nSC"/>
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
<constraint firstItem="wUo-kb-NIn" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="16" id="ogC-1W-upw"/>
</constraints>
</view>
<size key="freeformSize" width="375" height="778"/>
@@ -780,9 +799,59 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1" y="734"/>
<point key="canvasLocation" x="-2.1739130434782612" y="733.92857142857144"/>
</scene>
<!--Table View Controller For Adaptive Layout-->
<scene sceneID="rDI-lU-wEx">
<objects>
<viewController storyboardIdentifier="TableViewControllerForAdaptiveLayout" id="5nC-6E-bXf" customClass="TableViewControllerForAdaptiveLayout" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="jXL-Ss-NCJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="W7W-ET-Wco" customClass="IntrinsicTableView" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="Cell" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="Mqi-zK-WA7">
<rect key="frame" x="0.0" y="50" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Mqi-zK-WA7" id="X46-Fp-6Hr">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="5nC-6E-bXf" id="RHg-aY-HNW"/>
<outlet property="delegate" destination="5nC-6E-bXf" id="0YX-fh-bB8"/>
</connections>
</tableView>
</subviews>
<viewLayoutGuide key="safeArea" id="ZfG-sd-dcQ"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="W7W-ET-Wco" firstAttribute="trailing" secondItem="ZfG-sd-dcQ" secondAttribute="trailing" id="3kP-rg-7c6"/>
<constraint firstAttribute="bottom" secondItem="W7W-ET-Wco" secondAttribute="bottom" id="FdS-X9-D1D"/>
<constraint firstItem="W7W-ET-Wco" firstAttribute="leading" secondItem="ZfG-sd-dcQ" secondAttribute="leading" id="HXa-oO-jag"/>
<constraint firstItem="W7W-ET-Wco" firstAttribute="top" secondItem="jXL-Ss-NCJ" secondAttribute="top" id="gMX-Wq-7G8"/>
</constraints>
</view>
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="tableView" destination="W7W-ET-Wco" id="N54-Fv-2Jq"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="7hJ-XW-9az" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4005" y="734"/>
</scene>
</scenes>
<designables>
<designable name="noi-1a-5bZ">
<size key="intrinsicContentSize" width="30" height="30"/>
</designable>
</designables>
<inferredMetricsTieBreakers>
<segue reference="r1P-2i-NDe"/>
</inferredMetricsTieBreakers>
@@ -792,13 +861,13 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemOrangeColor">
<color red="1" green="0.58431372549019611" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemPurpleColor">
<color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
@@ -0,0 +1,175 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
@available(iOS 13.0, *)
class CollectionViewControllerForAdaptiveLayout: UIViewController {
class PanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
private unowned var targetGuide: UILayoutGuide
init(targetGuide: UILayoutGuide) {
self.targetGuide = targetGuide
}
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelAdaptiveLayoutAnchor(
absoluteOffset: 0.0,
contentLayout: targetGuide,
referenceGuide: .superview,
contentBoundingGuide: .safeArea
),
.half: FloatingPanelAdaptiveLayoutAnchor(
fractionalOffset: 0.5,
contentLayout: targetGuide,
referenceGuide: .superview,
contentBoundingGuide: .safeArea
),
]
}
}
enum LayoutType {
case flow
case compositional
}
weak var collectionView: UICollectionView!
var layoutType: LayoutType = .flow
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
private func setupCollectionView() {
let collectionViewLayout = {
switch layoutType {
case .flow:
CollectionViewLayoutFactory.flowLayout
case .compositional:
CollectionViewLayoutFactory.compositionalLayout
}
}()
let collectionView = IntrinsicCollectionView(
frame: .zero,
collectionViewLayout: collectionViewLayout
)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = .yellow
collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
view.addSubview(collectionView)
self.collectionView = collectionView
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
@available(iOS 13.0, *)
extension CollectionViewControllerForAdaptiveLayout: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5 // Only three cells needed to fill the space
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as! Cell
cell.configure(text: "Item \(indexPath.row)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width
return CGSize(width: width, height: 100)
}
}
@available(iOS 13.0, *)
extension CollectionViewControllerForAdaptiveLayout {
enum CollectionViewLayoutFactory {
static var flowLayout: UICollectionViewLayout {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 8 // Vertical spacing between rows
return layout
}
@available(iOS 13.0, *)
static var compositionalLayout: UICollectionViewLayout {
UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 8 // Spacing between each group/item
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
return section
}
}
}
private final class Cell: UICollectionViewCell {
static let reuseIdentifier = "Cell"
private let label: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.textColor = .white
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
backgroundColor = .systemBlue
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
func configure(text: String) {
label.text = text
}
}
private final class IntrinsicCollectionView: UICollectionView {
override public var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override public var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
}
@@ -5,29 +5,25 @@ import FloatingPanel
final class ImageViewController: UIViewController {
class PanelLayout: FloatingPanelLayout {
weak var targetGuide: UILayoutGuide?
init(targetGuide: UILayoutGuide?) {
private unowned var targetGuide: UILayoutGuide
init(targetGuide: UILayoutGuide) {
self.targetGuide = targetGuide
}
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
if #available(iOS 11.0, *), let targetGuide = targetGuide {
return [
.full: FloatingPanelAdaptiveLayoutAnchor(absoluteOffset: 0,
contentLayout: targetGuide,
referenceGuide: .superview),
.half: FloatingPanelAdaptiveLayoutAnchor(fractionalOffset: 0.5,
contentLayout: targetGuide,
referenceGuide: .superview)
]
} else {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 500,
edge: .bottom,
referenceGuide: .superview)
]
}
return [
.full: FloatingPanelAdaptiveLayoutAnchor(
absoluteOffset: 0,
contentLayout: targetGuide,
referenceGuide: .superview
),
.half: FloatingPanelAdaptiveLayoutAnchor(
fractionalOffset: 0.5,
contentLayout: targetGuide,
referenceGuide: .superview
)
]
}
}
@@ -41,7 +37,6 @@ final class ImageViewController: UIViewController {
case withHeaderFooter
}
@available(iOS 11.0, *)
func layoutGuideFor(mode: Mode) -> UILayoutGuide {
switch mode {
case .onlyImage:
@@ -0,0 +1,88 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class TableViewControllerForAdaptiveLayout: UIViewController, UITableViewDataSource, UITableViewDelegate {
class PanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
private unowned var targetGuide: UILayoutGuide
init(targetGuide: UILayoutGuide) {
self.targetGuide = targetGuide
}
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelAdaptiveLayoutAnchor(
absoluteOffset: 0.0,
contentLayout: targetGuide,
referenceGuide: .superview,
contentBoundingGuide: .safeArea
),
.half: FloatingPanelAdaptiveLayoutAnchor(
fractionalOffset: 0.5,
contentLayout: targetGuide,
referenceGuide: .superview,
contentBoundingGuide: .safeArea
),
]
}
}
@IBOutlet weak var tableView: IntrinsicTableView!
private let cellID = "Cell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableView.automaticDimension
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
}
// MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = .orange
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
44.0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
40
}
}
class IntrinsicTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
@@ -0,0 +1,77 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
@available(iOS 14, *)
class DebugListCollectionViewController: UIViewController {
enum Section {
case main
}
var dataSource: UICollectionViewDiffableDataSource<Section, Int>! = nil
var collectionView: UICollectionView! = nil
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
configureHierarchy()
configureDataSource()
}
}
@available(iOS 14, *)
extension DebugListCollectionViewController {
/// - Tag: List
private func createLayout() -> UICollectionViewLayout {
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.trailingSwipeActionsConfigurationProvider = { indexPath -> UISwipeActionsConfiguration? in
return UISwipeActionsConfiguration(
actions: [UIContextualAction(
style: .destructive,
title: "Delete",
handler: { _, _, completion in
// Do nothing now
}
)]
)
}
return UICollectionViewCompositionalLayout.list(using: config)
}
}
@available(iOS 14, *)
extension DebugListCollectionViewController {
private func configureHierarchy() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(collectionView)
collectionView.delegate = self
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
content.text = "\(item)"
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
snapshot.appendSections([.main])
snapshot.appendItems(Array(0..<94))
dataSource.apply(snapshot, animatingDifferences: false)
}
}
@available(iOS 14, *)
extension DebugListCollectionViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
}
@@ -18,7 +18,7 @@ class DebugTableViewController: InspectableViewController {
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .trailing
stackView.spacing = 10.0
stackView.spacing = 4
return stackView
}()
private lazy var reorderButton: UIButton = {
@@ -28,40 +28,48 @@ class DebugTableViewController: InspectableViewController {
button.addTarget(self, action: #selector(reorderItems), for: .touchUpInside)
return button
}()
private lazy var trackingSwitchWrapper: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fillProportionally
stackView.alignment = .center
stackView.spacing = 8.0
stackView.addArrangedSubview(trackingLabel)
stackView.addArrangedSubview(trackingSwitch)
return stackView
private lazy var trackingButton: UIButton = {
let button = UIButton()
button.setTitle(Menu.trackScrolling.rawValue, for: .normal)
button.setTitleColor(view.tintColor, for: .normal)
button.addTarget(self, action: #selector(toggleTrackingScroll), for: .touchUpInside)
return button
}()
private lazy var trackingLabel: UILabel = {
let label = UILabel()
label.text = "Tracking"
label.font = UIFont.systemFont(ofSize: 17.0, weight: .regular)
return label
}()
private lazy var trackingSwitch: UISwitch = {
let trackingSwitch = UISwitch()
trackingSwitch.isOn = true
trackingSwitch.addTarget(self, action: #selector(turnTrackingOn), for: .touchUpInside)
return trackingSwitch
private lazy var followingButton: UIButton = {
let button = UIButton()
button.setTitleColor(view.tintColor, for: .normal)
button.addTarget(self, action: #selector(toggleFollowingScroll), for: .touchUpInside)
return button
}()
// MARK: - Properties
var kvoObservers: [NSKeyValueObservation] = []
private var fpc: FloatingPanelController? { parent as? FloatingPanelController }
private lazy var items: [String] = {
let items = (0..<100).map { "Items \($0)" }
return Command.replace(items: items)
}()
private var itemHeight: CGFloat = 66.0
// MARK: Flags
private var tracksScrollView: Bool = false {
didSet {
let title = "\(Menu.trackScrolling.rawValue): \(tracksScrollView ? "on" : "off")"
trackingButton.setTitle(title, for: .normal)
}
}
private var followsScrollViewBouncing: Bool = false {
didSet {
let title = "\(Menu.followScrolling.rawValue): \(followsScrollViewBouncing ? "on" : "off")"
followingButton.setTitle(title, for: .normal)
}
}
enum Menu: String, CaseIterable {
case turnOffTracking = "Tracking"
case trackScrolling = "Tracking"
case followScrolling = "Following"
case reorder = "Reorder"
}
@@ -136,13 +144,19 @@ class DebugTableViewController: InspectableViewController {
switch menu {
case .reorder:
buttonStackView.addArrangedSubview(reorderButton)
case .turnOffTracking:
buttonStackView.addArrangedSubview(trackingSwitchWrapper)
case .trackScrolling:
buttonStackView.addArrangedSubview(trackingButton)
case .followScrolling:
buttonStackView.addArrangedSubview(followingButton)
}
}
// Set titles
tracksScrollView = true
followsScrollViewBouncing = false
}
// MARK: - Menu
@objc
private func reorderItems() {
if reorderButton.titleLabel?.text == Menu.reorder.rawValue {
@@ -155,15 +169,21 @@ class DebugTableViewController: InspectableViewController {
}
@objc
private func turnTrackingOn(_ sender: UISwitch) {
guard let fpc = self.parent as? FloatingPanelController else { return }
if sender.isOn {
private func toggleTrackingScroll() {
tracksScrollView.toggle()
guard let fpc = fpc else { return }
if tracksScrollView {
fpc.track(scrollView: tableView)
} else {
fpc.untrack(scrollView: tableView)
}
}
@objc
private func toggleFollowingScroll() {
followsScrollViewBouncing.toggle()
}
// MARK: - Actions
private func execute(command: Command, sourceView: UIView) {
@@ -250,6 +270,9 @@ extension DebugTableViewController: UITableViewDataSource {
extension DebugTableViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if followsScrollViewBouncing {
fpc?.followScrollViewBouncing()
}
print("TableView --- ", scrollView.contentOffset, scrollView.contentInset)
}
@@ -11,9 +11,7 @@ final class DebugTextViewController: UIViewController, UITextViewDelegate {
textView.delegate = self
print("viewDidLoad: TextView --- ", textView.contentOffset, textView.contentInset)
if #available(iOS 11.0, *) {
textView.contentInsetAdjustmentBehavior = .never
}
textView.contentInsetAdjustmentBehavior = .never
}
override func viewWillLayoutSubviews() {
@@ -32,8 +30,14 @@ final class DebugTextViewController: UIViewController, UITextViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("TextView --- ", scrollView.contentOffset, scrollView.contentInset)
if #available(iOS 11.0, *) {
print("TextView --- ", scrollView.adjustedContentInset)
print("TextView --- ", scrollView.adjustedContentInset)
}
@IBAction func toggleTopMargin(_ sender: UISwitch) {
if sender.isOn {
textViewTopConstraint.constant = 160
} else {
textViewTopConstraint.constant = 16
}
}
@@ -65,14 +65,12 @@ final class ModalViewController: UIViewController, FloatingPanelControllerDelega
}
class ModalSecondLayout: FloatingPanelLayout {
var position: FloatingPanelPosition = .bottom
var initialState: FloatingPanelState { .half }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .half
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}
@@ -57,11 +57,9 @@ final class MultiPanelController: FloatingPanelController, FloatingPanelControll
private final class FirstViewLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 40.0, edge: .top, referenceGuide: .superview)
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 40.0, edge: .top, referenceGuide: .superview)
]
}
}
@@ -4,8 +4,8 @@ import UIKit
import FloatingPanel
final class SettingsViewController: InspectableViewController {
@IBOutlet weak var largeTitlesSwicth: UISwitch!
@IBOutlet weak var translucentSwicth: UISwitch!
@IBOutlet weak var largeTitlesSwitch: UISwitch!
@IBOutlet weak var translucentSwitch: UISwitch!
@IBOutlet weak var versionLabel: UILabel!
override func viewDidLoad() {
@@ -14,23 +14,31 @@ final class SettingsViewController: InspectableViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11.0, *) {
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
largeTitlesSwicth.setOn(prefersLargeTitles, animated: false)
} else {
largeTitlesSwicth.isEnabled = false
}
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
largeTitlesSwitch.setOn(prefersLargeTitles, animated: false)
let isTranslucent = navigationController!.navigationBar.isTranslucent
translucentSwicth.setOn(isTranslucent, animated: false)
translucentSwitch.setOn(isTranslucent, animated: false)
}
@IBAction func toggleLargeTitle(_ sender: UISwitch) {
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
}
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
}
@IBAction func toggleTranslucent(_ sender: UISwitch) {
navigationController?.navigationBar.isTranslucent = sender.isOn
// White non-translucent navigation bar, supports dark appearance
if #available(iOS 15, *) {
let appearance = UINavigationBarAppearance()
if sender.isOn {
appearance.configureWithTransparentBackground()
} else {
appearance.configureWithOpaqueBackground()
}
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
} else {
navigationController?.navigationBar.isTranslucent = sender.isOn
}
}
}
@@ -195,14 +195,12 @@ class OneTabBarPanelLayout: FloatingPanelLayout {
}
class TwoTabBarPanelLayout: FloatingPanelLayout {
var initialState: FloatingPanelState { .half }
var position: FloatingPanelPosition { .bottom }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 261.0, edge: .bottom, referenceGuide: .safeArea)
]
}
let initialState: FloatingPanelState = .half
let position: FloatingPanelPosition = .bottom
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 261.0, edge: .bottom, referenceGuide: .safeArea)
]
}
class TwoTabBarPanelBehavior: FloatingPanelBehavior {
@@ -240,13 +238,8 @@ class ThreeTabBarPanelLayout: FloatingPanelLayout {
}
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)
}
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
return [ leftConstraint, rightConstraint ]
}
}
@@ -0,0 +1,24 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
class UnavailableViewController: UIViewController {
weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Unavailable content"
label.numberOfLines = 0
label.textAlignment = .center
label.frame = view.bounds
label.autoresizingMask = [
.flexibleTopMargin,
.flexibleLeftMargin,
.flexibleBottomMargin,
.flexibleRightMargin
]
view.addSubview(label)
self.label = label
}
}
+16 -14
View File
@@ -2,6 +2,20 @@
import UIKit
extension UIView {
func makeBoundsLayoutGuide() -> UILayoutGuide {
let guide = UILayoutGuide()
addLayoutGuide(guide)
NSLayoutConstraint.activate([
guide.topAnchor.constraint(equalTo: topAnchor),
guide.leftAnchor.constraint(equalTo: leftAnchor),
guide.bottomAnchor.constraint(equalTo: bottomAnchor),
guide.rightAnchor.constraint(equalTo: rightAnchor),
])
return guide
}
}
protocol LayoutGuideProvider {
var topAnchor: NSLayoutYAxisAnchor { get }
var bottomAnchor: NSLayoutYAxisAnchor { get }
@@ -19,22 +33,10 @@ class CustomLayoutGuide: LayoutGuideProvider {
extension UIViewController {
var layoutInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return view.safeAreaInsets
} else {
return UIEdgeInsets(top: topLayoutGuide.length,
left: 0.0,
bottom: bottomLayoutGuide.length,
right: 0.0)
}
return view.safeAreaInsets
}
var layoutGuide: LayoutGuideProvider {
if #available(iOS 11.0, *) {
return view!.safeAreaLayoutGuide
} else {
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
bottomAnchor: bottomLayoutGuide.topAnchor)
}
return view.safeAreaLayoutGuide
}
}
@@ -15,14 +15,11 @@ extension MainViewController {
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
automaticallyAdjustsScrollViewInsets = false
let searchController = UISearchController(searchResultsController: nil)
if #available(iOS 11.0, *) {
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.largeTitleDisplayMode = .automatic
}
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.largeTitleDisplayMode = .automatic
var insets = UIEdgeInsets.zero
insets.bottom += 69.0
tableView.contentInset = insets
@@ -33,12 +30,10 @@ extension MainViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
self.tableView.reloadData()
}) {
observations.append(observation)
}
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
self.tableView.reloadData()
}) {
observations.append(observation)
}
}
@@ -56,12 +51,8 @@ extension MainViewController {
extension MainViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if #available(iOS 11.0, *) {
if navigationController?.navigationBar.prefersLargeTitles == true {
return UseCase.allCases.count + 30
} else {
return UseCase.allCases.count
}
if navigationController?.navigationBar.prefersLargeTitles == true {
return UseCase.allCases.count + 30
} else {
return UseCase.allCases.count
}
+14 -26
View File
@@ -23,14 +23,11 @@ extension MainViewController: FloatingPanelLayout {
class TopPositionedPanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .top
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 88.0, edge: .bottom, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .top, referenceGuide: .safeArea)
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 88.0, edge: .bottom, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .top, referenceGuide: .safeArea)
]
}
class IntrinsicPanelLayout: FloatingPanelBottomLayout {
@@ -45,13 +42,10 @@ class IntrinsicPanelLayout: FloatingPanelBottomLayout {
class RemovablePanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .half
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 130.0, edge: .bottom, referenceGuide: .safeArea)
]
}
]
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
@@ -61,13 +55,10 @@ class RemovablePanelLayout: FloatingPanelLayout {
class RemovablePanelLandscapeLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .bottom, referenceGuide: .safeArea)
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .bottom, referenceGuide: .safeArea)
]
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
@@ -77,12 +68,9 @@ class RemovablePanelLandscapeLayout: FloatingPanelLayout {
class ModalPanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
]
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
@@ -5,11 +5,13 @@ import UIKit
enum UseCase: Int, CaseIterable {
case trackingTableView
case trackingTextView
case trackingCollectionViewList
case showDetail
case showModal
case showPanelModal
case showMultiPanelModal
case showPanelInSheetModal
case showOnWindow
case showTabBar
case showPageView
case showPageContentView
@@ -21,19 +23,24 @@ enum UseCase: Int, CaseIterable {
case showNavigationController
case showTopPositionedPanel
case showAdaptivePanel
case showAdaptivePanelWithCustomGuide
case showAdaptivePanelWithTableView
case showAdaptivePanelWithCollectionView
case showAdaptivePanelWithCompositionalCollectionView
case showCustomStatePanel
case showCustomBackdrop
}
extension UseCase {
var name: String {
switch self {
case .trackingTableView: return "Scroll tracking(TableView)"
case .trackingCollectionViewList: return "Scroll tracking(List CollectionView)"
case .trackingTextView: return "Scroll tracking(TextView)"
case .showDetail: return "Show Detail Panel"
case .showModal: return "Show Modal"
case .showPanelModal: return "Show Panel Modal"
case .showMultiPanelModal: return "Show Multi Panel Modal"
case .showOnWindow: return "Show Panel over Window"
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
case .showTabBar: return "Show Tab Bar"
case .showPageView: return "Show Page View"
@@ -46,11 +53,16 @@ extension UseCase {
case .showNavigationController: return "Show Navigation Controller"
case .showTopPositionedPanel: return "Show Top Positioned Panel"
case .showAdaptivePanel: return "Show Adaptive Panel"
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
case .showAdaptivePanelWithTableView: return "Show Adaptive Panel (TableView)"
case .showAdaptivePanelWithCollectionView: return "Show Adaptive Panel (CollectionView)"
case .showAdaptivePanelWithCompositionalCollectionView: return "Show Adaptive Panel (Compositional CollectionView)"
case .showCustomStatePanel: return "Show Panel with Custom state"
case .showCustomBackdrop: return "Show Panel with Custom Backdrop"
}
}
}
extension UseCase {
private enum Content {
case storyboard(String)
case viewController(UIViewController)
@@ -59,10 +71,18 @@ extension UseCase {
private var content: Content {
switch self {
case .trackingTableView: return .viewController(DebugTableViewController())
case .trackingCollectionViewList:
if #available(iOS 14, *) {
return .viewController(DebugListCollectionViewController())
} else {
let msg = "UICollectionLayoutListConfiguration is unavailable.\nBuild this app on iOS 14 and later."
return makeUnavailableViewContent(message: msg)
}
case .trackingTextView: return .storyboard("ConsoleViewController") // Storyboard only
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
case .showModal: return .storyboard(String(describing: ModalViewController.self))
case .showMultiPanelModal: return .viewController(DebugTableViewController())
case .showOnWindow: return .viewController(DebugTableViewController())
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
case .showPanelModal: return .viewController(DebugTableViewController())
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
@@ -76,8 +96,19 @@ extension UseCase {
case .showNavigationController: return .storyboard("RootNavigationController") // Storyboard only
case .showTopPositionedPanel: return .viewController(DebugTableViewController())
case .showAdaptivePanel: return .storyboard(String(describing: ImageViewController.self))
case .showAdaptivePanelWithCustomGuide: return .storyboard(String(describing: ImageViewController.self))
case .showAdaptivePanelWithTableView: return .storyboard(String(describing: TableViewControllerForAdaptiveLayout.self))
case .showAdaptivePanelWithCollectionView,
.showAdaptivePanelWithCompositionalCollectionView:
if #available(iOS 13, *) {
let vc = CollectionViewControllerForAdaptiveLayout()
vc.layoutType = self == .showAdaptivePanelWithCollectionView ? .flow : .compositional
return .viewController(vc)
} else {
let msg = "Compositional layout is unavailable.\nBuild this app on iOS 13 and later."
return makeUnavailableViewContent(message: msg)
}
case .showCustomStatePanel: return .viewController(DebugTableViewController())
case .showCustomBackdrop: return .viewController(UIViewController())
}
}
@@ -86,7 +117,15 @@ extension UseCase {
case .storyboard(let id):
return storyboard.instantiateViewController(withIdentifier: id)
case .viewController(let vc):
vc.loadViewIfNeeded()
return vc
}
}
private func makeUnavailableViewContent(message: String) -> Content {
let vc = UnavailableViewController()
vc.loadViewIfNeeded()
vc.label.text = message
return .viewController(vc)
}
}
@@ -11,6 +11,7 @@ final class UseCaseController: NSObject {
private var detailPanelVC: FloatingPanelController!
private var settingsPanelVC: FloatingPanelController!
private lazy var pagePanelController = PagePanelController()
private lazy var overWindowPanelVC = FloatingPanelController()
init(mainVC: MainViewController) {
self.mainVC = mainVC
@@ -51,6 +52,30 @@ extension UseCaseController {
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .trackingCollectionViewList:
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(UseCaseController.handleSurface(tapGesture:)))
tapGesture.cancelsTouchesInView = false
tapGesture.numberOfTapsRequired = 2
// Prevents a delay to response a tap in menus of DebugTableViewController.
tapGesture.delaysTouchesEnded = false
fpc.surfaceView.addGestureRecognizer(tapGesture)
fpc.set(contentViewController: contentVC)
if #available(iOS 14, *),
let scrollView = (fpc.contentViewController as? DebugListCollectionViewController)?.collectionView {
fpc.track(scrollView: scrollView)
}
addMain(panel: fpc)
case .trackingTextView:
let fpc = FloatingPanelController()
fpc.delegate = self
@@ -157,6 +182,20 @@ extension UseCaseController {
let fpc = MultiPanelController()
mainVC.present(fpc, animated: true, completion: nil)
case .showOnWindow:
let fpc = overWindowPanelVC
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
fpc.isRemovalInteractionEnabled = true
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
guard let window = UIApplication.shared.windows.first else { fatalError("Any window not found") }
window.addSubview(fpc.view)
fpc.view.frame = window.bounds
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
fpc.show(animated: true)
case .showPanelInSheetModal:
let fpc = FloatingPanelController()
let contentVC = UIViewController()
@@ -212,25 +251,52 @@ extension UseCaseController {
addMain(panel: fpc)
case .showTopPositionedPanel: // For debug
let fpc = FloatingPanelController()
let fpc = FloatingPanelController(delegate: self)
let contentVC = UIViewController()
contentVC.view.backgroundColor = .red
fpc.set(contentViewController: contentVC)
addMain(panel: fpc)
case .showAdaptivePanel, .showAdaptivePanelWithCustomGuide:
case .showAdaptivePanel:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
if case let contentVC as ImageViewController = contentVC {
if #available(iOS 11.0, *) {
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithCustomGuide) ? .withHeaderFooter : .onlyImage
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
fpc.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
} else {
fpc.layout = ImageViewController.PanelLayout(targetGuide: nil)
}
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithTableView) ? .withHeaderFooter : .onlyImage
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
fpc.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
}
addMain(panel: fpc)
case .showAdaptivePanelWithTableView:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
fpc.set(contentViewController: contentVC)
fpc.track(scrollView: (contentVC as! TableViewControllerForAdaptiveLayout).tableView)
fpc.layout = TableViewControllerForAdaptiveLayout.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
addMain(panel: fpc)
case .showAdaptivePanelWithCollectionView, .showAdaptivePanelWithCompositionalCollectionView:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
fpc.set(contentViewController: contentVC)
if #available(iOS 13, *) {
fpc.track(scrollView: (contentVC as! CollectionViewControllerForAdaptiveLayout).collectionView)
fpc.layout = CollectionViewControllerForAdaptiveLayout.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
}
addMain(panel: fpc)
@@ -246,6 +312,52 @@ extension UseCaseController {
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .showCustomBackdrop:
class BlurBackdropView: BackdropView {
var effectView: UIVisualEffectView!
override var alpha: CGFloat {
set {
effectView.alpha = newValue
}
get {
effectView.alpha
}
}
override init() {
super.init()
let effect = UIBlurEffect(style: .prominent)
let effectView = UIVisualEffectView(effect: effect)
addSubview(effectView)
effectView.frame = bounds
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.effectView = effectView
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CustomBottomLayout: FloatingPanelBottomLayout {
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(fractionalInset: 0.1, edge: .bottom, referenceGuide: .safeArea),
]
}
override func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return state == .full ? 0.8 : 0.0
}
}
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.set(contentViewController: contentVC)
fpc.backdropView = BlurBackdropView()
fpc.layout = CustomBottomLayout()
addMain(panel: fpc)
}
}
@@ -283,7 +395,7 @@ extension UseCaseController {
}
}
@objc
@objc
private func handleSurface(tapGesture: UITapGestureRecognizer) {
switch mainPanelVC.state {
case .full:
@@ -295,8 +407,16 @@ extension UseCaseController {
}
extension UseCaseController: FloatingPanelControllerDelegate {
func floatingPanel(
_ fpc: FloatingPanelController,
shouldAllowToScroll scrollView: UIScrollView,
in state: FloatingPanelState
) -> Bool {
return state == .full || state == .half
}
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint {
if useCase == .showNavigationController, #available(iOS 11.0, *) {
if useCase == .showNavigationController {
// 148.0 is the SafeArea's top value for a navigation bar with a large title.
return CGPoint(x: 0.0, y: 0.0 - trackingScrollView.contentInset.top - 148.0)
}
@@ -377,6 +497,9 @@ private extension FloatingPanelController {
case let contentVC as ImageViewController:
track(scrollView: contentVC.scrollView)
case let contentVC as TableViewControllerForAdaptiveLayout:
track(scrollView: contentVC.tableView)
default:
break
}
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
-28
View File
@@ -1,28 +0,0 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import XCTest
@testable import FloatingPanelSample
class SampleTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
@@ -1,28 +0,0 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import XCTest
class SampleUITests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
XCUIApplication().launch()
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
}
@@ -44,6 +44,7 @@
545BA71321BA3217007F7846 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
545BA72221BA3867007F7846 /* SamplesObjC-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SamplesObjC-Bridging-Header.h"; sourceTree = "<group>"; };
545BA72521BA3BAF007F7846 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -64,6 +65,7 @@
545BA72521BA3BAF007F7846 /* FloatingPanel.framework */,
545BA70321BA3214007F7846 /* SamplesObjC */,
545BA70221BA3214007F7846 /* Products */,
5D82A6AE28D18443006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -92,6 +94,14 @@
path = SamplesObjC;
sourceTree = "<group>";
};
5D82A6AE28D18443006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -320,6 +330,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.SamplesObjC;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "SamplesObjC/SamplesObjC-Bridging-Header.h";
@@ -342,6 +356,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.SamplesObjC;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "SamplesObjC/SamplesObjC-Bridging-Header.h";
@@ -14,6 +14,7 @@
548DF95E21705BE10041922A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95C21705BE10041922A /* LaunchScreen.storyboard */; };
549D23CF233C77CF008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */; };
549D23D0233C77CF008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
5D82A6AA28D18432006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6A928D1842B006A44BA /* libswiftCoreGraphics.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -39,6 +40,7 @@
548DF95D21705BE10041922A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
548DF95F21705BE10041922A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5D82A6A928D1842B006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -46,6 +48,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5D82A6AA28D18432006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
549D23CF233C77CF008EF4D7 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -59,6 +62,7 @@
549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */,
548DF95221705BE00041922A /* Stocks */,
548DF95121705BE00041922A /* Products */,
5D82A6A828D1842A006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -83,6 +87,14 @@
path = Stocks;
sourceTree = "<group>";
};
5D82A6A828D1842A006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6A928D1842B006A44BA /* libswiftCoreGraphics.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -312,6 +324,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -331,6 +347,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -81,7 +81,7 @@ class MainViewController: UIViewController, FloatingPanelControllerDelegate {
}
private func hideStockTickerBanner() {
// Dimiss top bar with dissolve animation
// Dismiss top bar with dissolve animation
UIView.animate(withDuration: 0.25) {
self.topBannerView.alpha = 0.0
self.labelStackView.alpha = 1.0
@@ -101,14 +101,12 @@ class FloatingPanelStocksLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .tip
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 56.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262.0, edge: .bottom, referenceGuide: .safeArea),
/* Visible + ToolView */
.tip: FloatingPanelLayoutAnchor(absoluteInset: 85.0 + 44.0, edge: .bottom, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 56.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262.0, edge: .bottom, referenceGuide: .safeArea),
/* Visible + ToolView */
.tip: FloatingPanelLayoutAnchor(absoluteInset: 85.0 + 44.0, edge: .bottom, referenceGuide: .safeArea),
]
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
+5 -5
View File
@@ -1,20 +1,20 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "2.5.4"
s.version = "2.8.6"
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
s.description = <<-DESC
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
The new interface displays the related contents and utilities in parallel as a user wants.
DESC
s.homepage = "https://github.com/SCENEE/FloatingPanel"
s.homepage = "https://github.com/scenee/FloatingPanel"
s.author = "Shin Yamamoto"
s.social_media_url = "https://twitter.com/scenee"
s.platform = :ios, "10.0"
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => s.version.to_s }
s.platform = :ios, "11.0"
s.source = { :git => "https://github.com/scenee/FloatingPanel.git", :tag => s.version.to_s }
s.source_files = "Sources/*.swift"
s.swift_versions = ['5.1', '5.2', '5.3']
s.swift_version = '5.0'
s.framework = "UIKit"
+35 -152
View File
@@ -17,23 +17,23 @@
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* Controller.swift */; };
545DBA2B2152383100CA77B8 /* GrabberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberView.swift */; };
546055BF2333C4740069F400 /* TestSupports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542753C722C49A8F00D17955 /* TestSupports.swift */; };
5469F4A224B003EF00537F8A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */; };
5469F4A324B003EF00537F8A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4A024B003EF00537F8A /* AppDelegate.swift */; };
5469F4AE24B30D7E00537F8A /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AD24B30D7E00537F8A /* State.swift */; };
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */; };
5469F4B224B30F1100537F8A /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B124B30F1100537F8A /* Position.swift */; };
5469F4B424B30F3500537F8A /* LayoutReferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutReferences.swift */; };
5469F4B424B30F3500537F8A /* LayoutProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutProperties.swift */; };
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F7A9B2A6E946000303905 /* GestureTests.swift */; };
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549C371E2361E15D007D8058 /* ExtensionTests.swift */; };
549E944522CF295D0050AECF /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E944422CF295D0050AECF /* StateTests.swift */; };
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* CoreTests.swift */; };
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */; };
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logging.swift */; };
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* SurfaceView.swift */; };
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* BackdropView.swift */; };
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* Layout.swift */; };
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* Core.swift */; };
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DBA3DB262E938500D75969 /* Extensions.swift */; };
54E3992727141F5100A8F9ED /* FloatingPanel.docc in Sources */ = {isa = PBXBuildFile; fileRef = 54E3992627141F5100A8F9ED /* FloatingPanel.docc */; };
5D82A6B528D18464006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -44,13 +44,6 @@
remoteGlobalIDString = 545DB9C02151169500CA77B8;
remoteInfo = FloatingModalController;
};
54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 545DB9B82151169500CA77B8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 54E740C9218AFD67005C1A34;
remoteInfo = TestingHost;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@@ -67,25 +60,23 @@
545DB9D12151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DB9DF21511AC100CA77B8 /* Controller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Controller.swift; sourceTree = "<group>"; };
545DBA2A2152383100CA77B8 /* GrabberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabberView.swift; sourceTree = "<group>"; };
5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
5469F4A024B003EF00537F8A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
5469F4A124B003EF00537F8A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5469F4AD24B30D7E00537F8A /* State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = "<group>"; };
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAnchoring.swift; sourceTree = "<group>"; };
5469F4B124B30F1100537F8A /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = "<group>"; };
5469F4B324B30F3500537F8A /* LayoutReferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutReferences.swift; sourceTree = "<group>"; };
5469F4B324B30F3500537F8A /* LayoutProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutProperties.swift; sourceTree = "<group>"; };
547F7A9B2A6E946000303905 /* GestureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureTests.swift; sourceTree = "<group>"; };
549C371E2361E15D007D8058 /* ExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionTests.swift; sourceTree = "<group>"; };
549E944422CF295D0050AECF /* StateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateTests.swift; sourceTree = "<group>"; };
54A6B6B022968B530077F348 /* CoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTests.swift; sourceTree = "<group>"; };
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceViewTests.swift; sourceTree = "<group>"; };
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
54ABD7AE216CCFF7002E6C13 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
54CDC5D4215B6D8D007D205C /* BackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackdropView.swift; sourceTree = "<group>"; };
54CFBFC2215CD045006B5735 /* Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; };
54CFBFC4215CD09C006B5735 /* Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = "<group>"; };
54DBA3DB262E938500D75969 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
54E3992627141F5100A8F9ED /* FloatingPanel.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = FloatingPanel.docc; sourceTree = "<group>"; };
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FloatingPanelTesting.app; sourceTree = BUILT_PRODUCTS_DIR; };
5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = DEVELOPER_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -93,6 +84,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5D82A6B528D18464006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -104,13 +96,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
54E740C7218AFD67005C1A34 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -120,6 +105,7 @@
545DB9C32151169500CA77B8 /* Sources */,
545DB9CE2151169500CA77B8 /* Tests */,
545DB9C22151169500CA77B8 /* Products */,
5D82A6B328D18460006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -128,7 +114,6 @@
children = (
545DB9C12151169500CA77B8 /* FloatingPanel.framework */,
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */,
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */,
);
name = Products;
sourceTree = "<group>";
@@ -141,7 +126,7 @@
5469F4B124B30F1100537F8A /* Position.swift */,
54CFBFC4215CD09C006B5735 /* Core.swift */,
54CFBFC2215CD045006B5735 /* Layout.swift */,
5469F4B324B30F3500537F8A /* LayoutReferences.swift */,
5469F4B324B30F3500537F8A /* LayoutProperties.swift */,
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */,
5450EEE321646DF500135936 /* Behavior.swift */,
54352E9721A521CA00CBCA08 /* PassthroughView.swift */,
@@ -150,7 +135,7 @@
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
54DBA3DB262E938500D75969 /* Extensions.swift */,
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
54ABD7AE216CCFF7002E6C13 /* Logging.swift */,
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
545DB9C52151169500CA77B8 /* Info.plist */,
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
@@ -161,9 +146,9 @@
545DB9CE2151169500CA77B8 /* Tests */ = {
isa = PBXGroup;
children = (
5469F49E24B003EF00537F8A /* TestingApp */,
54A6B6B022968B530077F348 /* CoreTests.swift */,
545DB9CF2151169500CA77B8 /* ControllerTests.swift */,
547F7A9B2A6E946000303905 /* GestureTests.swift */,
542753C522C49A6E00D17955 /* LayoutTests.swift */,
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */,
549E944422CF295D0050AECF /* StateTests.swift */,
@@ -174,14 +159,12 @@
path = Tests;
sourceTree = "<group>";
};
5469F49E24B003EF00537F8A /* TestingApp */ = {
5D82A6B328D18460006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */,
5469F4A024B003EF00537F8A /* AppDelegate.swift */,
5469F4A124B003EF00537F8A /* Info.plist */,
5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */,
);
path = TestingApp;
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
@@ -228,30 +211,12 @@
);
dependencies = (
545DB9CD2151169500CA77B8 /* PBXTargetDependency */,
54E740DD218AFE9F005C1A34 /* PBXTargetDependency */,
);
name = FloatingPanelTests;
productName = FloatingModalControllerTests;
productReference = 545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
54E740C9218AFD67005C1A34 /* TestingApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 54E740D9218AFD6A005C1A34 /* Build configuration list for PBXNativeTarget "TestingApp" */;
buildPhases = (
54E740C6218AFD67005C1A34 /* Sources */,
54E740C7218AFD67005C1A34 /* Frameworks */,
54E740C8218AFD67005C1A34 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TestingApp;
productName = TestingHost;
productReference = 54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -268,10 +233,6 @@
};
545DB9C92151169500CA77B8 = {
CreatedOnToolsVersion = 10.0;
TestTargetID = 54E740C9218AFD67005C1A34;
};
54E740C9218AFD67005C1A34 = {
CreatedOnToolsVersion = 10.1;
};
};
};
@@ -290,7 +251,6 @@
targets = (
545DB9C02151169500CA77B8 /* FloatingPanel */,
545DB9C92151169500CA77B8 /* FloatingPanelTests */,
54E740C9218AFD67005C1A34 /* TestingApp */,
);
};
/* End PBXProject section */
@@ -310,14 +270,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
54E740C8218AFD67005C1A34 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5469F4A224B003EF00537F8A /* LaunchScreen.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -329,11 +281,11 @@
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */,
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */,
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */,
5469F4B424B30F3500537F8A /* LayoutReferences.swift in Sources */,
5469F4B424B30F3500537F8A /* LayoutProperties.swift in Sources */,
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */,
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */,
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */,
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */,
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */,
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
@@ -355,14 +307,7 @@
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */,
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */,
546055BF2333C4740069F400 /* TestSupports.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
54E740C6218AFD67005C1A34 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5469F4A324B003EF00537F8A /* AppDelegate.swift in Sources */,
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -374,11 +319,6 @@
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
};
54E740DD218AFE9F005C1A34 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 54E740C9218AFD67005C1A34 /* TestingApp */;
targetProxy = 54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -522,12 +462,13 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -553,12 +494,13 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -575,17 +517,17 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
};
name = Debug;
};
@@ -596,53 +538,17 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
};
name = Release;
};
54E740DA218AFD6A005C1A34 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
PRODUCT_NAME = FloatingPanelTesting;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
54E740DB218AFD6A005C1A34 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
PRODUCT_NAME = FloatingPanelTesting;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -725,16 +631,17 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG __FP_LOG";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG FP_LOG";
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -749,31 +656,17 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
};
name = Test;
};
54E8AC6A2286CFB6000C5A12 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/TestingApp/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
PRODUCT_NAME = FloatingPanelTesting;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Test;
};
@@ -810,16 +703,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
54E740D9218AFD6A005C1A34 /* Build configuration list for PBXNativeTarget "TestingApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
54E740DA218AFD6A005C1A34 /* Debug */,
54E740DB218AFD6A005C1A34 /* Release */,
54E8AC6A2286CFB6000C5A12 /* Test */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 545DB9B82151169500CA77B8 /* Project object */;
+1 -1
View File
@@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "FloatingPanel",
platforms: [
.iOS(.v10)
.iOS(.v11)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
+99 -113
View File
@@ -1,15 +1,14 @@
[![Build Status](https://travis-ci.org/SCENEE/FloatingPanel.svg?branch=master)](https://travis-ci.org/SCENEE/FloatingPanel)
[![Version](https://img.shields.io/cocoapods/v/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Swift 5](https://img.shields.io/badge/swift-5-orange.svg?style=flat)](https://swift.org/)
[![Platform](https://img.shields.io/cocoapods/p/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
[![Swift 5](https://img.shields.io/badge/Swift-5-orange.svg?style=flat)](https://swift.org/)
[![Version](https://img.shields.io/cocoapods/v/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/scenee/FloatingPanel/ci.yml?branch=master)
# FloatingPanel
FloatingPanel is a simple and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
The new interface displays the related contents and utilities in parallel as a user wants.
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in Apple Maps, Shortcuts and Stocks app.
The user interface displays related content and utilities alongside the main content.
📝[Here](https://docs.scenee.com/documentation/floatingpanel) is the API references for the latest version powered by [DocC](https://developer.apple.com/documentation/docc).
Please see also [the API reference@SPI](https://swiftpackageindex.com/scenee/FloatingPanel/2.8.6/documentation/floatingpanel) for more details.
![Maps](https://github.com/SCENEE/FloatingPanel/blob/master/assets/maps.gif)
![Stocks](https://github.com/SCENEE/FloatingPanel/blob/master/assets/stocks.gif)
@@ -21,47 +20,46 @@ The new interface displays the related contents and utilities in parallel as a u
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [CocoaPods](#cocoapods)
- [Carthage](#carthage)
- [Swift Package Manager](#swift-package-manager)
- [CocoaPods](#cocoapods)
- [Swift Package Manager](#swift-package-manager)
- [Getting Started](#getting-started)
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
- [Present a floating panel as a modality](#present-a-floating-panel-as-a-modality)
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
- [Present a floating panel as a modality](#present-a-floating-panel-as-a-modality)
- [View hierarchy](#view-hierarchy)
- [Usage](#usage)
- [Show/Hide a floating panel in a view with your view hierarchy](#showhide-a-floating-panel-in-a-view-with-your-view-hierarchy)
- [Scale the content view when the surface position changes](#scale-the-content-view-when-the-surface-position-changes)
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
- [Change the initial layout](#change-the-initial-layout)
- [Update your panel layout](#update-your-panel-layout)
- [Support your landscape layout](#support-your-landscape-layout)
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
- [Change the backdrop alpha](#change-the-backdrop-alpha)
- [Using custome panel states](#using-custome-panel-states)
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
- [Manage the projection of a pan gesture momentum](#manage-the-projection-of-a-pan-gesture-momentum)
- [Specify the panel move's boundary](#specify-the-panel-moves-boundary)
- [Customize the surface design](#customize-the-surface-design)
- [Modify your surface appearance](#modify-your-surface-appearance)
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
- [Customize layout of the grabber handle](#customize-layout-of-the-grabber-handle)
- [Customize content padding from surface edges](#customize-content-padding-from-surface-edges)
- [Customize margins of the surface edges](#customize-margins-of-the-surface-edges)
- [Customize gestures](#customize-gestures)
- [Suppress the panel interaction](#suppress-the-panel-interaction)
- [Add tap gestures to the surface view](#add-tap-gestures-to-the-surface-view)
- [Interrupt the delegate methods of `FloatingPanelController.panGestureRecognizer`](#interrupt-the-delegate-methods-of-floatingpanelcontrollerpangesturerecognizer)
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
- [Move a position with an animation](#move-a-position-with-an-animation)
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
- [Enabling the tap-to-dismiss action of the backdrop view](#enabling-the-tap-to-dismiss-action-of-the-backdrop-view)
- [Show/Hide a floating panel in a view with your view hierarchy](#showhide-a-floating-panel-in-a-view-with-your-view-hierarchy)
- [Scale the content view when the surface position changes](#scale-the-content-view-when-the-surface-position-changes)
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
- [Change the initial layout](#change-the-initial-layout)
- [Update your panel layout](#update-your-panel-layout)
- [Support your landscape layout](#support-your-landscape-layout)
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
- [Change the backdrop alpha](#change-the-backdrop-alpha)
- [Using custome panel states](#using-custome-panel-states)
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
- [Manage the projection of a pan gesture momentum](#manage-the-projection-of-a-pan-gesture-momentum)
- [Specify the panel move's boundary](#specify-the-panel-moves-boundary)
- [Customize the surface design](#customize-the-surface-design)
- [Modify your surface appearance](#modify-your-surface-appearance)
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
- [Customize layout of the grabber handle](#customize-layout-of-the-grabber-handle)
- [Customize content padding from surface edges](#customize-content-padding-from-surface-edges)
- [Customize margins of the surface edges](#customize-margins-of-the-surface-edges)
- [Customize gestures](#customize-gestures)
- [Suppress the panel interaction](#suppress-the-panel-interaction)
- [Add tap gestures to the surface view](#add-tap-gestures-to-the-surface-view)
- [Interrupt the delegate methods of `FloatingPanelController.panGestureRecognizer`](#interrupt-the-delegate-methods-of-floatingpanelcontrollerpangesturerecognizer)
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
- [Move a position with an animation](#move-a-position-with-an-animation)
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
- [Enabling the tap-to-dismiss action of the backdrop view](#enabling-the-tap-to-dismiss-action-of-the-backdrop-view)
- [Allow to scroll content of the tracking scroll view in addition to the most expanded state](#allow-to-scroll-content-of-the-tracking-scroll-view-in-addition-to-the-most-expanded-state)
- [Notes](#notes)
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
- [UISearchController issue](#uisearchcontroller-issue)
- [FloatingPanelSurfaceView's issue on iOS 10](#floatingpanelsurfaceviews-issue-on-ios-10)
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
- [UISearchController issue](#uisearchcontroller-issue)
- [Maintainer](#maintainer)
- [License](#license)
@@ -75,14 +73,14 @@ The new interface displays the related contents and utilities in parallel as a u
- [x] Removal interaction
- [x] Multi panel support
- [x] Modal presentation
- [x] 4 positioning support(top, left, bottom, right)
- [x] Support for 4 positions (top, left, bottom, right)
- [x] 1 or more magnetic anchors(full, half, tip and more)
- [x] Layout support for all trait environments(i.e. Landscape orientation)
- [x] Common UI elements: surface, backdrop and grabber handle
- [x] Free from common issues of Auto Layout and gesture handling
- [x] Free from common Auto Layout and gesture handling issues
- [x] Compatible with Objective-C
Examples are here.
Examples can be found here:
- [Examples/Maps](https://github.com/SCENEE/FloatingPanel/tree/master/Examples/Maps) like Apple Maps.app.
- [Examples/Stocks](https://github.com/SCENEE/FloatingPanel/tree/master/Examples/Stocks) like Apple Stocks.app.
@@ -91,11 +89,7 @@ Examples are here.
## Requirements
FloatingPanel is written in Swift 5.0+. Compatible with iOS 11.0+.
The deployment is still iOS 10, but it is recommended to use this library on iOS 11+.
:pencil2: You would like to use Swift 4.0. Please use FloatingPanel v1.
FloatingPanel is written in Swift 5.0+ and compatible with iOS 11.0+.
## Installation
@@ -108,16 +102,6 @@ it, simply add the following line to your Podfile:
pod 'FloatingPanel'
```
:pencil2: FloatingPanel v1.7.0 or later requires CocoaPods v1.7.0+ for `swift_versions` support.
### Carthage
For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`:
```ogdl
github "scenee/FloatingPanel"
```
### Swift Package Manager
Follow [this doc](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).
@@ -168,7 +152,8 @@ self.present(fpc, animated: true, completion: nil)
You can show a floating panel over UINavigationController from the container view controllers as a modality of `.overCurrentContext` style.
:pencil2: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [Transitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Sources/Transitioning.swift).
> [!NOTE]
> FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [Transitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Sources/Transitioning.swift).
## View hierarchy
@@ -252,7 +237,8 @@ fpc.contentMode = .fitToBounds
Otherwise, `FloatingPanelController` fixes the content by the height of the top most position.
:pencil2: In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
> [!NOTE]
> In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
### Customize the layout with `FloatingPanelLayout` protocol
@@ -269,13 +255,11 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
class MyFloatingPanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .tip
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea),
]
}
```
@@ -321,12 +305,11 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
class LandscapePanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .tip
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
]
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
return [
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
@@ -345,17 +328,16 @@ class LandscapePanelLayout: FloatingPanelLayout {
class IntrinsicPanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0, referenceGuide: .safeArea),
.half: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0, referenceGuide: .safeArea),
.half: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
]
...
}
```
:pencil2: `FloatingPanelIntrinsicLayout` is deprecated on v1.
> [!WARNING]
> `FloatingPanelIntrinsicLayout` is deprecated on v1.
#### Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame
@@ -364,18 +346,16 @@ Use `.superview` reference guide in your anchors.
```swift
class MyFullScreenLayout: FloatingPanelLayout {
...
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .superview),
]
}
```
:pencil2: `FloatingPanelFullScreenLayout` is deprecated on v1.
> [!WARNING]
> `FloatingPanelFullScreenLayout` is deprecated on v1.
#### Change the backdrop alpha
@@ -433,13 +413,14 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
class CustomPanelBehavior: FloatingPanelBehavior {
let springDecelerationRate = UIScrollView.DecelerationRate.fast.rawValue + 0.02
let springResponseTime = 0.4
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
return true
}
}
```
:pencil2: `floatingPanel(_ vc:behaviorFor:)` is deprecated on v1.
> [!WARNING]
> `floatingPanel(_ vc:behaviorFor:)` is deprecated on v1.
#### Activate the rubber-band effect on panel edges
@@ -459,7 +440,7 @@ This allows full projectional panel behavior. For example, a user can swipe up a
```swift
class MyPanelBehavior: FloatingPanelBehavior {
...
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelPosition) -> Bool {
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelPosition) -> Bool {
return true
}
}
@@ -481,7 +462,8 @@ func floatingPanelDidMove(_ vc: FloatingPanelController) {
}
```
:pencil2: `{top,bottom}InteractionBuffer` property is removed from `FloatingPanelLayout` since v2.
> [!WARNING]
> `{top,bottom}InteractionBuffer` property is removed from `FloatingPanelLayout` since v2.
### Customize the surface design
@@ -522,7 +504,8 @@ fpc.surfaceView.grabberHandlePadding = 10.0
fpc.surfaceView.grabberHandleSize = .init(width: 44.0, height: 12.0)
```
:pencil2: Note that `grabberHandleSize` width and height are reversed in the left/right position.
> [!NOTE]
> `grabberHandleSize` width and height are reversed in the left/right position.
#### Customize content padding from surface edges
@@ -671,6 +654,24 @@ The tap-to-dismiss action is disabled by default. So it needs to be enabled as b
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
```
### Allow to scroll content of the tracking scroll view in addition to the most expanded state
Just define conditions to allow content scrolling in `floatingPanel(:_:shouldAllowToScroll:in)` delegate method. If the returned value is true, the scroll content scrolls when its scroll position is not at the top of the content.
```swift
class MyViewController: FloatingPanelControllerDelegate {
...
func floatingPanel(
_ fpc: FloatingPanelController,
shouldAllowToScroll trackingScrollView: UIScrollView,
in state: FloatingPanelState
) -> Bool {
return state == .full || state == .half
}
}
```
## Notes
### 'Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller
@@ -711,21 +712,6 @@ It's a great way to decouple between a floating panel and the content VC.
Because `UISearchController` automatically presents itself modally when a user interacts with the search bar, and then it swaps the superview of the search bar to the view managed by itself while it displays. As a result, `FloatingPanelController` can't control the search bar when it's active, as you can see from [the screen shot](https://github.com/SCENEE/FloatingPanel/issues/248#issuecomment-521263831).
### FloatingPanelSurfaceView's issue on iOS 10
* On iOS 10, `FloatingPanelSurfaceView.cornerRadius` isn't not automatically masked with the top rounded corners because of `UIVisualEffectView` issue. See https://forums.developer.apple.com/thread/50854.
So you need to draw top rounding corners of your content. Here is an example in Examples/Maps.
```swift
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 10, *) {
visualEffectView.layer.cornerRadius = 9.0
visualEffectView.clipsToBounds = true
}
}
```
* If you sets clear color to `FloatingPanelSurfaceView.backgroundColor`, please note the bottom overflow of your content on bouncing at full position. To prevent it, you need to expand your content. For example, See Example/Maps App's Auto Layout settings of `UIVisualEffectView` in Main.storyboard.
## Maintainer
Shin Yamamoto <shin@scenee.com> | [@scenee](https://twitter.com/scenee)
+16 -2
View File
@@ -4,8 +4,22 @@ import UIKit
/// A view that presents a backdrop interface behind a panel.
@objc(FloatingPanelBackdropView)
public class BackdropView: UIView {
open class BackdropView: UIView {
/// The gesture recognizer for tap gestures to dismiss a panel.
@objc public var dismissalTapGestureRecognizer: UITapGestureRecognizer!
///
/// By default, this gesture recognizer is disabled as following the default behavior of iOS modalities.
/// To dismiss a panel by tap gestures on the backdrop, `dismissalTapGestureRecognizer.isEnabled` is set to true.
@objc public var dismissalTapGestureRecognizer: UITapGestureRecognizer
public init() {
dismissalTapGestureRecognizer = UITapGestureRecognizer()
dismissalTapGestureRecognizer.isEnabled = false
super.init(frame: .zero)
addGestureRecognizer(dismissalTapGestureRecognizer)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
+8 -4
View File
@@ -25,12 +25,16 @@ public protocol FloatingPanelBehavior {
@objc optional
var momentumProjectionRate: CGFloat { get }
/// Asks the behavior if a panel should project a momentum of a user interaction to move the proposed position.
/// Asks the behavior if a panel should project a momentum of a user interaction to move the
/// proposed state.
///
/// 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 `FloatingPanelState.tip` or `FloatingPanelState.full`.
/// The default implementation of this method returns `false`. This method is called for called
/// for all states defined by the current layout object.
@objc optional
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool
func shouldProjectMomentum(
_ fpc: FloatingPanelController,
to proposedState: FloatingPanelState
) -> Bool
/// Returns the progress to redirect to the previous position.
///
+121 -67
View File
@@ -1,6 +1,7 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import os.log
/// A set of methods implemented by the delegate of a panel controller allows the adopting delegate to respond to
/// messages from the FloatingPanelController class and thus respond to, and in some affect, operations such as
@@ -37,9 +38,9 @@ import UIKit
@objc optional
func floatingPanelShouldBeginDragging(_ fpc: FloatingPanelController) -> Bool
/// Called when the user drags the surface or the surface is attracted to a state anchor.
/// Called while the user drags the surface or the surface moves to a state anchor.
@objc optional
func floatingPanelDidMove(_ fpc: FloatingPanelController) // any surface frame changes in dragging
func floatingPanelDidMove(_ fpc: FloatingPanelController)
/// Called on start of dragging (may require some time and or distance to move)
@objc optional
@@ -51,7 +52,12 @@ import UIKit
/// Called on finger up if the user dragged.
///
/// If `attract` is true, it will continue moving afterwards to a nearby state anchor.
/// If `attract` is true, the panel continues moving towards the nearby state
/// anchor. Otherwise, it stops at the closest state anchor.
///
/// - Note: If `attract` is false, ``FloatingPanelController.state`` property has
/// already changed to the closest anchor's state by the time this delegate method
/// is called.
@objc optional
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool)
@@ -90,6 +96,35 @@ import UIKit
@objc(floatingPanel:contentOffsetForPinningScrollView:)
optional
func floatingPanel(_ fpc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint
/// Returns a Boolean value that determines whether the tracking scroll view should
/// scroll or not
///
///
/// If you return true, the scroll content scrolls when its scroll position is not
/// at the top of the content. If the delegate doesnt implement this method, its
/// content can be scrolled only in the most expanded state.
///
/// Basically, the decision to scroll is based on the `state` property like the
/// following code.
/// ```swift
/// func floatingPanel(
/// _ fpc: FloatingPanelController,
/// shouldAllowToScroll scrollView: UIScrollView,
/// in state: FloatingPanelState
/// ) -> Bool {
/// return state == .full || state == .half
/// }
/// ```
///
/// - Attention: It is recommended that this method always returns the most expanded state(i.e.
/// .full). If it excludes the state, the panel might do unexpected behaviors.
@objc(floatingPanel:shouldAllowToScroll:in:)
optional func floatingPanel(
_ fpc: FloatingPanelController,
shouldAllowToScroll scrollView: UIScrollView,
in state: FloatingPanelState
) -> Bool
}
///
@@ -114,7 +149,7 @@ open class FloatingPanelController: UIViewController {
}
/// The delegate of a panel controller object.
@objc
@objc
public weak var delegate: FloatingPanelControllerDelegate?{
didSet{
didUpdateDelegate()
@@ -123,14 +158,15 @@ open class FloatingPanelController: UIViewController {
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
@objc
public var surfaceView: SurfaceView! {
public var surfaceView: SurfaceView {
return floatingPanel.surfaceView
}
/// Returns the backdrop view managed by the controller object.
@objc
public var backdropView: BackdropView! {
return floatingPanel.backdropView
public var backdropView: BackdropView {
set { floatingPanel.backdropView = newValue }
get { return floatingPanel.backdropView }
}
/// Returns the scroll view that the controller tracks.
@@ -167,7 +203,8 @@ open class FloatingPanelController: UIViewController {
set {
_layout = newValue
if let parent = parent, let layout = newValue 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.")
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its layout object. Don't allow the parent to adopt FloatingPanelLayout."
os_log(msg, log: sysLog, type: .error, log)
}
}
}
@@ -179,7 +216,8 @@ open class FloatingPanelController: UIViewController {
set {
_behavior = newValue
if let parent = parent, let behavior = newValue 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.")
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its behavior object. Don't allow the parent to adopt FloatingPanelBehavior."
os_log(msg, log: sysLog, type: .error, log)
}
}
}
@@ -193,7 +231,7 @@ open class FloatingPanelController: UIViewController {
/// The behavior for determining the adjusted content offsets.
///
/// This property specifies how the content area of the tracking scroll view is modified using ``adjustedContentInsets``. The default value of this property is FloatingPanelController.ContentInsetAdjustmentBehavior.always.
@objc
@objc
public var contentInsetAdjustmentBehavior: ContentInsetAdjustmentBehavior = .always
/// A Boolean value that determines whether the removal interaction is enabled.
@@ -213,7 +251,7 @@ open class FloatingPanelController: UIViewController {
/// The NearbyState determines that finger's nearby state.
public var nearbyState: FloatingPanelState {
let currentY = surfaceLocation.y
return floatingPanel.targetPosition(from: currentY, with: .zero)
return floatingPanel.targetState(from: currentY, with: .zero)
}
/// Constants that define how a panel content fills in the surface.
@@ -293,19 +331,19 @@ open class FloatingPanelController: UIViewController {
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11.0, *) {
// Ensure to update the static constraint of a panel after rotating a device in static mode
if contentMode == .static {
floatingPanel.layoutAdapter.updateStaticConstraint()
}
} else {
// Because {top,bottom}LayoutGuide is managed as a view
if floatingPanel.isAttracting == false {
self.update(safeAreaInsets: fp_safeAreaInsets)
}
// Ensure to update the static constraint of a panel after rotating a device in static mode
if contentMode == .static {
floatingPanel.layoutAdapter.updateStaticConstraint()
}
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Need to call this method just after the view appears, as the safe area is not
// correctly set before this time, for example, `show(animated:completion:)`.
floatingPanel.adjustScrollContentInsetIfNeeded()
}
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
@@ -316,7 +354,7 @@ open class FloatingPanelController: UIViewController {
// Change a layout for the new view size
if let newLayout = self.delegate?.floatingPanel?(self, layoutFor: size) {
layout = newLayout
activateLayout(forceLayout: false)
activateLayout(forceLayout: true)
}
if view.translatesAutoresizingMaskIntoConstraints {
@@ -335,7 +373,7 @@ open class FloatingPanelController: UIViewController {
// Change a layout for the new trait collection
if let newLayout = self.delegate?.floatingPanel?(self, layoutFor: newCollection) {
self.layout = newLayout
activateLayout(forceLayout: false)
activateLayout(forceLayout: true)
}
}
@@ -375,7 +413,7 @@ open class FloatingPanelController: UIViewController {
preSafeAreaInsets != safeAreaInsets
else { return }
log.debug("Update safeAreaInsets", safeAreaInsets)
os_log(msg, log: devLog, type: .debug, "Update safeAreaInsets = \(safeAreaInsets)")
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
preSafeAreaInsets = safeAreaInsets
@@ -387,6 +425,7 @@ open class FloatingPanelController: UIViewController {
}
floatingPanel.layoutAdapter.updateStaticConstraint()
floatingPanel.adjustScrollContentInsetIfNeeded()
if let contentOffset = contentOffset {
trackingScrollView?.contentOffset = contentOffset
@@ -401,8 +440,10 @@ open class FloatingPanelController: UIViewController {
}
private func activateLayout(forceLayout: Bool = false) {
floatingPanel.activateLayout(forceLayout: forceLayout,
contentInsetAdjustmentBehavior: contentInsetAdjustmentBehavior)
floatingPanel.activateLayout(
forceLayout: forceLayout,
contentInsetAdjustmentBehavior: contentInsetAdjustmentBehavior
)
}
func remove() {
@@ -412,8 +453,15 @@ open class FloatingPanelController: UIViewController {
guard let self = self else { return }
self.delegate?.floatingPanelDidRemove?(self)
}
} else {
} else if parent != nil {
removePanelFromParent(animated: true)
} else {
delegate?.floatingPanelWillRemove?(self)
hide(animated: true) { [weak self] in
guard let self = self else { return }
self.view.removeFromSuperview()
self.delegate?.floatingPanelDidRemove?(self)
}
}
}
@@ -428,22 +476,23 @@ open class FloatingPanelController: UIViewController {
// Must apply the current layout here
activateLayout(forceLayout: true)
if #available(iOS 11.0, *) {
// Must track the safeAreaInsets of `self.view` to update the layout.
// There are 2 reasons.
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
// 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.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
self.update(safeAreaInsets: self.view.safeAreaInsets)
}
} else {
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
// Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()`
// Must track the safeAreaInsets of `self.view` to update the layout.
// There are 2 reasons.
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
// 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.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
// Sometimes the bounding rectangle of the controlled view becomes invalid when the screen is rotated.
// This results in its safeAreaInsets change. In that case, `self.update(safeAreaInsets:)` leads
// an unsatisfied constraints error. So this method should not be called with those bounds.
guard self.view.bounds.height > 0 && self.view.bounds.width > 0 else { return }
self.update(safeAreaInsets: self.view.safeAreaInsets)
}
move(to: floatingPanel.layoutAdapter.initialState,
@@ -468,7 +517,7 @@ open class FloatingPanelController: UIViewController {
@objc(addPanelToParent:at:animated:completion:)
public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false, completion: (() -> Void)? = nil) {
guard self.parent == nil else {
log.warning("Already added to a parent(\(parent))")
os_log(msg, log: sysLog, type: .error, "Warning: already added to a parent(\(parent))")
return
}
assert((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.")
@@ -575,18 +624,22 @@ open class FloatingPanelController: UIViewController {
switch contentInsetAdjustmentBehavior {
case .always:
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
} else {
children.forEach { (vc) in
vc.automaticallyAdjustsScrollViewInsets = false
}
}
scrollView.contentInsetAdjustmentBehavior = .never
default:
break
}
}
/// [Experimental] Allows the panel to move as its tracking scroll view bounces.
///
/// This method must be called in the delegate method, `UIScrollViewDelegate.scrollViewDidScroll(_:)`,
/// of its tracking scroll view. This method only supports a bottom positioned panel for now.
///
/// - TODO: Support top, left and right positioned panels.
public func followScrollViewBouncing() {
floatingPanel.followScrollViewBouncing()
}
/// Cancel tracking the specify scroll view.
///
@objc(untrackScrollView:)
@@ -674,30 +727,31 @@ extension FloatingPanelController {
}
}
// MARK: - Swizzling
private var originalDismissImp: IMP?
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
extension FloatingPanelController {
private static let dismissSwizzling: Void = {
guard originalDismissImp == nil else { return }
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
if let imp = class_getMethodImplementation(aClass, #selector(dismiss(animated:completion:))),
let originalAltMethod = class_getInstanceMethod(aClass, #selector(fp_original_dismiss(animated:completion:))) {
method_setImplementation(originalAltMethod, imp)
}
let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:)))
let swizzledMethod = class_getInstanceMethod(aClass, #selector(fp_dismiss(animated:completion:)))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
method_exchangeImplementations(originalMethod, swizzledMethod)
if let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:))),
let swizzledImp = class_getMethodImplementation(aClass, #selector(__swizzled_dismiss(animated:completion:))) {
originalDismissImp = method_setImplementation(originalMethod, swizzledImp)
}
}()
}
public extension UIViewController {
@objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
}
@objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
extension UIViewController {
@objc
fileprivate func __swizzled_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
let dismissImp = unsafeBitCast(originalDismissImp, to: DismissFunction.self)
let sel = #selector(UIViewController.dismiss(animated:completion:))
// Call dismiss(animated:completion:) to a content view controller
if let fpc = parent as? FloatingPanelController {
if fpc.presentingViewController != nil {
self.fp_original_dismiss(animated: flag, completion: completion)
dismissImp(self, sel, flag, completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
}
@@ -707,7 +761,7 @@ public extension UIViewController {
if let fpc = self as? FloatingPanelController {
// When a panel is presented modally and it's not a child view controller of the presented view controller.
if fpc.presentingViewController != nil, fpc.parent == nil {
self.fp_original_dismiss(animated: flag, completion: completion)
dismissImp(self, sel, flag, completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
}
@@ -715,6 +769,6 @@ public extension UIViewController {
}
// For other view controllers
self.fp_original_dismiss(animated: flag, completion: completion)
dismissImp(self, sel, flag, completion)
}
}
+423 -235
View File
File diff suppressed because it is too large Load Diff
+11 -68
View File
@@ -7,10 +7,12 @@ import UIKit
extension CGFloat {
/// Returns this value rounded to an logical pixel value by a display scale
func rounded(by displayScale: CGFloat) -> CGFloat {
return (self * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
let p = CGFloat(1.0e9)
let v = (self * p).rounded(.towardZero) / p
return (v * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
}
func isEqual(to: CGFloat, on displayScale: CGFloat) -> Bool {
return self.rounded(by: displayScale) == to.rounded(by: displayScale)
return rounded(by: displayScale) == to.rounded(by: displayScale)
}
}
@@ -45,65 +47,16 @@ protocol LayoutGuideProvider {
extension UILayoutGuide: LayoutGuideProvider {}
extension UIView: LayoutGuideProvider {}
private class CustomLayoutGuide: LayoutGuideProvider {
let topAnchor: NSLayoutYAxisAnchor
let leftAnchor: NSLayoutXAxisAnchor
let bottomAnchor: NSLayoutYAxisAnchor
let rightAnchor: NSLayoutXAxisAnchor
let widthAnchor: NSLayoutDimension
let heightAnchor: NSLayoutDimension
init(topAnchor: NSLayoutYAxisAnchor,
leftAnchor: NSLayoutXAxisAnchor,
bottomAnchor: NSLayoutYAxisAnchor,
rightAnchor: NSLayoutXAxisAnchor,
widthAnchor: NSLayoutDimension,
heightAnchor: NSLayoutDimension) {
self.topAnchor = topAnchor
self.leftAnchor = leftAnchor
self.bottomAnchor = bottomAnchor
self.rightAnchor = rightAnchor
self.widthAnchor = widthAnchor
self.heightAnchor = heightAnchor
}
}
extension UIViewController {
/// The proxy property to be used in `LayoutAdapter`
///
/// This property is to allow the safe area inset to change in unit testing
@objc var fp_safeAreaInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return view.safeAreaInsets
} else {
return UIEdgeInsets(top: topLayoutGuide.length,
left: 0.0,
bottom: bottomLayoutGuide.length,
right: 0.0)
}
}
var fp_safeAreaLayoutGuide: LayoutGuideProvider {
if #available(iOS 11.0, *) {
return view!.safeAreaLayoutGuide
} else {
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
leftAnchor: view.leftAnchor,
bottomAnchor: bottomLayoutGuide.topAnchor,
rightAnchor: view.rightAnchor,
widthAnchor: view.widthAnchor,
heightAnchor: topLayoutGuide.bottomAnchor.anchorWithOffset(to: bottomLayoutGuide.topAnchor))
}
return view.safeAreaInsets
}
}
// The reason why UIView has no extensions of safe area insets and top/bottom guides
// is for iOS10 compatibility.
extension UIView {
var fp_safeAreaLayoutGuide: LayoutGuideProvider {
if #available(iOS 11.0, *) {
return safeAreaLayoutGuide
} else {
return self
}
}
var presentationFrame: CGRect {
return layer.presentation()?.frame ?? frame
}
@@ -139,7 +92,7 @@ extension UIView {
}
}
#if __FP_LOG
#if FP_LOG
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
@@ -156,19 +109,9 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
#endif
extension UIScrollView {
var isLocked: Bool {
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
}
var fp_contentInset: UIEdgeInsets {
if #available(iOS 11.0, *) {
return adjustedContentInset
} else {
return contentInset
}
}
var fp_contentOffsetMax: CGPoint {
return CGPoint(x: max((contentSize.width + fp_contentInset.right) - bounds.width, 0.0),
y: max((contentSize.height + fp_contentInset.bottom) - bounds.height, 0.0))
return CGPoint(x: max((contentSize.width + adjustedContentInset.right) - bounds.width, 0.0),
y: max((contentSize.height + adjustedContentInset.bottom) - bounds.height, 0.0))
}
}
+5 -3
View File
@@ -1,11 +1,12 @@
# ``FloatingPanel``
The new interface displays the related contents and utilities in parallel as a user wants.
Create a user interface to display the related content and utilities alongside the main content.
## Overview
FloatingPanel is a simple and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
The new interface displays the related contents and utilities in parallel as a user wants.
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in the Apple Maps, Shortcuts and Stocks app.
The user interface displays related content and utilities alongside the main content.
## Topics
@@ -44,6 +45,7 @@ The new interface displays the related contents and utilities in parallel as a u
- ``FloatingPanelPosition``
- ``FloatingPanelReferenceEdge``
- ``FloatingPanelLayoutReferenceGuide``
- ``FloatingPanelLayoutContentBoundingGuide``
### Behaviors
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.5.4</string>
<string>2.8.6</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+62 -24
View File
@@ -1,6 +1,7 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import os.log
/// An interface for generating layout information for a panel.
@objc public protocol FloatingPanelLayout {
@@ -44,8 +45,8 @@ open class FloatingPanelBottomLayout: NSObject, FloatingPanelLayout {
open func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
return [
surfaceView.leftAnchor.constraint(equalTo: view.fp_safeAreaLayoutGuide.leftAnchor, constant: 0.0),
surfaceView.rightAnchor.constraint(equalTo: view.fp_safeAreaLayoutGuide.rightAnchor, constant: 0.0),
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
]
}
@@ -92,6 +93,9 @@ class LayoutAdapter {
private var staticConstraint: NSLayoutConstraint?
/// A layout constraint to limit the content size in ``FloatingPanelAdaptiveLayoutAnchor``.
private var contentBoundingConstraint: NSLayoutConstraint?
private var anchorStates: Set<FloatingPanelState> {
return Set(layout.anchors.keys)
}
@@ -262,12 +266,25 @@ class LayoutAdapter {
}
var offsetFromMostExpandedAnchor: CGFloat {
return offset(from: mostExpandedState)
}
/// The distance from the given state position to the current surface location.
///
/// If the returned value is positive, it indicates that the surface is moving from
/// the given state position to closer to the `hidden` state position. In other
/// words, the surface is within the given state position. Otherwise, it indicates
/// that the surface is outside this position and is moving away from the `hidden`
/// state position.
func offset(from state: FloatingPanelState) -> CGFloat {
let offset: CGFloat
switch position {
case .top, .left:
return edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
offset = position(for: state) - edgePosition(surfaceView.frame)
case .bottom, .right:
return position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
offset = edgePosition(surfaceView.frame) - position(for: state)
}
return offset.rounded(by: surfaceView.fp_displayScale)
}
private var hiddenAnchor: FloatingPanelLayoutAnchoring {
@@ -330,12 +347,27 @@ class LayoutAdapter {
if anchor.referenceGuide == .safeArea {
referenceBoundsLength += position.inset(safeAreaInsets)
}
return dimension - diff
let maxPosition: CGFloat = {
if let maxBounds = anchor.contentBoundingGuide.maxBounds(vc) {
return layout.position.mainLocation(maxBounds.origin)
+ layout.position.mainDimension(maxBounds.size)
} else {
return .infinity
}
}()
return min(dimension - diff, maxPosition)
case .bottom, .right:
if anchor.referenceGuide == .safeArea {
referenceBoundsLength -= position.inset(safeAreaInsets)
}
return referenceBoundsLength - dimension + diff
let minPosition: CGFloat = {
if let maxBounds = anchor.contentBoundingGuide.maxBounds(vc) {
return layout.position.mainLocation(maxBounds.origin)
} else {
return -(.infinity)
}
}()
return max(referenceBoundsLength - dimension + diff, minPosition)
}
case let anchor as FloatingPanelLayoutAnchor:
let referenceBounds = anchor.referenceGuide == .safeArea ? bounds.inset(by: safeAreaInsets) : bounds
@@ -403,13 +435,13 @@ class LayoutAdapter {
switch position {
case .top, .bottom:
surfaceConstraints = [
surfaceView.leftAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.leftAnchor, constant: 0.0),
surfaceView.rightAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.rightAnchor, constant: 0.0),
surfaceView.leftAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
surfaceView.rightAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
]
case .left, .right:
surfaceConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.topAnchor, constant: 0.0),
surfaceView.bottomAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.bottomAnchor, constant: 0.0),
surfaceView.topAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
surfaceView.bottomAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.bottomAnchor, constant: 0.0),
]
}
}
@@ -517,7 +549,7 @@ class LayoutAdapter {
let layoutGuideProvider: LayoutGuideProvider
switch anchor.referenceGuide {
case .safeArea:
layoutGuideProvider = vc.fp_safeAreaLayoutGuide
layoutGuideProvider = vc.view.safeAreaLayoutGuide
case .superview:
layoutGuideProvider = vc.view
}
@@ -629,8 +661,9 @@ class LayoutAdapter {
// The method is separated from prepareLayout(to:) for the rotation support
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
func updateStaticConstraint() {
NSLayoutConstraint.deactivate(constraint: staticConstraint)
NSLayoutConstraint.deactivate([staticConstraint, contentBoundingConstraint].compactMap{ $0 })
staticConstraint = nil
contentBoundingConstraint = nil
if vc.contentMode == .fitToBounds {
surfaceView.containerOverflow = 0
@@ -654,7 +687,18 @@ class LayoutAdapter {
constant = 0.0
}
let baseAnchor = position.mainDimensionAnchor(anchor.contentLayoutGuide)
staticConstraint = surfaceAnchor.constraint(equalTo: baseAnchor, constant: constant)
if let boundingLayoutGuide = anchor.contentBoundingGuide.layoutGuide(vc) {
if anchor.isAbsolute {
contentBoundingConstraint = baseAnchor.constraint(lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
constant: anchor.offset)
} else {
contentBoundingConstraint = baseAnchor.constraint(lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
multiplier: anchor.offset)
}
staticConstraint = surfaceAnchor.constraint(lessThanOrEqualTo: baseAnchor, constant: constant)
} else {
staticConstraint = surfaceAnchor.constraint(equalTo: baseAnchor, constant: constant)
}
default:
switch position {
case .top, .left:
@@ -673,14 +717,14 @@ class LayoutAdapter {
staticConstraint?.identifier = "FloatingPanel-static-width"
}
NSLayoutConstraint.activate(constraint: staticConstraint)
NSLayoutConstraint.activate([staticConstraint, contentBoundingConstraint].compactMap{ $0 })
surfaceView.containerOverflow = position.mainDimension(vc.view.bounds.size)
}
func updateInteractiveEdgeConstraint(diff: CGFloat, scrollingContent: Bool, allowsRubberBanding: (UIRectEdge) -> Bool) {
defer {
log.debug("update surface location = \(surfaceLocation)")
os_log(msg, log: devLog, type: .debug, "update surface location = \(surfaceLocation)")
}
let minConst: CGFloat = position(for: leastCoordinateState)
@@ -720,9 +764,9 @@ class LayoutAdapter {
defer {
if forceLayout {
layoutSurfaceIfNeeded()
log.debug("activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
} else {
log.debug("activateLayout for \(state)")
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state)")
}
}
@@ -738,12 +782,6 @@ class LayoutAdapter {
NSLayoutConstraint.activate(constraint: self.fitToBoundsConstraint)
}
var state = state
if validStates.contains(state) == false {
state = layout.initialState
}
// Recalculate the intrinsic size of a content view. This is because
// UIView.systemLayoutSizeFitting() returns a different size between an
// on-screen and off-screen view which includes
@@ -757,7 +795,7 @@ class LayoutAdapter {
if let constraints = stateConstraints[state] {
NSLayoutConstraint.activate(constraints)
} else {
log.error("Couldn't find any constraints for \(state)")
os_log(msg, log: sysLog, type: .fault, "Error: can not find any constraints for \(state)")
}
}
}
+72 -16
View File
@@ -15,6 +15,11 @@ import UIKit
///
/// The inset is an amount to inset a panel from an edge of the reference guide. The edge refers to a panel
/// positioning.
///
/// - Parameters:
/// - absoluteInset: An absolute distance to attach the panel from the specified edge.
/// - edge: Specify the edge of ``FloatingPanelController``'s view. This is the staring point of the offset.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(absoluteInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
self.inset = absoluteInset
self.referenceGuide = referenceGuide
@@ -27,6 +32,11 @@ import UIKit
/// The inset is an amount to inset a panel from the edge of the specified reference guide. The value is
/// a floating-point number in the range 0.0 to 1.0, where 0.0 represents zero distance from the edge and
/// 1.0 represents a distance to the opposite edge.
///
/// - Parameters:
/// - fractionalInset: A fractional value of the size of ``FloatingPanelController``'s view to attach the panel from the specified edge.
/// - edge: Specify the edge of ``FloatingPanelController``'s view. This is the staring point of the offset.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(fractionalInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
self.inset = fractionalInset
self.referenceGuide = referenceGuide
@@ -49,7 +59,7 @@ public extension FloatingPanelLayoutAnchor {
case .left:
return layoutConstraints(layoutGuide, for: vc.surfaceView.rightAnchor)
case .bottom:
return layoutConstraints(layoutGuide, for: vc.surfaceView.topAnchor)
return layoutConstraints(layoutGuide, for: vc.surfaceView.topAnchor)
case .right:
return layoutConstraints(layoutGuide, for: vc.surfaceView.leftAnchor)
}
@@ -101,8 +111,12 @@ public extension FloatingPanelLayoutAnchor {
///
/// The offset is an amount to offset a position of panel that displays the entire content from an edge of
/// the reference guide. The edge refers to a panel positioning.
@objc public init(absoluteOffset offset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
///
/// - Parameters:
/// - absoluteOffset: An absolute offset from the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(absoluteOffset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = absoluteOffset
self.referenceGuide = referenceGuide
self.isAbsolute = true
}
@@ -111,8 +125,12 @@ public extension FloatingPanelLayoutAnchor {
///
/// The offset value is a floating-point number in the range 0.0 to 1.0, where 0.0 represents the full content
/// is displayed and 0.5 represents the half of content is displayed.
@objc public init(fractionalOffset offset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
///
/// - Parameters:
/// - fractionalOffset: A fractional offset of the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
@objc public init(fractionalOffset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = fractionalOffset
self.referenceGuide = referenceGuide
self.isAbsolute = false
}
@@ -144,37 +162,72 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
/// An object that defines how to settles a panel with a layout guide of a content view.
@objc final public class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
/// Returns a layout anchor with the specified offset by an absolute value, layout guide to display content and reference guide for a panel.
/// Returns a layout anchor with the specified offset by an absolute value to display a panel with its intrinsic content size.
///
/// The offset is an amount to offset a position of panel that displays the entire content of the specified guide from an edge of
/// the reference guide. The edge refers to a panel positioning.
@objc public init(absoluteOffset offset: CGFloat, contentLayout: UILayoutGuide, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
///
/// ``contentBoundingGuide`` restricts the content size which a panel displays. For example, given ``referenceGuide`` is `.superview` and ``contentBoundingGuide`` is `.safeArea` for a bottom positioned panel, the panel content is laid out inside the superview of the view of FloatingPanelController(not its safe area), but its content size is limited to its safe area size. Normally both of ``referenceGuide`` and ``contentBoundingGuide`` are specified with the same rectangle area.
///
/// - Parameters:
/// - absoluteOffset: An absolute offset from the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
/// - contentLayout: The content layout guide to calculate the content size in the panel.
/// - referenceGuide: The rectangular area to lay out the content of a panel. If it's set to `.safeArea`, the panel content displays inside the safe area of its ``FloatingPanelController``'s view. This argument doesn't limit its content size.
/// - contentBoundingGuide: The rectangular area to restrict the content size of a panel in the main dimension(i.e. y axis is the main dimension for a bottom panel).
///
/// - Warning: If ``contentBoundingGuide`` is set to none, the panel may expand out of the screen size, depending on the intrinsic size of its content.
@objc public init(
absoluteOffset: CGFloat,
contentLayout: UILayoutGuide,
referenceGuide: FloatingPanelLayoutReferenceGuide,
contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide = .none
) {
self.offset = absoluteOffset
self.contentLayoutGuide = contentLayout
self.referenceGuide = referenceGuide
self.contentBoundingGuide = contentBoundingGuide
self.isAbsolute = true
}
/// Returns a layout anchor with the specified offset by a fractional value, layout guide to display content and reference guide for a panel.
/// Returns a layout anchor with the specified offset by a fractional value to display a panel with its intrinsic content size.
///
/// The offset value is a floating-point number in the range 0.0 to 1.0, where 0.0 represents the full content
/// is displayed and 0.5 represents the half of content is displayed.
@objc public init(fractionalOffset offset: CGFloat, contentLayout: UILayoutGuide, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
///
/// ``contentBoundingGuide`` restricts the content size which a panel displays. For example, given ``referenceGuide`` is `.superview` and ``contentBoundingGuide`` is `.safeArea` for a bottom positioned panel, the panel content is laid out inside the superview of the view of FloatingPanelController(not its safe area), but its content size is limited to its safe area size. Normally both of ``referenceGuide`` and ``contentBoundingGuide`` are specified with the same rectangle area.
///
/// - Parameters:
/// - fractionalOffset: A fractional offset of the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
/// - contentLayout: The content layout guide to calculate the content size in the panel.
/// - referenceGuide: The rectangular area to lay out the content of a panel. If it's set to `.safeArea`, the panel content displays inside the safe area of its ``FloatingPanelController``'s view. This argument doesn't limit its content size.
/// - contentBoundingGuide: The rectangular area to restrict the content size of a panel in the main dimension(i.e. y axis is the main dimension for a bottom panel).
///
/// - Warning: If ``contentBoundingGuide`` is set to none, the panel may expand out of the screen size, depending on the intrinsic size of its content.
@objc public init(
fractionalOffset: CGFloat,
contentLayout: UILayoutGuide,
referenceGuide: FloatingPanelLayoutReferenceGuide,
contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide = .none
) {
self.offset = fractionalOffset
self.contentLayoutGuide = contentLayout
self.referenceGuide = referenceGuide
self.contentBoundingGuide = contentBoundingGuide
self.isAbsolute = false
}
fileprivate let offset: CGFloat
fileprivate let isAbsolute: Bool
let offset: CGFloat
let isAbsolute: Bool
let contentLayoutGuide: UILayoutGuide
@objc public let referenceGuide: FloatingPanelLayoutReferenceGuide
@objc public let contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide
}
public extension FloatingPanelAdaptiveLayoutAnchor {
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
var constraints = [NSLayoutConstraint]()
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
let offsetConstraint: NSLayoutConstraint
let offsetAnchor: NSLayoutDimension
switch position {
case .top:
@@ -187,10 +240,13 @@ public extension FloatingPanelAdaptiveLayoutAnchor {
offsetAnchor = vc.surfaceView.leftAnchor.anchorWithOffset(to: layoutGuide.rightAnchor)
}
if isAbsolute {
return [offsetAnchor.constraint(equalTo: position.mainDimensionAnchor(contentLayoutGuide), constant: -offset)]
offsetConstraint = offsetAnchor.constraint(equalTo: position.mainDimensionAnchor(contentLayoutGuide), constant: -offset)
} else {
return [offsetAnchor.constraint(equalTo: position.mainDimensionAnchor(contentLayoutGuide), multiplier: (1 - offset))]
offsetConstraint = offsetAnchor.constraint(equalTo: position.mainDimensionAnchor(contentLayoutGuide), multiplier: (1 - offset))
}
constraints.append(offsetConstraint)
return constraints
}
}
@@ -27,7 +27,7 @@ extension FloatingPanelReferenceEdge {
}
}
/// Constants that specify a layout guide to lay out a panel.
/// A representation to specify a rectangular area to lay out a panel.
@objc public enum FloatingPanelLayoutReferenceGuide: Int {
case superview = 0
case safeArea = 1
@@ -37,9 +37,40 @@ extension FloatingPanelLayoutReferenceGuide {
func layoutGuide(vc: UIViewController) -> LayoutGuideProvider {
switch self {
case .safeArea:
return vc.fp_safeAreaLayoutGuide
return vc.view.safeAreaLayoutGuide
case .superview:
return vc.view
}
}
}
/// A representation to specify a bounding box which limit the content size of a panel.
@objc public enum FloatingPanelLayoutContentBoundingGuide: Int {
case none = 0
case superview = 1
case safeArea = 2
}
extension FloatingPanelLayoutContentBoundingGuide {
func layoutGuide(_ fpc: FloatingPanelController) -> LayoutGuideProvider? {
switch self {
case .superview:
return fpc.view
case .safeArea:
return fpc.view.safeAreaLayoutGuide
case .none:
return nil
}
}
func maxBounds(_ fpc: FloatingPanelController) -> CGRect? {
switch self {
case .superview:
return fpc.view.bounds
case .safeArea:
return fpc.view.bounds.inset(by: fpc.fp_safeAreaInsets)
case .none:
return nil
}
}
}
-100
View File
@@ -1,100 +0,0 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import Foundation
import os.log
// Must be a variable to use `hook` property in testing
var log = {
return Logger()
}()
struct Logger {
private let osLog: OSLog
private let s = DispatchSemaphore(value: 1)
enum Level: Int, Comparable {
case debug = 0
case info = 1
case warning = 2
case error = 3
var displayName: String {
switch self {
case .debug:
return "Debug:"
case .info:
return "Info:"
case .warning:
return "Warning:"
case .error:
return "Error:"
}
}
@available(iOS 10.0, *)
var osLogType: OSLogType {
switch self {
case .debug: return .debug
case .info: return .info
case .warning: return .default
case .error: return .error
}
}
static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
typealias Hook = ((String, Level) -> Void)
var hook: Hook?
fileprivate init() {
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
}
private func log(_ level: Level, _ message: Any, _ arguments: [Any], tag: String, function: String, line: UInt) {
_ = s.wait(timeout: .now() + 0.033)
defer { s.signal() }
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
let _tag = tag.isEmpty ? "" : "\(tag):"
let log: String = {
switch level {
case .debug:
return "\(level.displayName)\(_tag) \(message) \(extraMessage) (\(function):\(line))"
default:
return "\(level.displayName)\(_tag) \(message) \(extraMessage)"
}
}()
hook?(log, level)
os_log("%{public}@", log: osLog, type: level.osLogType, log)
}
private func getPrettyFunction(_ function: String, _ file: String) -> String {
if let filename = file.split(separator: "/").last {
return filename + ":" + function
} else {
return file + ":" + function
}
}
func debug(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
#if __FP_LOG
self.log(.debug, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
#endif
}
func info(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.info, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
}
func warning(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.warning, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
}
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.error, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
}
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import os.log
let msg = StaticString("%{public}@")
let sysLog = OSLog(subsystem: Logging.subsystem, category: Logging.category)
#if FP_LOG
let devLog = OSLog(subsystem: Logging.subsystem, category: "\(Logging.category):dev")
#else
let devLog = OSLog.disabled
#endif
struct Logging {
static let subsystem = "com.scenee.FloatingPanel"
static let category = "FloatingPanel"
private init() {}
}
+41 -27
View File
@@ -1,6 +1,7 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import os.log
/// An object for customizing the appearance of a surface view
@objc(FloatingPanelSurfaceAppearance)
@@ -55,11 +56,11 @@ public class SurfaceAppearance: NSObject {
/// Defaults to `.circular`.
@available(iOS 13.0, *)
public var cornerCurve: CALayerCornerCurve {
get { _cornerCurve ?? .circular }
get { _cornerCurve as? CALayerCornerCurve ?? .circular }
set { _cornerCurve = newValue }
}
private var _cornerCurve: CALayerCornerCurve?
private var _cornerCurve: Any?
/// An array of shadows used to create drop shadows underneath a surface view.
public var shadows: [Shadow] = [Shadow()]
@@ -318,7 +319,7 @@ public class SurfaceView: UIView {
public override func layoutSubviews() {
super.layoutSubviews()
log.debug("surface view frame = \(frame)")
os_log(msg, log: devLog, type: .debug, "surface view frame = \(frame)")
containerView.backgroundColor = appearance.backgroundColor
@@ -382,29 +383,24 @@ public class SurfaceView: UIView {
}
containerView.layer.masksToBounds = true
if position.inset(containerMargins) != 0 {
if #available(iOS 11, *) {
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,
.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
containerView.layer.maskedCorners = [
.layerMinXMinYCorner, .layerMaxXMinYCorner,
.layerMinXMaxYCorner, .layerMaxXMaxYCorner
]
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.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.
switch position {
case .top:
containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
case .left:
containerView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
case .bottom:
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
case .right:
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
}
} else {
// Can't use `containerView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
// Instead, a user should display rounding corners appropriately.
// 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.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.
switch position {
case .top:
containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
case .left:
containerView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
case .bottom:
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
case .right:
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
}
}
@@ -422,12 +418,30 @@ public class SurfaceView: UIView {
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: containerMargins.left + contentPadding.left)
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: containerMargins.right + contentPadding.right)
let bottomConstraint = bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: containerMargins.bottom + contentPadding.bottom)
NSLayoutConstraint.activate([
var constraints = [
topConstraint,
leftConstraint,
rightConstraint,
bottomConstraint,
].map {
bottomConstraint
]
// This constraint is for UICollectionView using UICollectionViewCompositionalLayout.
// It's seemingly obvious, but the UICollectionView doesn't work without setting it. (#628)
switch position {
case .top, .bottom:
constraints += [
heightAnchor.constraint(greaterThanOrEqualToConstant: 1.0)
]
case .left, .right:
constraints += [
widthAnchor.constraint(greaterThanOrEqualToConstant: 1.0)
]
}
NSLayoutConstraint.activate(
constraints
.map {
switch mode {
case .static:
$0.priority = .required
+2 -2
View File
@@ -84,7 +84,7 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
guard
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
else { fatalError()}
else { return 0.0 }
let animator = fpc.animatorForPresenting(to: fpc.layout.initialState)
return TimeInterval(animator.duration)
@@ -119,7 +119,7 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
guard
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
else { fatalError()}
else { return 0.0 }
let animator = fpc.animatorForDismissing(with: .zero)
return TimeInterval(animator.duration)
+73 -27
View File
@@ -1,5 +1,6 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import OSLog
import XCTest
@testable import FloatingPanel
@@ -7,22 +8,32 @@ class ControllerTests: XCTestCase {
override func setUp() {}
override func tearDown() {}
func test_warningRetainCycle() {
let exp = expectation(description: "Warning retain cycle")
exp.expectedFulfillmentCount = 2 // For layout & behavior logs
log.hook = {(log, level) in
if log.contains("A memory leak will occur by a retain cycle because") {
XCTAssert(level == .warning)
exp.fulfill()
}
#if swift(>=5.5) // Avoid the 'No exact matches in call to initializer' build failure for OSLogStore when running this test case on iOS 13.7 using Xcode 12.5.1
func test_warningRetainCycle() throws {
guard #available(iOS 15.0, *) else {
throw XCTSkip("Unsupported iOS version: this test needs iOS 15 or later")
}
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
myVC.loadViewIfNeeded()
wait(for: [exp], timeout: 10)
let store = try OSLogStore(scope: .currentProcessIdentifier)
let found = try store
.getEntries(
at: store.position(timeIntervalSinceLatestBoot: 0),
matching: .init(format: "subsystem == '\(Logging.subsystem)'")
)
.contains {
$0.composedMessage.contains("A memory leak occurs due to a retain cycle, as")
}
XCTAssertTrue(found)
}
#endif
func test_addPanel() {
guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else { fatalError() }
let rootVC = UIViewController()
rootVC.loadViewIfNeeded()
rootVC.view.bounds = .init(origin: .zero, size: .init(width: 390, height: 844))
let fpc = FloatingPanelController()
fpc.addPanel(toParent: rootVC)
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .half).y)
@@ -30,8 +41,13 @@ class ControllerTests: XCTestCase {
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .tip).y)
}
@available(iOS 12.0, *)
func test_updateLayout_willTransition() {
func test_updateLayout_willTransition() throws {
guard #available(iOS 12, *) else {
throw XCTSkip("Unsupported iOS version: this test needs iOS 12 or later")
}
if #available(iOS 17, *) {
throw XCTSkip("Unsupported iOS version: this test doesn't support iOS 17 or later")
}
class MyDelegate: FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
if newCollection.userInterfaceStyle == .dark {
@@ -48,6 +64,7 @@ class ControllerTests: XCTestCase {
}
func test_moveTo() {
let timeout = 3.0
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
XCTAssertEqual(delegate.position, .hidden)
@@ -86,7 +103,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .full)
XCTAssertEqual(delegate.position, .full)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to half(animated)") { act in
@@ -97,7 +114,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .half)
XCTAssertEqual(delegate.position, .half)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to tip(animated)") { act in
@@ -108,7 +125,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .tip)
XCTAssertEqual(delegate.position, .tip)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
fpc.move(to: .hidden, animated: true)
@@ -121,6 +138,7 @@ class ControllerTests: XCTestCase {
class MyFloatingPanelTop2BottomLayout: FloatingPanelTop2BottomTestLayout {
override var initialState: FloatingPanelState { return .half }
}
let timeout = 3.0
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = MyFloatingPanelTop2BottomLayout()
@@ -159,7 +177,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .full)
XCTAssertEqual(delegate.position, .full)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to half(animated)") { act in
@@ -170,7 +188,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .half)
XCTAssertEqual(delegate.position, .half)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
XCTContext.runActivity(named: "move to tip(animated)") { act in
@@ -181,7 +199,7 @@ class ControllerTests: XCTestCase {
}
XCTAssertEqual(fpc.state, .tip)
XCTAssertEqual(delegate.position, .tip)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
}
fpc.move(to: .hidden, animated: true)
@@ -207,6 +225,7 @@ class ControllerTests: XCTestCase {
}
func test_moveTo_didMoveDelegate() {
let timeout = 3.0
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
XCTAssertEqual(delegate.position, .hidden)
@@ -221,7 +240,7 @@ class ControllerTests: XCTestCase {
exp.fulfill()
}
fpc.move(to: .full, animated: false)
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertEqual(count, 1)
}
@@ -237,7 +256,7 @@ class ControllerTests: XCTestCase {
fpc.move(to: .half, animated: true) {
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertGreaterThan(count, 1)
}
@@ -254,7 +273,7 @@ class ControllerTests: XCTestCase {
exp.fulfill()
}
}
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertEqual(count, 1)
}
@@ -272,7 +291,7 @@ class ControllerTests: XCTestCase {
exp.fulfill()
}
}
wait(for: [exp], timeout: 1.0)
wait(for: [exp], timeout: timeout)
XCTAssertGreaterThan(count, 1)
}
@@ -314,11 +333,40 @@ class ControllerTests: XCTestCase {
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .full).y)
fpc.move(to: .half, animated: false)
print(1 / fpc.surfaceView.fp_displayScale)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .half).y)
fpc.move(to: .tip, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
}
func test_switching_layout() {
final class FirstLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .half
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
final class SecondLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .half
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea)
]
}
let fpc = FloatingPanelController()
fpc.layout = FirstLayout()
fpc.showForTest()
fpc.move(to: .tip, animated: false)
// Switch to another layout
fpc.layout = SecondLayout()
fpc.invalidateLayout()
XCTAssertEqual(fpc.state, .half)
}
}
private class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
@@ -336,8 +384,7 @@ private class MyZombieViewController: UIViewController, FloatingPanelLayout, Flo
return .half
}
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0,
edge: .top,
referenceGuide: .superview),
@@ -347,6 +394,5 @@ private class MyZombieViewController: UIViewController, FloatingPanelLayout, Flo
.tip: FloatingPanelLayoutAnchor(absoluteInset: 60.0,
edge: .bottom,
referenceGuide: .superview),
]
}
]
}
+242 -80
View File
@@ -7,7 +7,7 @@ class CoreTests: XCTestCase {
override func setUp() {}
override func tearDown() {}
func test_scrolllock() {
func test_scrollLock() {
let fpc = FloatingPanelController()
let contentVC1 = UITableViewController(nibName: nil, bundle: nil)
@@ -61,9 +61,8 @@ class CoreTests: XCTestCase {
class FloatingPanelLayout1Positions: FloatingPanelLayout {
let initialState: FloatingPanelState = .full
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview)]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] =
[.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview)]
}
let delegate = FloatingPanelTestDelegate()
@@ -106,12 +105,10 @@ class CoreTests: XCTestCase {
class FloatingPanelLayout2Positions: FloatingPanelLayout {
let initialState: FloatingPanelState = .half
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
]
}
let delegate = FloatingPanelTestDelegate()
@@ -198,51 +195,111 @@ class CoreTests: XCTestCase {
}
}
}
let fpc = FloatingPanelController()
class BackdropTestLayout2: FloatingPanelTestLayout {
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
}
}
class TestDelegate: FloatingPanelControllerDelegate {
var layout: FloatingPanelLayout = BackdropTestLayout2()
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { layout }
func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout { layout }
}
func _floor(_ alpha: CGFloat) -> CGFloat {
return floor(fpc.backdropView.alpha * 1e+06) / 1e+06
}
let timeout = 3.0
let delegate = TestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = BackdropTestLayout()
fpc.showForTest()
fpc.move(to: .full, animated: false)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.backdropView.alpha, 0.0)
fpc.move(to: .tip, animated: false)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
let exp1 = expectation(description: "move to full with animation")
fpc.move(to: .full, animated: true) {
exp1.fulfill()
}
wait(for: [exp1], timeout: 1.0)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
wait(for: [exp1], timeout: timeout)
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
let exp2 = expectation(description: "move to half with animation")
fpc.move(to: .half, animated: true) {
exp2.fulfill()
}
wait(for: [exp2], timeout: 1.0)
wait(for: [exp2], timeout: timeout)
XCTAssertEqual(fpc.backdropView.alpha, 0.0)
// Test a content mode change of FloatingPanelController
let exp3 = expectation(description: "move to tip with animation")
fpc.move(to: .tip, animated: true) {
exp3.fulfill()
}
fpc.contentMode = .fitToBounds
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must not affect the backdrop alpha by changing the content mode
wait(for: [exp3], timeout: 1.0)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must not affect the backdrop alpha by changing the content mode
wait(for: [exp3], timeout: timeout)
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
// Test a size class change of FloatingPanelController.view
fpc.move(to: .full, animated: false)
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
fpc.willTransition(to: UITraitCollection(horizontalSizeClass: .regular), with: MockTransitionCoordinator())
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must update the alpha by BackdropTestLayout2 in TestDelegate.
// Test a view size change of FloatingPanelController.view
fpc.move(to: .full, animated: false)
delegate.layout = BackdropTestLayout()
fpc.invalidateLayout()
XCTAssertEqual(_floor(fpc.backdropView.alpha), 0.3)
delegate.layout = BackdropTestLayout2()
fpc.viewWillTransition(to: CGSize.zero, with: MockTransitionCoordinator())
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must update the alpha by BackdropTestLayout2 in TestDelegate.
}
func test_targetPosition_1positions() {
func test_initial_surface_position() {
class FloatingPanelTestDelegate: FloatingPanelControllerDelegate {
class Layout: FloatingPanelLayout {
let initialState: FloatingPanelState = .full
let position: FloatingPanelPosition = .top
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
= [.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .bottom, referenceGuide: .superview)]
}
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
Layout()
}
}
do {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
XCTAssertEqual(fpc.surfaceView.position, .top)
}
do {
let fpc = FloatingPanelController()
fpc.layout = FloatingPanelTestDelegate.Layout()
XCTAssertEqual(fpc.surfaceView.position, .top)
}
}
func test_targetState_1positions() {
class FloatingPanelLayout1Positions: FloatingPanelLayout {
let initialState: FloatingPanelState = .full
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview)]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
= [.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview)]
}
let delegate = FloatingPanelTestDelegate()
@@ -254,7 +311,7 @@ class CoreTests: XCTestCase {
let fullPos = fpc.surfaceLocation(for: .full).y
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full), // redirect
@@ -265,16 +322,14 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_2positions() {
func test_targetState_2positions() {
class FloatingPanelLayout2Positions: FloatingPanelLayout {
let initialState: FloatingPanelState = .half
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
]
}
let delegate = FloatingPanelTestDelegate()
@@ -287,7 +342,7 @@ class CoreTests: XCTestCase {
let halfPos = fpc.surfaceLocation(for: .half).y
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
@@ -304,7 +359,7 @@ class CoreTests: XCTestCase {
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
])
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
@@ -322,16 +377,14 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_2positionsWithHidden() {
func test_targetState_2positionsWithHidden() {
class FloatingPanelLayout2Positions: FloatingPanelLayout {
let initialState: FloatingPanelState = .hidden
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
}
let delegate = FloatingPanelTestDelegate()
@@ -344,7 +397,7 @@ class CoreTests: XCTestCase {
let hiddenPos = fpc.surfaceLocation(for: .hidden).y
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
@@ -361,7 +414,7 @@ class CoreTests: XCTestCase {
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
])
fpc.move(to: .hidden, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
@@ -379,7 +432,7 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsFromFull() {
func test_targetState_3positionsFromFull() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = FloatingPanelLayout3Positions()
@@ -391,7 +444,7 @@ class CoreTests: XCTestCase {
let tipPos = fpc.surfaceLocation(for: .tip).y
// From .full
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
@@ -423,7 +476,7 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsFromFull_bottomEdge() {
func test_targetState_3positionsFromFull_bottomEdge() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
@@ -435,7 +488,7 @@ class CoreTests: XCTestCase {
let tipPos = fpc.surfaceLocation(for: .tip).y
// From .full
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
@@ -467,7 +520,7 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsFromHalf() {
func test_targetState_3positionsFromHalf() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = FloatingPanelLayout3Positions()
@@ -479,7 +532,7 @@ class CoreTests: XCTestCase {
let tipPos = fpc.surfaceLocation(for: .tip).y
// From .half
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
@@ -509,7 +562,7 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsFromHalf_bottomEdge() {
func test_targetState_3positionsFromHalf_bottomEdge() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
@@ -521,7 +574,7 @@ class CoreTests: XCTestCase {
let tipPos = fpc.surfaceLocation(for: .tip).y
// From .half
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
@@ -551,7 +604,7 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsFromTip() {
func test_targetState_3positionsFromTip() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = FloatingPanelLayout3Positions()
@@ -564,7 +617,7 @@ class CoreTests: XCTestCase {
// From .tip
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
@@ -594,7 +647,7 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsFromTip_bottomEdge() {
func test_targetState_3positionsFromTip_bottomEdge() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
@@ -607,7 +660,7 @@ class CoreTests: XCTestCase {
// From .tip
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
@@ -637,7 +690,7 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsAllProjection() {
func test_targetState_3positionsAllProjection() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
fpc.layout = FloatingPanelLayout3Positions()
@@ -651,7 +704,7 @@ class CoreTests: XCTestCase {
// From .full
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
@@ -664,7 +717,7 @@ class CoreTests: XCTestCase {
// From .half
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
@@ -673,7 +726,7 @@ class CoreTests: XCTestCase {
// From .tip
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
@@ -685,17 +738,15 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsWithHidden() {
func test_targetState_3positionsWithHidden() {
class FloatingPanelLayout3PositionsWithHidden: FloatingPanelLayout {
let initialState: FloatingPanelState = .hidden
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
}
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
@@ -705,11 +756,11 @@ class CoreTests: XCTestCase {
XCTAssertEqual(fpc.state, .hidden)
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .half),
])
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -100.0), .half),
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -1000.0), .full),
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 0.0), .half),
@@ -717,17 +768,15 @@ class CoreTests: XCTestCase {
])
}
func test_targetPosition_3positionsWithHiddenWithoutFull() {
func test_targetState_3positionsWithHiddenWithoutFull() {
class FloatingPanelLayout3Positions: FloatingPanelLayout {
let initialState: FloatingPanelState = .hidden
let position: FloatingPanelPosition = .bottom
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 60.0, edge: .bottom, referenceGuide: .superview),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0, edge: .bottom, referenceGuide: .superview),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 60.0, edge: .bottom, referenceGuide: .superview),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
}
let delegate = FloatingPanelTestDelegate()
@@ -743,7 +792,7 @@ class CoreTests: XCTestCase {
//let hiddenPos = fpc.surfaceLocation(for: .hidden)
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 385.0), .tip), // projection
@@ -759,7 +808,7 @@ class CoreTests: XCTestCase {
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
])
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
assertTargetState(fpc.floatingPanel, with: [
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half),
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
@@ -773,6 +822,119 @@ class CoreTests: XCTestCase {
fpc.showForTest()
XCTAssertFalse(fpc.panGestureRecognizer.isEnabled)
}
func test_is_scrollable() {
class Delegate: FloatingPanelControllerDelegate {
var shouldScroll = false
func floatingPanel(
_ fpc: FloatingPanelController,
shouldAllowToScroll scrollView: UIScrollView,
in state: FloatingPanelState
) -> Bool {
return shouldScroll
}
}
let fpc = FloatingPanelController()
let scrollView = UIScrollView()
let delegate = Delegate()
fpc.layout = FloatingPanelBottomLayout()
fpc.track(scrollView: scrollView)
fpc.showForTest()
XCTAssertTrue(fpc.floatingPanel.isScrollable(state: .full))
XCTAssertFalse(fpc.floatingPanel.isScrollable(state: .half))
fpc.delegate = delegate
XCTAssertFalse(fpc.floatingPanel.isScrollable(state: .full))
XCTAssertFalse(fpc.floatingPanel.isScrollable(state: .half))
delegate.shouldScroll = true
XCTAssertTrue(fpc.floatingPanel.isScrollable(state: .full))
XCTAssertTrue(fpc.floatingPanel.isScrollable(state: .half))
}
func test_adjustScrollContentInsetIfNeeded() {
class CustomScrollView: UIScrollView {
var customSafeAreaInsets: UIEdgeInsets = .zero
override var safeAreaInsets: UIEdgeInsets {
customSafeAreaInsets
}
}
do {
let scrollView = CustomScrollView()
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 34, right: 0)
let fpc = FloatingPanelController()
fpc.track(scrollView: scrollView)
fpc.layout = FloatingPanelBottomLayout()
fpc.contentInsetAdjustmentBehavior = .always
fpc.contentMode = .static
fpc.showForTest()
fpc.move(to: .half, animated: false)
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
let expect = 34 + (fpc.surfaceLocation(for: .half).y - fpc.surfaceLocation(for: .full).y)
XCTAssertEqual(
scrollView.contentInset,
UIEdgeInsets(top: 0, left: 0, bottom: expect, right: 0)
)
fpc.contentMode = .fitToBounds
XCTAssertEqual(
scrollView.contentInset,
scrollView.customSafeAreaInsets
)
}
do {
let scrollView = CustomScrollView()
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 91, left: 0, bottom: 0, right: 0)
let fpc = FloatingPanelController()
fpc.track(scrollView: scrollView)
fpc.layout = FloatingPanelTopPositionedLayout()
fpc.contentInsetAdjustmentBehavior = .always
fpc.contentMode = .static
fpc.showForTest()
fpc.move(to: .half, animated: false)
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
let expect = 91 + (fpc.surfaceLocation(for: .full).y - fpc.surfaceLocation(for: .half).y)
XCTAssertEqual(
scrollView.contentInset,
UIEdgeInsets(top: expect, left: 0, bottom: 0, right: 0)
)
fpc.contentMode = .fitToBounds
XCTAssertEqual(
scrollView.contentInset,
scrollView.customSafeAreaInsets
)
}
}
func test_handleGesture_endWithoutAttraction() throws {
class Delegate: FloatingPanelControllerDelegate {
var willAttract: Bool?
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool) {
willAttract = attract
}
}
let fpc = FloatingPanelController()
let delegate = Delegate()
fpc.showForTest()
fpc.delegate = delegate
XCTAssertEqual(fpc.state, .half)
fpc.floatingPanel.endWithoutAttraction(.full)
XCTAssertEqual(fpc.state, .full)
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, fpc.surfaceLocation.y)
XCTAssertEqual(delegate.willAttract, false)
}
}
private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
@@ -788,9 +950,9 @@ private class FloatingPanelLayout3PositionsBottomEdge: FloatingPanelTop2BottomTe
}
private typealias TestParameter = (UInt, CGFloat, CGPoint, FloatingPanelState)
private func assertTargetPosition(_ floatingPanel: Core, with params: [TestParameter]) {
private func assertTargetState(_ floatingPanel: Core, with params: [TestParameter]) {
params.forEach { (line, pos, velocity, result) in
floatingPanel.surfaceView.frame.origin.y = pos
XCTAssertEqual(floatingPanel.targetPosition(from: pos, with: velocity.y), result, line: line)
XCTAssertEqual(floatingPanel.targetState(from: pos, with: velocity.y), result, line: line)
}
}
+16
View File
@@ -9,4 +9,20 @@ class ExtensionTests: XCTestCase {
XCTAssertNotEqual(CGFloat(333.5).rounded(by: 3), 333.66666666666674)
XCTAssertTrue(CGFloat(333.5).isEqual(to: 333.66666666666674, on: 3.0))
}
func test_roundedByDisplayScale_2() {
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 3), 0.0)
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 3), 0.0)
XCTAssertEqual(CGFloat(-0.3333333333374246).rounded(by: 3), -0.3333333333333333)
XCTAssertEqual(CGFloat(-0.3333333333074246).rounded(by: 3), -0.3333333333333333)
XCTAssertEqual(CGFloat(0.33333333333374246).rounded(by: 3), 0.3333333333333333)
XCTAssertEqual(CGFloat(0.33333333333074246).rounded(by: 3), 0.3333333333333333)
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 2), 0.0)
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 2), 0.0)
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 6), -0.16666666666666666)
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 6), 0.16666666666666666)
}
}
+139
View File
@@ -0,0 +1,139 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import XCTest
@testable import FloatingPanel
final class GestureTests: XCTestCase {
func test_delegateProxy_shouldRecognizeSimultaneouslyWith() throws {
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
var callsOfShouldRecognizeSimultaneouslyWith = 0
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
callsOfShouldRecognizeSimultaneouslyWith += 1
return true
}
}
let fpc = FloatingPanelController()
fpc.showForTest()
let delegateProxy = GestureDelegateProxy()
// Set a proxy delegate
fpc.panGestureRecognizer.delegateProxy = delegateProxy
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
UIGestureRecognizer(),
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
)
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
// Check whether the default delegate method is called when the proxy delegate doesn't implement it.
XCTAssertTrue(
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
fpc.panGestureRecognizer,
shouldRequireFailureOf: FloatingPanelPanGestureRecognizer()
)
)
// Clear the proxy delegate
fpc.panGestureRecognizer.delegateProxy = nil
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
UIGestureRecognizer(),
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
)
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
}
func test_delegateProxy_shouldRequireFailureOf() throws {
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
var callsOfShouldRequireFailureOf = 0
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
callsOfShouldRequireFailureOf += 1
return true
}
}
let fpc = FloatingPanelController()
fpc.showForTest()
let delegateProxy = GestureDelegateProxy()
// Set a proxy delegate
fpc.panGestureRecognizer.delegateProxy = delegateProxy
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
UIGestureRecognizer(),
shouldRequireFailureOf: UIGestureRecognizer()
)
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
// Clear the proxy delegate
fpc.panGestureRecognizer.delegateProxy = nil
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
UIGestureRecognizer(),
shouldRequireFailureOf: UIGestureRecognizer()
)
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
}
func test_delegateProxy_shouldBeRequiredToFailBy() throws {
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
var callsOfShouldBeRequiredToFailBy = 0
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
callsOfShouldBeRequiredToFailBy += 1
return false
}
}
let fpc = FloatingPanelController()
fpc.showForTest()
let delegateProxy = GestureDelegateProxy()
fpc.panGestureRecognizer.delegateProxy = delegateProxy
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
UIGestureRecognizer(),
shouldBeRequiredToFailBy: UIGestureRecognizer()
)
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 1)
// Check whether the delegate method of the "proxy" object is called.
let otherPanGesture = UIPanGestureRecognizer()
otherPanGesture.name = "_UISheetInteractionBackgroundDismissRecognizer"
XCTAssertFalse(
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
fpc.panGestureRecognizer,
shouldBeRequiredToFailBy: otherPanGesture
)
)
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
fpc.panGestureRecognizer.delegateProxy = nil
// Check whether the delegate method of the "default" object is called.
let otherPanGesture2 = UIPanGestureRecognizer()
otherPanGesture2.name = "_UISheetInteractionBackgroundDismissRecognizer"
XCTAssertTrue(
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
fpc.panGestureRecognizer,
shouldBeRequiredToFailBy: otherPanGesture2
)
)
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
}
}
+80 -65
View File
@@ -17,23 +17,19 @@ class LayoutTests: XCTestCase {
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.leastExpandedState, .tip)
class FloatingPanelLayoutWithHidden: FloatingPanelLayout {
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview)
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview)
]
let initialState: FloatingPanelState = .hidden
let position: FloatingPanelPosition = .bottom
}
class FloatingPanelLayout2Positions: FloatingPanelLayout {
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
]
let initialState: FloatingPanelState = .tip
let position: FloatingPanelPosition = .bottom
}
@@ -224,12 +220,10 @@ class LayoutTests: XCTestCase {
func test_updateInteractiveEdgeConstraintWithHidden() {
class FloatingPanelLayout2Positions: FloatingPanelLayout {
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .bottom, referenceGuide: .safeArea),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .bottom, referenceGuide: .safeArea),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
]
let initialState: FloatingPanelState = .hidden
let position: FloatingPanelPosition = .bottom
}
@@ -267,12 +261,10 @@ class LayoutTests: XCTestCase {
func test_updateInteractiveEdgeConstraintWithHidden_bottomEdge() {
class FloatingPanelLayout2Positions: FloatingPanelLayout {
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
[
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .bottom, referenceGuide: .safeArea),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .top, referenceGuide: .superview),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .bottom, referenceGuide: .safeArea),
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .top, referenceGuide: .superview),
]
let initialState: FloatingPanelState = .hidden
let position: FloatingPanelPosition = .top
}
@@ -310,12 +302,10 @@ class LayoutTests: XCTestCase {
func test_updateInteractiveTopConstraintWithMinusInsets() {
class FloatingPanelLayoutMinusInsets: FloatingPanelLayout {
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
[
.full: FloatingPanelLayoutAnchor(absoluteInset: -200, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: -200, edge: .bottom, referenceGuide: .safeArea),
]
}
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: -200, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: -200, edge: .bottom, referenceGuide: .safeArea),
]
let initialState: FloatingPanelState = .full
let position: FloatingPanelPosition = .bottom
}
@@ -421,18 +411,18 @@ class LayoutTests: XCTestCase {
for prop in [
// from top edge
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
// from bottom edge
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
result: (#line, constant: 0.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
result: (#line, constant: 100.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
@@ -450,7 +440,7 @@ class LayoutTests: XCTestCase {
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
@@ -460,7 +450,7 @@ class LayoutTests: XCTestCase {
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
@@ -469,12 +459,7 @@ class LayoutTests: XCTestCase {
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
if #available(iOS 11, *) {
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
}
print(c)
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
}
}
func test_layoutAnchor_bottomPosition() {
@@ -487,9 +472,9 @@ class LayoutTests: XCTestCase {
for prop in [
// from top edge
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
@@ -497,9 +482,9 @@ class LayoutTests: XCTestCase {
// from bottom edge
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
result: (#line, constant: 0.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
result: (#line, constant: 100.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
@@ -517,7 +502,7 @@ class LayoutTests: XCTestCase {
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
@@ -527,7 +512,7 @@ class LayoutTests: XCTestCase {
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
result: (#line, multiplier: 1.0, secondAnchor: nil)),
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
@@ -536,12 +521,7 @@ class LayoutTests: XCTestCase {
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
if #available(iOS 11, *) {
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
}
print(c)
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
}
}
@@ -567,20 +547,20 @@ class LayoutTests: XCTestCase {
for prop in [
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
@@ -617,20 +597,20 @@ class LayoutTests: XCTestCase {
for prop in [
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
@@ -644,6 +624,41 @@ class LayoutTests: XCTestCase {
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
}
}
func test_offsetFromMostExpandedAnchor() {
do {
let fpc = FloatingPanelController()
fpc.layout = FloatingPanelBottomLayout()
fpc.showForTest()
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor, 0.0)
fpc.move(to: .half, animated: false)
XCTAssertEqual(
fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor,
(fpc.surfaceLocation(for: .half) - fpc.surfaceLocation(for: .full)).y
)
}
do {
let fpc = FloatingPanelController()
fpc.layout = FloatingPanelTopPositionedLayout()
fpc.showForTest()
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor, 0.0)
fpc.move(to: .half, animated: false)
XCTAssertEqual(
fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor,
-(fpc.surfaceLocation(for: .half) - fpc.surfaceLocation(for: .full)).y
)
}
}
}
private typealias LayoutSegmentTestParameter = (UInt, pos: CGFloat, forwardY: Bool, lower: FloatingPanelState?, upper: FloatingPanelState?)
+33 -1
View File
@@ -72,8 +72,40 @@ class FloatingPanelTop2BottomTestLayout: FloatingPanelLayout {
}
}
class FloatingPanelTopPositionedLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .top
let initialState: FloatingPanelState = .full
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 88.0, edge: .bottom, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .top, referenceGuide: .safeArea)
]
}
class FloatingPanelProjectableBehavior: FloatingPanelBehavior {
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
return true
}
}
class MockTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator {
func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) -> Bool { true }
func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) -> Bool { true }
func notifyWhenInteractionEnds(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> Void) {}
func notifyWhenInteractionChanges(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> Void) {}
var isAnimated: Bool = false
var presentationStyle: UIModalPresentationStyle = .fullScreen
var initiallyInteractive: Bool = false
var isInterruptible: Bool = false
var isInteractive: Bool = false
var isCancelled: Bool = false
var transitionDuration: TimeInterval = 0.25
var percentComplete: CGFloat = 0
var completionVelocity: CGFloat = 0
var completionCurve: UIView.AnimationCurve = .easeInOut
func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController? { nil }
func view(forKey key: UITransitionContextViewKey) -> UIView? { nil }
var containerView: UIView { UIView() }
var targetTransform: CGAffineTransform = .identity
}
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let rootVC = UIViewController(nibName: nil, bundle: nil)
rootVC.view.backgroundColor = .gray
let window = UIWindow()
window.rootViewController = rootVC
window.makeKeyAndVisible()
self.window = window
return true
}
}
-43
View File
@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
-48
View File
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Copyright © 2019 scenee. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="obG-Y5-kRd">
<rect key="frame" x="0.0" y="626.5" width="375" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="TestingApp" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="obG-Y5-kRd" secondAttribute="centerX" id="5cz-MP-9tL"/>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
<constraint firstItem="obG-Y5-kRd" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="SfN-ll-jLj"/>
<constraint firstAttribute="bottom" secondItem="obG-Y5-kRd" secondAttribute="bottom" constant="20" id="Y44-ml-fuU"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="x7j-FC-K8j"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>