Compare commits

..

632 Commits

Author SHA1 Message Date
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
Shin Yamamoto b00a05b9ed Version 2.5.4 2022-09-08 12:42:23 +09:00
Shin Yamamoto bfbb7ad004 Remove the CI job with Xcode 13.1 and iOS 15.0
This job is unstable because of its environment.
2022-09-08 12:42:14 +09:00
Shin Yamamoto 4dd52b1790 Replace PR #551 change using fatalError() (#563)
This resolves issue #561. Instead of continuing to work a modal presentation transition even if a panel state is not .hidden, this library calls fatalError as a programmer error on the occasion.
2022-09-07 20:35:50 +09:00
Shin Yamamoto aef914e0fd Remove unused '.swiftformat' file 2022-07-30 11:04:29 +09:00
Shin Yamamoto 2ef35f58a3 Merge pull request #556 from scenee/iss-536
Fix the view index of FloatingPanelView for SwiftUI
2022-07-30 11:04:11 +09:00
Shin Yamamoto 8aaf8b7b25 Fix the view index of FloatingPanelView for SwiftUI
Resolve #536
2022-07-30 09:28:33 +09:00
Shin Yamamoto c330b59a9f Merge pull request #550 from scenee/release/2.5.3
Release 2.5.3
2022-06-14 21:14:09 +09:00
Shin Yamamoto 639e4221e8 Version 2.5.3 2022-06-13 21:06:44 +09:00
Shin Yamamoto 1ff3c28de8 Ensure the initial state is hidden in presenting it as a modality (#551) 2022-06-13 21:06:24 +09:00
Sean Hernandez 89092676e3 Fix floating panel content view constraints (#549)
Resolved Maps-SwiftUI Example Animation Stuttering (#546)
2022-06-13 20:00:12 +09:00
Anton Siliuk bacc871dd2 Fix cornerCurve property availability compilation fail (#548)
With Xcode 14 Beta 1 library fails to compile with following error.

> Stored properties cannot be marked potentially unavailable with `@available`

Because CALayerCornerCurve is not marked with any @available annotation it is possible to declare private optional storage of it.
2022-06-13 19:54:54 +09:00
Shin Yamamoto 0bc9812c23 Update GitHub Actions workflow using macOS-12 (#538)
* Updated 'ci' workflow for Xcode 13.3.1
* Modernized and simplified the workflow using 'strategy.matrix'
* 'arm64-apple-ios15.4' target is not included in swiftpm because its build gets an build error.
2022-05-31 21:42:36 +09:00
Shin Yamamoto a4d98971b3 Fix a warning in SamplesObjC 2022-04-20 21:16:45 +09:00
little-huang 1b0ec64489 fix: add missing import UIKit (#534)
Co-authored-by: 黄宝成 <huangbc@publink.cn>
2022-04-20 20:29:28 +09:00
Shin Yamamoto 345e894007 Stop scrolling if the initial location of a pan gesture is in the grabber area 2022-02-21 21:16:39 +09:00
Shin Yamamoto 1fb1708df9 Fix the grabber area detecion 2022-02-19 08:08:56 +09:00
Shin Yamamoto b9edbf766e Merge pull request #528 from scenee/release/2.5.2
Release 2.5.2
2022-02-10 17:07:24 +09:00
Shin Yamamoto fe30e60235 Version 2.5.2 2022-02-04 23:59:07 +09:00
Jakub Dudek 6e7c31110f Fix dragging on content placed above the trackedView resets scroll position (#527)
Changed how `initialScrollOffset` is set. This issue was described in #526.
2022-02-04 23:56:46 +09:00
Shin Yamamoto e39f634d34 Unlock a scroll view when untracked 2021-12-04 11:12:11 +09:00
Shin Yamamoto 5851fd77c4 Merge pull request #521 from scenee/release/2.5.1
Release 2.5.1
2021-12-03 18:06:12 +09:00
Shin Yamamoto 0fbbbc8d99 Version 2.5.1 2021-11-29 20:46:32 +09:00
Shin Yamamoto 69078366de Change constraint priorities of the content view on the default content mode (#519)
They were set to `.required - 1` is #359, which fixed #294. However #294
was an issue on `.fitToBounds` content mode. But because of #444 and #515,
if your panel’s content mode is `.static`, their priorities should be set to
`.required` on `.static` content mode.
2021-11-29 20:40:26 +09:00
Shin Yamamoto 084b589a10 Fix invalidateLayout() implementation as following the doc comment (#510)
* Fix FloatingPanelController.invalidateLayout() implementation as following the doc comment
* Revise the doc comments of invalidateLayout() method, layout and behavior properties of FloatingPanelController.
* Add a note in README 
* Fix some grammar errors
2021-11-25 23:19:05 +09:00
Shin Yamamoto 07ae324586 Clean up the example projects and codes (#512)
* Clean up Samples and Maps examples
* Rename the root view controller in Maps/Stocks/SampleObjC examples
* Reorder resource file references
* Rename examples' bundle ids with `example` domain.
* Remove 'Run Script' to modify CFBundleVersion: now this has not been used for testing since unit tests were added
* Revise methods to handle content view controllers in UseCaseController
* Fix a bug on PagePanelController
2021-11-24 20:26:35 +09:00
Shin Yamamoto 819c87c530 Stop changing the content mode whose panel is attracting (#513)
This code causes #511. Unfortunately I don't remember the reason
why the example code do that (I should have added a comment...).
And it's a bit tricky for the example. So I remove it.
2021-11-16 08:29:43 +09:00
Shin Yamamoto 3bb9c4fd0f Apply a part of build settings recommended by Xcode 13 2021-10-30 11:19:52 +09:00
Shin Yamamoto 910a304849 Update README 2021-10-30 11:04:24 +09:00
Shin Yamamoto 2ecdbbae52 Add the documentation catalog 2021-10-11 16:33:16 +09:00
Shin Yamamoto fe2e3173b8 Support DocC symbols 2021-10-09 10:09:14 +09:00
Shin Yamamoto 21c6e3c715 Merge pull request #505 from scenee/release/2.5.0
Release 2.5.0
2021-09-28 20:44:31 +09:00
Shin Yamamoto 29beea0ff9 Version 2.5.0 2021-09-28 08:43:42 +09:00
Shin Yamamoto 1e16d9c1fb Fix a tab bar's appearance in Samples app on iOS 15 2021-09-28 08:43:42 +09:00
milettal 31d7a2a301 Fix a crash in Samples app on iPad (#497)
* Fix a crash when the app shows an action sheet on iPad
* Use the UITableViewCell as the sourceView
2021-09-25 10:11:38 +09:00
Federico Zanetello 5f1d49b0bd Add SwiftUI proof of concept in Maps-SwiftUI example (#481)
Partially solves #281. This adds a new example app which mimics the Maps.app, written in SwiftUI. The code works for iOS 13+, however:
* the project has been created with Xcode 13
* the project uses the SwiftUI lifecycle (iOS 14+)

The source code in Examples/Maps-SwiftUI/Maps/FloatingPanel is ready to move into the library, but there is an issue on SwiftUI’s environment propagation into FloatingPanel. SwiftUI’s environment is propagated to all subviews. However FloatingPanel is not a subview, but a new view controller in the screen (and not a child view controller).

It’s possible to lead behaviors unexpected by SwiftUI users so that this is merged as a sample code until it will be resolved.
2021-09-25 10:06:23 +09:00
Shin Yamamoto ee66f218ca Merge pull request #502 from scenee/release/2.4.1
Release 2.4.1
2021-09-18 12:11:12 +09:00
Shin Yamamoto 3a0831a334 Version 2.4.1 2021-09-18 11:20:52 +09:00
Shin Yamamoto add2f9ba4f Remove .travis.yml 2021-09-18 11:20:52 +09:00
Shin Yamamoto ca6a8d5c16 Update ci workflow 2021-09-18 10:57:03 +09:00
Iran-Q 84eba1aefa fix objc can not obtain dismissalTapGestureRecognizer (#496)
Co-authored-by: CodingIran <codingiran@163.com>
2021-08-27 00:02:02 +09:00
Shin Yamamoto 289ea4c971 Fix a compile error on SampleObjC 2021-08-18 07:29:39 +09:00
Shin Yamamoto 821b03376c Make the pan gesture keep disabled (#486)
Because a panel's pan gesture becomes enabled in showing it even
after it is disabled. This issue was reported in #485.
2021-08-10 11:57:19 +09:00
Shin Yamamoto 19fe1cffef Merge pull request #472 from scenee/release/2.4.0
Release 2.4.0
2021-06-05 15:09:39 +09:00
Shin Yamamoto b8fcb50874 Version 2.4.0 2021-06-05 14:28:00 +09:00
Shin Yamamoto 9c45c31190 Fix a regression for the backdrop behavior (#466,#471)
The backdrop alpha of a panel must be updated only in `Controller.show(animated:completion:)`.
Because that prevents a backdrop flicking just before presenting a panel.

Resolve #466.
2021-06-05 14:24:19 +09:00
Shin Yamamoto 895790f697 Update README for the backdrop tap-to-dismiss action 2021-05-28 13:00:30 +09:00
Shin Yamamoto 43c7e8c2a0 Refactor LayoutAdapter
* Remove `edgeMostY`
* Rename the following properties
    * activeStates -> anchorStates
    * orderedStates -> sortedAnchorStates
    * sortedDirectionalStates -> sortedAnchorStatesByCoordinate
    * directionalLeastState -> leastCoordinateState
    * directionalMostState -> mostCoordinateState
    * edgeMostState -> mostExpandedState
    * edgeLeastState -> leastExpandedState
    * offsetFromEdgeMost -> offsetFromMostExpandedAnchor
2021-05-10 22:57:04 +09:00
Shin Yamamoto 904a66115c Add completion handler in addPanel(toParent:) (#402) 2021-05-10 22:14:01 +09:00
Shin Yamamoto d0932e8e37 Rename floatingPanelDidChangePosition to floatingPanelDidChangeState 2021-05-10 22:14:01 +09:00
Shin Yamamoto 22009013eb Merge pull request #461 from scenee/release/2.3.1
Release v2.3.1
2021-05-10 21:37:17 +09:00
Shin Yamamoto da4668aa2b Version 2.3.1 2021-05-08 13:52:17 +09:00
Shin Yamamoto 00ce232420 Refactor the samples app (#459)
* Add UseCaseController
* Add Layouts.swift
* Add PagePanelController
* Append access modifiers into some objects
* Split view controllers into each file
* Organize the project
* Rename currentMenu with currentUseCase
* Rename SampleListVC with MainVC
* Update UseCase enum
* Update DebugTableViewController to test the scroll (un)tracking functions
2021-05-08 13:17:02 +09:00
Shin Yamamoto 5b176b5cef Tidy up Extensions.swift 2021-04-24 09:38:13 +09:00
Shin Yamamoto 15709f62f8 Remove 'UI' prefix from UIExtensions 2021-04-24 09:38:13 +09:00
Shin Yamamoto 9168236534 Improve the function rounding a dimension by a display scale 2021-04-24 09:37:58 +09:00
Shin Yamamoto 16e709e8ab Fix backdrop flickering (#449)
* Add test_updateBackdropAlpha
* Fix backdrop alpha's flickers in Maps.app

This issue occurs when swinging down a panel with all one's might.
The trigger is here.
```
    func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
        if targetState.pointee != .full {
            owner.searchVC.hideHeader(animated: true)
        }
        if targetState.pointee == .tip {
>>>         vc.contentMode = .static
        }
    }
```
However, any library users expect to affect the backdrop by this code.
And then I reconsidered the reason why the backdrop alpha changes in
activateLayout(for:forceLayout:) and it's because the animation using
CAAnimation on v1.

Therefore I decided to move the point to change the backdrop alpha into
the move animation's completion handler.

And also the responsibility of `setBackdropAlpha(of:)` was moved into
`Core` because `Core` takes on a role of changing the backdrop alpha.
2021-04-13 18:26:22 +09:00
Shin Yamamoto 94c39f0f34 Merge pull request #442 from scenee/release-2.3.0
Release v2.3.0
2021-02-27 09:41:36 +09:00
Shin Yamamoto 16fea625be Version 2.3.0 2021-02-23 13:45:46 +09:00
Shin Yamamoto 6c69694cfe Update readme 2021-02-23 13:45:46 +09:00
Shin Yamamoto 561d783479 Prevent a memory leak in a nested function (#441)
To fix a crash "Layout.swift: Fatal error: Attempted to read an unowned reference but the object was already deallocated"  which is reported at issue #440.
2021-02-23 09:37:35 +09:00
Shin Yamamoto 1bd2e60200 Enable to add custom panel states (#438)
* Support over 3 states in LayoutAdapter
* Allow to inherite FloatingPanelState
* Support a custome FloatingPanelState in ObjC
* Replace Menu enum with UseCase enum in Samples.app
* Rename UIExtensions to Extensions
* Add CustomState use case in Samples app
2021-02-15 21:05:12 +09:00
Shin Yamamoto ec0e1b2dad Remove class keyword
class keyword will be deprecated and in Swift 5 doesn't need it for a
protocol attributed by @objc.
2021-02-12 12:15:58 +09:00
Shin Yamamoto 9958fc5017 Prevent the potential memory leaks in the modal transition (#429)
This dismisses the frame 'FloatingPanel Core.move(from:to:animated:completion:)'
in the following memory leaks

> BoardServices -[BSXPCServiceConnectionEventHandler remoteTarget]
> BoardServices __63+[BSXPCServiceConnectionProxy createImplementationForProtocol:]_block_invoke

These leaks happens when a panel showes and hides using "Show Multi Panel Modal"
in the Samples app.
2021-02-06 09:16:11 +09:00
Shin Yamamoto 11dfc0d2f3 Fix workaround for bouncing a scroll content 2021-02-06 09:05:40 +09:00
Shin Yamamoto 34246d1f37 Merge pull request #435 from SCENEE/release-2.2.0
Prepare v2.2.0
2021-01-24 16:08:13 +09:00
Shin Yamamoto 3d6c0220e0 Version 2.2.0 2021-01-22 07:45:12 +09:00
Shin Yamamoto ecbd318186 Add a note for log variable 2021-01-22 07:45:12 +09:00
Shin Yamamoto 34809bd8ea Show debug logs in console.app 2021-01-22 07:45:12 +09:00
Shin Yamamoto aa5fbe9e94 Move the main ci to github actions (#437)
Because of the migration from travis-ci.org to travis-ci.com. As a
bonus, ci builds on github actions are much faster.

This repo still needs travis-ci for testing on iOS 10/11/12 simulators
on Xcode11.x. So this doesn't remove .travis.yml
2021-01-22 07:44:30 +09:00
nickcheng 9fbb7df15a Fixed the crash when ownerVC is nil. (#436)
`ownerVC` should be checked in the completion as well.
2021-01-20 23:10:43 +09:00
Shin Yamamoto d55f9a0abf Revise the swizzling prop to make it nonnull 2021-01-15 21:54:21 +09:00
Shin Yamamoto a932f3b782 Stop moving a panel while the tracking table view is editing (#431)
To fix https://github.com/SCENEE/FloatingPanel/issues/427
2021-01-11 22:46:34 +09:00
Shin Yamamoto 9ea95d69a1 Fix an issue where not dragging a panel by priority of Layout.interactionConstraint (#428)
I applied `.defaultHigh` priority which might be safe to prevent
an ambiguous layout error. But that causes this issue, so I have to
use `.required` priority.

See more detail [#424
issue](https://github.com/SCENEE/FloatingPanel/issues/424).
2021-01-11 20:13:04 +09:00
Shin Yamamoto e783b92905 Fix a crash by the move animator (#423)
* Prevent a possible memory leak at Core.move(from:to:animated:completion:)
* Replace ``self`` with `self`.
* Change `LayoutAdapter.vc` property to an unowned reference
* Prevent a crash at LayoutAdapter.surfaceLocation called in `NumericSpringAnimator.update` closure.
2021-01-09 12:20:53 +09:00
Shin Yamamoto d380fdeb13 Address the grabber area detection in scroll tracking (#407)
Allows to expand the panel while touching in the grabber area initially.
2021-01-09 11:59:02 +09:00
Fumito Nakazawa 987bf79121 Fix swiftformat (#426) 2021-01-08 22:20:38 +09:00
Federico Zanetello be0ebd0fae Add cornerCurve option to SurfaceAppearance (#417)
* Add cornerCurve option to SurfaceAppearance
* Add cornerCurve usage in Maps example

Co-authored-by: Shin Yamamoto <shin@scenee.com>
2021-01-05 18:20:58 +09:00
Benjamin Otto a6538b7a2a Add optional removalInteractionVelocityThreshold value to Behavior (#425)
Added an optional variable `removalInteractionVelocityThreshold` to `Behavior`, which let’s the user configure the velocity threshold for the default `floatingPanel:shouldRemoveAt:with` implementation.
2021-01-04 23:09:33 +09:00
Shin Yamamoto 40194d91c0 Add a description for the backdropAlpha(for:) API (#416) 2020-12-24 23:53:42 +09:00
Federico Zanetello c8b2b33de0 fix example typo (#418) 2020-12-22 18:22:19 +09:00
Shin Yamamoto 7114f545ff Merge pull request #414 from SCENEE/release-2.1.0
Prepare version 2.1.0
2020-12-12 12:43:52 +09:00
Shin Yamamoto b4423bcaa2 Version 2.1.0 2020-12-07 19:25:47 +09:00
Shin Yamamoto b34fd41650 Fix Maps samlpe (#411)
* Fix the detail vc layout in Maps sample
* Deactivate search bar when the detail vc shows in Maps sample
2020-12-05 11:36:54 +09:00
Ryan McLeod 199a77182b Work magic numbers out of Adaptive Panel (Custom Layout Guide) example (#412) 2020-12-05 11:36:21 +09:00
Shin Yamamoto 0a0f00172d Add FloatingPanelAdaptiveLayoutAnchor (#390)
* Rename PassThroughView to PassthroughView
* Refactor LayoutAnchor initializer
* Add FloatignPanelAdaptiveLayoutAnchor
* Add samples for FloatingPanelAdaptiveLayoutAnchor
* Revise updateStaticConstraint
2020-12-01 19:33:19 +09:00
Shin Yamamoto 25ca9487fb Open the default behavior class (#406)
* Allow open access to FloatingPanelDefaultBehavior
* Move the default presenting & dismissing animators
* Add the public initializer of FloatingPanelDefaultBehavior
2020-11-30 21:21:21 +09:00
Shin Yamamoto 231ee4c9af Create FUNDING.yml for GitHub Sponsors
According to #400 request.
2020-11-28 10:32:03 +09:00
Ryan McLeod c872218446 Add escape gesture to close modal for VoiceOver users (#383)
* Allow dismissing of panel with Accessibility escape gesture
2020-11-07 11:29:23 +09:00
Takao Horiguchi 8e8c6527d4 update readme url for Transitioning.swift (#398) 2020-10-23 19:24:10 +09:00
Shin Yamamoto 3b4e237eba Merge pull request #397 from SCENEE/release-2.0.1
Release 2.0.1
2020-10-21 20:41:34 +09:00
Shin Yamamoto 9b1cb68f0a Version 2.0.1 2020-10-19 19:22:53 +09:00
Shin Yamamoto 4ba8acaf08 Ease the default velocity for a panel removal (#395)
Because the current threshold(10.0) makes it hard to remove a panel.

Resolve #389
2020-10-19 19:20:16 +09:00
Shin Yamamoto 02d8d4516c Reset the moveAnimator prop after a move animation (#396)
* Reset the moveAnimator prop after a move animation

If `moveAnimator` isn't null, `FloatingPanelPanGestureRecognizer.touchesBegan`
detects `began` state quickly so that it doesn’t allow to work a tap gesture or
tap action in a panel.

Resolve #392

* Add 2 buttons in DebugTableViewController
2020-10-19 19:17:53 +09:00
Federico Zanetello 94829d2749 add missing commas (#387) 2020-10-07 20:11:04 +09:00
Shin Yamamoto 1522a0990f Merge pull request #372 from SCENEE/v2-dev
Release FloatingPanel 2.0.0
2020-10-03 09:01:19 +09:00
Shin Yamamoto 0a613a7339 Version 2.0.0 2020-10-01 08:26:01 +09:00
Shin Yamamoto b4a518fe3e Suppress 'brew update' error on travis ci 2020-09-30 21:53:47 +09:00
Shin Yamamoto 538e046368 Change SurfaceView.contentView to an optional type 2020-09-30 20:19:19 +09:00
Shin Yamamoto bb44d14370 Double lay out the surface view for uistackview's intrinsic behavior 2020-09-30 20:19:19 +09:00
Shin Yamamoto 230ec689d4 Prevent a crash in a device rotation 2020-09-22 15:20:42 +09:00
Shin Yamamoto c7605a94b9 Fix a content view layout using intrinsic anchor 2020-09-21 15:00:50 +09:00
Shin Yamamoto 864104de3e Recalculate state constraints in layout activation for intrinsic layout
Because sometimes UIView.systemLayoutSizeFitting() returns a different
size between an onscreen and offscreen view which includes UIStackView.

This behavior depends on UIStackView behavior and the lib can not match
the difference.
2020-09-21 15:00:50 +09:00
Shin Yamamoto a8e1cfa2bc Fix intrinsic layout 2020-09-21 15:00:50 +09:00
Shin Yamamoto 207dd27113 Fix Samples app 2020-09-21 15:00:50 +09:00
Shin Yamamoto 802b6f9b6b Ease time out conditions for ControllerTests.test_moveTo_bottomEdge() 2020-09-19 16:06:17 +09:00
Shin Yamamoto 0ff9d5dd9b ci: reduce the build time 2020-09-19 15:12:47 +09:00
Shin Yamamoto 6e509481bb Ease time out conditions in ControllerTests.test_moveTo() 2020-09-19 14:00:14 +09:00
Shin Yamamoto 054a21f3f8 ci: support xcode 12 and fix errors
- Since April 2020 Xcode 11 is required to submit an app so this drops
  Xcode 10.3(Swift 5.0) build.
- Specify `-workspace` for testing
- Travis CI doesn't have Xcode 11.7 environment.
2020-09-19 13:16:09 +09:00
Shin Yamamoto 2e3bbaeec2 Merge pull request #386 from SCENEE/release-1.7.6
Release 1.7.6
2020-09-19 12:10:10 +09:00
Shin Yamamoto 18e739fc7b Merge branch 'release-1.7.6' into v2-dev 2020-09-19 11:24:39 +09:00
Shin Yamamoto ca7596e1ca Version 1.7.6 2020-09-19 11:09:30 +09:00
Shin Yamamoto 38103da2eb Update travis ci config 2020-09-05 14:46:23 +09:00
Shin Yamamoto 0c2275def1 Address test failures on iOS 10 and 11 2020-09-05 14:46:23 +09:00
Shin Yamamoto a0da1b99c0 Use BackdropView.dismissalTapGestureRecognizer to dismiss panels in Samples 2020-09-05 14:46:23 +09:00
Shin Yamamoto 427814839a Fix typo 2020-09-05 14:46:23 +09:00
Shin Yamamoto 007f9af3eb Enable the removal interaction at any positions upon the conditions (#335)
If a library consumer allows a panel projectable movement with the
FloatingPanelBehavior object, the panel is able to invoke the removal
interaction when the next moving position projected the momentum is
hidden.
2020-09-03 23:07:33 +09:00
Michal Raška da4e1d26d3 Fix quick pull down (#385) 2020-09-03 21:41:16 +09:00
Shin Yamamoto 2ce1375ce7 Fix an issue where keyboard opens above image picker (#381)
This issue was reported in
https://github.com/SCENEE/FloatingPanel/issues/369.

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

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

By the way, the issue doesn't happen when a FloatingPanelController
shows as a child view controller
2020-09-03 20:42:21 +09:00
Shin Yamamoto 016bf3e185 Revise the migration guide according to @zntfdr reviews 2020-08-31 21:12:35 +09:00
Shin Yamamoto 214942f918 Fix the panel behavior of 'Show Detail Panel' on 'fitToBounds' mode in Samples app 2020-08-29 16:17:02 +09:00
Shin Yamamoto af53ac1964 Update README 2020-08-29 15:23:41 +09:00
Shin Yamamoto 991166534f Add the migration guide for FloatingPanel 2.0 2020-08-29 15:19:32 +09:00
Shin Yamamoto 39feab437d Fix layouts using backdropAlpaFor(position:) in Samples app 2020-08-29 13:28:15 +09:00
Shin Yamamoto ba8d37e8e0 Update .gitignore 2020-08-22 15:33:30 +09:00
Shin Yamamoto 885493f2e5 Add doc comments 2020-08-21 17:42:02 +09:00
Shin Yamamoto 65fd04975d Clean up core code 2020-08-12 12:26:18 +09:00
Shin Yamamoto 1a45f43232 Update the file license headers
I reconsidered the author part because it's better to make the ownership
obvious to get rid of problems according to the unclearness.
2020-08-12 09:37:52 +09:00
Greg Hazel 28c384aa0d use 'prominent' blur effect (#379)
'prominent' blue effect is adaptive to dark mode.
2020-08-10 12:36:47 +09:00
Shin Yamamoto 99c922bf3a Update doc comment 2020-08-10 12:22:14 +09:00
Shin Yamamoto 009033be79 Fix typo 2020-08-10 12:22:14 +09:00
Shin Yamamoto 5d2e6dd417 Fix method names in core 2020-08-10 12:22:14 +09:00
Christopher Truman 9c71a47d9b Small typo fixes (#378) 2020-08-10 09:55:10 +09:00
Shin Yamamoto db0d73b428 2.0.0 Beta 1 2020-07-11 13:15:36 +09:00
Shin Yamamoto 968cb47163 Remove an unnecessary file 2020-07-11 13:15:33 +09:00
Shin Yamamoto 6f28edc92c Clean up SampleObjC code 2020-07-11 13:04:13 +09:00
Shin Yamamoto 2b94139435 Fix platform names in available conditions 2020-07-11 13:00:15 +09:00
Shin Yamamoto cb21b4075d Fix podspec 2020-07-11 13:00:15 +09:00
Shin Yamamoto d23e17c20b Fix SwiftPM errors 2020-07-11 13:00:15 +09:00
Shin Yamamoto 92f2a089ee Update README 2020-07-11 13:00:15 +09:00
Shin Yamamoto 983f9e2835 Remove a line with only whitespace 2020-07-11 13:00:15 +09:00
Shin Yamamoto 14fad8b745 Add Layout{Anchoring,References} and Position 2020-07-11 13:00:15 +09:00
Shin Yamamoto 098bd14a03 Remove an unused import 2020-07-11 13:00:15 +09:00
Shin Yamamoto cfa4047adf Add State.swift 2020-07-11 13:00:15 +09:00
Shin Yamamoto bbf437735f Modify Logger display name 2020-07-11 13:00:15 +09:00
Shin Yamamoto 5540c94e6c Add a mark comment 2020-07-11 13:00:15 +09:00
Shin Yamamoto 78981651c4 Modify NumericSpringAnimator
- Change the access control of isRunning
- Remove an unused parameter
- Fix format
2020-07-11 13:00:15 +09:00
Shin Yamamoto 5df377e43d Change the file order 2020-07-11 13:00:15 +09:00
Shin Yamamoto 02af74894a Rename Tests 2020-07-11 13:00:15 +09:00
Shin Yamamoto 8533ecfcdf Simplify file and object names 2020-07-11 13:00:15 +09:00
Shin Yamamoto 6d37df16b2 Reorganize the project structure 2020-07-11 13:00:15 +09:00
Shin Yamamoto d4542a4948 Use incremental complication mode for code coverage 2020-07-11 09:48:25 +09:00
Shin Yamamoto 0d99c14847 Update doc comments 2020-07-11 09:48:25 +09:00
Shin Yamamoto fc1aed9605 Replace 'decelerate' term with 'attract' 2020-07-11 09:48:25 +09:00
Shin Yamamoto ebf0bcc07f Rename constraint props 2020-07-11 09:48:25 +09:00
Shin Yamamoto fb0221226f Add constraint identifiers 2020-07-11 09:48:25 +09:00
Shin Yamamoto 6277bfc89f Update docs 2020-07-11 09:48:25 +09:00
Shin Yamamoto 4181918fc5 Add IDETemplateMacros.plist 2020-07-11 09:48:25 +09:00
Shin Yamamoto cd06dfdb28 Update the license headers 2020-07-11 09:48:25 +09:00
Shin Yamamoto 9509e3c32b Add .swiftformat 2020-07-11 09:48:25 +09:00
Shin Yamamoto b574cf97a0 Update Maps.app
* Support iPad panel
* Change the panel's content mode
* Add DetailViewController for left/right panels.
2020-07-11 09:48:25 +09:00
Shin Yamamoto c2fc35fc41 Update Stocks.app 2020-07-11 09:48:25 +09:00
Shin Yamamoto 1bbb4dcc23 Update Samples.app
* Add BottomEdgeInteractionLayout sample
* Support v2
    * Use FloatingPanelSurfaceAppearance
    * Fix 'Tab Bar > Layout 3' in Samples.app
    * Remove interactionBuffer(for:) from Samples.app
2020-07-11 09:48:25 +09:00
Shin Yamamoto f9a5386dc7 Add SamplesObjC app
* Add build job in .travis.yml
2020-07-11 09:48:25 +09:00
Shin Yamamoto c962395581 Fix LayoutAdapter.setUpAnimationEdgeConstraint(to:) 2020-07-11 09:48:25 +09:00
Shin Yamamoto 015454238a Use LayoutAdapter.positon in itself 2020-07-11 09:48:25 +09:00
Shin Yamamoto a5341f37c6 Fix a surface container constraint on left position 2020-07-11 09:48:25 +09:00
Shin Yamamoto c519404752 Fix LayoutAdapter.position(for:) 2020-07-11 09:48:25 +09:00
Shin Yamamoto 4c9ebbbd61 Fix containerOverflow 2020-07-11 09:48:25 +09:00
Shin Yamamoto 0d958b2d04 Rename edgeY to edgePosition 2020-07-11 09:48:25 +09:00
Shin Yamamoto ab7b16e789 Fix the timing to call floatingPanelDidMove delegate 2020-07-11 09:48:25 +09:00
Shin Yamamoto c482da8ceb Rename 'stateAnchors' to 'anchors' 2020-07-11 09:48:25 +09:00
Shin Yamamoto b348684ed5 Rename 'anchorPosition' to 'position' 2020-07-11 09:48:25 +09:00
Shin Yamamoto 6d63ccd754 Fix the rounding corners of the grabber handle 2020-07-11 09:48:25 +09:00
Shin Yamamoto 579bcbd9dd Calculate the actual frame duration in numeric springing 2020-07-11 09:48:25 +09:00
Shin Yamamoto bcba7c8844 Improve delegate methods for the removal interaction 2020-07-11 09:48:25 +09:00
Shin Yamamoto 0adb374146 Prevent potential errors of unsatisfiable constraints 2020-07-11 09:48:25 +09:00
Shin Yamamoto 74d7cdfb42 Fix unsatisfiable constraints on fitToBounds mode 2020-07-11 09:48:25 +09:00
Shin Yamamoto 3de61d5ee2 Reset maskedCorners 2020-07-11 09:48:25 +09:00
Shin Yamamoto ba1b6170eb Add FloatingPanelController.untrack(scrollView:) for multiple scroll views' tracking 2020-07-11 09:48:25 +09:00
Shin Yamamoto 1f4daa04c2 Fix hidden state handling with 1 state anchor 2020-07-11 09:48:25 +09:00
Shin Yamamoto 5cebed07d8 Use multi string literal for long log prints 2020-07-11 09:48:25 +09:00
Shin Yamamoto 9b33687c10 Modify FloatingPanelPanGestureRecognizer name 2020-07-11 09:48:25 +09:00
Shin Yamamoto da4162b307 Fix failure requirements of the pan gesture in the grabber area 2020-07-11 09:48:25 +09:00
Shin Yamamoto 92ca4909ce Intercept delegate methods of the pan gesture recognizer
This replaces floatingPanel(_:shouldRecognizeSimultaneouslyWith)
delegate method.
2020-07-11 09:48:25 +09:00
Shin Yamamoto 65d03846a1 Clean up code 2020-07-11 09:48:25 +09:00
Shin Yamamoto fe86659db7 Refactor updateStaticConstraint() 2020-07-11 09:48:25 +09:00
Shin Yamamoto 153bd32cfe Rename fpc.scrollView to fpc.trackingScrollView 2020-07-11 09:48:25 +09:00
Shin Yamamoto 99fd41c58b Suppress the shadow fade-out animation when the surface jumps to a location. 2020-07-11 09:48:25 +09:00
Shin Yamamoto d217c3ad2b Rename heightConstraint to staticConstraint 2020-07-11 09:48:25 +09:00
Shin Yamamoto 0f678d20fe Support multiple shadows in the surface appearance 2020-07-11 09:48:25 +09:00
Shin Yamamoto 102c9b3019 Fix the height break after rotating a device twice in static mode 2020-07-11 09:48:25 +09:00
Shin Yamamoto fd4e6fea61 Add UISpringTimingParameters(decelerationRate:frequencyResponse:initialVelocity:) 2020-07-11 09:48:25 +09:00
Shin Yamamoto 54570ac7c6 Fix updating the container height when the content mode is changed
This change is necessary to fix a layout break when the contentMode is
changed after laying out a floating panel.
For example, the following patch lets the bug reproduce in Maps.app.

```
diff --git a/Examples/Maps/Maps/ViewController.swift b/Examples/Maps/Maps/ViewController.swift
index c5f25f1..b71c313 100644
--- a/Examples/Maps/Maps/ViewController.swift
+++ b/Examples/Maps/Maps/ViewController.swift
@@ -14,7 +14,6 @@ class ViewController: UIViewController, MKMapViewDelegate {
     override func viewDidLoad() {
         super.viewDidLoad()

-        fpc.contentMode = .fitToBounds
         fpc.delegate = fpcDelegate

         // Set a content view controller
@@ -33,6 +32,7 @@ class ViewController: UIViewController, MKMapViewDelegate {

     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
+        fpc.contentMode = .fitToBounds

         // Must be here
         searchVC.searchBar.delegate = self
```
2020-07-11 09:48:25 +09:00
Shin Yamamoto 38359e25ad v2.0
* Core
    * Support bottom/left/right positioned panel.
    * Replace FloatingPanelPosition with FloatingPanelState
    * Redefine FloatingPanelPosition
    * Fix pan interaction of the intrinsic layout
    * Allow to modify a target position
    * Fix floating point problem
    * Use UIScrollView.adjustedContentInset by default
    * Add shouldDecelerate(to:)
    * Rename contentOrigin to contentOffsetForPinning
    * Improve initialLocation

* Introduce numeric springing
    * Support moving a panel with numeric springing
    * Update FloatingPanelBehavior
    * Remove {top,Bottom}InteractionBuffer from FloatingPanelLayout
    * Remove the move animator to be same as UIScrollView behavior
        * Add floatingPanelDidMove(_:) tests in calling move(to:animated:)
    * Update FloatingPanelControllerDelegate
        * Move {add,remove}PanelAnimator delegation

* Add FloatingPanelBehaviorAdapter
    * Modify access to `behavior` prop

* Update FloatingPanelLayout
    * Support bottom/left/right positioned panel.
    * Add anchoredPosition property
    * Introduce FloatingLayoutAnchor & FloatingPanelIntrinsicLayoutAnchor
    * Add FloatingPanelReferenceEdge
    * Rename FloatingPanelDefaultLayout to FloatingPanelBottomLayout

* Update FloatingPanelLayoutAdapter
    * Add LayoutAdapter.sortedDirectionalPositions
    * Reimplement positionY(for:)
    * Add edge{most,least}state
    * Add orderedStates
    * Add edge{Most,Least} properties in LayoutAdapter
    * Modify `bottomOverflow` value and rename it to `containerOverflow`
    * Modify access to `layout` prop

* Add FloatingPanelLayoutAnchoring/LayoutAnchor/IntrinsicLayoutAnchor
    * Add FloatingPanel{Intrinsic}LayoutAnchor tests

* Update FloatingPanelController
    * Add surfaceLocation
    * update addPanelToParent API
    * Update layout reloading logic
    * Rename updateLayout() to invalidateLayout()
    * Fix contentViewController access level

* Update FloatingPanelControllerDelegate
    * Add floatingPanelDidEndDragging(_ vc:willDecelerate:)
    * Update floatingPanelWillBeginDecelerating delegate method
    * Remove behavior delegate method
    * Reimplement the removal action
        * Use floatingPanel(_:shouldRemoveAt:with:) delegate

* Update SurfaceView
    * Support bottom/left/right positioned panel.
    * Add FloatingPanelSurfaceAppearance
    * Add SurfaceView.intrinsicContentSize
    * Rename FloatingPanelSurfaceView.add(contentView:)
    * GrabberHandle for bottom edge grabber
    * Rename GrabberHandleView to FloatingPanelGrabberView
    * Rename containerMargin & contentInsets to content{Margins,Padding}
    * Rename bottomOverflow to containerOverflow

* ObjC Support
    * Add @objc to FloatingPanelControllerDelegate
    * Add @objc to FloatingPanelController
    * Use @objc protocol FloatingPanelBehavior
    * Redefine FloatingPanelState for objc.

* Update Utils
    * Modify logger prefix
    * Update extensions for left/right positions

* Update unit tests
    * Update layout tests
    * Add test_surfaceView_{contentView,containerView}
    * Add test_positionY_top2bottom()
    * Add test_moveTo_bottomEdge()
    * Update test_warningRetainCycle()
    * Fix FloatingPanelControllerTests
    * Fix LayoutTests
    * Fix FloatingPanelStateTests
    * Fix FloatingPanelTest
2020-07-11 09:48:25 +09:00
Shin Yamamoto 2cf63838ae Drop preserveContentVCLayoutIfNeeded() 2020-07-11 09:48:25 +09:00
Shin Yamamoto 49e8545aec Rename trackedScrollView to trackingSrollView 2020-07-11 09:48:25 +09:00
Shin Yamamoto b34ea2444d Disable dismissal action of the backdrop by default
fix #341
2020-07-11 09:48:25 +09:00
Shin Yamamoto 984d4c41d8 Replace 'precondition' to 'assert' 2020-07-11 09:48:25 +09:00
Shin Yamamoto 8d372452b4 Remove 'unavailable' annotations 2020-07-11 09:48:25 +09:00
Shin Yamamoto 2fc16bb1f4 Upgrade swift version to 5.0 2020-07-11 09:48:25 +09:00
Leko Murphy 8903e4e610 don't remove panel on view disappearance (#367) 2020-07-11 09:46:41 +09:00
Shin Yamamoto 5634de2eee Merge pull request #371 from knchst/modify/readme
Modify README.md sample code.
2020-07-11 09:42:16 +09:00
Kenichi Saito 1957ae3919 Modify README 2020-07-09 19:59:20 +09:00
Shin Yamamoto a4f8c0528c Merge pull request #356 from SCENEE/release-1.7.5
Release 1.7.5
2020-06-04 08:30:31 +09:00
Shin Yamamoto e4548b26bd Release 1.7.5 2020-06-03 22:33:02 +09:00
Shin Yamamoto d540b1ddde Fix the panel behavior in a sheet modal (#358)
* Add "Show Panel in Sheet Modal" sample
* Fix the behavior in a sheet modal
2020-06-03 22:31:30 +09:00
Shin Yamamoto aaeb752911 No need to recognize both of the pan gesture and dismiss gesuter of sheet modal 2020-05-30 10:11:12 +09:00
Grigory 966caad519 Fix the constraints break on fitToBounds mode (#359) 2020-05-30 09:44:58 +09:00
Shin Yamamoto a62c3a23dc Fix some view controllers in Samples.app for a sheet modal 2020-05-23 09:07:01 +09:00
Shin Yamamoto 7bbc3d5910 fix the behavior in a sheet modal 2020-05-23 08:12:01 +09:00
Shin Yamamoto e2afb1e22f Add "Show Panel in Sheet Modal" sample 2020-05-23 08:11:30 +09:00
Shin Yamamoto 8cca1178fd fix {top,bottom} constant's boundary in updating panel interactively (#352)
Because {top,bottom}Y can be {less,more} than the SafeArea/Superview bounds,
if a minus value is set to the inset for {top,bottom} most position.
2020-05-21 08:59:52 +09:00
Shin Yamamoto a09a0e9e32 fix the animation velocity's sign (#354)
Using a directional distance to calculate an animation velocity fixes an
issue where a panel's animation was wrong when a user swipes up a panel
at the top most position.
2020-05-16 09:18:59 +09:00
Shin Yamamoto 43c76faa20 fix invalid safearea insets in a table view with static cells (#353)
See also https://github.com/SCENEE/FloatingPanel/issues/330
2020-05-16 09:17:34 +09:00
Shin Yamamoto 5787a350ab fix the memory leak of FloatingPanelController object (#350) 2020-05-15 07:41:18 +09:00
Shin Yamamoto 9abb80de64 Fix an invalid indicator insets of the tracking scroll view (#346) 2020-04-29 14:22:34 +09:00
Shin Yamamoto 7d90458d99 Support the initial hidden position not including the supported positions (#345) 2020-04-29 14:20:49 +09:00
Shin Yamamoto 5d5f14acd8 fix a build error on Xcode 11.4 (#337) 2020-03-30 17:52:00 +09:00
Shin Yamamoto bed519f0c0 Merge pull request #326 from SCENEE/release-1.7.4
Release 1.7.4
2020-02-29 18:52:15 +09:00
Shin Yamamoto 7c47e2e20e Release 1.7.4 2020-02-29 13:25:11 +09:00
Federico Zanetello f909cd4101 ignore .swiftpm/ folder (#324) 2020-02-29 13:20:50 +09:00
Federico Zanetello 8c24aa3fc9 fix api typo (#325)
fixes floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning) name
2020-02-29 13:18:09 +09:00
Shin Yamamoto 7531d80f1c Merge pull request #323 from SCENEE/release-1.7.3
Release 1.7.3
2020-02-29 10:40:38 +09:00
Shin Yamamoto c4c1906cae Release 1.7.3 2020-02-28 21:00:44 +09:00
Shin Yamamoto 4f717c5840 update github issue template 2020-02-28 21:00:40 +09:00
Shin Yamamoto fbd83ef500 fix a run script error 2020-02-26 22:53:44 +09:00
Shin Yamamoto 2a91145366 Fix breaking content offset of the tracked scroll view (#315)
* Convert the tracked scroll view frame to the surface coordinate space
2020-02-26 22:49:51 +09:00
Shin Yamamoto f5d72aa0a5 Add failure requirements for multiple panels (#322)
* add multi panel sample
2020-02-26 22:47:52 +09:00
Shin Yamamoto 65f67c98f4 Add floatingPanel(_:contentOffsetForPinning:) delegate method (#314)
* add floatingPanel(_:contentOffsetForPinning:)
* add 'Show NavigationController' sample
* fix the initial content offset in a navigation bar with  a large text
    The content offset preservation should be applied only when
    `FloatingPanelController.contentInsetAdjustmentBehavior` is `.always`.
    This is because the library user loses control of the initial offset.
2020-02-24 11:16:10 +09:00
Shin Yamamoto 1f79c2573f Merge pull request #316 from jacksonjude/master
Minor README.md typo
2020-02-15 10:32:03 +09:00
jacksonjude bc840dde46 Minor README.md typo 2020-02-08 22:36:50 -08:00
Shin Yamamoto 57ed039857 Merge pull request #312 from SCENEE/release-1.7.2
Release 1.7.2
2020-01-30 09:29:00 +09:00
Shin Yamamoto 11f0e8c84e Release 1.7.2 2020-01-29 11:33:11 +09:00
Shin Yamamoto dd19c866d4 Merge pull request #311 from SCENEE/return-childvc-to-consult
Return the child view controller to consult
2020-01-29 11:31:01 +09:00
Shin Yamamoto 801fed9843 Return the child view controller to consult 2020-01-29 08:54:53 +09:00
Shin Yamamoto 847b5c0917 Merge pull request #310 from SCENEE/fix-didenddecelerating-call
Fix delegate calls
2020-01-28 22:39:37 +09:00
Shin Yamamoto c64056ca7b Make floatingPanelDidEndDragging's velocity zero when it won't animate
Ideally, it's better to define a delegate method like
scrollViewDidEndDragging(_:willDecelerate:) in FloatingPanelControllerDelegate
to notify whether a panel will be decelerated or not.
However it's a broken change so I add this change as workaround.
The delegate method definition will be improved on v2.0.
2020-01-28 11:40:31 +09:00
Shin Yamamoto 269c3e29b5 Call floatingPanelDidEndDecelerating even if an animation interrupted 2020-01-27 16:50:16 +09:00
Shin Yamamoto 002bbb4a4a Merge pull request #307 from SCENEE/iss-293
Fix a panel's move-up while dragging it down
2020-01-21 20:40:48 +09:00
Shin Yamamoto 14011a5bc2 Fix grabber area behavior
The grabber area was not working expectedly.
2020-01-18 17:27:58 +09:00
Shin Yamamoto 23f2242c9a Fix a panel's move-up in dragging it down
This issue is that a panel moved up while dragging it
down if content offset of the tracking scroll view in
a content view controller was greater than its top interaction buffer.

Ref. #293
2020-01-18 17:26:38 +09:00
Shin Yamamoto 4fd92a4002 Fix Maps.app's crash on device after the second launch (#306)
This seems to be Xcode 11's bug of linking frameworks.
2020-01-18 15:05:46 +09:00
Ramesh R C 9c57089b0e Add FloatingPanelController.nearbyPosition (#303)
* Added nearbyPosition : always a position of a user's finger.
* debugging nearby position in Maps.app.
* Added test cases move with nearby position.
2020-01-09 13:26:30 +09:00
Shin Yamamoto 3b11cdc72a Merge pull request #291 from SCENEE/release-1.7.1
Release 1.7.1
2019-11-27 21:59:57 +09:00
Shin Yamamoto 4edaad2cf4 Release 1.7.1 2019-11-21 21:17:11 +09:00
Shin Yamamoto 92fc0621e2 Merge pull request #292 from TadeasKriz/patch-1
Improve manual `show` and `hide` example.
2019-11-21 21:16:32 +09:00
Tadeas Kriz e9f4392c48 Improve manual show and hide example. 2019-11-20 15:56:17 +01:00
Shin Yamamoto 4df40becaf Merge pull request #290 from SCENEE/fix/addpanel
Pass parent to didMove(toParent:)
2019-11-20 10:36:22 +09:00
Shin Yamamoto ba11e7c7d7 Pass parent to didMove(toParent:)
4ad7f11 commit causes the wrong parameter.
2019-11-20 09:51:47 +09:00
Shin Yamamoto ae671f22c6 Merge pull request #288 from SCENEE/fix-swiftinterface-error
Rename the internal FloatingPanel object for .swiftinterface issue
2019-11-19 23:01:36 +09:00
Shin Yamamoto f22f58212b Clean up lines with only white spaces 2019-11-19 18:37:52 +09:00
Shin Yamamoto 54ff1c360d Rename 'FloatingPanel' type for '.swiftinterface' issue
See also https://forums.swift.org/t/frameworkname-is-not-a-member-type-of-frameworkname-errors-inside-swiftinterface/28962
2019-11-19 18:37:44 +09:00
dmytrofrolov1 772d6c3ef3 Improve the surface position evaluation and top scroll bouncing of content
* Evaluate the surface position approximately by 1px with a display scale
* Allow a top scroll bouncing of content without closing floating panel when a user scrolls it a lot
2019-11-19 18:29:20 +09:00
Shin Yamamoto a94c3b3c26 Merge pull request #287 from SCENEE/fix-module-stability
Enable the swift module interfaces
2019-11-15 22:07:01 +09:00
Shin Yamamoto d0ffc4ceb1 Enable the swift module interfaces 2019-11-15 14:32:33 +09:00
Shin Yamamoto 597ce487aa Merge pull request #275 from peka2/patch-1
Update README
2019-10-09 00:32:57 +09:00
peka2 87eb8d94fd Update README 2019-10-08 18:58:01 +09:00
Shin Yamamoto 4944fc516a Update README 2019-10-05 14:09:41 +09:00
Shin Yamamoto 7537384339 Merge pull request #273 from SCENEE/release-1.7.0
Release 1.7.0
2019-10-05 14:07:40 +09:00
Shin Yamamoto 8fd134512f Release v1.7.0 2019-09-28 23:02:45 +09:00
Shin Yamamoto f566fc6475 Add a sample of panel including a PageVC content 2019-09-28 23:02:45 +09:00
Shin Yamamoto 9cbcb48a9b Merge pull request #266 from SCENEE/add-containerinsets
Add FloatingPanelSurfaceView.containerMargins
2019-09-28 22:47:21 +09:00
Shin Yamamoto 817956cef3 Update README 2019-09-28 21:49:47 +09:00
Shin Yamamoto 3c1aa7aa42 Fix FloatingPanelFullScreenLayout is not working 2019-09-28 13:18:58 +09:00
Shin Yamamoto 7598e8f160 ci: Update xcode versions 2019-09-28 13:18:58 +09:00
Shin Yamamoto ba011e7242 Add 'Show with ContainerMargins' sample 2019-09-28 13:18:44 +09:00
Shin Yamamoto ecdf20db8f Add FloatingPanelSurfaceView.containerMargins
`FloatingPanelSurfaceView.containerTopInset` is replaced by the top
inset.
2019-09-28 13:18:44 +09:00
Shin Yamamoto 3a7f39321c Update allowsRubberBanding() sample 2019-09-28 13:18:44 +09:00
Shin Yamamoto e75d83e7a4 Merge pull request #270 from SCENEE/release-1.6.6
Release 1.6.6
2019-09-28 13:17:37 +09:00
Shin Yamamoto 0461c49d23 Release v1.6.6 2019-09-27 23:47:36 +09:00
Shin Yamamoto c4f7fa5332 Remove target-action for an untracked scroll view 2019-09-27 23:47:36 +09:00
Shin Yamamoto 3a9f304735 Suppress UITableViewAlertForLayoutOutsideViewHierarchy alert
Following the alert suggestion
> [TableView] Warning once only: UITableView was told to layout its visible
> cells and other contents without being in the view hierarchy (the table
> view or one of its superviews has not been added to a window).
2019-09-27 23:47:36 +09:00
Shin Yamamoto 1d5cb1744f CI: Add Xcode 11 & Swift 5.1 build
* Add a build on Xcode 11 image with SUPPORTS_MACCATALYST=NO because Xcode 11 runs on macOS 10.14 in Travis CI
* Add Swift 5.1 badge
2019-09-27 23:47:36 +09:00
Shin Yamamoto b61b0b5451 Update framework links of Example apps
Try to resolve an issue to need a clean-build of framework as following
this site,
https://developer.apple.com/library/archive/technotes/tn2435/_index.html
2019-09-27 23:47:36 +09:00
Shin Yamamoto e3a4631e44 Remove unused delegate conformances 2019-09-27 23:47:36 +09:00
Shin Yamamoto d7f798e9a0 Fix layout of the root table view in Samples 2019-09-27 23:47:36 +09:00
Shin Yamamoto ae2c83e32b Modify modal style of Samples.app for iOS 13 2019-09-27 23:47:36 +09:00
Shin Yamamoto 059b2ed4f0 Fix File 'Utils.swift' target 2019-09-27 23:47:36 +09:00
Shin Yamamoto 338658cd9f Clean up some tests
* test_surfaceView_constraintsUpdate()
* test_moveTo()
2019-09-25 22:16:45 +09:00
Shin Yamamoto 2cdb4a6bc2 Suppress UITableViewAlertForLayoutOutsideViewHierarchy alert
Following the alert suggestion
> [TableView] Warning once only: UITableView was told to layout its visible
> cells and other contents without being in the view hierarchy (the table
> view or one of its superviews has not been added to a window).
2019-09-16 21:58:28 +09:00
Shin Yamamoto 1a4d5a7954 CI: Add Xcode 11 & Swift 5.1 build
* Add a build on Xcode 11 image with SUPPORTS_MACCATALYST=NO because Xcode 11 runs on macOS 10.14 in Travis CI
* Add Swift 5.1 badge
2019-09-16 21:56:28 +09:00
Shin Yamamoto 22ef3e7cd9 Merge pull request #253 from SCENEE/release-1.6.5
Release 1.6.5
2019-08-31 13:48:32 +09:00
Shin Yamamoto 5325e707e6 Release v1.6.5 2019-08-31 12:51:01 +09:00
Nikolay Derkach 1dc0a6b76a Support bottom content inset for container view (#257)
and also fix height of a content view resized by the inset
Fix #256
2019-08-31 12:49:59 +09:00
David Hart 2689d68bab Improve floatingPanelDidChangePosition and tigger it on removal 2019-08-31 12:49:57 +09:00
Nikolay Derkach f8b8176988 Support bottom content inset for container view (#257)
and also fix height of a content view resized by the inset
Fix #256
2019-08-31 12:43:53 +09:00
Shin Yamamoto 0c5bf2bfe9 Merge pull request #252 from hartbit/updating-layout
Improve the documentation of floatingPanelDidChangePosition
2019-08-24 17:02:33 +09:00
Shin Yamamoto 218a12962f Don't unregister safeAreaInsetsObservation in hide()
Because the observation can't be active when a user makes a panel visible
by move(to:) instead of show().
2019-08-24 15:37:15 +09:00
Shin Yamamoto 916d2ec76a Add move-to-hidden tests 2019-08-24 15:37:15 +09:00
Shin Yamamoto 1b1ba5deef Update README for UISearchController issue 2019-08-24 15:37:15 +09:00
Shin Yamamoto 58b2df4996 Fix UISearchBar's _searchField access 2019-08-24 09:54:54 +09:00
David Hart 8b45517915 Improve floatingPanelDidChangePosition and tigger it on removal 2019-08-22 14:09:02 +02:00
Shin Yamamoto 3cca07fefd Merge pull request #251 from rikusouda/fix/call_did_end_remove_by_backdrop_view
Call floatingPanelDidEndRemove when dismiss with tap on backdrop view
2019-08-21 15:21:55 +09:00
Yuki Yoshioka 276ae23f13 Call floatingPanelDidEndRemove when dismiss with tap on backdrop view
By #205 added dismissing by backdrop view. But `floatingPanelDidEndRemove` is not called from it.
2019-08-19 14:34:11 +09:00
Shin Yamamoto c1b2ffeb78 Merge pull request #212 from joshuafinch/feature/cocoapods-1.7
Add support for specifying swift_versions in CocoaPods 1.7 and above
2019-08-15 13:47:44 +09:00
Shin Yamamoto 3b812be84e Return true for FloatingPanelSurfaceView.requiresConstraintBasedLayout 2019-08-13 15:27:27 +09:00
Shin Yamamoto 262ee34201 Merge pull request #247 from SCENEE/release-1.6.4
Release 1.6.4
2019-08-09 23:52:04 +09:00
Shin Yamamoto 5d86bd5d02 Release v1.6.4 2019-08-09 09:55:04 +09:00
Shin Yamamoto 3b6271c4f4 Fix stopping a panel b/w anchors after an interruption
The panel(surface view) could stop b/w anchors if the pan gesture doesn't
pass through `.changed` state after an interruptible animator is interrupted.

The possible reason is the constraints have never changed since the last animation
is committed so that `surfaceView.superview!.layoutIfNeeded()` doesn't trigger
a layout update by the constraint-based layout system in
`FloatingPanelLayoutAdapter.activateLayout(of:)`.

Thus the inserted code changes a panel interactive constraint by the least
positive number. It allows the constraint-based layout system to update the
surface layout expectedly.
2019-08-08 10:52:51 +09:00
Shin Yamamoto 1671a3d50f Always call startInteraction before endInteraction 2019-08-07 22:27:13 +09:00
Shin Yamamoto 0ab318e804 Fix not calling floatingPanelDidEndDecelerating delegate after interruption
If the decelerating animation is interrupted and
floatingPanelShouldBeginDragging delegate method returns false,
floatingPanelDidEndDecelerating delegate method will not be called
after calling floatingPanelWillBeginDecelerating method.

A panel have to run an animation after the interruption and also
floatingPanelDidEndDecelerating(_:) delegate should be called always
after calling floatingPanelWillBeginDecelerating method.

Therefore floatingPanelShouldBeginDragging delegate method shouldn't be
called in the panel decelerating.
2019-08-07 22:07:35 +09:00
Shin Yamamoto 53719bd94a Feat elastic layout (#145)
* Move the prepareLayout(in:) call
* Support 'fitToBounds' content mode
* Add NSLayoutConstraint.{de}activate(constraint:)
* Update README
2019-08-03 14:45:35 +09:00
Nikolay Derkach 935b7d9e10 Allow to disable tap on backdrop view for panel dismissal (#205)
* Add 'FloatingPanelBackdropView. dismissalTapGestureRecognizer'
* Enable tap on backdropview gesture recognizer only for the modal presentation
2019-08-03 12:37:39 +09:00
Shin Yamamoto e3bf19b972 Merge pull request #224 from SCENEE/feat-position-reference
Add FloatingPanelLayout.positionReference
2019-07-27 15:22:18 +09:00
Shin Yamamoto c36f09d3e9 Update README 2019-07-27 13:47:13 +09:00
Shin Yamamoto 9936a89118 Add FloatingPanelLayoutTests.test_positionReference() 2019-07-27 11:44:33 +09:00
Shin Yamamoto 562424cd8f Add FloatingPanelLayout.positionReference 2019-07-27 11:44:33 +09:00
Shin Yamamoto 0c3fb83d0a Merge pull request #241 from SCENEE/release-1.6.3
Release 1.6.3
2019-07-26 23:00:45 +09:00
Shin Yamamoto 7df352a44b Release v1.6.3 2019-07-26 19:03:53 +09:00
Shin Yamamoto 1443d377ad ci: reorder build stages
CocoaPods stage can be failed at `pod spec lint` job if the git tag hasn't
existing yet. We can restart the job after the tag is pushed so it's
best to run it as the last job to ensure other builds are passed.

It's helpful on the release workflow.
2019-07-26 16:23:37 +09:00
Shin Yamamoto e0bca25411 Fix scroll lock just before/after dragging down in the grabber area 2019-07-26 15:57:45 +09:00
Shin Yamamoto e94d47b1a5 Fix SafeArea insets update
According to 5c0ed4c commit, `floatingPanel.isDecelerating` is needed
only on iOS 10. The flag causes a problem of the safe area update with
the parent view controller containing a large title navigation bar.

And then the large title navigation bar has been introduced since iOS 11.

So the guard condition should be working only for iOS 10.
2019-07-25 14:15:02 +09:00
Shin Yamamoto 9d3a1674c4 Fix scroll unlock 2019-07-22 10:56:17 +09:00
Shin Yamamoto 24d81a4153 Merge pull request #239 from SCENEE/fix-regressions
Fix regressions
2019-07-22 09:41:51 +09:00
Shin Yamamoto 5723a8017b Stop the edge bouncing when a tracking scroll is decelerating 2019-07-20 16:47:37 +09:00
Shin Yamamoto 72055cd998 Add an Attention comment 2019-07-20 10:51:43 +09:00
Shin Yamamoto 9cd8b4d960 Fix preserveContentVCLayoutIfNeeded() 2019-07-20 00:07:13 +09:00
Shin Yamamoto f39b368c1e Fix bottomMaxY 2019-07-19 23:39:55 +09:00
Shin Yamamoto a4543351fe Modify the guard of an animation interruption
The previous condition disturb the quick redirect action of a panel
around the top most position. However removing the condition causes a
buggy behavior by an interruption over the top buffer.

After the consideration, I decided to allow an interruption under the
top interaction buffer.
2019-07-19 23:39:40 +09:00
Shin Yamamoto 88ac013166 Fix scroll unlock again
`2ef096b` commit isn't correct. `self.animator` must be set to nil on
the animation interruption, or the interruption occurs repeatedly. To
unlock the scroll correctly, an unlock operation needs to be added in
the scroll pan gesture's callback.
2019-07-19 23:29:43 +09:00
Shin Yamamoto 5d336b9090 Merge pull request #238 from SCENEE/revert-interruptible-animator
* Revert isInterruptible property of the default animator
* Fix a scroll unlock on an animation interruption
* Remove the velocity vector limit
* Fix the bottom buffer of a removable panel
2019-07-19 19:38:05 +09:00
Shin Yamamoto 45b3209b9b Escape pod warnings
This warning blocks CI build,
> url: The URL (https://twitter.com/scenee) is not reachable.
2019-07-19 18:51:34 +09:00
Shin Yamamoto da16cf6ada Fix the bottom buffer of a removable panel 2019-07-19 18:51:34 +09:00
Shin Yamamoto 774a841fb5 Fix Tab3 sample 2019-07-19 18:51:34 +09:00
Shin Yamamoto 020ffdaa84 Remove the velocity vector limit 2019-07-19 18:51:33 +09:00
Shin Yamamoto 2ef096b3a0 Fix a scroll unlock on an animation interruption
Fix 2 cases on an animation interruption

1. A user interrupts a panel animation below the top Y.
    - On the case, a scroll indicator must not appear.
2. A user interrupts a pane animation and soon swipes it up to scroll a
content in a tracking scroll view.
    - On the case, a scroll indicator must appear.

NOTE: A UIViewPropertyAnimator which isn't interruptible doesn't stop
the animation even if `self.animator` is set to nil. As a result, the
completion block is called after an interruption and a panel is moving
a bit like going against a user's dragging.

According to the behavior, the scroll unlock wasn't be able to work
expectedly if `self.animator` was set to nil on the interruption and then
I allow a delay until a scroll view is unlocked on the animation completion.
2019-07-19 18:51:33 +09:00
Shin Yamamoto 69bde3e80d Revert isInterruptible property of the default animator
Because it causes an unexpected propagation of the spring animation to
the content view. The propagation is reproduced on `fitToVisible` mode.
2019-07-19 13:25:54 +09:00
Shin Yamamoto e6aa7db35a Merge pull request #234 from SCENEE/fix-hidden-position
Fix hidden position and animation interruption
2019-07-15 12:59:55 +09:00
Shin Yamamoto 0124d98111 Remove a unecessary file ref 2019-07-15 11:12:14 +09:00
Shin Yamamoto c00a3836a5 Add tests for LayoutSegment 2019-07-15 11:12:14 +09:00
Shin Yamamoto 66f9118e78 Revise comments 2019-07-15 11:12:14 +09:00
Shin Yamamoto f261b90a73 Fix the removal interaction trigger 2019-07-13 14:15:37 +09:00
Shin Yamamoto a1602e0221 Fix fit-to-bounds behaviour
FloatingPanel.{fitToBounds,settle}(scrollView:) don't work because the
surface frame isn't updated expectedly by AutoLayout. Instead of that,
I update FloatingPanelLayoutAdapter.startInteraction(at:) to fit a
surface frame to a scroll offset content.
2019-07-12 11:07:46 +09:00
Shin Yamamoto b4e9ce8478 Fix scroll unlocking at the top position in dragging 2019-07-12 00:18:50 +09:00
Shin Yamamoto 35d7cbb1d3 Fix the animation interruption and scroll locking 2019-07-11 12:58:12 +09:00
Shin Yamamoto 6ab678bb18 Add SwiftPM section in README 2019-07-10 19:45:32 +09:00
Shin Yamamoto 14ec9cf0a1 Merge pull request #236 from SCENEE/release-1.6.2
Release v1.6.2
2019-07-10 19:44:20 +09:00
Shin Yamamoto a225bf2cf1 Release v1.6.2 2019-07-10 18:55:09 +09:00
Shin Yamamoto 9b904cd895 Merge branch 'master' into fix-hidden-position 2019-07-09 22:03:18 +09:00
Shin Yamamoto 11a16092a7 Merge pull request #231 from SCENEE/prevent-found-nil-error
Prevent 'unexpectedly found nil' fatal error
2019-07-09 21:53:40 +09:00
Shin Yamamoto b9b7f940b9 Prevent 'unexpectedly found nil' fatal error
Use FloatingPanel.viewcontroller as an optional value instead of
an implicitly unwrapping optional one.
2019-07-09 20:06:16 +09:00
Shin Yamamoto e542728ff6 Fix build break on Swift 4.1 2019-07-09 20:00:54 +09:00
Shin Yamamoto 1eeb6e2d73 Refactor FloatingPanelLayoutAdapter.{top,bottom}Y 2019-07-09 19:30:48 +09:00
Shin Yamamoto cf9d53aca2 Add test_updateInteractiveTopConstraint() 2019-07-09 19:30:48 +09:00
Shin Yamamoto 83463c792c Remove FloatingPanelLayoutAdapter.middleY 2019-07-09 19:30:31 +09:00
Shin Yamamoto d5c7571a97 Remove FloatingPanel.getPosition(at:with:directional:) 2019-07-09 19:27:18 +09:00
Shin Yamamoto 75c27bc232 Add test_getBackdropAlpha() 2019-07-06 16:16:22 +09:00
Shin Yamamoto cbcc35268d Add FloatingPanelPositionTests 2019-07-06 16:16:20 +09:00
Shin Yamamoto 11ba247ac4 Fix .hidden position's support
* Refactor FloatingPanel.targetPosition()
* Add test_targetPosition tests
* Fix bottomY
* Call shouldProjectMomentum(_:for:) only when a projection occurs on next
or pre segment. It means the delegate method not called for redirection.
* Improve all projection
2019-07-06 16:15:32 +09:00
Shin Yamamoto f411e81949 Add FloatingPanelControllerTests.test_moveTo() 2019-07-06 16:15:32 +09:00
Shin Yamamoto 45d7cb7218 Add FloatingPanelController.swhoForTest() 2019-07-06 16:15:32 +09:00
Shin Yamamoto 81f42d3951 Add LayoutSegment 2019-07-06 16:15:32 +09:00
Shin Yamamoto 2f7aed3e34 Add FloatingPanelPosition.{next,pre}(in:) 2019-07-06 16:15:32 +09:00
Shin Yamamoto 01f8261f0b Add an assertion to check an invalid move
- Add FloatingPanelLayoutAdapter.isValid(_:)
2019-07-06 16:15:32 +09:00
Shin Yamamoto 489d7696cc Add test_originSurfaceY 2019-07-06 16:15:32 +09:00
Shin Yamamoto 0661f08a07 Fix FloatingPanelLayoutTests 2019-07-03 14:25:57 +09:00
Shin Yamamoto 206475e6ab Merge pull request #232 from SCENEE/refactor-layout-adapter
Refactor layout adapter
2019-07-03 14:24:47 +09:00
Shin Yamamoto a4a68e5b39 Add test_surfaceView_constraintsUpdate() 2019-07-03 11:46:45 +09:00
Shin Yamamoto de7ab0e0cb Rename FloatingPanelViewTests to FloatingPanelSurfaceViewTests 2019-07-03 11:46:45 +09:00
Shin Yamamoto 5f7b5ce81c Add FloatingPanelLayoutTests & Utils 2019-07-03 11:46:45 +09:00
Shin Yamamoto 36d7ea5100 Improve testing speed 2019-07-03 11:34:21 +09:00
Shin Yamamoto 33f8cf3802 Modify FloatingPanel.distance(to:) 2019-07-03 11:34:21 +09:00
Shin Yamamoto f6da876fdf Add botomMostState prop 2019-07-03 11:34:21 +09:00
Shin Yamamoto 96c5dc7b74 Add FloatingPanelLayoutTests 2019-07-03 11:34:02 +09:00
Shin Yamamoto a37931b62d Merge pull request #230 from SCENEE/fix-scrollindicator
Fix the scroll indicator lock on a contentVC reset
2019-07-03 09:55:32 +09:00
Shin Yamamoto 5c848d9bf5 Fix the scroll indicator lock on a contentVC reset
The locking logic couldn't take care of the case where a content view
controller of a FloatingPanelController object is replaced.
2019-07-02 19:12:58 +09:00
Shin Yamamoto 265b805fa9 No more need FloatingPanel to conform UIScrollViewDelegate 2019-07-02 14:21:10 +09:00
Shin Yamamoto c4dfe33a5e Merge pull request #229 from SCENEE/release-1.6.1
Release v1.6.1
2019-06-29 09:31:17 +09:00
Shin Yamamoto 999eeb47ba Release v1.6.1 2019-06-29 08:33:24 +09:00
Shin Yamamoto a5bf02cfec Merge pull request #228 from SCENEE/fix-unexpected-layout-update
Fix an unexpected layout update on iOS13
2019-06-29 08:32:48 +09:00
Shin Yamamoto c10186e50a Prevent an unexpected layout update on iOS13
On iOS13, UITraitCollection.userInterfaceStyle can be changed
from .light to .dark when an app transitions to the background.
2019-06-29 07:41:52 +09:00
Shin Yamamoto 7a1cbf99d4 Rename setUpLayout to activateLayout 2019-06-28 20:23:10 +09:00
Shin Yamamoto c9c4000536 Merge pull request #225 from SCENEE/fix-seamless-scrolling
Remove workaround for tableView(_:didSelectRowAt:) issue
2019-06-19 10:34:57 +09:00
Shin Yamamoto 656bbc1b1c Remove workaround for tableView(_:didSelectRowAt:) issue
The workaround was added to avoid `tableView(_:didSelectRowAt:)` not
being called on first tap after the moving animation. However, it
doesn't only resolved the issue, but also has side effects.

For example, it affects the seamless scrolling in dragging up a panel from
half to full after bouncing it in the bottom buffer. The problem occurs
on "Tab2" sample of "Show Tab Bar".

Moreover the UITableView issue seems to be relieved on iOS 13.

Therefore I remove the workaround.
2019-06-19 09:39:56 +09:00
Shin Yamamoto 3815a08af5 Merge pull request #221 from SCENEE/fix-closing-panel-in-bounce
Fix closing panel during internal scroll view bounce
2019-06-17 08:04:56 +09:00
Shin Yamamoto 404fdb6496 Fix flushing a scroll indicator
1. A scroll indicator flushed at the first time when a tacking scroll view's
offset is zero and a user swipes down a panel at the top most position
2. A scroll indicator flushed at the first time when a tacking scroll view's
offset is zero and a user swipes up a panel at non top most position
2019-06-16 21:33:37 +09:00
Shin Yamamoto 573f355c15 Remove unnecessary code
There is not reason why the code is needed because the scroll tracking
logic is working well without it.
2019-06-16 21:32:35 +09:00
Shin Yamamoto bd0c891795 Fix closing panel during internal scroll view bounce
Now the scroll tracking is working well without the scroll offset handling
at the top most position in the callback of a scroll pan gesture.
2019-06-14 14:00:55 +09:00
Robbie Trencheny f4857a3da9 Add Swift Package Manager support (#219)
* Add Package.swift
2019-06-13 07:59:12 +09:00
Shin Yamamoto e074c3caf1 Merge pull request #220 from SCENEE/fix-removal-crash
Fix the crash while closeing via dragging
2019-06-12 08:56:31 +09:00
Shin Yamamoto 0f4c7503b1 Fix the crash while closeing via dragging
While closing the viewcontroller via dragging, calling floatPanelController's hide() will cause a crash.
2019-06-11 08:26:16 +09:00
Shin Yamamoto 2cb142a31f Merge pull request #213 from SCENEE/release-1.6.0
Release v1.6.0
2019-06-03 22:12:36 +09:00
Shin Yamamoto 2b05ea8d92 Release v1.6.0 2019-06-03 20:56:04 +09:00
Shin Yamamoto d255e1ea4a Call `super.updateConstraints()' as the final step 2019-06-03 20:51:58 +09:00
Joshua Finch 23846dbf23 Add support for CocoaPods version 1.7 swift_version 2019-06-01 22:56:21 +01:00
Shin Yamamoto 6fcb817fb8 Add the rubberbanding behavior for top & bottom buffer (#144)
* Add sample code
* Fix updateInteractiveTopConstraint()
    * {min,max}Y variables are confusing because it's not a value of coordinate Y, 
       but a constant value from the `interactiveTopConstraint`.
2019-06-01 16:19:09 +09:00
Shin Yamamoto e2ebfd01df Merge pull request #211 from SCENEE/avoid-weird-crash
* Use wholemodule compilation mode on Debug
* Set APPLICATION_EXTENSION_API_ONLY to YES by default
2019-06-01 13:55:00 +09:00
Sven Tiigi cf70929204 Added ContentInset Property on SurfaceView API (#200)
* Added Show ContentInset to Example application
2019-06-01 13:46:18 +09:00
Shin Yamamoto 624e3f7553 Set APPLICATION_EXTENSION_API_ONLY to YES by default 2019-05-31 13:34:26 +09:00
Shin Yamamoto 3cc8538db3 Use wholemodule compilation mode on Debug 2019-05-31 13:34:07 +09:00
Shin Yamamoto a9a65436bb Merge pull request #209 from SCENEE/improve-tests
Add unit tests
2019-05-27 22:21:37 +09:00
Shin Yamamoto 353dabfc47 Update Maps example for iOS 10 shadow 2019-05-25 16:07:22 +09:00
Shin Yamamoto 1bdf0f5b78 Remove unnecessary frame update 2019-05-25 16:07:22 +09:00
Shin Yamamoto 6696d7f71d Fix UIVisualEffectView on iOS10
This regression has happened since v1.2.0
2019-05-25 16:07:22 +09:00
Shin Yamamoto 59a6c7e576 Fix errors on simulator testing
Fix the following errors.
- 'dyld: program was built for a platform that is not supported by this runtime'
- 'dyld: Library not loaded: @rpath/libswiftCore.dylib'
2019-05-25 16:07:22 +09:00
Shin Yamamoto 0b0148635e Fix .travis.yml 2019-05-25 16:07:22 +09:00
Shin Yamamoto c354d8ea92 Modify FloatingSurfaceView.cornerRadius 2019-05-25 16:07:22 +09:00
Shin Yamamoto 9562cdaccb Clean up surface props 2019-05-25 16:07:22 +09:00
Shin Yamamoto bcfff8a33a Add FloatingPanelViewTests 2019-05-25 16:07:21 +09:00
Shin Yamamoto f5c409ba90 Fix test failed on iOS 10.3.1
This resolves the following error.

> xctest (86533) encountered an error (Failed to load the test bundle. (Underlying error: The bundle “FloatingPanelTests” couldn’t be loaded because it is damaged or missing necessary resources. The bundle is damaged or missing necessary resources. dlopen_preflight(..omitted../Build/Products/Test-iphonesimulator/FloatingPanelTests.xctest/FloatingPanelTests): no suitable image found.  Did find:
>	    ..omitted../Build/Products/Test-iphonesimulator/FloatingPanelTests.xctest/FloatingPanelTests: mach-o, but not built for iOS simulator))
2019-05-25 16:06:56 +09:00
Shin Yamamoto 2f23520330 Improve ViewTests.test_WarningRetainCycle() 2019-05-25 16:06:56 +09:00
Shin Yamamoto a95694cbfc Add testing jobs in Travic CI 2019-05-25 16:06:56 +09:00
Shin Yamamoto 6cfba6495f Add TestingApp 2019-05-25 16:06:52 +09:00
Shin Yamamoto b9f3de1c64 Merge pull request #207 from SCENEE/improve-surface-w-lazy-props
Improve surface w lazy props
2019-05-21 22:56:02 +09:00
Shin Yamamoto c67b56e7af Clean up code 2019-05-17 22:54:47 +09:00
Shin Yamamoto bf39f07691 Use lazy properties in the surface view 2019-05-16 10:43:41 +09:00
Shin Yamamoto a9e46f0de6 Merge pull request #204 from SCENEE/fix-scroll-lock-2
Avoid calling {lock,unlock}ScrollView() unexpectedly
2019-05-16 10:41:04 +09:00
Shin Yamamoto 05478fa8fa Avoid calling {lock,unlock}ScrollView() unexpectedly 2019-05-11 15:16:58 +09:00
Shin Yamamoto d123afc3f7 Fix a crash 2019-05-11 13:40:58 +09:00
Shin Yamamoto b1b3c15300 Merge pull request #201 from SCENEE/fix-scroll-lock
Fix scroll lock
2019-05-11 12:51:53 +09:00
Shin Yamamoto 49bae50739 Merge pull request #203 from SCENEE/patch-pr194
Improve surface container API
2019-05-11 12:49:48 +09:00
Shin Yamamoto 9b5459af8e Fix the content height changed by a container inset 2019-05-11 12:11:04 +09:00
Shin Yamamoto 96d2ea57f5 Clean up prop naming 2019-05-11 11:53:05 +09:00
Shin Yamamoto b78c5f4ece Decouple between grabberTopPadding and containerTopInset 2019-05-11 11:02:06 +09:00
Shin Yamamoto 341522ccaa Fix grabberhandle corner 2019-05-11 11:02:06 +09:00
Shin Yamamoto 833628e42f Update the content view height by containerTopInset 2019-05-11 11:02:06 +09:00
Shin Yamamoto 50c1c6fdc9 Fix the dependency on ordering prop updates 2019-05-11 11:02:06 +09:00
Shin Yamamoto 213386e822 Fix the sign of containerTopInset 2019-05-11 11:02:06 +09:00
Shin Yamamoto 17317ed274 Merge pull request #194 from nderkach/master
Grabber handle and top offset customizations
2019-05-11 10:58:25 +09:00
Shin Yamamoto 652ae8c967 Remove a condition to prevent animation cancel
Because I can't confirm any effect to fix that selecting a table view cell
sometimes isn't working after flicking to half from full.
2019-05-04 16:15:10 +09:00
Shin Yamamoto ec0e8cbdaf Avoid any tap gesture recognition while dragging a panel 2019-05-04 16:03:19 +09:00
Shin Yamamoto c15d4c9035 Fix the moving animation's interruption 2019-05-04 16:03:19 +09:00
Shin Yamamoto 39dfdd0ef0 Remove an unused code 2019-05-04 16:03:19 +09:00
Shin Yamamoto d25bc58249 Add a sample for tap-to-move 2019-05-04 16:03:18 +09:00
Shin Yamamoto 194a197e83 Fix a scroll lock after moving a panel 2019-05-04 14:46:39 +09:00
Shin Yamamoto bd02f34bcf Merge pull request #196 from SCENEE/release-1.5.1
Release v1.5.1
2019-04-26 13:43:54 +09:00
Shin Yamamoto 7d5f03bb6e Release v1.5.1 2019-04-25 16:18:59 +09:00
Nikolay Derkach 60f41e168f configure grabber handle and top container offset 2019-04-24 14:07:46 +02:00
Shin Yamamoto 680b16aa25 Merge pull request #185 from SCENEE/fix-tap-abort
Fix a touch abort on the interaction interrupted
2019-04-24 14:54:20 +09:00
Shin Yamamoto 2394c03dca Polish a doc comment 2019-04-20 16:00:22 +09:00
Shin Yamamoto c8f211f2bf fix typo 2019-04-19 08:56:54 +09:00
Shin Yamamoto 9076ba8933 Prevent an UIScrollViewDelayedTouchesBeganGestureRecognizer failed
It causes tableView(_:didSelectRowAt:) not being called on first tap
after an nimation. The change hasn't fixed the problem completely, but
it works better.
2019-04-19 08:46:51 +09:00
Shin Yamamoto 08e79bfc5c Merge pull request #191 from SCENEE/add-pages-sample
Add pages sample
2019-04-17 19:22:40 +09:00
Shin Yamamoto e4808516aa Merge pull request #190 from SCENEE/fix-surface-mask
Fix surface mask
2019-04-17 19:21:56 +09:00
Shin Yamamoto 5888104e98 Remove obsolete SWIFT_WHOLE_MODULE_OPTIMIZATION setting
Removing this setting will fix a warning emitted by Xcode to update
project settings.
2019-04-17 09:36:36 +09:00
Shin Yamamoto 2b8d29759a Rename the remaining backgroundView variable 2019-04-16 22:53:06 +09:00
Shin Yamamoto 8e4b56ff17 Fix the doc comment 2019-04-16 07:51:27 +09:00
Shin Yamamoto 23c5761c14 Update README 2019-04-15 21:05:46 +09:00
Shin Yamamoto 835ec0c3a0 Replace 'backgroundView' with 'containerView' 2019-04-15 21:05:16 +09:00
Shin Yamamoto d2dce0b6f8 Add a doc comment 2019-04-15 21:00:19 +09:00
Shin Yamamoto 3f8628af01 Fix typo 2019-04-11 22:52:49 +09:00
Shin Yamamoto 40c6fae07c Add a sample for panels in UIPageViewController 2019-04-11 22:52:29 +09:00
Shin Yamamoto e1185fda93 Merge pull request #188 from SCENEE/allow-subclassing
Allow FloatingPanelController subclassing
2019-04-11 22:31:37 +09:00
Shin Yamamoto 458ed903c5 Update README 2019-04-11 11:28:35 +09:00
Shin Yamamoto 4b640f4f01 Add updateBorder() 2019-04-11 11:25:11 +09:00
Shin Yamamoto 8743c5efd0 Fix the mask bounds of the surface view
- Move `contentView` into `backgroundView` to fix the mask
- Also fix the mask problem on iOS 10-11. See for detail,
https://github.com/SCENEE/FloatingPanel/issues/109.
2019-04-11 11:21:16 +09:00
Shin Yamamoto 9bd9d31d40 Merge pull request #186 from zntfdr/patch-1
Fix FloatingPanelBehavior.swift typos
2019-04-08 21:24:48 +09:00
Federico Zanetello 7e3d720720 Fix FloatingPanelBehavior.swift typos 2019-04-08 08:26:30 +07:00
Shin Yamamoto 7a512191ab Fix a touch abort on the interaction interrupted
Because touch events seem to be cancelled when an animator is released.
2019-04-06 11:25:38 +09:00
Shin Yamamoto af767863bb Merge pull request #177 from SCENEE/support-swift5
[Release v1.5.0] Support both of Swift 5 and 4.2
2019-04-06 10:54:54 +09:00
Shin Yamamoto 1b233f4f87 Update README 2019-04-05 08:21:49 +09:00
Shin Yamamoto 3a840df79e Release v1.5.0 2019-04-04 12:08:27 +09:00
Shin Yamamoto 6851e3b072 Support both of Swift 5 and 4.2
The default Swift version leaves 4.0. Because it avoids build errors
with Carthage on each Xcode version from the source compatibility
between Swift 4.0, 4.2 and 5.0.

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

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

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

...

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

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

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

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

According to the change, .full position means a position where a content
is not just visible fully, but also filled in a full screen almost.
2019-03-26 22:23:16 +09:00
Shin Yamamoto 9fa8a48c56 Fix top panning boundary 2019-03-26 11:41:13 +09:00
Shin Yamamoto cb54a2a7e1 Merge pull request #151 from SCENEE/remove-swapping-scroll-delegate
Remove swapping delegate
2019-03-26 09:55:47 +09:00
Shin Yamamoto ed02713ccc Merge pull request #168 from SCENEE/fix-safeareainsets
Change FloatingPanelLayoutAdapter.safeAreaInsets as a read-only property
2019-03-25 09:50:46 +09:00
Shin Yamamoto 68e3fd2093 ChangeFloatingPanelLayoutAdapter.safeAreaInsets as a read-only property
This solves the problem that a panel's layout and dragging can be
inappropriate. The root cause is that a value of the `safeAreaInsets`
is sometimes different from an actual value referenced from Auto Layout.
It's led by the design that the `safeAreaInsets` is defined as a stored
property. Therefore this commit resolves the issue.
2019-03-23 18:36:26 +09:00
Shin Yamamoto a43f73d7b1 Merge pull request #159 from SCENEE/fix-interruption
Fix interruption
2019-03-20 21:39:20 +09:00
Shin Yamamoto 61e0c4ed0a Merge pull request #165 from SCENEE/fix-ui-extensions
Fix UI extensions
2019-03-20 21:38:28 +09:00
Shin Yamamoto 53e0629b1d Merge pull request #162 from zntfdr/patch-2
Fix typo
2019-03-19 09:31:13 +09:00
Shin Yamamoto 2b1d6a3d8a Fix 'Redundant conformance' error of UIGestureRecognizerState 2019-03-18 10:21:36 +09:00
Federico Zanetello 5ba1fb3d95 Add missing space 2019-03-16 12:42:51 +07:00
Federico Zanetello 1b7c15cdb5 Fix typo 2019-03-16 12:38:54 +07:00
Shin Yamamoto 81fd85e993 Prevent bouncing a scroll content in a panel animating
This issue happens when a panel interacts from tip to half
in 'Show Tab Bar' -> 'Tab 3'.
2019-03-09 10:41:26 +09:00
Shin Yamamoto 8f4c08d5b3 Fix TabBarContentViewController for Tab 3 2019-03-09 10:36:31 +09:00
Shin Yamamoto 61b6429851 Fix the buggy top buffer in scroll tracking
Should not handle a panel gesture even when a zero scroll velocity.
If not, it causes that a scroll view will be handled after a panel
moves a bit in a top buffer. As a result, a panel frame jumps from
a location in a top buffer to topY.
2019-03-09 10:04:18 +09:00
Shin Yamamoto bbc6b39c08 Fix issues on an animation interruption
* Adjust a final position to work scroll tracking well
* Fix issues on an animation cancellation
2019-03-09 10:04:18 +09:00
Shin Yamamoto 3f812f4d6d Merge pull request #156 from SCENEE/fix-layout-assertion
Fix the layout assertion
2019-03-09 10:03:44 +09:00
Shin Yamamoto c9b15e4239 Merge pull request #158 from bryansum/master
Fix retain cycle in FloatingPanel
2019-03-09 09:57:11 +09:00
Bryan Summersett 5fbdb3d481 Make floatingPanel weak 2019-03-08 15:02:33 -08:00
Shin Yamamoto c59e1cd7fc Fix the layout assertion
The assertion didn't allow `.hidden` initial position.
2019-03-08 19:39:10 +09:00
Shin Yamamoto ce891e47da Merge pull request #155 from kfinteractive/master
Adds support for respecting initial safeAreaInsets.
2019-03-08 19:23:58 +09:00
Gunnar Herzog 7160e4a42e Removes unnecessary guard statement. 2019-03-07 08:56:29 +01:00
Gunnar Herzog eba857a285 Adds support for respecting initial safeAreaInsets. 2019-03-07 08:45:05 +01:00
Shin Yamamoto a1dd02c780 Stop scroll deceleration without swapping delegate 2019-03-05 08:31:27 +09:00
Shin Yamamoto 87ff5d629b Merge pull request #143 from SCENEE/release-v1.4.0
Release v1.4.0
2019-02-27 10:00:47 +09:00
Shin Yamamoto 6b75523428 Release v1.4.0 2019-02-27 09:33:14 +09:00
Shin Yamamoto 773434d4f6 Merge pull request #119 from SCENEE/improve-operability
Improve the operability
2019-02-27 09:32:15 +09:00
Shin Yamamoto ad6dcd0314 Clean up sample 2019-02-27 09:04:42 +09:00
Shin Yamamoto b9e29ad87d Fix typo 2019-02-26 10:07:30 +09:00
Shin Yamamoto 32b965ba87 Merge pull request #142 from zntfdr/patch-1
Add README missing character
2019-02-26 09:22:30 +09:00
Federico Zanetello f1b315c9ea Add missing character 2019-02-25 12:50:37 +07:00
Shin Yamamoto 459fc75af3 Tidy up logs 2019-02-25 13:02:49 +09:00
Shin Yamamoto 9b0cd3511f Revamp scroll draw-down action
A scroll view offset often isn't around zero by a user action.
Therefore, rather than checking a offset value, it's working well
to fits a scroll view offset to the surface view bounds by
manipulating their frames a bit temporarily.
2019-02-25 13:02:49 +09:00
Shin Yamamoto af9b988507 Merge pull request #136 from SCENEE/release-v1.3.5
Release v1.3.5
2019-02-23 11:18:21 +09:00
Shin Yamamoto 36f297c35b Allow the grabber area just on touch began 2019-02-23 10:28:34 +09:00
Shin Yamamoto ff959f71a7 Prevent the unexpected scrolling on full state by the second finger 2019-02-23 10:28:34 +09:00
Shin Yamamoto 0a4312ada6 Reduce logger function calls 2019-02-23 10:28:34 +09:00
Shin Yamamoto 5411cdc07a Change time to call floatingPanelWillBeginDecelerating() 2019-02-23 10:28:34 +09:00
Shin Yamamoto a8c6fba3c1 Call floatingPanelDidEndDecelerating(_:) when an animation is interruptted 2019-02-23 10:28:34 +09:00
Shin Yamamoto 11b115b47b Tear down the active interaction in moving programmatically 2019-02-23 10:28:34 +09:00
Shin Yamamoto 22edf5ce46 Prevent unexpected crash
This assertion can be called unexpectedly by the gesture recognizer behavior
of UIScrollView
2019-02-23 10:28:34 +09:00
Shin Yamamoto f43f7df7f3 Prevent calling didMove delegate when it's not moving 2019-02-23 10:28:34 +09:00
Shin Yamamoto 3a2633d818 Must update a panel layout after it becomes a new state
To synchronize layout updates in floatingPanelDidChangePosition(_:) by
a user
2019-02-23 10:28:34 +09:00
Shin Yamamoto 04a62bcf74 Refactor FloatingPanelSurfaceView for AutoLayout backed animation 2019-02-23 10:28:34 +09:00
Shin Yamamoto 6c1320168c Fix a buggy scroll offset on a scroll inset change by a user
* The controller can bother a user who wants to adjust a scroll offset
for a position at `floatingPanelDidChangePosition()` delegate method.
* This prevents interrupting a tracking scroll offset at the end of interaction
2019-02-23 10:28:34 +09:00
Shin Yamamoto 8657c91002 Fix the boundary of the interactive top constraint 2019-02-23 10:28:34 +09:00
Shin Yamamoto bafe492009 Normalize the projected position
It resolves the behavior is determined by the screen size.
2019-02-23 10:28:34 +09:00
Shin Yamamoto c6197ef6a3 Modify the default momentum projection behavior 2019-02-23 10:28:34 +09:00
Shin Yamamoto 1b3f16bcd5 Fix disabling bottom constraints 2019-02-23 10:28:34 +09:00
Shin Yamamoto 28712fdeca Add ThreeTabBarPanelLayout as an advanced layout sample 2019-02-23 10:28:34 +09:00
Shin Yamamoto 0c30b68a9e Update FloatingPanelFullScreenLayout spec
It's useful that all insets(tip, half and full) indicates a inset
from the superview, not the safe area because a user can layout
a floating panel regardless of the safe area or layout guides(iOS10)
whose values depend on iOS system behavior.
2019-02-21 19:19:48 +09:00
Shin Yamamoto 30c4bee432 Remove FloatingPanel.getCurrentY(from:with) 2019-02-21 19:19:48 +09:00
Shin Yamamoto ece9ced085 Configure momentum project with FloatingPanelBehavior
* shouldProjectMomentum(_:for:)
* momentumProjectionRate(_:)
2019-02-21 19:19:48 +09:00
Shin Yamamoto f231105752 Enable to cancel tracking a scroll view 2019-02-21 19:19:48 +09:00
Shin Yamamoto 91dfc1e086 Open the default layout/behavior 2019-02-21 19:19:48 +09:00
Shin Yamamoto b2c59c17aa Modify setBackdropAlpha(of:) as private 2019-02-21 19:19:48 +09:00
Shin Yamamoto 10d1a920f0 Add floatingPanelShouldBeginDragging(_:) delegate method
* Update doc comments
2019-02-21 19:19:48 +09:00
Shin Yamamoto 4cb79a14fc Move the surface view with a top layout constraint 2019-02-21 19:19:48 +09:00
Shin Yamamoto 402b9bd8dc Release v1.3.5 2019-02-21 18:42:02 +09:00
Shin Yamamoto c39cc9d93b Fix a regression of the interaction 2019-02-21 18:42:02 +09:00
Shin Yamamoto aad56ab0a7 Merge pull request #135 from SCENEE/fix-interaction
Fix interaction
2019-02-16 14:51:56 +09:00
Shin Yamamoto fe18e493a9 Fix dragging outside safe area 2019-02-16 10:31:17 +09:00
Shin Yamamoto 5d14166508 Fix an interruptive animator handling
* A velocity parameter passed by an animator had a wrong sign so that an
interruptive animator could be buggy.
* Improve the default pan gesture recognizer to interrupt an animation smoothly.
2019-02-16 10:29:11 +09:00
Shin Yamamoto e1a745e3b5 Clean up 2019-02-15 15:02:10 +09:00
Shin Yamamoto a0cac28ed0 Improve {re}directional position calc 2019-02-15 15:02:10 +09:00
Shin Yamamoto c205dc8672 Improve animator handling 2019-02-15 15:02:10 +09:00
Shin Yamamoto 5c0ed4cf7d Fix the wrong layout update on iOS10
On iOS 10, there is a case when a floating panel is updated by a
different position(the previous position) from the target position in
animating. This is because `FloatingPanelController` calls
`update(safeAreaInsets:)` in `viewDidLayoutSubviews()` unexpectedly.
2019-02-15 10:29:01 +09:00
Shin Yamamoto 780472a17f Stop unexpected scrolling by decelerating on tip and half 2019-02-15 10:15:34 +09:00
Shin Yamamoto 0264db3d54 Should always recognize tap/long press gestures in parallel 2019-02-15 09:55:36 +09:00
Shin Yamamoto c117594669 Merge pull request #132 from SCENEE/release-v1.3.4
Release v1.3.4
2019-02-14 09:45:31 +09:00
Shin Yamamoto 5214bd8936 Release v1.3.4 2019-02-14 09:13:09 +09:00
Shin Yamamoto a1195be08e Merge pull request #134 from g00m/feature/fix_typo_in_readme
Fix typo in `README.md`
2019-02-14 09:11:53 +09:00
Etienne Negro b69d366538 Fix typo in README-md 2019-02-13 14:28:39 +01:00
Shin Yamamoto c9de6f0dc3 Merge pull request #131 from modestman/master
Fix issue with jumping floating panel while dragging
2019-02-12 09:50:10 +09:00
Anton Glezman 21be693a9a Fix issue with jumping floating panel while dragging 2019-02-11 12:28:13 +03:00
Shin Yamamoto 129362fcd0 Merge pull request #129 from SCENEE/fix-typo
Fix typo
2019-02-06 09:40:58 +09:00
Shin Yamamoto 75e2fcc3ce Fix README 2019-02-05 22:46:56 +09:00
Shin Yamamoto 4f56b57b0e Replace FloatingPanel.panGesture with panGestureRecognizer 2019-02-05 22:10:03 +09:00
Shin Yamamoto f9bbdf3427 Update ISSUE_TEMPLATE.md 2019-02-02 18:19:06 +09:00
Shin Yamamoto fcf200e169 Merge pull request #124 from SCENEE/release-1.3.3
Release v1.3.3
2019-02-02 12:19:59 +09:00
Shin Yamamoto 7d668c8525 Release v1.3.3 2019-02-02 11:38:37 +09:00
Shin Yamamoto ebd4a32bfc Merge pull request #123 from SCENEE/fix-cut-off-by-mask
Expand the surface mask's height
2019-02-02 11:37:20 +09:00
Shin Yamamoto 6aa739231d Expand the surface mask's height
`FloatingPanelSurfaceView.updateContentViewMask()` causes a content to be cut off.
2019-02-01 10:19:51 +09:00
Shin Yamamoto 8877d32ced Merge pull request #122 from SCENEE/fix-animated-presentation-modally
Fix the presentation modally when fpc is reused
2019-01-31 19:22:46 +09:00
Shin Yamamoto 7f025ae845 Remove the unnecessary code to fix the presentation modally
It's added at `a095ace` commit, but now not needed by the previous
commit.
2019-01-31 12:48:36 +09:00
Shin Yamamoto 1b2dae2135 Fix presentation modally when fpc is reused 2019-01-30 13:09:04 +09:00
Shin Yamamoto 6e4e9df616 Merge pull request #114 from datwelk/hotfix/restore-scroll-view-delegate
Restore original scroll view delegate when updating content VC
2019-01-28 19:11:39 +09:00
Damiaan Twelker 49e868a505 restore original scroll view delegate when updating content viewcontroller 2019-01-26 12:41:17 +01:00
Shin Yamamoto f1b70e0367 Add the issue template 2019-01-25 22:08:44 +09:00
Shin Yamamoto 1d0e747578 Update README.md 2019-01-24 11:00:45 +09:00
Shin Yamamoto 2c72d07cab Merge pull request #97 from SCENEE/support-full-screen
Support full screen layout
2019-01-19 16:41:32 +09:00
Shin Yamamoto 31c057f9f8 Fix typo 2019-01-19 14:53:36 +09:00
Shin Yamamoto d3033df9da Add FloatingPanelFullScreenLayout doc comment 2019-01-19 14:51:04 +09:00
Shin Yamamoto 459d82b1c6 Update 'Show Tab Bar' for full screen layout 2019-01-19 14:06:23 +09:00
Shin Yamamoto 85d7ca640e Stop manipulating a scroll content inset for FloatingPanelFullScreenLayout
It's better that the manipulation should be operated in a user application code.
2019-01-19 14:06:23 +09:00
Shin Yamamoto c1b7f2f092 Merge pull request #106 from SCENEE/release-1.3.2
Release v1.3.2
2019-01-18 09:31:28 +09:00
Shin Yamamoto b7a7e0d4ad Release v1.3.2 2019-01-17 10:47:45 +09:00
Shin Yamamoto dc7f6d58f9 Fix the swift version in podspec 2019-01-17 10:47:45 +09:00
Shin Yamamoto a2a10bd0d3 Merge pull request #104 from SCENEE/fix-modal-presentation
Fix the modal presentation
2019-01-15 09:31:39 +09:00
Shin Yamamoto b54c8ee6ee Merge pull request #105 from CedricGatay/fix/delegateIgnored
fix(delegateIgnored): Allow to set delegate on init
2019-01-14 19:43:39 +09:00
Cedric Gatay 08d275690a fix(delegateIgnored): fix for animation use case
Do not force update layout to allow the user to animate the change later on.
2019-01-14 11:05:50 +01:00
Cedric Gatay 1c307f751e fix(delegateIgnored): listen on delegate change
Properly listens for delegate changes and trigger behavior / layout changes accordingly.

Made constructor parameter name explicit.
2019-01-14 08:49:23 +01:00
Cedric Gatay 6f06a0f7fc fix(delegateIgnored): Allow to set delegate on init
Typical use case is :

```
let floatingVC = FloatingPanelController()
floatingVC.delegate = self
```

This PR allows to set the delegate straight away by using `let floatingVC = FloatingPanelController(self)`
Its true reason is that the setup code that fetches behavior / layout through delegate is called without the delegate being set is useless on `init`ing.
Another implementation could be observing the `didSet` event on delegate to do the `setUp` for `FloatingPanel`
2019-01-12 15:15:48 +01:00
Shin Yamamoto a095ace30e Fix the modal presentation 2019-01-11 19:04:39 +09:00
Shin Yamamoto a486f61f5f Merge pull request #102 from SCENEE/fix-secound-touch-crash
Fix a crash on tap dismissal with a second touch
2019-01-10 09:38:06 +09:00
Shin Yamamoto 14e0abc240 Merge pull request #103 from SCENEE/fix-unsatisfiable-constraints-error
Fix unsatisfiable constraints error for safe area bottom on full state
2019-01-10 09:37:46 +09:00
Shin Yamamoto 32203c48bd Fix unsatisfiable constraints error for safe area bottom on full state 2019-01-10 09:05:17 +09:00
Shin Yamamoto 9d6024f603 Fix a crash on tap dismissal with a second touch
For example, a user tap the backdrop view to dismiss it while dragging
the surface view on presentation modally.
2019-01-10 08:43:46 +09:00
Shin Yamamoto a4dd4e48e7 Merge pull request #98 from SCENEE/support-swift4.1
Support Swift 4.1
2019-01-10 08:25:50 +09:00
Shin Yamamoto e6f7456a0f Merge pull request #101 from SCENEE/fix-touch-handling-on-presentation-modally
Fix the event handling on presentation modally
2019-01-10 08:25:26 +09:00
Shin Yamamoto fca79c9b0c Fix the event handling on presentation modally
If an alpha of the controller's backdrop view is zero, the presentation controller
must not block any touch event outside of surface view.
2019-01-09 09:32:17 +09:00
Shin Yamamoto 4ad7f11e93 Support Swift 4.1 2019-01-05 21:44:05 +09:00
Shin Yamamoto 0412bdc996 Support full screen layout 2019-01-05 12:26:49 +09:00
Shin Yamamoto 31faeaada3 Merge pull request #93 from SCENEE/release-1.3.1
Release v1.3.1
2018-12-30 09:52:25 +09:00
Shin Yamamoto 72539ca973 Release v1.3.1 2018-12-30 09:22:33 +09:00
Shin Yamamoto ef94630aa1 Update README 2018-12-30 09:22:33 +09:00
Shin Yamamoto cb696f9992 Merge pull request #86 from SCENEE/improve-samples-app
Fix layout bugs on v1.3.0
2018-12-29 14:33:13 +09:00
Shin Yamamoto aa23e404e1 Fix a content offset of a tracking scroll view
This bug only happens on 'Shoe Tab Bar' in Samples app on landscape
orientation.
2018-12-28 16:08:10 +09:00
Shin Yamamoto 4c0749640f Remove an unnecessary comment 2018-12-28 15:13:59 +09:00
Shin Yamamoto ee5661f304 Fix UI freezes on presentation modally from the second time 2018-12-28 15:13:59 +09:00
Shin Yamamoto ddefdc4f34 Fix crash when content view is nil 2018-12-28 15:13:59 +09:00
Shin Yamamoto f2a0af1646 Fix intrinsic height 2018-12-28 15:13:59 +09:00
Shin Yamamoto 7ded61c2bc No longer need traitCollectionDidChange(_:) 2018-12-28 15:13:59 +09:00
Shin Yamamoto 08aabcf6dd Fix bugs on iOS 10 2018-12-28 15:13:59 +09:00
Shin Yamamoto e19af7e67d Set up the height constraints with the root view's heightAnchor
* `vc.view.bounds.height` causes some layout problems. This change
  makes a content view layout more robust.
* Enable to configure the content-hugging and compression-resistance
2018-12-28 15:13:59 +09:00
Shin Yamamoto 62aa07e28e Refactor layout logic
- Update README
- Remove FloatingPanelSurfaceWrapperView
- Remove content wrapper view of FloatingPanelSurfaceView
- Remove {setUp,tearDown}Views in FloatingPanel
- Modify timing to call FloatingPanelLayoutAdapter.checkLayoutConsistance()
- Fix invalid surface height on orientation change
- Fix a layout problem on SafeArea.Top
    - Fix invalid top inset of safe area on a navigation bar with search bar
- Fix content offsets of a tracking scroll view
    - Fix the content offsets on orientation change(Regression)
- Fix FloatingPanelPresentationController
- Fix intrinsic height handling
- Reduce re-rendering the surface view unexpectedly
2018-12-28 15:13:59 +09:00
Shin Yamamoto 03b0bf747e Improve Sample apps for debugging
- Add InspectableViewController
- Add version label in Samples app
2018-12-28 15:13:59 +09:00
Shin Yamamoto 8dc75aed55 Always disable Auto Layout for Safe area bottom 2018-12-28 15:13:59 +09:00
Shin Yamamoto d5f5e99010 Fix dismiss swizzling 2018-12-28 15:13:59 +09:00
Shin Yamamoto 18c46d191e Fix panel is duplicated on orientation change 2018-12-28 15:13:59 +09:00
Shin Yamamoto e9f92430b2 Add a disable tracking sample to edit a table view 2018-12-28 15:13:59 +09:00
Shin Yamamoto 3106865449 Add SettingsViewController in Samples App 2018-12-28 15:13:44 +09:00
Shin Yamamoto 375e7a59e2 Merge pull request #89 from SCENEE/fix-failure-requirements
Fix failure requirements
2018-12-28 12:59:37 +09:00
Shin Yamamoto bc4a2def42 Add FloatingPanelControllerDelegate.floatingPanel(_:shouldRecognizeSimultaneouslyWith:) 2018-12-20 11:20:22 +09:00
Shin Yamamoto 8204a6cf27 Any gestures don't wait for the pan gesture 2018-12-18 21:43:18 +09:00
Shin Yamamoto 797292dbe5 Merge pull request #85 from SCENEE/release-1.3.0
Release v1.3.0(conflicts resolved )
2018-12-15 09:32:16 +09:00
Shin Yamamoto 6e7a33b3a1 Merge branch 'next' into release-1.3.0 2018-12-15 08:55:39 +09:00
Shin Yamamoto 56557f0092 Merge pull request #81 from SCENEE/release-1.2.3
Release v1.2.3
2018-12-07 19:10:14 +09:00
Shin Yamamoto 1e6cb7b1ad Release v1.2.3 2018-12-07 16:50:08 +09:00
Shin Yamamoto 8ba4ce36a1 Merge pull request #75 from SCENEE/fix-animation-wobbling
The default interaction animator should be uninterruptible
2018-12-05 16:01:56 +09:00
Shin Yamamoto cf60b09225 Fix invalid backdrop alpha
The bug was found when I commented out `animator.startAnimation()`.
2018-12-05 14:08:31 +09:00
Shin Yamamoto 427ec45d42 Let the default interaction animator be uninterruptible
Because an interruptible animator causes a wobbling at the animation start.
when a user flick a panel quickly to move to full position nearby the position.
2018-12-05 13:56:43 +09:00
134 changed files with 14145 additions and 4508 deletions
+1
View File
@@ -0,0 +1 @@
github: SCENEE
+40
View File
@@ -0,0 +1,40 @@
> Please fill out this template appropriately when filing a bug report.
>
> Please remove this line and everything above it before submitting.
### Description
### Expected behavior
### Actual behavior
### Steps to reproduce
**Code example that reproduces the issue**
**How do you display panel(s)?**
* Add as child view controllers
* Present modally
**How many panels do you displays?**
* 1
* 2+
### Environment
**Library version**
**Installation method**
* CocoaPods
* Carthage
* Swift Package Manager
**iOS version(s)**
**Xcode version**
+132
View File
@@ -0,0 +1,132 @@
name: ci
on:
push:
branches:
- master
pull_request:
branches:
- '*'
jobs:
build:
runs-on: ${{ matrix.runsOn }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- swift: "5.7"
xcode: "14.1"
runsOn: macos-12
- swift: "5.6"
xcode: "13.3.1"
runsOn: 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
steps:
- uses: actions/checkout@v2
- name: Building in Swift ${{ matrix.swift }}
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
test:
runs-on: ${{ matrix.runsOn }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- os: "16.1"
xcode: "14.1"
sim: "iPhone 14 Pro"
runsOn: macos-12
- os: "15.4"
xcode: "13.3.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
steps:
- uses: actions/checkout@v2
- 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 }}'
example:
runs-on: macos-12
env:
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- example: "Maps"
- example: "Stocks"
- example: "Samples"
steps:
- uses: actions/checkout@v2
- name: Building ${{ matrix.example }}
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
swiftpm:
runs-on: macos-12
env:
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
# 15.7
- target: "x86_64-apple-ios15.7-simulator"
- target: "arm64-apple-ios15.7-simulator"
# 16.1
- target: "x86_64-apple-ios16.1-simulator"
- target: "arm64-apple-ios16.1-simulator"
steps:
- uses: actions/checkout@v2
- 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
cocoapods:
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- name: "CocoaPods: pod lib lint"
run: pod lib lint --allow-warnings
- name: "CocoaPods: pod spec lint"
run: pod spec lint --allow-warnings
+5
View File
@@ -17,11 +17,13 @@ DerivedData/
*.perspectivev3
!default.perspectivev3
xcuserdata/
IDEWorkspaceChecks.plis
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
*.xcsettings
## Obj-C/Swift specific
*.hmap
@@ -29,6 +31,9 @@ xcuserdata/
*.dSYM.zip
*.dSYM
## Swift Package Manager Specific
.swiftpm/
## Playgrounds
timeline.xctimeline
playground.xcworkspace
-45
View File
@@ -1,45 +0,0 @@
language: swift
branches:
only:
- master
- next
cache:
directories:
- build
- vendor
- /usr/local/Homebrew
- $HOME/Library/Caches/Homebrew
env:
global:
- LANG=en_US.UTF-8
- LC_ALL=en_US.UTF-8
skip_cleanup: true
jobs:
include:
- stage: carthage
osx_image: xcode10
before_install:
- brew update
- brew outdated carthage || brew upgrade carthage
script:
- carthage build --no-skip-current
- stage: podspec
osx_image: xcode10
script:
- pod spec lint
- stage: check Maps example
osx_image: xcode10
script:
- xcodebuild -scheme Maps -sdk iphonesimulator clean build
- stage: check Stocks example
osx_image: xcode10
script:
- xcodebuild -scheme Stocks -sdk iphonesimulator clean build
- stage: check Samples example
osx_image: xcode10
script:
- xcodebuild -scheme Samples -sdk iphonesimulator clean build
@@ -0,0 +1,294 @@
# FloatingPanel 2.0 Migration Guide
FloatingPanel 2.0 is the latest major release of FloatingPanel. As a major release, following Semantic Versioning conventions, 2.0 introduces API-breaking changes.
This guide is provided in order to ease the transition of existing applications using FloatingPanel 1.x to the latest APIs, as well as explain the design and structure of new and updated functionality.
## Updated Minimum Requirements
* Swift 5.0
* iOS 11 (iOS 10 is still the deployment target, but not tested well)
* Xcode 11.0
## Benefits of Upgrading
* __Top, left and right positioned panel__
* FloatingPanel is not just a library for a bottom positioned panel, but also top, left and right positioned ones.
* __Objective-C compatibility__
* The entire APIs are exposed in Objective-C. So you can use them in Objective-C directly.
* __Flexible and explicit layout customization__
* `FloatingPanelLayout` is redesigned. There is no implicit rules to lay out a panel anymore.
* __New spring animation without UIViewPropertyAnimator__
* The new spring animation uses [Numeric springing](http://allenchou.net/2015/04/game-math-precise-control-over-numeric-springing/) which is a very powerful tool for procedural animation. Therefore a library consumer is easy to modify a panel behavior by 2 paramters of the deceleration rate and response time.
* __Handle the panel position anytime__
* `floatingPanelDidMove(_:)` delegate method is also called while a panel is moving. The method behavior becomes same as `scrollViewDidScroll(_:)` in `UIScrollViewDelegate`. And in the method a library consumer is able to change a panel location.
* __Update the removal interaction's invocation__
* Now you can invoke the removal interaction at any time where you want. There is no restrictions in the library.
* __Fix many issues depending on API design__
* See the following sections for details.
## API Name Changes
* `FloatingPanelPosition` is now `FloatingPanelState`.
* `FloatingPanelPosition` in v2 is used to specify a panel position(top, left, bottom and right) in a screen.
* `FloatingPanelSurfaceView` is `SurfaceView` only in Swift.
* `FloatingPanelBackdropView` is `BackdropView` only in Swift.
* `FloatingPanelGrabberHandleView` is `GrabberView` only in Swift.
* "decelerate" term is replaced with "attract" because the panel's behavior is not unidirectional, but going back and forth so that it is settled to a location.
## `FloatingPanelController`
* `layout` and `behavior` properties can be changed directly without using the delegate methods.
```swift
fpc.behavior = SearchPaneliPadBehavior()
fpc.layout = SearchPaneliPadLayout()
fpc.invalidateLayout() // If needed
```
* The second argument of `addPanel(toParent:)` changes to specify an index of subviews of a view in which a panel is added.
```diff
- public func addPanel(toParent parent: UIViewController, belowView: UIView? = nil, animated: Bool = false) {
+ public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false) {
```
* `surfaceOriginY` is now `surfaceLocation`.
* `updateLayout` is now `invalidateLayout`.
* The scroll tracking API is changed a bit to support multiple scroll view tracking in the future.
* Now `untrack(scrollView:)` is used to disable the scroll tracking.
## `FloatingPanelControllerDelegate`
* `floatingPanelDidEndDragging(_ vc:willAttract:)` is added to check whether a panel will continue to move after dragging.
* `floatingPanelDidMove(_:)` behavior changes. The method is also called in the spring animation.
* The removal interaction delegate is updated.
* `floatingPanel(_:shouldRemoveAt:with:)` is added to determine whether it invokes the removal interaction in any state.
* `floatingPanelWillRemove(_:)` is added.
* `floatingPanel(_: FloatingPanelController, layoutFor size: CGSize)` is added to respond to a layout change in regular size classes on iPad.
```swift
func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout {
if aCondition(for: size) {
return SearchPanelLayout()
}
return SearchPanel2Layout()
}
```
* The `targetState` argument type of `floatingPanelWillEndDragging(_:withVelocity:targetState:)` is changed from `FloatingPanelState` to `UnsafeMutablePointer<FloatingPanelState>` to modify a target state on demand.
```swift
func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
switch targetState.pointee {
case .full:
// do something...
case .half:
if aCondition {
targetState.pointee = .tip
}
default:
break
}
}
```
### Deprecated APIs
* `floatingPanel(_:behaviorFor:)`
* Please update `FloatingPanelController.behavior` directly.
* `floatingPanel(_:shouldRecognizeSimultaneouslyWith:)`
* Please use `FloatingPanelController.panGestureRecognizer.delegateProxy`.
## `FloatingPanelLayout`
* `position` property is added to determine a panel position.
* `initialPosition` is now `initialState`.
* `supportedPositions` and `insetFor(position:)` are replaced with `anchors` property.
* `backdropAlphaFor(position:)` is now `backdropAlpha(for:)`.
```swift
class SearchPanelPadLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .top
let initialState: FloatingPanelState = .tip
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
...
]
}
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
}
...
}
```
### New `FloatingPanelLayoutAnchoring` classes
The following objects adopting `FloatingPanelLayoutAnchoring` protocol are added to configure the flexible and explicit layout.
#### `FloatingPanelLayoutAnchor`
This class is used to specify a panel layout using insets from a rectangle area of the superview or safe area.
* `FloatingPanelFullScreenLayout` is replaced with anchors using `.superview` reference guide.
* `FloatingPanelLayoutAnchor(fractionalInset:edge:referenceGuide:)` lets you lay out a panel at a relative position in a reference rectangle area.
```swift
// Before:
class MyPanelLayout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .half: return 262.0
case .tip: return 44.0
case .hidden: return nil
}
}
}
// After:
class MyPanelLayout: 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(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}
```
#### `FloatingPanelIntrinsicLayoutAnchor`
This class is used to specify a panel layout using offsets from the intrinsic size layout.
* This replaces `FloatingPanelIntrinsicLayout`.
* This is also able to configure a fractional layout in the intrinsic size.
```swift
// Before:
class MyPanelIntrinsicLayout: FloatingPanelIntrinsicLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .half: return 262.0
case .tip: return 44.0
case .hidden: return nil
}
}
}
// After:
class MyPanelIntrinsicLayout: FloatingPanelLayout {
var position: FloatingPanelPosition = .bottom
var initialState: FloatingPanelState { .full }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 16.0, referenceGuide: .safeArea),
.half: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}
```
### Deprecated APIs
* `.topInteractionBuffer` and `.bottomInteractionBuffer`.
* Please control the max/min range of the motion in `floatingPanelDidMove(_:)` delegate method as below.
```swift
func floatingPanelDidMove(_ fpc: FloatingPanelController) {
if fpc.isAttracting == false {
let loc = fpc.surfaceLocation
let minY = fpc.surfaceLocation(for: .full).y - 6.0
let maxY = fpc.surfaceLocation(for: .tip).y + 6.0
fpc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY))
}
}
```
## `FloatingPanelBehavior`
* `.springDecelerationRate` and `.springResponseTime` properties are added to control the new spring effect of Numeric springing.
### Deprecated APIs
* `addAnimator(_:to:)`, `removeAnimator(_:from:)`
* They are moved into `floatingPanel(_:animatorForPresentingTo:)` and `floatingPanel(_:animatorForDismissingWith:)` of `FloatingPanelControllerDelegate` because they are used for view transitions.
* `interactionAnimator(_:to:with:)`, `moveAnimator(_:from:to:)`
* They are removed because the animators are replaced with the new spring effect.
* `removalVelocity`, `removalProgress`
* They are replaced with `floatingPanel(_:shouldRemoveAt:with:)` of `FloatingPanelControllerDelegate`
* `removalInteractionAnimator(_:with:)`
* It is integrated with `floatingPanel(_:animatorForDismissingWith:)` of `FloatingPanelControllerDelegate`.
## `SurfaceView`
* `SurfaceAppearance` class and `SurfaceView.appearance` property are added to specify the rounding corners, shadows and background color.
* `SurfaceView.appearance` property avoids `Ambiguous use of 'cornerRadius'` error, for instance.
* `SurfaceAppearance` enables to apply layered box shadows into a surface to materialize it.
```swift
// Before:
fpc.surfaceView.cornerRadius = 6.0
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.shadowHidden = false
fpc.surfaceView.shadowColor = .black
fpc.surfaceView.shadowOffset = CGSize(width: 0, height: 16)
fpc.surfaceView.shadowRadius = 16.0
// After:
let appearance = SurfaceAppearance()
appearance.cornerRadius = 8.0
appearance.backgroundColor = .clear
let shadow = SurfaceAppearance.Shadow()
shadow.color = .black
shadow.offset = CGSize(width: 0, height: 16)
shadow.radius = 16
shadow.spread = 8
appearance.shadows = [shadow]
fpc.surfaceView.appearance = appearance
```
* These properties are changed for the top, left and right positioned panel.
* `grabberTopPadding` is now `grabberHandlePadding`.
* `topGrabberBarHeight` is now `grabberAreaOffset`.
* `grabberHandleWidth` and `grabberHandleHeight` are replaced with `grabberHandleSize`.
## `BackdropView`
* The dismissal action of the backdrop is disabled by default.
* You can enable it to set `BackdropView.dismissalTapGestureRecognizer.isEnabled` to `true`.
## `FloatingPanelPanGestureRecognizer`
* `delegateProxy` property is added to intercept the gesture recognizer delegate.
```swift
func layoutPanelForPad() {
fpc.behavior = SearchPaneliPadBehavior()
fpc.panGestureRecognizer.delegateProxy = self
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
```
## Miscellaneous
* `UISpringTimingParameters(decelerationRate:frequencyResponse:initialVelocity:)` initializer is added.
* The directory structure and file names in the Xcode project changes.
@@ -0,0 +1,426 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
6467E8642699AC5F00565F4F /* SurfaceAppearance+phone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6467E8632699AC5F00565F4F /* SurfaceAppearance+phone.swift */; };
6467E86A2699B19D00565F4F /* SearchPanelPhoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6467E8692699B19D00565F4F /* SearchPanelPhoneDelegate.swift */; };
649A122926C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A122826C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift */; };
649A122D26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A122C26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift */; };
64A5B7232691323900BCAA05 /* MapsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B7222691323900BCAA05 /* MapsApp.swift */; };
64A5B7252691323900BCAA05 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B7242691323900BCAA05 /* ContentView.swift */; };
64A5B734269133DC00BCAA05 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A5B733269133DC00BCAA05 /* FloatingPanel.framework */; };
64A5B735269133DC00BCAA05 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 64A5B733269133DC00BCAA05 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
64A5B738269134CA00BCAA05 /* VisualEffectBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B737269134CA00BCAA05 /* VisualEffectBlur.swift */; };
64A5B73C2691469900BCAA05 /* FloatingPanelContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B73B2691469900BCAA05 /* FloatingPanelContentView.swift */; };
64A5B73E269147DC00BCAA05 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B73D269147DC00BCAA05 /* SearchBar.swift */; };
64A5B7402691532400BCAA05 /* ResultsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B73F2691532400BCAA05 /* ResultsList.swift */; };
64A5B7422691541A00BCAA05 /* HostingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A5B7412691541A00BCAA05 /* HostingCell.swift */; };
64F7E83126AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82C26AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift */; };
64F7E83226AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82D26AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift */; };
64F7E83326AD70EB00A0E0F7 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82E26AD70EB00A0E0F7 /* FloatingPanelView.swift */; };
64F7E83426AD70EB00A0E0F7 /* View+floatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E82F26AD70EB00A0E0F7 /* View+floatingPanel.swift */; };
64F7E83526AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F7E83026AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
64A5B736269133DC00BCAA05 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
64A5B735269133DC00BCAA05 /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
6467E8632699AC5F00565F4F /* SurfaceAppearance+phone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SurfaceAppearance+phone.swift"; sourceTree = "<group>"; };
6467E8692699B19D00565F4F /* SearchPanelPhoneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchPanelPhoneDelegate.swift; sourceTree = "<group>"; };
649A122826C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIHostingController+ignoreKeyboard.swift"; sourceTree = "<group>"; };
649A122C26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelGrabberHandlePadding.swift"; sourceTree = "<group>"; };
64A5B71F2691323900BCAA05 /* Maps-SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Maps-SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; };
64A5B7222691323900BCAA05 /* MapsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapsApp.swift; sourceTree = "<group>"; };
64A5B7242691323900BCAA05 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
64A5B733269133DC00BCAA05 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
64A5B737269134CA00BCAA05 /* VisualEffectBlur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectBlur.swift; sourceTree = "<group>"; };
64A5B73B2691469900BCAA05 /* FloatingPanelContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelContentView.swift; sourceTree = "<group>"; };
64A5B73D269147DC00BCAA05 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
64A5B73F2691532400BCAA05 /* ResultsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsList.swift; sourceTree = "<group>"; };
64A5B7412691541A00BCAA05 /* HostingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingCell.swift; sourceTree = "<group>"; };
64F7E82C26AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelContentInsetAdjustmentBehavior.swift"; sourceTree = "<group>"; };
64F7E82D26AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelContentMode.swift"; sourceTree = "<group>"; };
64F7E82E26AD70EB00A0E0F7 /* FloatingPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
64F7E82F26AD70EB00A0E0F7 /* View+floatingPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanel.swift"; sourceTree = "<group>"; };
64F7E83026AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+floatingPanelSurfaceAppearance.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
64A5B71C2691323900BCAA05 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
64A5B734269133DC00BCAA05 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
64A5B7162691323900BCAA05 = {
isa = PBXGroup;
children = (
64A5B7212691323900BCAA05 /* Maps */,
64A5B7202691323900BCAA05 /* Products */,
64A5B732269133DC00BCAA05 /* Frameworks */,
);
sourceTree = "<group>";
};
64A5B7202691323900BCAA05 /* Products */ = {
isa = PBXGroup;
children = (
64A5B71F2691323900BCAA05 /* Maps-SwiftUI.app */,
);
name = Products;
sourceTree = "<group>";
};
64A5B7212691323900BCAA05 /* Maps */ = {
isa = PBXGroup;
children = (
64F7E82B26AD70EB00A0E0F7 /* FloatingPanel */,
64BE55702691BBB0006D98BD /* Representable */,
64A5B7222691323900BCAA05 /* MapsApp.swift */,
64A5B7242691323900BCAA05 /* ContentView.swift */,
64A5B73B2691469900BCAA05 /* FloatingPanelContentView.swift */,
6467E8692699B19D00565F4F /* SearchPanelPhoneDelegate.swift */,
6467E8632699AC5F00565F4F /* SurfaceAppearance+phone.swift */,
64A5B7412691541A00BCAA05 /* HostingCell.swift */,
64A5B73F2691532400BCAA05 /* ResultsList.swift */,
);
path = Maps;
sourceTree = "<group>";
};
64A5B732269133DC00BCAA05 /* Frameworks */ = {
isa = PBXGroup;
children = (
64A5B733269133DC00BCAA05 /* FloatingPanel.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
64BE55702691BBB0006D98BD /* Representable */ = {
isa = PBXGroup;
children = (
64A5B73D269147DC00BCAA05 /* SearchBar.swift */,
64A5B737269134CA00BCAA05 /* VisualEffectBlur.swift */,
);
path = Representable;
sourceTree = "<group>";
};
64F7E82B26AD70EB00A0E0F7 /* FloatingPanel */ = {
isa = PBXGroup;
children = (
64F7E82E26AD70EB00A0E0F7 /* FloatingPanelView.swift */,
64F7E82F26AD70EB00A0E0F7 /* View+floatingPanel.swift */,
64F7E82C26AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift */,
64F7E82D26AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift */,
64F7E83026AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift */,
649A122C26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift */,
649A122826C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift */,
);
path = FloatingPanel;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
64A5B71E2691323900BCAA05 /* Maps-SwiftUI */ = {
isa = PBXNativeTarget;
buildConfigurationList = 64A5B72D2691323A00BCAA05 /* Build configuration list for PBXNativeTarget "Maps-SwiftUI" */;
buildPhases = (
64A5B71B2691323900BCAA05 /* Sources */,
64A5B71C2691323900BCAA05 /* Frameworks */,
64A5B71D2691323900BCAA05 /* Resources */,
64A5B736269133DC00BCAA05 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "Maps-SwiftUI";
productName = "Maps-SwiftUI";
productReference = 64A5B71F2691323900BCAA05 /* Maps-SwiftUI.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
64A5B7172691323900BCAA05 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1300;
LastUpgradeCheck = 1300;
TargetAttributes = {
64A5B71E2691323900BCAA05 = {
CreatedOnToolsVersion = 13.0;
};
};
};
buildConfigurationList = 64A5B71A2691323900BCAA05 /* Build configuration list for PBXProject "Maps-SwiftUI" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 64A5B7162691323900BCAA05;
productRefGroup = 64A5B7202691323900BCAA05 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
64A5B71E2691323900BCAA05 /* Maps-SwiftUI */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
64A5B71D2691323900BCAA05 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
64A5B71B2691323900BCAA05 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6467E86A2699B19D00565F4F /* SearchPanelPhoneDelegate.swift in Sources */,
64A5B7422691541A00BCAA05 /* HostingCell.swift in Sources */,
64A5B738269134CA00BCAA05 /* VisualEffectBlur.swift in Sources */,
64A5B7402691532400BCAA05 /* ResultsList.swift in Sources */,
6467E8642699AC5F00565F4F /* SurfaceAppearance+phone.swift in Sources */,
64F7E83226AD70EB00A0E0F7 /* View+floatingPanelContentMode.swift in Sources */,
64A5B7252691323900BCAA05 /* ContentView.swift in Sources */,
64A5B7232691323900BCAA05 /* MapsApp.swift in Sources */,
64A5B73C2691469900BCAA05 /* FloatingPanelContentView.swift in Sources */,
649A122D26C168CF00DAB961 /* View+floatingPanelGrabberHandlePadding.swift in Sources */,
64F7E83326AD70EB00A0E0F7 /* FloatingPanelView.swift in Sources */,
64F7E83426AD70EB00A0E0F7 /* View+floatingPanel.swift in Sources */,
64F7E83526AD70EB00A0E0F7 /* View+floatingPanelSurfaceAppearance.swift in Sources */,
64F7E83126AD70EB00A0E0F7 /* View+floatingPanelContentInsetAdjustmentBehavior.swift in Sources */,
64A5B73E269147DC00BCAA05 /* SearchBar.swift in Sources */,
649A122926C14D0900DAB961 /* UIHostingController+ignoreKeyboard.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
64A5B72B2691323A00BCAA05 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
64A5B72C2691323A00BCAA05 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
64A5B72E2691323A00BCAA05 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "example.Maps-SwiftUI";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
64A5B72F2691323A00BCAA05 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "exmaple.Maps-SwiftUI";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
64A5B71A2691323900BCAA05 /* Build configuration list for PBXProject "Maps-SwiftUI" */ = {
isa = XCConfigurationList;
buildConfigurations = (
64A5B72B2691323A00BCAA05 /* Debug */,
64A5B72C2691323A00BCAA05 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
64A5B72D2691323A00BCAA05 /* Build configuration list for PBXNativeTarget "Maps-SwiftUI" */ = {
isa = XCConfigurationList;
buildConfigurations = (
64A5B72E2691323A00BCAA05 /* Debug */,
64A5B72F2691323A00BCAA05 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 64A5B7172691323900BCAA05 /* Project object */;
}
@@ -0,0 +1,33 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
import MapKit
struct ContentView: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.623198015869235, longitude: -122.43066818432008),
span: MKCoordinateSpan(latitudeDelta: 0.4425100023575723, longitudeDelta: 0.28543697435880233)
)
var body: some View {
ZStack {
Map(coordinateRegion: $region)
.ignoresSafeArea()
statusBarBlur
}
}
private var statusBarBlur: some View {
GeometryReader { geometry in
VisualEffectBlur()
.frame(height: geometry.safeAreaInsets.top)
.ignoresSafeArea()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@@ -0,0 +1,133 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
import SwiftUI
/// A proxy for exposing the methods of the floating panel controller.
public struct FloatingPanelProxy {
/// The associated floating panel controller.
public weak var fpc: FloatingPanelController?
/// Tracks the specified scroll view to correspond with the scroll.
///
/// - Parameter scrollView: Specify a scroll view to continuously and
/// seamlessly work in concert with interactions of the surface view.
public func track(scrollView: UIScrollView) {
fpc?.track(scrollView: scrollView)
}
/// Moves the floating panel to the specified position.
///
/// - Parameters:
/// - floatingPanelState: The state to move to.
/// - animated: `true` to animate the transition to the new state; `false`
/// otherwise.
public func move(
to floatingPanelState: FloatingPanelState,
animated: Bool,
completion: (() -> Void)? = nil
) {
fpc?.move(to: floatingPanelState, animated: animated, completion: completion)
}
}
/// A view with an associated floating panel.
struct FloatingPanelView<Content: View, FloatingPanelContent: View>: UIViewControllerRepresentable {
/// A type that conforms to the `FloatingPanelControllerDelegate` protocol.
var delegate: FloatingPanelControllerDelegate?
/// The behavior for determining the adjusted content offsets.
@Environment(\.contentInsetAdjustmentBehavior) var contentInsetAdjustmentBehavior
/// Constants that define how a panel content fills in the surface.
@Environment(\.contentMode) var contentMode
/// The floating panel grabber handle offset.
@Environment(\.grabberHandlePadding) var grabberHandlePadding
/// The floating panel `surfaceView` appearance.
@Environment(\.surfaceAppearance) var surfaceAppearance
/// The view builder that creates the floating panel parent view content.
@ViewBuilder var content: Content
/// The view builder that creates the floating panel content.
@ViewBuilder var floatingPanelContent: (FloatingPanelProxy) -> FloatingPanelContent
public func makeUIViewController(context: Context) -> UIHostingController<Content> {
let hostingController = UIHostingController(rootView: content)
hostingController.view.backgroundColor = nil
// We need to wait for the current runloop cycle to complete before our
// view is actually added (into the view hierarchy), otherwise the
// environment is not ready yet.
DispatchQueue.main.async {
context.coordinator.setupFloatingPanel(hostingController)
}
return hostingController
}
public func updateUIViewController(
_ uiViewController: UIHostingController<Content>,
context: Context
) {
context.coordinator.updateIfNeeded()
}
public func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
/// `FloatingPanelView` coordinator.
///
/// Responsible to setup the view hierarchy and floating panel.
final class Coordinator {
private let parent: FloatingPanelView<Content, FloatingPanelContent>
private lazy var fpc = FloatingPanelController()
init(parent: FloatingPanelView<Content, FloatingPanelContent>) {
self.parent = parent
}
func setupFloatingPanel(_ parentViewController: UIViewController) {
updateIfNeeded()
let panelContent = parent.floatingPanelContent(FloatingPanelProxy(fpc: fpc))
let hostingViewController = UIHostingController(
rootView: panelContent,
ignoresKeyboard: true
)
hostingViewController.view.backgroundColor = nil
let contentViewController = UIViewController()
contentViewController.view.addSubview(hostingViewController.view)
fpc.set(contentViewController: contentViewController)
fpc.addPanel(toParent: parentViewController, animated: false)
hostingViewController.view.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = hostingViewController.view.bottomAnchor.constraint(equalTo: contentViewController.view.bottomAnchor)
bottomConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
hostingViewController.view.topAnchor.constraint(equalTo: contentViewController.view.topAnchor),
hostingViewController.view.leadingAnchor.constraint(equalTo: contentViewController.view.leadingAnchor),
hostingViewController.view.trailingAnchor.constraint(equalTo: contentViewController.view.trailingAnchor),
bottomConstraint
])
}
func updateIfNeeded() {
if fpc.contentInsetAdjustmentBehavior != parent.contentInsetAdjustmentBehavior {
fpc.contentInsetAdjustmentBehavior = parent.contentInsetAdjustmentBehavior
}
if fpc.contentMode != parent.contentMode {
fpc.contentMode = parent.contentMode
}
if fpc.delegate !== parent.delegate {
fpc.delegate = parent.delegate
}
if fpc.surfaceView.grabberHandlePadding != parent.grabberHandlePadding {
fpc.surfaceView.grabberHandlePadding = parent.grabberHandlePadding
}
if fpc.surfaceView.appearance != parent.surfaceAppearance {
fpc.surfaceView.appearance = parent.surfaceAppearance
}
}
}
}
@@ -0,0 +1,44 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
/// This extension makes sure SwiftUI views are not affected by iOS keyboard.
///
/// Credits to https://steipete.me/posts/disabling-keyboard-avoidance-in-swiftui-uihostingcontroller/
extension UIHostingController {
public convenience init(rootView: Content, ignoresKeyboard: Bool) {
self.init(rootView: rootView)
if ignoresKeyboard {
guard let viewClass = object_getClass(view) else { return }
let viewSubclassName = String(
cString: class_getName(viewClass)
).appending("_IgnoresKeyboard")
if let viewSubclass = NSClassFromString(viewSubclassName) {
object_setClass(view, viewSubclass)
} else {
guard
let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String,
let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0)
else { return }
if let method = class_getInstanceMethod(
viewClass,
NSSelectorFromString("keyboardWillShowWithNotification:")
) {
let keyboardWillShow: @convention(block) (AnyObject, AnyObject) -> Void = { _, _ in }
class_addMethod(
viewSubclass,
NSSelectorFromString("keyboardWillShowWithNotification:"),
imp_implementationWithBlock(keyboardWillShow),
method_getTypeEncoding(method)
)
}
objc_registerClassPair(viewSubclass)
object_setClass(view, viewSubclass)
}
}
}
}
@@ -0,0 +1,31 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
import SwiftUI
extension View {
/// Presents a floating panel using the given closure as its content.
///
/// The modifier's content view builder receives a `FloatingPanelProxy`
/// instance; you use the proxy's methods to interact with the associated
/// `FloatingPanelController`.
///
/// - Parameters:
/// - delegate: A type that conforms to the
/// `FloatingPanelControllerDelegate` protocol. You have comprehensive
/// control over the floating panel behavior when you use a delegate.
/// - floatingPanelContent: The floating panel content. This view builder
/// receives a `FloatingPanelProxy` instance that you use to interact
/// with the `FloatingPanelController`.
public func floatingPanel<FloatingPanelContent: View>(
delegate: FloatingPanelControllerDelegate? = nil,
@ViewBuilder _ floatingPanelContent: @escaping (_: FloatingPanelProxy) -> FloatingPanelContent
) -> some View {
FloatingPanelView(
delegate: delegate,
content: { self },
floatingPanelContent: floatingPanelContent
)
.ignoresSafeArea()
}
}
@@ -0,0 +1,38 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
import SwiftUI
struct ContentInsetKey: EnvironmentKey {
static var defaultValue: FloatingPanelController.ContentInsetAdjustmentBehavior = .always
}
extension EnvironmentValues {
/// The behavior for determining the adjusted content offsets.
var contentInsetAdjustmentBehavior: FloatingPanelController.ContentInsetAdjustmentBehavior {
get { self[ContentInsetKey.self] }
set { self[ContentInsetKey.self] = newValue }
}
}
extension View {
/// Sets the content inset adjustment behavior for floating panels within
/// this view.
///
/// Use this modifier to set a specific content inset adjustment behavior
/// for floating panel instances within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelContentInsetAdjustmentBehavior(.never)
///
/// - Parameter contentInsetAdjustmentBehavior: The content inset adjustment
/// behavior to set.
public func floatingPanelContentInsetAdjustmentBehavior(
_ contentInsetAdjustmentBehavior: FloatingPanelController.ContentInsetAdjustmentBehavior
) -> some View {
environment(\.contentInsetAdjustmentBehavior, contentInsetAdjustmentBehavior)
}
}
@@ -0,0 +1,37 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
import SwiftUI
struct ContentModeKey: EnvironmentKey {
static var defaultValue: FloatingPanelController.ContentMode = .static
}
extension EnvironmentValues {
/// Used to determine how the floating panel controller lays out the content
/// view when the surface position changes.
var contentMode: FloatingPanelController.ContentMode {
get { self[ContentModeKey.self] }
set { self[ContentModeKey.self] = newValue }
}
}
extension View {
/// Sets the content mode for floating panels within this view.
///
/// Use this modifier to set a specific content mode for floating panel
/// instances within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelContentMode(.static)
///
/// - Parameter contentMode: The content mode to set.
public func floatingPanelContentMode(
_ contentMode: FloatingPanelController.ContentMode
) -> some View {
environment(\.contentMode, contentMode)
}
}
@@ -0,0 +1,36 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
import SwiftUI
struct GrabberHandlePaddingKey: EnvironmentKey {
static var defaultValue: CGFloat = 6.0
}
extension EnvironmentValues {
/// The offset of the grabber handle from the interactive edge.
var grabberHandlePadding: CGFloat {
get { self[GrabberHandlePaddingKey.self] }
set { self[GrabberHandlePaddingKey.self] = newValue }
}
}
extension View {
/// Sets the grabber handle padding for floating panels within this view.
///
/// Use this modifier to set a specific padding to floating panel instances
/// within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelGrabberHandlePadding(16)
///
/// - Parameter padding: The grabber handle padding to set.
public func floatingPanelGrabberHandlePadding(
_ padding: CGFloat
) -> some View {
environment(\.grabberHandlePadding, padding)
}
}
@@ -0,0 +1,44 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
import SwiftUI
struct SurfaceAppearanceKey: EnvironmentKey {
static var defaultValue = SurfaceAppearance()
}
extension EnvironmentValues {
/// The appearance of a surface view.
var surfaceAppearance: SurfaceAppearance {
get { self[SurfaceAppearanceKey.self] }
set { self[SurfaceAppearanceKey.self] = newValue }
}
}
extension View {
/// Sets the surface appearance for floating panels within this view.
///
/// Use this modifier to set a specific surface appearance for floating
/// panel instances within a view:
///
/// MainView()
/// .floatingPanel { _ in
/// FloatingPanelContent()
/// }
/// .floatingPanelSurfaceAppearance(.transparent)
///
/// extension SurfaceAppearance {
/// static var transparent: SurfaceAppearance {
/// let appearance = SurfaceAppearance()
/// appearance.backgroundColor = .clear
/// return appearance
/// }
/// }
///
/// - Parameter surfaceAppearance: The surface appearance to set.
public func floatingPanelSurfaceAppearance(
_ surfaceAppearance: SurfaceAppearance
) -> some View {
environment(\.surfaceAppearance, surfaceAppearance)
}
}
@@ -0,0 +1,43 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
struct FloatingPanelContentView: View {
@State private var searchText = ""
@State private var isShowingCancelButton = false
var proxy: FloatingPanelProxy
var body: some View {
VStack(spacing: 0) {
searchBar
resultsList
}
// 👇🏻 for the floating panel grabber handle.
.padding(.top, 6)
.background(
VisualEffectBlur(blurStyle: .systemMaterial)
// If the `VisualEffectBlur` view receives taps, it's going
// to mess up with the whole panel and render it
// non-interactive, make sure it never receives any taps.
.allowsHitTesting(false)
)
.ignoresSafeArea()
}
var searchBar: some View {
SearchBar(
"Search for a place or address",
text: $searchText,
isShowingCancelButton: $isShowingCancelButton
) { isFocused in
proxy.move(to: isFocused ? .full : .half, animated: true)
isShowingCancelButton = isFocused
} onCancel: {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
var resultsList: some View {
ResultsList(onScrollViewCreated: proxy.track(scrollView:))
}
}
@@ -0,0 +1,49 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
/// A `UITableViewCell` that accepts a SwiftUI view as its content.
///
/// Credits to https://noahgilmore.com/blog/swiftui-self-sizing-cells/ .
public final class HostingCell<Content: View>: UITableViewCell {
private let hostingController = UIHostingController<Content?>(
rootView: nil,
ignoresKeyboard: true
)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
hostingController.view.backgroundColor = nil
backgroundColor = .clear
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func set(rootView: Content, parentController: UIViewController) {
hostingController.rootView = rootView
hostingController.view.invalidateIntrinsicContentSize()
let requiresControllerMove = hostingController.parent != parentController
if requiresControllerMove {
parentController.addChild(hostingController)
}
if !contentView.subviews.contains(hostingController.view) {
contentView.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraints([
hostingController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
hostingController.view.topAnchor.constraint(equalTo: contentView.topAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
if requiresControllerMove {
hostingController.didMove(toParent: parentController)
}
}
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
@main
struct MapsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.floatingPanel(delegate: SearchPanelPhoneDelegate()) { proxy in
FloatingPanelContentView(proxy: proxy)
}
.floatingPanelSurfaceAppearance(.phone)
.floatingPanelContentMode(.fitToBounds)
.floatingPanelContentInsetAdjustmentBehavior(.never)
}
}
}
@@ -0,0 +1,74 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
/// UIKit's `UISearchBar`brought to SwiftUI.
public struct SearchBar: UIViewRepresentable {
var title: String
@Binding var text: String
@Binding var isShowingCancelButton: Bool
var onEditingChanged: (Bool) -> Void
var onCancel: () -> Void
public init(
_ title: String = "",
text: Binding<String>,
isShowingCancelButton: Binding<Bool>,
onEditingChanged: @escaping (Bool) -> Void = { _ in },
onCancel: @escaping () -> Void
) {
self.title = title
self._text = text
self._isShowingCancelButton = isShowingCancelButton
self.onEditingChanged = onEditingChanged
self.onCancel = onCancel
}
public func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.searchBarStyle = .minimal
searchBar.isTranslucent = true
searchBar.placeholder = title
searchBar.delegate = context.coordinator
searchBar.autocapitalizationType = .none
return searchBar
}
public func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
uiView.placeholder = title
uiView.setShowsCancelButton(isShowingCancelButton, animated: true)
}
public func makeCoordinator() -> SearchBar.Coordinator {
Coordinator(parent: self)
}
public class Coordinator: NSObject, UISearchBarDelegate {
var parent: SearchBar
init(parent: SearchBar) {
self.parent = parent
}
public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
parent.text = searchText
}
public func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
parent.onEditingChanged(true)
}
public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
parent.onEditingChanged(false)
}
public func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
parent.onCancel()
}
}
}
@@ -0,0 +1,69 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
@available(iOS, introduced: 13, deprecated: 15, message: "Use iOS 15 material API.")
public struct VisualEffectBlur<Content: View>: UIViewRepresentable {
var blurStyle: UIBlurEffect.Style = .systemMaterial
var vibrancyStyle: UIVibrancyEffectStyle? = nil
@ViewBuilder var content: Content
public func makeUIView(context: Context) -> UIVisualEffectView {
context.coordinator.blurView
}
public func updateUIView(_ view: UIVisualEffectView, context: Context) {
context.coordinator.update(
content: content,
blurStyle: blurStyle,
vibrancyStyle: vibrancyStyle
)
}
public func makeCoordinator() -> Coordinator {
Coordinator(content: content)
}
public class Coordinator {
let blurView = UIVisualEffectView()
let vibrancyView = UIVisualEffectView()
let hostingController: UIHostingController<Content>
init(content: Content) {
hostingController = UIHostingController(rootView: content)
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostingController.view.backgroundColor = nil
blurView.contentView.addSubview(vibrancyView)
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
vibrancyView.contentView.addSubview(hostingController.view)
vibrancyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
func update(content: Content, blurStyle: UIBlurEffect.Style, vibrancyStyle: UIVibrancyEffectStyle?) {
hostingController.rootView = content
let blurEffect = UIBlurEffect(style: blurStyle)
blurView.effect = blurEffect
if let vibrancyStyle = vibrancyStyle {
vibrancyView.effect = UIVibrancyEffect(blurEffect: blurEffect, style: vibrancyStyle)
} else {
vibrancyView.effect = nil
}
hostingController.view.setNeedsDisplay()
}
}
}
public extension VisualEffectBlur where Content == EmptyView {
init(
blurStyle: UIBlurEffect.Style = .systemMaterial,
vibrancyStyle: UIVibrancyEffectStyle? = nil
) {
self.init(blurStyle: blurStyle, vibrancyStyle: vibrancyStyle) {
EmptyView()
}
}
}
@@ -0,0 +1,138 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import SwiftUI
struct ResultsList: UIViewControllerRepresentable {
var onScrollViewCreated: (_ scrollView: UIScrollView) -> Void
func makeUIViewController(
context: Context
) -> ResultsTableViewController {
let rtvc = ResultsTableViewController()
onScrollViewCreated(rtvc.tableView)
return rtvc
}
func updateUIViewController(
_ uiViewController: ResultsTableViewController,
context: Context
) {
}
}
final class ResultsTableViewController: UITableViewController {
private let reuseIdentifier = "HostingCell<ResultListCell>"
private enum Section: CaseIterable {
case main
}
private struct TableViewItem: Hashable {
let color: Color
let symbolName: String
let title: String
let description: String
}
private var dataSource: UITableViewDiffableDataSource<Section, TableViewItem>?
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = nil
tableView.register(HostingCell<ResultListCell>.self, forCellReuseIdentifier: reuseIdentifier)
configureDataSource()
}
// MARK: UITableViewDataSource
private func configureDataSource() {
dataSource = UITableViewDiffableDataSource
<Section, TableViewItem>(tableView: tableView) { [weak self] tableView, _, tableItem -> UITableViewCell? in
self?.tableView(tableView, cellForTableViewItem: tableItem)
}
tableView.dataSource = dataSource
var snapshot = NSDiffableDataSourceSnapshot<Section, TableViewItem>()
snapshot.appendSections([.main])
let results: [TableViewItem] = (1...100).map {
TableViewItem(
color: Color(red: 255 / 255.0, green: 94 / 255.0 , blue: 94 / 255.0),
symbolName: "heart.fill",
title: "Favorites",
description: "\($0) Places"
)
}
snapshot.appendItems(results, toSection: .main)
dataSource?.apply(snapshot, animatingDifferences: false)
}
private func tableView(
_ tableView: UITableView,
cellForTableViewItem tableViewItem: TableViewItem
) -> UITableViewCell {
let cell: HostingCell<ResultListCell> = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) as! HostingCell<ResultListCell>
setupResultTableViewCell(
cell,
color: tableViewItem.color,
symbolName: tableViewItem.symbolName,
title: tableViewItem.title,
description: tableViewItem.description
)
return cell
}
private func setupResultTableViewCell(
_ cell: HostingCell<ResultListCell>,
color: Color,
symbolName: String,
title: String,
description: String
) {
cell.set(
rootView: ResultListCell(
color: color,
symbolName: symbolName,
title: title,
description: description
),
parentController: self
)
}
// MARK: UITableViewDelegate
override func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath
) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
struct ResultListCell: View {
let color: Color
let symbolName: String
let title: String
let description: String
var body: some View {
HStack {
Image(systemName: symbolName)
.foregroundColor(.white)
.font(.headline)
.padding(8)
.background(Circle().fill(color))
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.system(size: 20, weight: .bold))
.frame(maxWidth: .infinity, alignment: .leading)
Text(description)
.font(.system(size: 13))
.foregroundColor(Color(.secondaryLabel))
}
}
.padding()
}
}
@@ -0,0 +1,12 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
import UIKit
final class SearchPanelPhoneDelegate: FloatingPanelControllerDelegate {
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
if vc.state == .full {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
}
@@ -0,0 +1,13 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
extension FloatingPanel.SurfaceAppearance {
static var phone: SurfaceAppearance {
let appearance = SurfaceAppearance()
appearance.cornerCurve = .continuous
appearance.cornerRadius = 8.0
appearance.backgroundColor = .clear
return appearance
}
}
+57 -19
View File
@@ -7,13 +7,18 @@
objects = {
/* Begin PBXBuildFile section */
543844BD23D2BE2000D5EDE4 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 543844BC23D2BE2000D5EDE4 /* MapKit.framework */; };
549A5F59244673FE0025F312 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549A5F58244673FE0025F312 /* SearchViewController.swift */; };
549D23D2233C77D5008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */; };
549D23D3233C77D5008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B5112A216C3D840033A6F3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51129216C3D840033A6F3 /* AppDelegate.swift */; };
54B5112C216C3D840033A6F3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B5112B216C3D840033A6F3 /* ViewController.swift */; };
54B5112C216C3D840033A6F3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B5112B216C3D840033A6F3 /* MainViewController.swift */; };
54B5112F216C3D840033A6F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54B5112D216C3D840033A6F3 /* Main.storyboard */; };
54B51131216C3D860033A6F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54B51130216C3D860033A6F3 /* Assets.xcassets */; };
54B51134216C3D860033A6F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */; };
54B5113F216C407F0033A6F3 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113E216C407F0033A6F3 /* FloatingPanel.framework */; };
54B51140216C407F0033A6F3 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113E216C407F0033A6F3 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
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 */
@@ -23,7 +28,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
54B51140216C407F0033A6F3 /* FloatingPanel.framework in Embed Frameworks */,
549D23D3233C77D5008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -31,14 +36,19 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
543844BC23D2BE2000D5EDE4 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
549A5F58244673FE0025F312 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54B51126216C3D840033A6F3 /* Maps.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Maps.app; sourceTree = BUILT_PRODUCTS_DIR; };
54B51129216C3D840033A6F3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
54B5112B216C3D840033A6F3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
54B5112B216C3D840033A6F3 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
54B5112E216C3D840033A6F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
54B51130216C3D860033A6F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
54B51133216C3D860033A6F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
54B51135216C3D860033A6F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54B5113E216C407F0033A6F3 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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 */
@@ -46,19 +56,31 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
54B5113F216C407F0033A6F3 /* FloatingPanel.framework in Frameworks */,
543844BD23D2BE2000D5EDE4 /* MapKit.framework in Frameworks */,
5D82A6A728D18422006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
549D23D2233C77D5008EF4D7 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
543844BB23D2BE1F00D5EDE4 /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6A628D1841E006A44BA /* libswiftCoreGraphics.tbd */,
543844BC23D2BE2000D5EDE4 /* MapKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
54B5111D216C3D840033A6F3 = {
isa = PBXGroup;
children = (
54B5113E216C407F0033A6F3 /* FloatingPanel.framework */,
549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */,
54B51128216C3D840033A6F3 /* Maps */,
54B51127216C3D840033A6F3 /* Products */,
543844BB23D2BE1F00D5EDE4 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -73,11 +95,14 @@
54B51128216C3D840033A6F3 /* Maps */ = {
isa = PBXGroup;
children = (
54B51129216C3D840033A6F3 /* AppDelegate.swift */,
54B5112B216C3D840033A6F3 /* ViewController.swift */,
54B5112D216C3D840033A6F3 /* Main.storyboard */,
54B51130216C3D860033A6F3 /* Assets.xcassets */,
54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */,
54B5112D216C3D840033A6F3 /* Main.storyboard */,
54B51129216C3D840033A6F3 /* AppDelegate.swift */,
54B5112B216C3D840033A6F3 /* MainViewController.swift */,
549A5F58244673FE0025F312 /* SearchViewController.swift */,
54E26CB724A98E310066C720 /* DetailViewController.swift */,
54E26CB524A989090066C720 /* Utils.swift */,
54B51135216C3D860033A6F3 /* Info.plist */,
);
path = Maps;
@@ -111,7 +136,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
54B51125216C3D840033A6F3 = {
@@ -155,8 +180,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
54B5112C216C3D840033A6F3 /* ViewController.swift in Sources */,
549A5F59244673FE0025F312 /* SearchViewController.swift in Sources */,
54B5112C216C3D840033A6F3 /* MainViewController.swift in Sources */,
54E26CB824A98E310066C720 /* DetailViewController.swift in Sources */,
54B5112A216C3D840033A6F3 /* AppDelegate.swift in Sources */,
54E26CB624A989090066C720 /* Utils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -208,6 +236,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -269,6 +298,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -305,14 +335,18 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Maps/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -324,14 +358,18 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Maps/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
ReferencedContainer = "container:Maps.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -63,8 +61,6 @@
ReferencedContainer = "container:Maps.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
+1 -11
View File
@@ -1,19 +1,9 @@
//
// AppDelegate.swift
// Maps
//
// Created by Shin Yamamoto on 2018/10/09.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
// 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]?) -> Bool {
return true
}
}
+74 -40
View File
@@ -1,35 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina5_9" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<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-->
<!--Main View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
<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="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
</mapView>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d9i-3g-8Ja">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
<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="375" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</view>
<blurEffect style="light"/>
<blurEffect style="prominent"/>
</visualEffectView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="5Jw-n2-Cpw" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailing" id="1Fg-Be-qfh"/>
@@ -41,7 +38,6 @@
<constraint firstItem="6Tk-OE-BBY" firstAttribute="top" secondItem="d9i-3g-8Ja" secondAttribute="bottom" id="Y1G-hr-aEX"/>
<constraint firstItem="d9i-3g-8Ja" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="diB-Ij-HN3"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<connections>
<outlet property="mapView" destination="5Jw-n2-Cpw" id="WVC-rU-mLe"/>
@@ -51,36 +47,36 @@
</objects>
<point key="canvasLocation" x="136.80000000000001" y="133.00492610837438"/>
</scene>
<!--Search Panel View Controller-->
<!--Search View Controller-->
<scene sceneID="kXy-li-p3C">
<objects>
<viewController storyboardIdentifier="SearchPanel" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0S1-Lk-JgE" customClass="SearchPanelViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
<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="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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="375" height="900"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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="375" height="900"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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="375" height="56"/>
<rect key="frame" x="0.0" y="6" width="600" height="51"/>
<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="66" width="375" height="748"/>
<rect key="frame" x="0.0" y="61" width="600" height="539"/>
<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="375" height="116"/>
<rect key="frame" x="0.0" y="0.0" width="600" 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.666666666666664" width="327" height="97.333333333333343"/>
<rect key="frame" x="24" y="10.5" width="552" height="97.5"/>
<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.333333333333329"/>
<rect key="frame" x="0.0" y="0.0" width="60" height="97.5"/>
<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"/>
@@ -90,7 +86,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.333333333333329"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@@ -98,7 +94,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="0vd-sD-XKv">
<rect key="frame" x="89" y="0.0" width="60" height="97.333333333333329"/>
<rect key="frame" x="164" y="0.0" width="60" height="97.5"/>
<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"/>
@@ -108,7 +104,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.333333333333329"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<string key="text">Shopping
</string>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
@@ -118,7 +114,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Jd8-YL-b5s">
<rect key="frame" x="178" y="0.0" width="60" height="97.333333333333329"/>
<rect key="frame" x="328" y="0.0" width="60" height="97.5"/>
<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"/>
@@ -128,7 +124,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.333333333333329"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<string key="text">Fun
</string>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
@@ -138,7 +134,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="dTL-e1-Arz">
<rect key="frame" x="267" y="0.0" width="60" height="97.333333333333329"/>
<rect key="frame" x="492" y="0.0" width="60" height="97.5"/>
<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"/>
@@ -148,7 +144,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.333333333333329"/>
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
<string key="text">Travel
</string>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
@@ -170,10 +166,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="144" width="375" height="70"/>
<rect key="frame" x="0.0" y="160.5" width="600" 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="375" height="69.666666666666671"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="70"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="like" translatesAutoresizingMaskIntoConstraints="NO" id="GEk-yE-lLq">
@@ -185,16 +181,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="11.999999999999996" width="303" height="45.666666666666657"/>
<rect key="frame" x="57" y="12" width="528" 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="303" height="22"/>
<rect key="frame" x="0.0" y="0.0" width="528" 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="303" height="21.666666666666671"/>
<rect key="frame" x="0.0" y="24" width="528" 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"/>
@@ -227,22 +223,22 @@
<constraint firstAttribute="trailing" secondItem="D7r-re-InH" secondAttribute="trailing" id="BES-GA-Btp"/>
<constraint firstItem="D7r-re-InH" firstAttribute="leading" secondItem="ED1-gT-FBj" secondAttribute="leading" id="UTe-YL-17h"/>
<constraint firstItem="Zcj-SE-gb8" firstAttribute="top" secondItem="ED1-gT-FBj" secondAttribute="top" constant="6" id="apU-Pd-PEO"/>
<constraint firstAttribute="bottom" secondItem="D7r-re-InH" secondAttribute="bottom" constant="86" id="vfS-Lx-TXz"/>
<constraint firstAttribute="bottom" secondItem="D7r-re-InH" secondAttribute="bottom" id="vfS-Lx-TXz"/>
<constraint firstItem="D7r-re-InH" firstAttribute="top" secondItem="Zcj-SE-gb8" secondAttribute="bottom" constant="4" id="vro-cd-B9c"/>
<constraint firstItem="Zcj-SE-gb8" firstAttribute="leading" secondItem="ED1-gT-FBj" secondAttribute="leading" id="wMb-L2-Z0W"/>
</constraints>
</view>
<blurEffect style="extraLight"/>
<blurEffect style="prominent"/>
</visualEffectView>
</subviews>
<viewLayoutGuide key="safeArea" id="G74-X7-Za8"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Ye3-uU-bq3" firstAttribute="leading" secondItem="G74-X7-Za8" secondAttribute="leading" id="Kr2-sU-ZWZ"/>
<constraint firstItem="Ye3-uU-bq3" firstAttribute="bottom" secondItem="Ncl-E9-yRn" secondAttribute="bottom" constant="88" id="aWM-s3-3o4"/>
<constraint firstItem="Ye3-uU-bq3" firstAttribute="bottom" secondItem="Ncl-E9-yRn" secondAttribute="bottom" id="aWM-s3-3o4"/>
<constraint firstItem="Ye3-uU-bq3" firstAttribute="trailing" secondItem="G74-X7-Za8" secondAttribute="trailing" id="fEL-8y-Acc"/>
<constraint firstItem="Ye3-uU-bq3" firstAttribute="top" secondItem="Ncl-E9-yRn" secondAttribute="top" id="w77-ba-FrJ"/>
</constraints>
<viewLayoutGuide key="safeArea" id="G74-X7-Za8"/>
</view>
<connections>
<outlet property="searchBar" destination="Zcj-SE-gb8" id="BH7-Gy-RG5"/>
@@ -254,6 +250,44 @@
</objects>
<point key="canvasLocation" x="789.60000000000002" y="133.5832083958021"/>
</scene>
<!--Detail View Controller-->
<scene sceneID="5tH-Ya-PzB">
<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"/>
<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"/>
<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"/>
<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"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableView>
</subviews>
<constraints>
<constraint firstItem="kP7-56-wlG" firstAttribute="leading" secondItem="9fL-a5-0LS" secondAttribute="leading" id="6gf-GO-cnv"/>
<constraint firstAttribute="bottom" secondItem="kP7-56-wlG" secondAttribute="bottom" id="WrH-tz-UQF"/>
<constraint firstItem="kP7-56-wlG" firstAttribute="top" secondItem="9fL-a5-0LS" secondAttribute="top" id="aJk-bz-lVc"/>
<constraint firstAttribute="trailing" secondItem="kP7-56-wlG" secondAttribute="trailing" id="sE0-9V-Rot"/>
</constraints>
</view>
<blurEffect style="extraLight"/>
</visualEffectView>
</subviews>
<viewLayoutGuide key="safeArea" id="ctv-Dd-JUc"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="EDp-D2-xcT" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1447" y="133"/>
</scene>
</scenes>
<resources>
<image name="food" width="60" height="60"/>
@@ -0,0 +1,7 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
class DetailViewController: UIViewController {
var item: LocationItem?
}
+394
View File
@@ -0,0 +1,394 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import MapKit
import FloatingPanel
class MainViewController: UIViewController {
typealias PanelDelegate = FloatingPanelControllerDelegate & UIGestureRecognizerDelegate
// Search Panel
lazy var fpc = FloatingPanelController()
lazy var fpcDelegate: PanelDelegate =
(traitCollection.userInterfaceIdiom == .pad) ? SearchPanelPadDelegate(owner: self) : SearchPanelPhoneDelegate(owner: self)
lazy var searchVC =
storyboard?.instantiateViewController(withIdentifier: "SearchViewController") as! SearchViewController
// Detail Panel
lazy var detailFpc = FloatingPanelController()
lazy var detailFpcDelegate: PanelDelegate =
(traitCollection.userInterfaceIdiom == .pad) ? DetailPanelPadDelegate(owner: self) : DetailPanelPhoneDelegate(owner: self)
lazy var detailVC =
storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
@IBOutlet weak var mapView: MKMapView!
}
extension MainViewController {
override func viewDidLoad() {
super.viewDidLoad()
fpc.contentMode = .fitToBounds
fpc.delegate = fpcDelegate
fpc.set(contentViewController: searchVC)
fpc.track(scrollView: searchVC.tableView)
detailFpc.isRemovalInteractionEnabled = true
detailFpc.set(contentViewController: detailVC)
switch traitCollection.userInterfaceIdiom {
case .pad:
layoutPanelForPad()
default:
layoutPanelForPhone()
}
setUpMapView()
setUpSearchView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Must be here
searchVC.searchBar.delegate = self
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
tearDownMapView()
}
}
extension MainViewController {
func layoutPanelForPad() {
fpc.behavior = SearchPaneliPadBehavior()
fpc.panGestureRecognizer.delegateProxy = fpcDelegate
// Not use addPanel(toParent:) because of the Auto Layout configuration of fpc.view.
view.addSubview(fpc.view)
addChild(fpc)
fpc.view.frame = view.bounds // Needed for a correct safe area configuration
fpc.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
fpc.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
fpc.view.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0 ),
fpc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
fpc.view.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
])
fpc.show(animated: false) { [weak self] in
guard let self = self else { return }
self.didMove(toParent: self)
}
[fpc, detailFpc].forEach { $0.setAppearanceForPad() }
}
func layoutPanelForPhone() {
fpc.track(scrollView: searchVC.tableView) // Only track the table view on iPhone
fpc.addPanel(toParent: self, animated: true)
[fpc, detailFpc].forEach { $0.setAppearanceForPhone()}
}
}
extension FloatingPanelController {
func setAppearanceForPhone() {
let appearance = SurfaceAppearance()
if #available(iOS 13.0, *) {
appearance.cornerCurve = .continuous
}
appearance.cornerRadius = 8.0
appearance.backgroundColor = .clear
surfaceView.appearance = appearance
}
func setAppearanceForPad() {
view.clipsToBounds = false
let appearance = SurfaceAppearance()
appearance.cornerRadius = 8.0
let shadow = SurfaceAppearance.Shadow()
shadow.color = UIColor.black
shadow.offset = CGSize(width: 0, height: 16)
shadow.radius = 16
shadow.spread = 8
appearance.shadows = [shadow]
appearance.backgroundColor = .clear
surfaceView.appearance = appearance
}
}
// MARK: - UISearchBarDelegate
extension MainViewController: UISearchBarDelegate {
func activate(searchBar: UISearchBar) {
searchBar.showsCancelButton = true
searchVC.showHeader(animated: true)
searchVC.tableView.alpha = 1.0
detailVC.dismiss(animated: true, completion: nil)
}
func deactivate(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
searchBar.showsCancelButton = false
searchVC.hideHeader(animated: true)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
deactivate(searchBar: searchBar)
UIView.animate(withDuration: 0.25) {
self.fpc.move(to: .half, animated: false)
}
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
activate(searchBar: searchBar)
UIView.animate(withDuration: 0.25) { [weak self] in
self?.fpc.move(to: .full, animated: false)
}
}
}
// MARK: - iPhone
class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: MainViewController
init(owner: MainViewController) {
self.owner = owner
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
switch newCollection.verticalSizeClass {
case .compact:
let appearance = vc.surfaceView.appearance
appearance.borderWidth = 1.0 / owner.traitCollection.displayScale
appearance.borderColor = UIColor.black.withAlphaComponent(0.2)
vc.surfaceView.appearance = appearance
return SearchPanelLandscapeLayout()
default:
let appearance = vc.surfaceView.appearance
appearance.borderWidth = 0.0
appearance.borderColor = nil
vc.surfaceView.appearance = appearance
return FloatingPanelBottomLayout()
}
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {
debugPrint("surfaceLocation: ", vc.surfaceLocation)
let loc = vc.surfaceLocation
if vc.isAttracting == false {
let minY = vc.surfaceLocation(for: .full).y - 6.0
let maxY = vc.surfaceLocation(for: .tip).y + 6.0
vc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY))
}
let tipY = vc.surfaceLocation(for: .tip).y
if loc.y > tipY - 44.0 {
let progress = max(0.0, min((tipY - loc.y) / 44.0, 1.0))
owner.searchVC.tableView.alpha = progress
} else {
owner.searchVC.tableView.alpha = 1.0
}
debugPrint("NearbyState : ",vc.nearbyState)
}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
if vc.state == .full {
owner.searchVC.searchBar.showsCancelButton = false
owner.searchVC.searchBar.resignFirstResponder()
}
}
func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
if targetState.pointee != .full {
owner.searchVC.hideHeader(animated: true)
}
}
}
class SearchPanelLandscapeLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .tip
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),
]
}
}
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.0
}
}
class DetailPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: MainViewController
init(owner: MainViewController) {
self.owner = owner
}
}
class DetailPanelPhoneLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
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
}
}
// MARK: - iPad
class SearchPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: MainViewController
init(owner: MainViewController) {
self.owner = owner
}
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
if newCollection.horizontalSizeClass == .compact {
fpc.surfaceView.containerMargins = .zero
return FloatingPanelBottomLayout()
}
fpc.surfaceView.containerMargins = UIEdgeInsets(top: .leastNonzeroMagnitude, // For top left/right rounding corners
left: 16,
bottom: 0.0,
right: 0.0)
return SearchPanelPadLayout()
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
if vc.state == .full {
owner.searchVC.searchBar.showsCancelButton = false
owner.searchVC.searchBar.resignFirstResponder()
}
}
func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>) {
if targetState.pointee != .full {
owner.searchVC.hideHeader(animated: true)
}
}
}
class SearchPanelPadLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .top
let initialState: FloatingPanelState = .tip
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
}
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
return [
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor),
surfaceView.widthAnchor.constraint(equalToConstant: 375),
]
}
}
class SearchPaneliPadBehavior: FloatingPanelBehavior {
var springDecelerationRate: CGFloat {
return UIScrollView.DecelerationRate.fast.rawValue - 0.003
}
var springResponseTime: CGFloat {
return 0.3
}
var momentumProjectionRate: CGFloat {
return UIScrollView.DecelerationRate.fast.rawValue
}
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
return true
}
}
class DetailPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: MainViewController
init(owner: MainViewController) {
self.owner = owner
}
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
if newCollection.horizontalSizeClass == .compact {
fpc.surfaceView.containerMargins = .zero
return FloatingPanelBottomLayout()
}
if let item = owner.detailVC.item, item.title.contains("Right") {
fpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: .leastNonzeroMagnitude)
return DetailPanelPadRightLayout()
}
fpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: .leastNonzeroMagnitude, bottom: 0.0, right: 0.0)
return DetailPanelPadLeftLayout()
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
class DetailPanelPadLeftLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .left
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
}
}
class DetailPanelPadRightLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .right
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
}
}
// MARK: - MKMapViewDelegate
extension MainViewController: MKMapViewDelegate {
func setUpMapView() {
let center = CLLocationCoordinate2D(latitude: 37.623198015869235,
longitude: -122.43066818432008)
let span = MKCoordinateSpan(latitudeDelta: 0.4425100023575723,
longitudeDelta: 0.28543697435880233)
let region = MKCoordinateRegion(center: center, span: span)
mapView.region = region
mapView.showsCompass = true
mapView.showsUserLocation = true
mapView.delegate = self
}
func tearDownMapView() {
// Prevent a crash
mapView.delegate = nil
mapView = nil
}
}
@@ -0,0 +1,184 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
// MARK: - UITableViewDelegate
extension MainViewController: UITableViewDelegate {
func setUpSearchView() {
searchVC.loadViewIfNeeded()
searchVC.tableView.delegate = self
searchVC.searchBar.placeholder = "Search for a place or address"
let isPad = (traitCollection.userInterfaceIdiom == .pad)
searchVC.items = [
.init(mark: "mark", title: "Marked Location" + (isPad ? " (Left panel)" : ""), subtitle: "Golden Gate Bridge, San Francisco"),
.init(mark: "mark", title: "Marked Location" + (isPad ? " (Right panel)" : ""), subtitle: "San Francisco Museum of Modern Art"),
]
searchVC.items.append(contentsOf: (0...98).map {
.init(mark: "like", title: "Favorites", subtitle: "\($0) Places")
})
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
deactivate(searchBar: searchVC.searchBar)
// Show a detail panel
switch indexPath.row {
case 0:
detailVC.item = searchVC.items[safe: 0]
// Show detail vc in the left positioned panel
switch traitCollection.userInterfaceIdiom {
case .pad:
detailFpc.layout = DetailPanelPadLeftLayout()
detailFpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: 16.0, bottom: 0.0, right: 0.0)
default:
detailFpc.layout = DetailPanelPhoneLayout()
detailFpc.surfaceView.containerMargins = .zero
}
detailFpc.addPanel(toParent: self, animated: true)
case 1:
detailVC.item = searchVC.items[safe: 1]
// Show detail vc in the right positioned panel
switch traitCollection.userInterfaceIdiom {
case .pad:
detailFpc.layout = DetailPanelPadRightLayout()
detailFpc.surfaceView.containerMargins = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0)
default:
detailFpc.layout = DetailPanelPhoneLayout()
detailFpc.surfaceView.containerMargins = .zero
}
detailFpc.addPanel(toParent: self, animated: true)
default:
break
}
}
}
// MARK: - Models
struct LocationItem {
let mark: String
let title: String
let subtitle: String
init(mark: String, title: String, subtitle: String) {
self.mark = mark
self.title = title
self.subtitle = subtitle
}
}
// MARK: -
class SearchViewController: UIViewController, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var visualEffectView: UIVisualEffectView!
var items: [LocationItem] = []
// For iOS 10 only
private lazy var shadowLayer: CAShapeLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
searchBar.setSearchText(fontSize: 15.0)
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
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if let cell = cell as? SearchCell, let item = items[safe: indexPath.row] {
cell.iconImageView.image = UIImage(named: item.mark)
cell.titleLabel.text = item.title
cell.subTitleLabel.text = item.subtitle
}
return cell
}
func showHeader(animated: Bool) {
changeHeader(height: 116.0, aniamted: animated)
}
func hideHeader(animated: Bool) {
changeHeader(height: 0.0, aniamted: animated)
}
private func changeHeader(height: CGFloat, aniamted: Bool) {
guard let headerView = tableView.tableHeaderView, headerView.bounds.height != height else { return }
if aniamted == false {
updateHeader(height: height)
return
}
tableView.beginUpdates()
UIView.animate(withDuration: 0.25) {
self.updateHeader(height: height)
}
tableView.endUpdates()
}
private func updateHeader(height: CGFloat) {
guard let headerView = tableView.tableHeaderView else { return }
var frame = headerView.frame
frame.size.height = height
self.tableView.tableHeaderView?.frame = frame
}
}
class SearchCell: UITableViewCell {
@IBOutlet weak var iconImageView: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subTitleLabel: UILabel!
}
class SearchHeaderView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.clipsToBounds = true
}
}
extension UISearchBar {
func setSearchText(fontSize: CGFloat) {
if #available(iOS 13, *) {
let font = searchTextField.font
searchTextField.font = font?.withSize(fontSize)
} else {
let textField = value(forKey: "_searchField") as! UITextField
textField.font = textField.font?.withSize(fontSize)
}
}
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
extension UIViewController {
var isLandscape: Bool {
if #available(iOS 13.0, *) {
return view.window?.windowScene?.interfaceOrientation.isLandscape ?? false
} else {
return UIApplication.shared.statusBarOrientation.isLandscape
}
}
}
-254
View File
@@ -1,254 +0,0 @@
//
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
import MapKit
import FloatingPanel
class ViewController: UIViewController, MKMapViewDelegate, UISearchBarDelegate, FloatingPanelControllerDelegate {
var fpc: FloatingPanelController!
var searchVC: SearchPanelViewController!
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.cornerRadius = 9.0
fpc.surfaceView.shadowHidden = false
searchVC = storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as? SearchPanelViewController
// Set a content view controller
fpc.set(contentViewController: searchVC)
fpc.track(scrollView: searchVC.tableView)
setupMapView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Add FloatingPanel to a view with animation.
fpc.addPanel(toParent: self, animated: true)
// Must be here
searchVC.searchBar.delegate = self
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
teardownMapView()
}
func setupMapView() {
let center = CLLocationCoordinate2D(latitude: 37.623198015869235,
longitude: -122.43066818432008)
let span = MKCoordinateSpan(latitudeDelta: 0.4425100023575723,
longitudeDelta: 0.28543697435880233)
let region = MKCoordinateRegion(center: center, span: span)
mapView.region = region
mapView.showsCompass = true
mapView.showsUserLocation = true
mapView.delegate = self
}
func teardownMapView() {
// Prevent a crash
mapView.delegate = nil
mapView = nil
}
// MARK: UISearchBarDelegate
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
searchBar.showsCancelButton = false
searchVC.hideHeader()
fpc.move(to: .half, animated: true)
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = true
searchVC.showHeader()
searchVC.tableView.alpha = 1.0
fpc.move(to: .full, animated: true)
}
// MARK: FloatingPanelControllerDelegate
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
switch newCollection.verticalSizeClass {
case .compact:
fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale
fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2)
return SearchPanelLandscapeLayout()
default:
fpc.surfaceView.borderWidth = 0.0
fpc.surfaceView.borderColor = nil
return nil
}
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {
let y = vc.surfaceView.frame.origin.y
let tipY = vc.originYOfSurface(for: .tip)
if y > tipY - 44.0 {
let progress = max(0.0, min((tipY - y) / 44.0, 1.0))
self.searchVC.tableView.alpha = progress
}
}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
if vc.position == .full {
searchVC.searchBar.showsCancelButton = false
searchVC.searchBar.resignFirstResponder()
}
}
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {
if targetPosition != .full {
searchVC.hideHeader()
}
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: .allowUserInteraction,
animations: {
if targetPosition == .tip {
self.searchVC.tableView.alpha = 0.0
} else {
self.searchVC.tableView.alpha = 1.0
}
}, completion: nil)
}
}
class SearchPanelViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var visualEffectView: UIVisualEffectView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
searchBar.placeholder = "Search for a place or address"
let textField = searchBar.value(forKey: "_searchField") as! UITextField
textField.font = UIFont(name: textField.font!.fontName, size: 15.0)
hideHeader()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 10, *) {
visualEffectView.layer.cornerRadius = 9.0
visualEffectView.clipsToBounds = true
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if let cell = cell as? SearchCell {
switch indexPath.row {
case 0:
cell.iconImageView.image = UIImage(named: "mark")
cell.titleLabel.text = "Marked Location"
cell.subTitleLabel.text = "Golden Gate Bridge, San Francisco"
case 1:
cell.iconImageView.image = UIImage(named: "like")
cell.titleLabel.text = "Favorites"
cell.subTitleLabel.text = "0 Places"
default:
break
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func showHeader() {
changeHeader(height: 116.0)
}
func hideHeader() {
changeHeader(height: 0.0)
}
func changeHeader(height: CGFloat) {
tableView.beginUpdates()
if let headerView = tableView.tableHeaderView {
UIView.animate(withDuration: 0.25) {
var frame = headerView.frame
frame.size.height = height
self.tableView.tableHeaderView?.frame = frame
}
}
tableView.endUpdates()
}
}
public class SearchPanelLandscapeLayout: FloatingPanelLayout {
public var initialPosition: FloatingPanelPosition {
return .tip
}
public var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .tip]
}
public func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .tip: return 69.0
default: return nil
}
}
public 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),
]
}
}
public func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.0
}
}
class SearchCell: UITableViewCell {
@IBOutlet weak var iconImageView: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subTitleLabel: UILabel!
}
class SearchHeaderView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.clipsToBounds = true
}
}
@@ -7,17 +7,34 @@
objects = {
/* Begin PBXBuildFile section */
5442E22425FC51AF00A26F43 /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22325FC51AF00A26F43 /* ImageViewController.swift */; };
5442E22825FC51E200A26F43 /* MultiPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22725FC51E200A26F43 /* MultiPanelController.swift */; };
5442E22C25FC521F00A26F43 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22B25FC521F00A26F43 /* SettingsViewController.swift */; };
5442E23025FC525200A26F43 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E22F25FC525200A26F43 /* TabBarViewController.swift */; };
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E23325FC528400A26F43 /* DetailViewController.swift */; };
5442E23A25FC52CD00A26F43 /* ModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E23925FC52CD00A26F43 /* ModalViewController.swift */; };
5442E24025FC533800A26F43 /* DebugTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E23F25FC533800A26F43 /* DebugTableViewController.swift */; };
5442E24425FC538200A26F43 /* InspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E24325FC538200A26F43 /* InspectorViewController.swift */; };
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 /* AdaptiveLayoutTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */; };
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9ED21511E6300CA77B8 /* AppDelegate.swift */; };
545DB9F021511E6300CA77B8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9EF21511E6300CA77B8 /* ViewController.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 */; };
54B51116216AFE5F0033A6F3 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* UIExtensions.swift */; };
54B5113C216C40670033A6F3 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113B216C40670033A6F3 /* FloatingPanel.framework */; };
54B5113D216C40670033A6F3 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113B216C40670033A6F3 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* UIComponents.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 */; };
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD35A263A75EB006A36EA /* PanelLayouts.swift */; };
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD364263A765F006A36EA /* PagePanelController.swift */; };
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -38,13 +55,13 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
54B5111C216C3B300033A6F3 /* Embed Frameworks */ = {
549D23CD233C7779008EF4D7 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
54B5113D216C40670033A6F3 /* FloatingPanel.framework in Embed Frameworks */,
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -52,9 +69,21 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5442E22325FC51AF00A26F43 /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
5442E22725FC51E200A26F43 /* MultiPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPanelController.swift; sourceTree = "<group>"; };
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
5442E22F25FC525200A26F43 /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = "<group>"; };
5442E23325FC528400A26F43 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
5442E23925FC52CD00A26F43 /* ModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalViewController.swift; sourceTree = "<group>"; };
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTableViewController.swift; sourceTree = "<group>"; };
5442E24325FC538200A26F43 /* InspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorViewController.swift; sourceTree = "<group>"; };
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 /* AdaptiveLayoutTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveLayoutTestViewController.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 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
545DB9EF21511E6300CA77B8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
545DB9F221511E6300CA77B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
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>"; };
@@ -65,9 +94,14 @@
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>"; };
54B51115216AFE5F0033A6F3 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
54B5113B216C40670033A6F3 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54CDC5D7215BBE23007D205C /* UIComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIComponents.swift; 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>"; };
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>"; };
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 */
@@ -75,7 +109,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
54B5113C216C40670033A6F3 /* FloatingPanel.framework in Frameworks */,
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -96,15 +131,33 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
5442E22225FC519700A26F43 /* ContentViewControllers */ = {
isa = PBXGroup;
children = (
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */,
5442E24925FC53C100A26F43 /* DebugTextViewController.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 */,
544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */,
);
path = ContentViewControllers;
sourceTree = "<group>";
};
545DB9E121511E6300CA77B8 = {
isa = PBXGroup;
children = (
54B5113B216C40670033A6F3 /* FloatingPanel.framework */,
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */,
545DB9EC21511E6300CA77B8 /* Sources */,
545DBA0121511E6400CA77B8 /* Tests */,
545DBA0C21511E6400CA77B8 /* UITests */,
545DB9EB21511E6300CA77B8 /* Products */,
545DBA1B2151CC1000CA77B8 /* Frameworks */,
5D82A6AB28D18438006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -125,9 +178,13 @@
545DB9F621511E6400CA77B8 /* LaunchScreen.storyboard */,
545DB9F121511E6300CA77B8 /* Main.storyboard */,
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */,
545DB9EF21511E6300CA77B8 /* ViewController.swift */,
54B51115216AFE5F0033A6F3 /* UIExtensions.swift */,
54CDC5D7215BBE23007D205C /* UIComponents.swift */,
545DB9EF21511E6300CA77B8 /* MainViewController.swift */,
546341AA25C6421000CA0596 /* UseCases */,
5442E22225FC519700A26F43 /* ContentViewControllers */,
54EAD35A263A75EB006A36EA /* PanelLayouts.swift */,
546341AB25C6426500CA0596 /* CustomState.swift */,
54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */,
54B51115216AFE5F0033A6F3 /* Extensions.swift */,
545DB9F921511E6400CA77B8 /* Info.plist */,
);
path = Sources;
@@ -151,9 +208,20 @@
path = UITests;
sourceTree = "<group>";
};
545DBA1B2151CC1000CA77B8 /* Frameworks */ = {
546341AA25C6421000CA0596 /* UseCases */ = {
isa = PBXGroup;
children = (
546341A025C6415100CA0596 /* UseCase.swift */,
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */,
54EAD364263A765F006A36EA /* PagePanelController.swift */,
);
path = UseCases;
sourceTree = "<group>";
};
5D82A6AB28D18438006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -168,7 +236,7 @@
545DB9E621511E6300CA77B8 /* Sources */,
545DB9E721511E6300CA77B8 /* Frameworks */,
545DB9E821511E6300CA77B8 /* Resources */,
54B5111C216C3B300033A6F3 /* Embed Frameworks */,
549D23CD233C7779008EF4D7 /* Embed Frameworks */,
);
buildRules = (
);
@@ -222,7 +290,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
545DB9E921511E6300CA77B8 = {
@@ -290,10 +358,26 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */,
54B51116216AFE5F0033A6F3 /* UIExtensions.swift in Sources */,
545DB9F021511E6300CA77B8 /* ViewController.swift in Sources */,
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */,
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */,
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */,
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
544BC56826CC918200D0A436 /* AdaptiveLayoutTestViewController.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 */,
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */,
5442E22825FC51E200A26F43 /* MultiPanelController.swift in Sources */,
546341A125C6415100CA0596 /* UseCase.swift in Sources */,
545DB9F021511E6300CA77B8 /* MainViewController.swift in Sources */,
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */,
5442E23025FC525200A26F43 /* TabBarViewController.swift in Sources */,
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */,
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */,
5442E24425FC538200A26F43 /* InspectorViewController.swift in Sources */,
5442E22C25FC521F00A26F43 /* SettingsViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -374,6 +458,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -435,6 +520,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -471,14 +557,18 @@
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",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -490,14 +580,18 @@
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",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
ReferencedContainer = "container:Samples.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -61,8 +59,6 @@
ReferencedContainer = "container:Samples.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
+1 -4
View File
@@ -1,7 +1,4 @@
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "IMG_0003.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<device id="retina5_9" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" 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="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<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"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Navigation Controller-->
<scene sceneID="Cjh-iX-VQw">
<objects>
<navigationController id="RoN-h0-uBD" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="hNW-5m-Omi">
<rect key="frame" x="0.0" y="44" width="375" height="44"/>
<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="48" width="414" height="96"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
@@ -29,24 +29,24 @@
<!--Samples-->
<scene sceneID="35L-Gs-Vts">
<objects>
<viewController id="jF4-A0-Eq6" customClass="SampleListViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<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="375" height="812"/>
<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="375" height="778"/>
<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="375" height="44"/>
<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="375" height="43.666666666666664"/>
<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="15" y="0.0" width="345" height="43.666666666666664"/>
<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"/>
@@ -58,16 +58,22 @@
</prototypes>
</tableView>
</subviews>
<viewLayoutGuide key="safeArea" id="39L-Nq-qfp"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="7IS-PU-x0P" firstAttribute="top" secondItem="Smh-Bd-AAc" secondAttribute="top" id="6yd-jv-ey3"/>
<constraint firstItem="7IS-PU-x0P" firstAttribute="leading" secondItem="TkN-Oh-wF8" secondAttribute="leading" id="Z6Y-Dc-cei"/>
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="TkN-Oh-wF8" secondAttribute="bottom" id="fNW-DP-lhV"/>
<constraint firstItem="7IS-PU-x0P" firstAttribute="trailing" secondItem="TkN-Oh-wF8" secondAttribute="trailing" id="vfY-Rc-FOI"/>
<constraint firstItem="7IS-PU-x0P" firstAttribute="leading" secondItem="39L-Nq-qfp" secondAttribute="leading" id="Z6Y-Dc-cei"/>
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="Smh-Bd-AAc" secondAttribute="bottom" id="fNW-DP-lhV"/>
<constraint firstItem="7IS-PU-x0P" firstAttribute="trailing" secondItem="39L-Nq-qfp" secondAttribute="trailing" id="vfY-Rc-FOI"/>
</constraints>
<viewLayoutGuide key="safeArea" id="TkN-Oh-wF8"/>
</view>
<navigationItem key="navigationItem" title="Samples" id="wCF-su-7up"/>
<navigationItem key="navigationItem" title="Samples" id="wCF-su-7up">
<barButtonItem key="rightBarButtonItem" title="Settings" id="rbH-U3-XyA">
<connections>
<action selector="showDebugMenu:" destination="jF4-A0-Eq6" id="j02-db-ZM5"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="tableView" destination="7IS-PU-x0P" id="YFM-9W-eP4"/>
</connections>
@@ -76,22 +82,106 @@
</objects>
<point key="canvasLocation" x="57" y="27"/>
</scene>
<!--Item 2-->
<!--Settings View Controller-->
<scene sceneID="Bd0-D2-agO">
<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"/>
<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">
<rect key="frame" x="32" y="16" width="311" height="149.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="DCi-Iv-o6d">
<rect key="frame" x="0.0" y="0.0" width="311" height="53"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version: 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WmC-Tq-NDN">
<rect key="frame" x="118.5" y="0.0" width="74.5" height="17.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UINavigationBar" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ulg-gS-ah0">
<rect key="frame" x="78.5" y="25.5" width="154.5" height="27.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="J8j-7w-yCZ">
<rect key="frame" x="0.0" y="69" width="311" height="80.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="uEf-g4-CeU">
<rect key="frame" x="0.0" y="0.0" width="311" height="32"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Large Titles" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogl-S5-4tJ">
<rect key="frame" x="0.0" y="5.5" width="254" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<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="CxM-wn-r09"/>
</connections>
</switch>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="ZtZ-Dz-4cC">
<rect key="frame" x="0.0" y="48" width="311" height="32.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Translucent" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Z5i-rm-QgL">
<rect key="frame" x="0.0" y="6" width="254" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<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="w7r-AV-RqX"/>
</connections>
</switch>
</subviews>
</stackView>
</subviews>
</stackView>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="0hr-ty-yWm"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="n93-ZL-fmC" secondAttribute="bottom" constant="32" id="2Ey-ou-E1M"/>
<constraint firstAttribute="trailing" secondItem="n93-ZL-fmC" secondAttribute="trailing" constant="32" id="DdZ-eB-F5s"/>
<constraint firstItem="n93-ZL-fmC" firstAttribute="leading" secondItem="af9-Zr-Ppc" secondAttribute="leading" constant="32" id="TyK-GP-Ari"/>
<constraint firstItem="n93-ZL-fmC" firstAttribute="top" secondItem="af9-Zr-Ppc" secondAttribute="topMargin" constant="16" id="mbC-6H-z9M"/>
</constraints>
</view>
<size key="freeformSize" width="375" height="197.33000000000001"/>
<connections>
<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>
<placeholder placeholderIdentifier="IBFirstResponder" id="M9h-4V-3M0" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="708" y="-200"/>
</scene>
<!--Layout 2-->
<scene sceneID="lRc-OZ-sL4">
<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="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AiP-dx-mFn">
<rect key="frame" x="163.66666666666666" y="395.66666666666669" width="48" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<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="48" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="RpE-lI-27a" eventType="touchUpInside" id="hj3-Xv-6Gq"/>
@@ -99,37 +189,61 @@
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="954-Dk-zvc"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="2Cd-km-qEk" secondAttribute="top" id="18k-sV-PgT"/>
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerY" secondItem="JER-jz-KSq" secondAttribute="centerY" id="NUc-tM-0dN"/>
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerX" secondItem="JER-jz-KSq" secondAttribute="centerX" id="hwP-mu-Vmz"/>
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="2Cd-km-qEk" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="954-Dk-zvc" secondAttribute="top" id="18k-sV-PgT"/>
<constraint firstItem="954-Dk-zvc" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="IvG-yp-yzI" secondAttribute="trailing" id="mpr-u5-MZu"/>
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="954-Dk-zvc" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
</constraints>
<viewLayoutGuide key="safeArea" id="2Cd-km-qEk"/>
</view>
<tabBarItem key="tabBarItem" tag="1" title="Item 2" id="qb3-RB-B28"/>
<tabBarItem key="tabBarItem" tag="1" title="Layout 2" id="qb3-RB-B28"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="NhZ-u5-Beh" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="326" y="1575"/>
<point key="canvasLocation" x="-308" y="1546"/>
</scene>
<!--Item 1-->
<!--Layout 3-->
<scene sceneID="r9h-Ql-gIv">
<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="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="48" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="pOk-Zm-vD9" eventType="touchUpInside" id="111-PD-Pop"/>
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="1Rg-YG-TtU"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="0ao-SI-QZW"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="0ao-SI-QZW" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="NbG-e8-HdI" secondAttribute="trailing" id="K9F-6x-KWn"/>
<constraint firstItem="NbG-e8-HdI" firstAttribute="top" secondItem="0ao-SI-QZW" secondAttribute="top" id="nsE-so-rTl"/>
<constraint firstItem="NbG-e8-HdI" firstAttribute="leading" secondItem="0ao-SI-QZW" secondAttribute="leading" constant="20" id="sF4-Dm-aoY"/>
</constraints>
</view>
<tabBarItem key="tabBarItem" tag="2" title="Layout 3" id="RJD-TF-Sdh"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Oe3-FT-q1C" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="332" y="1546"/>
</scene>
<!--Layout 1-->
<scene sceneID="m6X-j6-yBM">
<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="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uoW-c8-9wx">
<rect key="frame" x="164.66666666666666" y="395.66666666666669" width="46" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<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="48" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="YL4-GP-ZEZ"/>
@@ -137,49 +251,113 @@
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="5Ns-4l-Ufg"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="f88-U8-Vja" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerY" secondItem="ji9-Ez-N7i" secondAttribute="centerY" id="Nyw-Wt-78z"/>
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="f88-U8-Vja" secondAttribute="top" id="hUV-3a-XkY"/>
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerX" secondItem="ji9-Ez-N7i" secondAttribute="centerX" id="wDv-OH-7PX"/>
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="5Ns-4l-Ufg" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
<constraint firstItem="5Ns-4l-Ufg" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="eFN-tN-4Ct" secondAttribute="trailing" id="OzZ-Dz-RNF"/>
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="5Ns-4l-Ufg" secondAttribute="top" id="hUV-3a-XkY"/>
</constraints>
<viewLayoutGuide key="safeArea" id="f88-U8-Vja"/>
</view>
<tabBarItem key="tabBarItem" title="Item 1" id="HEV-kf-jxH"/>
<tabBarItem key="tabBarItem" title="Layout 1" id="HEV-kf-jxH"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bkL-bc-hZC" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-380" y="1576"/>
<point key="canvasLocation" x="-962" y="1546"/>
</scene>
<!--Intrinsic View Controller-->
<scene sceneID="wtJ-qZ-aCl">
<objects>
<viewController storyboardIdentifier="IntrinsicViewController" title="Intrinsic View Controller" id="aK0-kv-mTu" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="eLM-xc-d9e">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" text="Change this text" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ge4-RW-Gmz">
<rect key="frame" x="24" y="68" width="327" height="20.333333333333329"/>
<rect key="frame" x="125.5" y="24" width="124" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="ouu-g9-OiX"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="vtu-Jb-oOn" firstAttribute="trailing" secondItem="ge4-RW-Gmz" secondAttribute="trailing" constant="24" id="V59-MD-Lcg"/>
<constraint firstItem="ge4-RW-Gmz" firstAttribute="leading" secondItem="eLM-xc-d9e" secondAttribute="leading" constant="24" id="hAO-P0-7Kw"/>
<constraint firstItem="ge4-RW-Gmz" firstAttribute="top" secondItem="vtu-Jb-oOn" secondAttribute="top" constant="24" id="j0s-fd-MYj"/>
<constraint firstItem="ge4-RW-Gmz" firstAttribute="top" secondItem="eLM-xc-d9e" secondAttribute="top" constant="24" id="j0s-fd-MYj"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ge4-RW-Gmz" secondAttribute="bottom" constant="24" id="tEn-PO-nVD"/>
<constraint firstItem="ge4-RW-Gmz" firstAttribute="centerX" secondItem="eLM-xc-d9e" secondAttribute="centerX" id="vh3-l7-uY8"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vtu-Jb-oOn"/>
</view>
<size key="freeformSize" width="375" height="778"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="DfE-fL-zy5" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-940" y="805"/>
<point key="canvasLocation" x="2753" y="734"/>
</scene>
<!--Image View Controller-->
<scene sceneID="NAI-Rh-ZQ6">
<objects>
<viewController storyboardIdentifier="ImageViewController" id="VWY-cF-RoY" customClass="ImageViewController" customModule="Samples" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="dAf-gD-ghB">
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Gs4-S6-Goh">
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="t7x-eG-MKh">
<rect key="frame" x="0.0" y="0.0" width="375" height="49"/>
<color key="backgroundColor" systemColor="systemOrangeColor"/>
<constraints>
<constraint firstAttribute="height" constant="49" id="DmG-pt-gij"/>
</constraints>
</view>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" alwaysBounceHorizontal="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kRA-qy-GpJ">
<rect key="frame" x="0.0" y="49" width="375" height="680"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" image="IMG_0003" translatesAutoresizingMaskIntoConstraints="NO" id="rGf-jW-WNf">
<rect key="frame" x="0.0" y="0.0" width="750" height="501"/>
<color key="backgroundColor" systemColor="systemPurpleColor"/>
</imageView>
</subviews>
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="rGf-jW-WNf" secondAttribute="trailing" id="472-Be-cUl"/>
<constraint firstAttribute="bottom" secondItem="rGf-jW-WNf" secondAttribute="bottom" id="ncs-tN-3Wx"/>
<constraint firstItem="rGf-jW-WNf" firstAttribute="leading" secondItem="kRA-qy-GpJ" secondAttribute="leading" id="rlv-5Y-utR"/>
<constraint firstItem="rGf-jW-WNf" firstAttribute="top" secondItem="kRA-qy-GpJ" secondAttribute="top" id="zum-Zl-Wzz"/>
</constraints>
</scrollView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SEa-7Y-wa9">
<rect key="frame" x="0.0" y="729" width="375" height="49"/>
<color key="backgroundColor" systemColor="systemTealColor"/>
<constraints>
<constraint firstAttribute="height" constant="49" id="3mS-zi-8BP"/>
</constraints>
</view>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="hCg-v5-nJs"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="Gs4-S6-Goh" secondAttribute="trailing" id="6z7-Md-pxr"/>
<constraint firstAttribute="bottom" secondItem="Gs4-S6-Goh" secondAttribute="bottom" id="PcQ-bu-yT3"/>
<constraint firstItem="Gs4-S6-Goh" firstAttribute="top" secondItem="dAf-gD-ghB" secondAttribute="top" id="zGx-Wd-hjz"/>
<constraint firstItem="Gs4-S6-Goh" firstAttribute="leading" secondItem="dAf-gD-ghB" secondAttribute="leading" id="zxi-Ty-U7L"/>
</constraints>
</view>
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="footerView" destination="SEa-7Y-wa9" id="Gzj-dP-YXl"/>
<outlet property="headerView" destination="t7x-eG-MKh" id="njM-un-U8q"/>
<outlet property="scrollView" destination="kRA-qy-GpJ" id="iWC-o4-APi"/>
<outlet property="stackView" destination="Gs4-S6-Goh" id="f1D-bO-mjr"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="pnR-69-Ek4" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3388" y="734.48275862068965"/>
</scene>
<!--Tab Bar View Controller-->
<scene sceneID="nQ5-PV-qFw">
@@ -193,57 +371,65 @@
<connections>
<segue destination="lto-Zc-Vtp" kind="relationship" relationship="viewControllers" id="6hP-AH-YiH"/>
<segue destination="RpE-lI-27a" kind="relationship" relationship="viewControllers" id="g6X-Sq-uSW"/>
<segue destination="pOk-Zm-vD9" kind="relationship" relationship="viewControllers" id="OPp-iO-iDK"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Z9x-EI-p2b" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-183" y="806"/>
<point key="canvasLocation" x="-706" y="749"/>
</scene>
<!--Modal View Controller-->
<scene sceneID="C9P-Ns-Qrq">
<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="812"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="720"/>
<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="778" width="375" height="34"/>
<rect key="frame" x="0.0" y="720" 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="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
<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="MSC-ch-YJK"/>
</connections>
</button>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="44" translatesAutoresizingMaskIntoConstraints="NO" id="9p4-06-y2T">
<rect key="frame" x="139.66666666666666" y="132" width="96" height="252"/>
<rect key="frame" x="134.5" y="136" width="106" height="326"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
<rect key="frame" x="0.0" y="0.0" width="80" height="30"/>
<state key="normal" title="Move to full"/>
<connections>
<action selector="moveToFullWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="TDe-3J-gIR"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2u5-cH-RAN">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2u5-cH-RAN">
<rect key="frame" x="0.0" y="74" width="85" height="30"/>
<state key="normal" title="Move to half"/>
<connections>
<action selector="moveToHalfWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="12s-o7-Et5"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M4A-iO-RIE">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M4A-iO-RIE">
<rect key="frame" x="0.0" y="148" width="77" height="30"/>
<state key="normal" title="Move to tip"/>
<connections>
<action selector="moveToTipWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="BmL-91-9ai"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="szf-HE-QTk">
<rect key="frame" x="0.0" y="222" width="96" height="30"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="swr-XM-GzZ">
<rect key="frame" x="0.0" y="222" width="106" height="30"/>
<state key="normal" title="Move to hidden"/>
<connections>
<action selector="moveToHiddenWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="jfJ-0f-fdk"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="szf-HE-QTk">
<rect key="frame" x="0.0" y="296" width="96" height="30"/>
<state key="normal" title="Update layout"/>
<connections>
<action selector="updateLayout:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="Woz-a7-YMJ"/>
@@ -252,37 +438,38 @@
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="kjr-TP-fcM"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="sbF-Az-7sy" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="top" id="3VR-hj-zeQ"/>
<constraint firstItem="9p4-06-y2T" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="top" constant="88" id="41n-Fn-hi3"/>
<constraint firstItem="sbF-Az-7sy" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="top" id="3VR-hj-zeQ"/>
<constraint firstItem="9p4-06-y2T" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="top" constant="88" id="41n-Fn-hi3"/>
<constraint firstAttribute="bottom" secondItem="vut-mK-Y4t" secondAttribute="bottom" id="6eq-Kt-heZ"/>
<constraint firstItem="sbF-Az-7sy" firstAttribute="leading" secondItem="GBa-yx-8to" secondAttribute="leading" constant="20" id="T2G-1L-PRs"/>
<constraint firstItem="vut-mK-Y4t" firstAttribute="leading" secondItem="qwo-GK-p1U" secondAttribute="leading" id="gVC-jv-VJX"/>
<constraint firstItem="vut-mK-Y4t" firstAttribute="trailing" secondItem="GBa-yx-8to" secondAttribute="trailing" id="jkq-p2-lUm"/>
<constraint firstItem="9p4-06-y2T" firstAttribute="centerX" secondItem="qwo-GK-p1U" secondAttribute="centerX" id="l8t-p3-ETf"/>
<constraint firstItem="vut-mK-Y4t" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="bottom" id="rMy-JT-t4B"/>
<constraint firstItem="sbF-Az-7sy" firstAttribute="leading" secondItem="kjr-TP-fcM" secondAttribute="leading" constant="20" id="T2G-1L-PRs"/>
<constraint firstItem="vut-mK-Y4t" firstAttribute="leading" secondItem="kjr-TP-fcM" secondAttribute="leading" id="gVC-jv-VJX"/>
<constraint firstItem="vut-mK-Y4t" firstAttribute="trailing" secondItem="kjr-TP-fcM" secondAttribute="trailing" id="jkq-p2-lUm"/>
<constraint firstItem="9p4-06-y2T" firstAttribute="centerX" secondItem="kjr-TP-fcM" secondAttribute="centerX" id="l8t-p3-ETf"/>
<constraint firstItem="vut-mK-Y4t" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="bottom" id="rMy-JT-t4B"/>
</constraints>
<viewLayoutGuide key="safeArea" id="GBa-yx-8to"/>
</view>
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="safeAreaView" destination="vut-mK-Y4t" id="r9P-XF-wLd"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="fbi-LZ-M4Y" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="561" y="806"/>
<point key="canvasLocation" x="1375" y="734"/>
</scene>
<!--Nested Scroll View Controller-->
<scene sceneID="TfC-A3-4R0">
<objects>
<viewController storyboardIdentifier="NestedScrollViewController" id="LAe-jm-k6f" customClass="NestedScrollViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="414-Wy-0t1">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sBe-tN-uMi">
<rect key="frame" x="0.0" y="32" width="375" height="635"/>
<rect key="frame" x="0.0" y="32" width="375" height="746"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="lFR-Sp-Sj1">
<rect key="frame" x="0.0" y="0.0" width="375" height="968"/>
@@ -355,23 +542,24 @@
</constraints>
</scrollView>
</subviews>
<viewLayoutGuide key="safeArea" id="ufS-Rf-F2F"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="sBe-tN-uMi" firstAttribute="leading" secondItem="414-Wy-0t1" secondAttribute="leading" id="8Qd-my-knA"/>
<constraint firstItem="sBe-tN-uMi" firstAttribute="leading" secondItem="ufS-Rf-F2F" secondAttribute="leading" id="8Qd-my-knA"/>
<constraint firstItem="sBe-tN-uMi" firstAttribute="top" secondItem="414-Wy-0t1" secondAttribute="top" constant="32" id="9Js-LU-lNr"/>
<constraint firstAttribute="bottom" secondItem="sBe-tN-uMi" secondAttribute="bottom" id="jzB-47-P7e"/>
<constraint firstAttribute="trailing" secondItem="sBe-tN-uMi" secondAttribute="trailing" id="nHG-wg-pLP"/>
<constraint firstItem="ufS-Rf-F2F" firstAttribute="trailing" secondItem="sBe-tN-uMi" secondAttribute="trailing" id="nHG-wg-pLP"/>
</constraints>
<viewLayoutGuide key="safeArea" id="sL5-d5-za2"/>
<connections>
<outletCollection property="gestureRecognizers" destination="tOa-bf-zGz" appends="YES" id="zle-Sz-M3U"/>
<outletCollection property="gestureRecognizers" destination="SCk-hG-weZ" appends="YES" id="OcK-FK-Lac"/>
<outletCollection property="gestureRecognizers" destination="Fvp-Z6-eVc" appends="YES" id="Fds-J5-YCg"/>
</connections>
</view>
<size key="freeformSize" width="375" height="667"/>
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="nestedScrollView" destination="xba-kG-VQ2" id="ddV-kf-37A"/>
<outlet property="scrollView" destination="sBe-tN-uMi" id="h4S-Zl-cLO"/>
</connections>
</viewController>
@@ -392,7 +580,7 @@
</connections>
</swipeGestureRecognizer>
</objects>
<point key="canvasLocation" x="1311" y="806"/>
<point key="canvasLocation" x="2097" y="734"/>
</scene>
<!--Detail View Controller-->
<scene sceneID="b6k-zi-3wn">
@@ -402,8 +590,19 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="319" y="12" width="44" height="44"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8yw-OC-Ubk">
<rect key="frame" x="0.0" y="690" width="375" height="88"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="88" id="jwV-YU-tXG"/>
</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="48" width="375" height="730"/>
<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="48" width="44" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="0jg-5D-A1F"/>
<constraint firstAttribute="width" constant="44" id="1Cq-PA-wgW"/>
@@ -412,31 +611,34 @@
<action selector="closeWithSender:" destination="YC8-ae-15L" eventType="touchUpInside" id="Z2v-19-S5k"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8yw-OC-Ubk">
<rect key="frame" x="0.0" y="690" width="375" height="88"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="88" id="jwV-YU-tXG"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="0.0" y="700" width="375" height="44"/>
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="DQJ-cY-cKx"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="tP3-oJ-4EB">
<rect key="frame" x="130.66666666666666" y="132" width="114" height="82"/>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="qux-uG-4o2">
<rect key="frame" x="8" y="56" width="148" height="31"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c5r-jU-haj">
<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"/>
<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" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0MA-lV-KjS">
<rect key="frame" x="99" y="0.0" width="51" height="31"/>
<connections>
<action selector="modeChanged:" destination="YC8-ae-15L" eventType="valueChanged" id="IQ8-u2-Rib"/>
</connections>
</switch>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="tP3-oJ-4EB">
<rect key="frame" x="130.5" y="88" width="114" height="134"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c5r-jU-haj">
<rect key="frame" x="0.0" y="0.0" width="114" height="30"/>
<state key="normal" title="Show"/>
<connections>
<action selector="buttonPressed:" destination="YC8-ae-15L" eventType="touchUpInside" id="Mi1-o6-TWt"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wmd-ab-Nz3">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wmd-ab-Nz3">
<rect key="frame" x="0.0" y="52" width="114" height="30"/>
<state key="normal" title="Present Modallly"/>
<connections>
@@ -444,25 +646,35 @@
<segue destination="bYI-y3-Rzb" kind="presentation" identifier="PresentModallySegue" id="3yq-HE-Tgn"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="01L-lp-oy6">
<rect key="frame" x="0.0" y="104" width="114" height="30"/>
<state key="normal" title="Update Layout"/>
<connections>
<action selector="buttonPressed:" destination="YC8-ae-15L" eventType="touchUpInside" id="zTb-sq-B6f"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="aOK-7l-cA6"/>
<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="g7l-kO-y7q" secondAttribute="top" constant="12" id="EQy-cr-F2Y"/>
<constraint firstItem="tP3-oJ-4EB" firstAttribute="centerX" secondItem="g7l-kO-y7q" secondAttribute="centerX" id="EsD-Vf-dNZ"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="bottom" secondItem="g7l-kO-y7q" secondAttribute="bottom" id="JOL-wC-w74"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="leading" secondItem="tAi-nk-rDB" secondAttribute="leading" id="RiJ-Hb-OOZ"/>
<constraint firstItem="8yw-OC-Ubk" firstAttribute="trailing" secondItem="tAi-nk-rDB" secondAttribute="trailing" id="Sof-yL-mwK"/>
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="tAi-nk-rDB" secondAttribute="top" constant="88" id="Zhb-Ss-epe"/>
<constraint firstItem="Kva-Z7-0qY" firstAttribute="trailing" secondItem="tAi-nk-rDB" secondAttribute="trailing" id="kkp-Yo-FQW"/>
<constraint firstItem="tAi-nk-rDB" firstAttribute="trailing" secondItem="noi-1a-5bZ" secondAttribute="trailing" constant="12" id="lv9-Nf-HNB"/>
<constraint firstItem="Kva-Z7-0qY" firstAttribute="leading" secondItem="tAi-nk-rDB" secondAttribute="leading" id="oVC-i1-TwS"/>
<constraint firstItem="tAi-nk-rDB" 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="8" symbolic="YES" id="vKQ-h9-uKt"/>
<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" 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" 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" 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>
<viewLayoutGuide key="safeArea" id="tAi-nk-rDB"/>
<connections>
<outletCollection property="gestureRecognizers" destination="6Ca-p8-7uF" appends="YES" id="xOy-f1-NZE"/>
<outletCollection property="gestureRecognizers" destination="SPY-Vr-XDT" appends="YES" id="vgS-Am-jhQ"/>
@@ -472,6 +684,8 @@
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="closeButton" destination="noi-1a-5bZ" id="eWQ-ha-8y7"/>
<outlet property="intrinsicHeightConstraint" destination="vKQ-h9-uKt" id="QpA-WD-b17"/>
<outlet property="modeChangeView" destination="qux-uG-4o2" id="1Nq-fE-dXw"/>
<segue destination="bYI-y3-Rzb" kind="show" identifier="ShowSegue" id="r1P-2i-NDe"/>
</connections>
</viewController>
@@ -492,7 +706,7 @@
</connections>
</pongPressGestureRecognizer>
</objects>
<point key="canvasLocation" x="1440.8" y="-23.388305847076463"/>
<point key="canvasLocation" x="653.60000000000002" y="733.74384236453204"/>
</scene>
<!--Debug Text View Controller-->
<scene sceneID="Bkq-O7-q4A">
@@ -548,26 +762,93 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</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="ix0-2W-gQN" secondAttribute="leading" id="7V3-KL-vXd"/>
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
<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="ix0-2W-gQN" secondAttribute="trailing" id="lfg-EE-euw"/>
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ix0-2W-gQN"/>
</view>
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="textView" destination="rN1-HL-YHv" id="gmr-Uf-jd8"/>
<outlet property="textViewTopConstraint" destination="fiO-LL-nSC" id="Rum-TN-c2e"/>
<outlet property="view" destination="9YG-0j-Zzg" id="jhb-eT-nEn"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="729" y="-23"/>
<point key="canvasLocation" x="-1" y="734"/>
</scene>
<!--Adaptive Layout Test View Controller-->
<scene sceneID="rDI-lU-wEx">
<objects>
<viewController storyboardIdentifier="AdaptiveLayoutTestViewController" id="5nC-6E-bXf" customClass="AdaptiveLayoutTestViewController" 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="3yq-HE-Tgn"/>
<segue reference="r1P-2i-NDe"/>
</inferredMetricsTieBreakers>
<resources>
<image name="IMG_0003" width="750" height="501"/>
<systemColor name="systemBackgroundColor">
<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"/>
</systemColor>
<systemColor name="systemPurpleColor">
<color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" 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"/>
</systemColor>
</resources>
</document>
@@ -0,0 +1,89 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class AdaptiveLayoutTestViewController: 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,279 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
class DebugTableViewController: InspectableViewController {
// MARK: - Views
lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
return tableView
}()
lazy var buttonStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .trailing
stackView.spacing = 10.0
return stackView
}()
private lazy var reorderButton: UIButton = {
let button = UIButton()
button.setTitle(Menu.reorder.rawValue, for: .normal)
button.setTitleColor(view.tintColor, for: .normal)
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 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
}()
// MARK: - Properties
var kvoObservers: [NSKeyValueObservation] = []
private lazy var items: [String] = {
let items = (0..<100).map { "Items \($0)" }
return Command.replace(items: items)
}()
private var itemHeight: CGFloat = 66.0
enum Menu: String, CaseIterable {
case turnOffTracking = "Tracking"
case reorder = "Reorder"
}
enum Command: Int, CaseIterable {
case animateScroll
case changeContentSize
case moveToFull
case moveToHalf
var text: String {
switch self {
case .animateScroll: return "Scroll in the middle"
case .changeContentSize: return "Change content size"
case .moveToFull: return "Move to Full"
case.moveToHalf: return "Move to Half"
}
}
static func replace(items: [String]) -> [String] {
return items.enumerated().map { (index, text) -> String in
if let action = Command(rawValue: index) {
return "\(index). \(action.text)"
}
return text
}
}
func execute(for vc: DebugTableViewController, sourceView: UIView) {
switch self {
case .animateScroll:
vc.animateScroll()
case .changeContentSize:
vc.changeContentSize(sourceView: sourceView)
case .moveToFull:
vc.moveToFull()
case .moveToHalf:
vc.moveToHalf()
}
}
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
layoutTableView()
layoutMenuStackView()
setUpMenu()
}
private func layoutTableView() {
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
private func layoutMenuStackView() {
view.addSubview(buttonStackView)
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
buttonStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 22.0),
buttonStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -22.0),
])
}
private func setUpMenu() {
for menu in Menu.allCases {
switch menu {
case .reorder:
buttonStackView.addArrangedSubview(reorderButton)
case .turnOffTracking:
buttonStackView.addArrangedSubview(trackingSwitchWrapper)
}
}
}
// MARK: - Menu
@objc
private func reorderItems() {
if reorderButton.titleLabel?.text == Menu.reorder.rawValue {
tableView.isEditing = true
reorderButton.setTitle("Cancel", for: .normal)
} else {
tableView.isEditing = false
reorderButton.setTitle(Menu.reorder.rawValue, for: .normal)
}
}
@objc
private func turnTrackingOn(_ sender: UISwitch) {
guard let fpc = self.parent as? FloatingPanelController else { return }
if sender.isOn {
fpc.track(scrollView: tableView)
} else {
fpc.untrack(scrollView: tableView)
}
}
// MARK: - Actions
private func execute(command: Command, sourceView: UIView) {
command.execute(for: self, sourceView: sourceView)
}
@objc
private func animateScroll() {
tableView.scrollToRow(at: IndexPath(row: lround(Double(items.count) / 2.0),
section: 0),
at: .top, animated: true)
}
@objc
private func changeContentSize(sourceView: UIView) {
let actionSheet = UIAlertController(title: "Change content size", message: "", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Large", style: .default, handler: { (_) in
self.itemHeight = 66.0
self.changeItems(100)
}))
actionSheet.addAction(UIAlertAction(title: "Match", style: .default, handler: { (_) in
switch self.tableView.bounds.height {
case 585: // iPhone 6,7,8
self.itemHeight = self.tableView.bounds.height / 13.0
self.changeItems(13)
case 656: // iPhone {6,7,8} Plus
self.itemHeight = self.tableView.bounds.height / 16.0
self.changeItems(16)
default: // iPhone X family
self.itemHeight = self.tableView.bounds.height / 12.0
self.changeItems(12)
}
}))
actionSheet.addAction(UIAlertAction(title: "Short", style: .default, handler: { (_) in
self.itemHeight = 66.0
self.changeItems(3)
}))
if let popoverController = actionSheet.popoverPresentationController {
popoverController.sourceView = sourceView
popoverController.sourceRect = sourceView.bounds
}
self.present(actionSheet, animated: true, completion: nil)
}
private func changeItems(_ count: Int) {
items = Command.replace(items: (0..<count).map{ "\($0). No action" })
tableView.reloadData()
}
@objc
private func moveToFull() {
(self.parent as! FloatingPanelController).move(to: .full, animated: true)
}
@objc
private func moveToHalf() {
(self.parent as! FloatingPanelController).move(to: .half, animated: true)
}
@objc
private func close(sender: UIButton) {
// Remove FloatingPanel from a view
(self.parent as! FloatingPanelController).removePanelFromParent(animated: true, completion: nil)
}
}
extension DebugTableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return itemHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
extension DebugTableViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("TableView --- ", scrollView.contentOffset, scrollView.contentInset)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("DebugTableViewController -- select row \(indexPath.row)")
guard let action = Command(rawValue: indexPath.row) else { return }
let cell = tableView.cellForRow(at: indexPath)
execute(command: action, sourceView: cell ?? tableView)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
return [
UITableViewRowAction(style: .destructive, title: "Delete", handler: { (action, path) in
self.items.remove(at: path.row)
tableView.deleteRows(at: [path], with: .automatic)
}),
]
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
items.insert(items.remove(at: sourceIndexPath.row), at: destinationIndexPath.row)
}
}
@@ -0,0 +1,44 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
final class DebugTextViewController: UIViewController, UITextViewDelegate {
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var textViewTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
print("viewDidLoad: TextView --- ", textView.contentOffset, textView.contentInset)
if #available(iOS 11.0, *) {
textView.contentInsetAdjustmentBehavior = .never
}
}
override func viewWillLayoutSubviews() {
print("viewWillLayoutSubviews: TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews: TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("TextView --- ", scrollView.contentOffset, scrollView.contentInset)
if #available(iOS 11.0, *) {
print("TextView --- ", scrollView.adjustedContentInset)
}
}
@IBAction func close(sender: UIButton) {
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
dismiss(animated: true, completion: nil)
}
}
@@ -0,0 +1,39 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class DetailViewController: InspectableViewController {
@IBOutlet weak var modeChangeView: UIStackView!
@IBOutlet weak var intrinsicHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var closeButton: UIButton!
@IBAction func close(sender: UIButton) {
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
dismiss(animated: true, completion: nil)
}
@IBAction func buttonPressed(_ sender: UIButton) {
switch sender.titleLabel?.text {
case "Show":
performSegue(withIdentifier: "ShowSegue", sender: self)
case "Present Modally":
performSegue(withIdentifier: "PresentModallySegue", sender: self)
default:
break
}
}
@IBAction func modeChanged(_ sender: Any) {
guard let fpc = parent as? FloatingPanelController else { return }
fpc.contentMode = (fpc.contentMode == .static) ? .fitToBounds : .static
}
@IBAction func tapped(_ sender: Any) {
print("Detail panel is tapped!")
}
@IBAction func swipped(_ sender: Any) {
print("Detail panel is swipped!")
}
@IBAction func longPressed(_ sender: Any) {
print("Detail panel is longPressed!")
}
}
@@ -0,0 +1,65 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class ImageViewController: UIViewController {
class PanelLayout: FloatingPanelLayout {
private unowned var targetGuide: UILayoutGuide
init(targetGuide: UILayoutGuide) {
self.targetGuide = targetGuide
}
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelAdaptiveLayoutAnchor(
absoluteOffset: 0,
contentLayout: targetGuide,
referenceGuide: .superview
),
.half: FloatingPanelAdaptiveLayoutAnchor(
fractionalOffset: 0.5,
contentLayout: targetGuide,
referenceGuide: .superview
)
]
}
}
@IBOutlet weak var headerView: UIView!
@IBOutlet weak var footerView: UIView!
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var stackView: UIStackView!
enum Mode {
case onlyImage
case withHeaderFooter
}
@available(iOS 11.0, *)
func layoutGuideFor(mode: Mode) -> UILayoutGuide {
switch mode {
case .onlyImage:
self.headerView.isHidden = true
self.footerView.isHidden = true
return scrollView.contentLayoutGuide
case .withHeaderFooter:
self.headerView.isHidden = false
self.footerView.isHidden = false
let guide = UILayoutGuide()
view.addLayoutGuide(guide)
NSLayoutConstraint.activate([
scrollView.heightAnchor.constraint(equalTo: scrollView.contentLayoutGuide.heightAnchor),
guide.topAnchor.constraint(equalTo: stackView.topAnchor),
guide.leftAnchor.constraint(equalTo: stackView.leftAnchor),
guide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor),
guide.rightAnchor.constraint(equalTo: stackView.rightAnchor),
])
return guide
}
}
}
@@ -0,0 +1,48 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
class InspectableViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
print(">>> Content View: viewWillLayoutSubviews", layoutInsets)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(">>> Content View: viewDidLayoutSubviews", layoutInsets)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(">>> Content View: viewWillAppear", layoutInsets)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(">>> Content View: viewDidAppear", view.bounds, layoutInsets)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print(">>> Content View: viewWillDisappear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print(">>> Content View: viewDidDisappear")
}
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
print(">>> Content View: willMove(toParent: \(String(describing: parent))")
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
print(">>> Content View: didMove(toParent: \(String(describing: parent))")
}
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
print(">>> Content View: willTransition(to: \(newCollection), with: \(coordinator))", layoutInsets)
}
}
@@ -0,0 +1,76 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
var fpc: FloatingPanelController!
var consoleVC: DebugTextViewController!
@IBOutlet weak var safeAreaView: UIView!
var isNewlayout: Bool = false
override func viewDidLoad() {
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
fpc.surfaceView.appearance = appearance
// Set a content view controller and track the scroll view
let consoleVC = storyboard?.instantiateViewController(withIdentifier: "ConsoleViewController") as! DebugTextViewController
fpc.set(contentViewController: consoleVC)
fpc.track(scrollView: consoleVC.textView)
self.consoleVC = consoleVC
// Add FloatingPanel to self.view
fpc.addPanel(toParent: self, at: view.subviews.firstIndex(of: safeAreaView) ?? -1)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Remove FloatingPanel from a view
fpc.removePanelFromParent(animated: false)
}
@IBAction func close(sender: UIButton) {
dismiss(animated: true, completion: nil)
}
@IBAction func moveToFull(sender: UIButton) {
fpc.move(to: .full, animated: true)
}
@IBAction func moveToHalf(sender: UIButton) {
fpc.move(to: .half, animated: true)
}
@IBAction func moveToTip(sender: UIButton) {
fpc.move(to: .tip, animated: true)
}
@IBAction func moveToHidden(sender: UIButton) {
fpc.move(to: .hidden, animated: true)
}
@IBAction func updateLayout(_ sender: Any) {
isNewlayout = !isNewlayout
UIView.animate(withDuration: 0.5) {
self.fpc.invalidateLayout()
}
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
return (isNewlayout) ? ModalSecondLayout() : FloatingPanelBottomLayout()
}
class ModalSecondLayout: 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)
]
}
}
@@ -0,0 +1,65 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
import WebKit
final class MultiPanelController: FloatingPanelController, FloatingPanelControllerDelegate {
private final class FirstPanelContentViewController: UIViewController {
lazy var webView: WKWebView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.frame = view.bounds
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.load(URLRequest(url: URL(string: "https://www.apple.com")!))
let vc = MultiSecondPanelController()
vc.setUpContent()
vc.addPanel(toParent: self)
}
}
private final class MultiSecondPanelController: FloatingPanelController {
private final class SecondPanelContentViewController: DebugTableViewController {}
func setUpContent() {
contentInsetAdjustmentBehavior = .never
let vc = SecondPanelContentViewController()
vc.loadViewIfNeeded()
vc.title = "Second Panel"
vc.buttonStackView.isHidden = true
let navigationController = UINavigationController(rootViewController: vc)
navigationController.navigationBar.barTintColor = .white
navigationController.navigationBar.titleTextAttributes = [
.foregroundColor: UIColor.black
]
set(contentViewController: navigationController)
self.track(scrollView: vc.tableView)
surfaceView.containerMargins = .init(top: 24.0, left: 0.0, bottom: layoutInsets.bottom, right: 0.0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
layout = FirstViewLayout()
isRemovalInteractionEnabled = true
let vc = FirstPanelContentViewController()
set(contentViewController: vc)
track(scrollView: vc.webView.scrollView)
}
private final class FirstViewLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 40.0, edge: .top, referenceGuide: .superview)
]
}
}
@@ -0,0 +1,18 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
final class NestedScrollViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var nestedScrollView: UIScrollView!
@IBAction func longPressed(_ sender: Any) {
print("LongPressed!")
}
@IBAction func swipped(_ sender: Any) {
print("Swipped!")
}
@IBAction func tapped(_ sender: Any) {
print("Tapped!")
}
}
@@ -0,0 +1,48 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class SettingsViewController: InspectableViewController {
@IBOutlet weak var largeTitlesSwitch: UISwitch!
@IBOutlet weak var translucentSwitch: UISwitch!
@IBOutlet weak var versionLabel: UILabel!
override func viewDidLoad() {
versionLabel.text = "Version: \(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "--")"
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11.0, *) {
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
largeTitlesSwitch.setOn(prefersLargeTitles, animated: false)
} else {
largeTitlesSwitch.isEnabled = false
}
let isTranslucent = navigationController!.navigationBar.isTranslucent
translucentSwitch.setOn(isTranslucent, animated: false)
}
@IBAction func toggleLargeTitle(_ sender: UISwitch) {
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
}
}
@IBAction func toggleTranslucent(_ sender: UISwitch) {
// 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
}
}
}
@@ -0,0 +1,250 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class TabBarViewController: UITabBarController {}
final class TabBarContentViewController: UIViewController {
enum Tab3Mode {
case changeOffset
case changeAutoLayout
var label: String {
switch self {
case .changeAutoLayout: return "Use AutoLayout(OK)"
case .changeOffset: return "Use ContentOffset(NG)"
}
}
}
lazy var fpc = FloatingPanelController()
var consoleVC: DebugTextViewController!
var threeLayout: ThreeTabBarPanelLayout!
var tab3Mode: Tab3Mode = .changeAutoLayout
var switcherLabel: UILabel!
override func viewDidLoad() {
fpc.delegate = self
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
fpc.surfaceView.appearance = appearance
// Set a content view controller and track the scroll view
let consoleVC = storyboard?.instantiateViewController(withIdentifier: "ConsoleViewController") as! DebugTextViewController
fpc.set(contentViewController: consoleVC)
consoleVC.textView.delegate = self // MUST call it before fpc.track(scrollView:)
fpc.track(scrollView: consoleVC.textView)
self.consoleVC = consoleVC
// Add FloatingPanel to self.view
fpc.addPanel(toParent: self)
if #available(iOS 15, *) {
tabBarController?.tabBar.scrollEdgeAppearance = UITabBarAppearance()
}
switch tabBarItem.tag {
case 1:
fpc.behavior = TwoTabBarPanelBehavior()
case 2:
let switcher = UISwitch()
fpc.view.addSubview(switcher)
switcher.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
switcher.bottomAnchor.constraint(equalTo: fpc.surfaceView.topAnchor, constant: -16.0),
switcher.rightAnchor.constraint(equalTo: fpc.surfaceView.rightAnchor, constant: -16.0),
])
switcher.isOn = true
switcher.tintColor = .white
switcher.backgroundColor = .white
switcher.layer.cornerRadius = 16.0
switcher.addTarget(self,
action: #selector(changeTab3Mode(_:)),
for: .valueChanged)
let label = UILabel()
label.text = tab3Mode.label
fpc.view.addSubview(label)
switcherLabel = label
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerYAnchor.constraint(equalTo: switcher.centerYAnchor, constant: 0.0),
label.rightAnchor.constraint(equalTo: switcher.leftAnchor, constant: -16.0),
])
// Turn off the mask instead of content inset change
consoleVC.textView.clipsToBounds = false
default:
break
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fpc.invalidateLayout()
}
// MARK: - Action
@IBAction func close(sender: UIButton) {
dismiss(animated: true, completion: nil)
}
// MARK: - Private
@objc
private func changeTab3Mode(_ sender: UISwitch) {
if sender.isOn {
tab3Mode = .changeAutoLayout
} else {
tab3Mode = .changeOffset
}
switcherLabel.text = tab3Mode.label
}
}
extension TabBarContentViewController: UITextViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard self.tabBarItem.tag == 2 else { return }
// Reset an invalid content offset by a user after updating the layout
// of `consoleVC.textView`.
// NOTE: FloatingPanel doesn't implicitly reset the offset(i.e.
// Using KVO of `scrollView.contentOffset`). Because it can lead to an
// infinite loop if a user also resets a content offset as below and,
// in the situation, a user has to modify the library.
if fpc.state != .full, fpc.surfaceLocation.y > fpc.surfaceLocation(for: .full).y {
scrollView.contentOffset = .zero
}
}
}
extension TabBarContentViewController: FloatingPanelControllerDelegate {
// MARK: - FloatingPanel
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
switch self.tabBarItem.tag {
case 0:
return OneTabBarPanelLayout()
case 1:
return TwoTabBarPanelLayout()
case 2:
threeLayout = ThreeTabBarPanelLayout(parent: self)
return threeLayout
default:
return FloatingPanelBottomLayout()
}
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {
guard self.tabBarItem.tag == 2 else { return }
switch tab3Mode {
case .changeAutoLayout:
/* Good solution: Manipulate top constraint */
assert(consoleVC.textViewTopConstraint != nil)
let safeAreaTop = vc.layoutInsets.top
if vc.surfaceLocation.y + threeLayout.topPadding < safeAreaTop {
consoleVC.textViewTopConstraint?.constant = min(safeAreaTop - vc.surfaceLocation.y,
safeAreaTop)
} else {
consoleVC.textViewTopConstraint?.constant = threeLayout.topPadding
}
case .changeOffset:
/*
Bad solution: Manipulate scroll content inset
FloatingPanelController keeps a content offset in moving a panel
so that changing content inset or offset causes a buggy behavior.
*/
guard let scrollView = consoleVC.textView else { return }
var insets = vc.adjustedContentInsets
if vc.surfaceView.frame.minY < vc.layoutInsets.top {
insets.top = vc.layoutInsets.top - vc.surfaceView.frame.minY
} else {
insets.top = 0.0
}
scrollView.contentInset = insets
if vc.surfaceView.frame.minY > 0 {
scrollView.contentOffset = CGPoint(x: 0.0,
y: 0.0 - scrollView.contentInset.top)
}
}
if vc.surfaceLocation.y > vc.surfaceLocation(for: .half).y {
let progress = (vc.surfaceLocation.y - vc.surfaceLocation(for: .half).y)
/ (vc.surfaceLocation(for: .tip).y - vc.surfaceLocation(for: .half).y)
threeLayout.leftConstraint.constant = max(min(progress, 1.0), 0.0) * threeLayout.sideMargin
threeLayout.rightConstraint.constant = -max(min(progress, 1.0), 0.0) * threeLayout.sideMargin
} else {
threeLayout.leftConstraint.constant = 0.0
threeLayout.rightConstraint.constant = 0.0
}
}
}
class OneTabBarPanelLayout: FloatingPanelLayout {
var initialState: FloatingPanelState { .tip }
var position: FloatingPanelPosition { .bottom }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 22.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}
class TwoTabBarPanelLayout: FloatingPanelLayout {
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 {
func allowsRubberBanding(for edges: UIRectEdge) -> Bool {
return [UIRectEdge.top, UIRectEdge.bottom].contains(edges)
}
}
class ThreeTabBarPanelLayout: FloatingPanelLayout {
weak var parentVC: UIViewController!
var leftConstraint: NSLayoutConstraint!
var rightConstraint: NSLayoutConstraint!
let topPadding: CGFloat = 17.0
let sideMargin: CGFloat = 16.0
init(parent: UIViewController) {
parentVC = parent
}
var initialState: FloatingPanelState { .half }
var position: FloatingPanelPosition { .bottom }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
.half: FloatingPanelLayoutAnchor(absoluteInset: 261.0 + parentVC.layoutInsets.bottom, edge: .bottom, referenceGuide: .superview),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 88.0 + parentVC.layoutInsets.bottom, edge: .bottom, referenceGuide: .superview),
]
}
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
}
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
if #available(iOS 11.0, *) {
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
} else {
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0)
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0)
}
return [ leftConstraint, rightConstraint ]
}
}
@@ -0,0 +1,22 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import Foundation
import FloatingPanel
extension FloatingPanelState {
static let lastQuart: FloatingPanelState = FloatingPanelState(rawValue: "lastQuart", order: 750)
static let firstQuart: FloatingPanelState = FloatingPanelState(rawValue: "firstQuart", order: 250)
}
class FloatingPanelLayoutWithCustomState: FloatingPanelBottomLayout {
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
.lastQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.75, edge: .bottom, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.firstQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.25, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .bottom, referenceGuide: .safeArea),
]
}
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
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 }
}
extension UILayoutGuide: LayoutGuideProvider {}
class CustomLayoutGuide: LayoutGuideProvider {
let topAnchor: NSLayoutYAxisAnchor
let bottomAnchor: NSLayoutYAxisAnchor
init(topAnchor: NSLayoutYAxisAnchor, bottomAnchor: NSLayoutYAxisAnchor) {
self.topAnchor = topAnchor
self.bottomAnchor = bottomAnchor
}
}
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)
}
}
var layoutGuide: LayoutGuideProvider {
if #available(iOS 11.0, *) {
return view!.safeAreaLayoutGuide
} else {
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
bottomAnchor: bottomLayoutGuide.topAnchor)
}
}
}
@@ -0,0 +1,93 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class MainViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private var observations: [NSKeyValueObservation] = []
private lazy var useCaseController = UseCaseController(mainVC: self)
}
extension MainViewController {
override func viewDidLoad() {
super.viewDidLoad()
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
}
var insets = UIEdgeInsets.zero
insets.bottom += 69.0
tableView.contentInset = insets
// Show the initial panel
useCaseController.set(useCase: .trackingTableView)
}
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)
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
observations.removeAll()
}
}
extension MainViewController {
@IBAction func showDebugMenu(_ sender: UIBarButtonItem) {
useCaseController.setUpSettingsPanel(for: self)
}
}
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
}
} else {
return UseCase.allCases.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if UseCase.allCases.count > indexPath.row {
let menu = UseCase.allCases[indexPath.row]
cell.textLabel?.text = menu.name
} else {
cell.textLabel?.text = "\(indexPath.row) row"
}
return cell
}
}
extension MainViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard UseCase.allCases.count > indexPath.row else { return }
// Change panels
useCaseController.set(useCase: UseCase.allCases[indexPath.row])
}
@objc func dismissPresentedVC() {
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
@@ -0,0 +1,78 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
/**
- Attention: `FloatingPanelLayout` must not be applied by the parent view
controller of a panel. But here `MainViewController` adopts it
purposely to check if the library prints an appropriate warning.
*/
extension MainViewController: FloatingPanelLayout {
var position: FloatingPanelPosition { .bottom }
var initialState: FloatingPanelState { .half }
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262.0, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}
class TopPositionedPanelLayout: 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 IntrinsicPanelLayout: FloatingPanelBottomLayout {
override var initialState: FloatingPanelState { .full }
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea)
]
}
}
class RemovablePanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .half
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
}
}
class RemovablePanelLandscapeLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
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
}
}
class ModalPanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
]
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
}
}
@@ -1,12 +1,12 @@
//
// Created by Shin Yamamoto on 2018/09/19.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
@IBDesignable
class CloseButton: UIButton {
final class CloseButton: UIButton {
override var isHighlighted: Bool { didSet { setNeedsDisplay() } }
override var isSelected: Bool { didSet { setNeedsDisplay() } }
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
render()
@@ -20,14 +20,11 @@ class CloseButton: UIButton {
self.backgroundColor = .clear
}
func p(_ p: CGFloat) -> CGFloat {
return p * (2.0 / 3.0)
}
override var isHighlighted: Bool { didSet { setNeedsDisplay() } }
override var isSelected: Bool { didSet { setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
func p(_ p: CGFloat) -> CGFloat {
return p * (2.0 / 3.0)
}
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setLineWidth(p(1.0))
@@ -74,7 +71,7 @@ class CloseButton: UIButton {
}
@IBDesignable
class SafeAreaView: UIView {
final class SafeAreaView: UIView {
override func prepareForInterfaceBuilder() {
let label = UILabel()
label.text = "Safe Area"
@@ -89,7 +86,7 @@ class SafeAreaView: UIView {
@IBDesignable
class OnSafeAreaView: UIView {
final class OnSafeAreaView: UIView {
override func prepareForInterfaceBuilder() {
let label = UILabel()
label.text = "On Safe Area"
@@ -1,25 +0,0 @@
//
// Created by Shin Yamamoto on 2018/10/08.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
extension UIView {
var layoutInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return safeAreaInsets
} else {
return layoutMargins
}
}
var layoutGuide: UILayoutGuide {
if #available(iOS 11.0, *) {
return safeAreaLayoutGuide
} else {
return layoutMarginsGuide
}
}
}
@@ -0,0 +1,85 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class PagePanelController: NSObject {
var pages: [UIViewController] = []
}
extension PagePanelController {
func makePageViewControllerForContent() -> UIPageViewController {
pages = [DebugTableViewController(), DebugTableViewController(), DebugTableViewController()]
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
pageVC.dataSource = self
pageVC.delegate = self
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
return pageVC
}
func makePageViewController(for vc: MainViewController) -> UIPageViewController {
pages = [UIColor.blue, .red, .green].compactMap({ (color) -> UIViewController in
let page = FloatingPanelController(delegate: self)
page.view.backgroundColor = color
page.panGestureRecognizer.delegateProxy = self
page.show()
return page
})
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
let closeButton = UIButton(type: .custom)
pageVC.view.addSubview(closeButton)
closeButton.setTitle("Close", for: .normal)
closeButton.translatesAutoresizingMaskIntoConstraints = false
closeButton.addTarget(vc, action: #selector(MainViewController.dismissPresentedVC), for: .touchUpInside)
NSLayoutConstraint.activate([
closeButton.topAnchor.constraint(equalTo: pageVC.layoutGuide.topAnchor, constant: 16.0),
closeButton.leftAnchor.constraint(equalTo: pageVC.view.leftAnchor, constant: 16.0),
])
pageVC.dataSource = self
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
pageVC.modalPresentationStyle = .fullScreen
return pageVC
}
}
extension PagePanelController: FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
return FloatingPanelBottomLayout()
}
}
extension PagePanelController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
extension PagePanelController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard
let index = pages.firstIndex(of: viewController),
index + 1 < pages.count
else { return nil }
return pages[index + 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard
let index = pages.firstIndex(of: viewController),
index - 1 >= 0
else { return nil }
return pages[index - 1]
}
}
extension PagePanelController: UIPageViewControllerDelegate {
// For showPageContent
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed, let page = pageViewController.viewControllers?.first {
(pageViewController.parent as! FloatingPanelController).track(scrollView: (page as! DebugTableViewController).tableView)
}
}
}
@@ -0,0 +1,95 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
enum UseCase: Int, CaseIterable {
case trackingTableView
case trackingTextView
case showDetail
case showModal
case showPanelModal
case showMultiPanelModal
case showPanelInSheetModal
case showTabBar
case showPageView
case showPageContentView
case showNestedScrollView
case showRemovablePanel
case showIntrinsicView
case showContentInset
case showContainerMargins
case showNavigationController
case showTopPositionedPanel
case showAdaptivePanel
case showAdaptivePanelWithCustomGuide
case showCustomStatePanel
}
extension UseCase {
var name: String {
switch self {
case .trackingTableView: return "Scroll tracking(TableView)"
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 .showPanelInSheetModal: return "Show Panel in Sheet Modal"
case .showTabBar: return "Show Tab Bar"
case .showPageView: return "Show Page View"
case .showPageContentView: return "Show Page Content View"
case .showNestedScrollView: return "Show Nested ScrollView"
case .showRemovablePanel: return "Show Removable Panel"
case .showIntrinsicView: return "Show Intrinsic View"
case .showContentInset: return "Show with ContentInset"
case .showContainerMargins: return "Show with ContainerMargins"
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 .showCustomStatePanel: return "Show Panel with Custom state"
}
}
}
extension UseCase {
private enum Content {
case storyboard(String)
case viewController(UIViewController)
}
private var content: Content {
switch self {
case .trackingTableView: return .viewController(DebugTableViewController())
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 .showPanelInSheetModal: return .viewController(DebugTableViewController())
case .showPanelModal: return .viewController(DebugTableViewController())
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
case .showPageView: return .viewController(DebugTableViewController())
case .showPageContentView: return .viewController(DebugTableViewController())
case .showNestedScrollView: return .storyboard(String(describing: NestedScrollViewController.self))
case .showRemovablePanel: return .storyboard(String(describing: DetailViewController.self))
case .showIntrinsicView: return .storyboard("IntrinsicViewController") // Storyboard only
case .showContentInset: return .viewController(DebugTableViewController())
case .showContainerMargins: return .viewController(DebugTableViewController())
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: AdaptiveLayoutTestViewController.self))
case .showCustomStatePanel: return .viewController(DebugTableViewController())
}
}
func makeContentViewController(with storyboard: UIStoryboard) -> UIViewController {
switch content {
case .storyboard(let id):
return storyboard.instantiateViewController(withIdentifier: id)
case .viewController(let vc):
vc.loadViewIfNeeded()
return vc
}
}
}
@@ -0,0 +1,399 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class UseCaseController: NSObject {
unowned let mainVC: MainViewController
private(set) var useCase: UseCase
private var mainPanelVC: FloatingPanelController!
private var detailPanelVC: FloatingPanelController!
private var settingsPanelVC: FloatingPanelController!
private lazy var pagePanelController = PagePanelController()
init(mainVC: MainViewController) {
self.mainVC = mainVC
self.useCase = .trackingTableView
}
}
extension UseCaseController {
func set(useCase: UseCase) {
self.useCase = useCase
let contentVC = useCase.makeContentViewController(with: mainVC.storyboard!)
if let fpc = detailPanelVC {
fpc.removePanelFromParent(animated: true, completion: nil)
self.detailPanelVC = nil
}
switch useCase {
case .trackingTableView:
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)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .trackingTextView:
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .showDetail:
// Initialize FloatingPanelController
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
// Set a content view controller
fpc.set(contentViewController: contentVC)
fpc.contentMode = .fitToBounds
(contentVC as? DetailViewController)?.intrinsicHeightConstraint.isActive = false
detailPanelVC = fpc
// Add FloatingPanel to self.view
fpc.addPanel(toParent: mainVC, animated: true)
case .showModal, .showTabBar:
let modalVC = contentVC
modalVC.modalPresentationStyle = .fullScreen
mainVC.present(modalVC, animated: true, completion: nil)
case .showPageView:
let pageVC = pagePanelController.makePageViewController(for: mainVC)
mainVC.present(pageVC, animated: true, completion: nil)
case .showPageContentView:
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
let pageVC = pagePanelController.makePageViewControllerForContent()
if let page = (fpc.contentViewController as? UIPageViewController)?.viewControllers?.first {
fpc.track(scrollView: (page as! DebugTableViewController).tableView)
}
fpc.set(contentViewController: pageVC)
addMain(panel: fpc)
case .showRemovablePanel, .showIntrinsicView:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
fpc.delegate = self
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .showNestedScrollView:
let fpc = FloatingPanelController()
fpc.panGestureRecognizer.delegateProxy = self
fpc.delegate = self
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .showPanelModal:
let fpc = FloatingPanelController()
let contentVC = mainVC.storyboard!.instantiateViewController(withIdentifier: "DetailViewController")
contentVC.loadViewIfNeeded()
(contentVC as? DetailViewController)?.modeChangeView.isHidden = true
fpc.set(contentViewController: contentVC)
fpc.delegate = self
let appearance = SurfaceAppearance()
appearance.cornerRadius = 38.5
fpc.surfaceView.appearance = appearance
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
fpc.isRemovalInteractionEnabled = true
mainVC.present(fpc, animated: true, completion: nil)
case .showMultiPanelModal:
let fpc = MultiPanelController()
mainVC.present(fpc, animated: true, completion: nil)
case .showPanelInSheetModal:
let fpc = FloatingPanelController()
let contentVC = UIViewController()
fpc.set(contentViewController: contentVC)
fpc.delegate = self
let apprearance = SurfaceAppearance()
apprearance.cornerRadius = 38.5
apprearance.shadows = []
fpc.surfaceView.appearance = apprearance
fpc.isRemovalInteractionEnabled = true
let mvc = UIViewController()
mvc.view.backgroundColor = UIColor(displayP3Red: 2/255, green: 184/255, blue: 117/255, alpha: 1.0)
fpc.addPanel(toParent: mvc)
mainVC.present(mvc, animated: true, completion: nil)
case .showContentInset:
let contentViewController = UIViewController()
contentViewController.view.backgroundColor = .green
let fpc = FloatingPanelController()
fpc.set(contentViewController: contentViewController)
fpc.surfaceView.contentPadding = .init(top: 20, left: 20, bottom: 20, right: 20)
fpc.delegate = self
fpc.isRemovalInteractionEnabled = true
mainVC.present(fpc, animated: true, completion: nil)
case .showContainerMargins:
let fpc = FloatingPanelController()
let appearance = SurfaceAppearance()
appearance.cornerRadius = 38.5
fpc.surfaceView.appearance = appearance
fpc.surfaceView.backgroundColor = .red
fpc.surfaceView.containerMargins = .init(top: 24.0, left: 8.0, bottom: max(mainVC.layoutInsets.bottom, 8.0), right: 8.0)
#if swift(>=5.1) // Actually Xcode 11 or later
if #available(iOS 13.0, *) {
fpc.surfaceView.layer.cornerCurve = .continuous
}
#endif
fpc.delegate = self
fpc.isRemovalInteractionEnabled = true
mainVC.present(fpc, animated: true, completion: nil)
case .showNavigationController:
let fpc = FloatingPanelController()
fpc.contentInsetAdjustmentBehavior = .never
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .showTopPositionedPanel: // For debug
let fpc = FloatingPanelController(delegate: self)
let contentVC = UIViewController()
contentVC.view.backgroundColor = .red
fpc.set(contentViewController: contentVC)
addMain(panel: fpc)
case .showAdaptivePanel:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
if case let contentVC as ImageViewController = contentVC {
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithCustomGuide) ? .withHeaderFooter : .onlyImage
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
fpc.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
}
addMain(panel: fpc)
case .showAdaptivePanelWithCustomGuide:
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.ext_trackScrollView(in: contentVC)
fpc.layout = AdaptiveLayoutTestViewController.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
addMain(panel: fpc)
case .showCustomStatePanel:
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
}
}
func setUpSettingsPanel(for mainVC: MainViewController) {
guard settingsPanelVC == nil else { return }
// Initialize FloatingPanelController
settingsPanelVC = FloatingPanelController()
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
settingsPanelVC.surfaceView.appearance = appearance
settingsPanelVC.isRemovalInteractionEnabled = true
settingsPanelVC.backdropView.dismissalTapGestureRecognizer.isEnabled = true
settingsPanelVC.delegate = self
let contentVC = mainVC.storyboard?.instantiateViewController(withIdentifier: "SettingsViewController")
// Set a content view controller
settingsPanelVC.set(contentViewController: contentVC)
// Add FloatingPanel to self.view
settingsPanelVC.addPanel(toParent: mainVC, animated: true)
}
private func addMain(panel fpc: FloatingPanelController) {
let oldMainPanelVC = mainPanelVC
mainPanelVC = fpc
if let oldMainPanelVC = oldMainPanelVC {
oldMainPanelVC.removePanelFromParent(animated: true, completion: {
self.mainPanelVC.addPanel(toParent: self.mainVC, animated: true)
})
} else {
mainPanelVC.addPanel(toParent: mainVC, animated: true)
}
}
@objc
private func handleSurface(tapGesture: UITapGestureRecognizer) {
switch mainPanelVC.state {
case .full:
mainPanelVC.move(to: .half, animated: true)
default:
mainPanelVC.move(to: .full, animated: true)
}
}
}
extension UseCaseController: FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint {
if useCase == .showNavigationController, #available(iOS 11.0, *) {
// 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)
}
return CGPoint(x: 0.0, y: 0.0 - trackingScrollView.contentInset.top)
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
if vc == settingsPanelVC {
return IntrinsicPanelLayout()
}
switch useCase {
case .showTopPositionedPanel:
return TopPositionedPanelLayout()
case .showRemovablePanel:
return newCollection.verticalSizeClass == .compact ? RemovablePanelLandscapeLayout() : RemovablePanelLayout()
case .showIntrinsicView:
return IntrinsicPanelLayout()
case .showPanelModal:
if vc != mainPanelVC && vc != detailPanelVC {
return ModalPanelLayout()
}
fallthrough
case .showContentInset:
return FloatingPanelBottomLayout()
case .showCustomStatePanel:
return FloatingPanelLayoutWithCustomState()
default:
return (newCollection.verticalSizeClass == .compact) ? FloatingPanelBottomLayout() : mainVC
}
}
func floatingPanelDidRemove(_ vc: FloatingPanelController) {
switch vc {
case settingsPanelVC:
settingsPanelVC = nil
default:
break
}
}
}
extension UseCaseController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if case .showNestedScrollView = useCase {
return true
} else {
return false
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
false
}
}
private extension FloatingPanelController {
func ext_trackScrollView(in contentVC: UIViewController) {
switch contentVC {
case let consoleVC as DebugTextViewController:
track(scrollView: consoleVC.textView)
case let contentVC as DebugTableViewController:
let ob = contentVC.tableView.observe(\.isEditing) { [weak self] (tableView, _) in
self?.panGestureRecognizer.isEnabled = !tableView.isEditing
}
contentVC.kvoObservers.append(ob)
track(scrollView: contentVC.tableView)
case let contentVC as NestedScrollViewController:
track(scrollView: contentVC.scrollView)
case let navVC as UINavigationController:
if let rootVC = (navVC.topViewController as? MainViewController) {
rootVC.loadViewIfNeeded()
track(scrollView: rootVC.tableView)
}
case let contentVC as ImageViewController:
track(scrollView: contentVC.scrollView)
case let contentVC as AdaptiveLayoutTestViewController:
track(scrollView: contentVC.tableView)
default:
break
}
}
}
@@ -1,674 +0,0 @@
//
// ViewController.swift
// FloatingModalSample
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
import FloatingPanel
class SampleListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FloatingPanelControllerDelegate, FloatingPanelLayout {
@IBOutlet weak var tableView: UITableView!
enum Menu: Int, CaseIterable {
case trackingTableView
case trackingTextView
case showDetail
case showModal
case showFloatingPanelModal
case showTabBar
case showNestedScrollView
case showRemovablePanel
case showIntrinsicView
var name: String {
switch self {
case .trackingTableView: return "Scroll tracking(TableView)"
case .trackingTextView: return "Scroll tracking(TextView)"
case .showDetail: return "Show Detail Panel"
case .showModal: return "Show Modal"
case .showFloatingPanelModal: return "Show Floating Panel Modal"
case .showTabBar: return "Show Tab Bar"
case .showNestedScrollView: return "Show Nested ScrollView"
case .showRemovablePanel: return "Show Removable Panel"
case .showIntrinsicView: return "Show Intrinsic View"
}
}
var storyboardID: String? {
switch self {
case .trackingTableView: return nil
case .trackingTextView: return "ConsoleViewController"
case .showDetail: return "DetailViewController"
case .showModal: return "ModalViewController"
case .showFloatingPanelModal: return nil
case .showTabBar: return "TabBarViewController"
case .showNestedScrollView: return "NestedScrollViewController"
case .showRemovablePanel: return "DetailViewController"
case .showIntrinsicView: return "IntrinsicViewController"
}
}
}
var mainPanelVC: FloatingPanelController!
var detailPanelVC: FloatingPanelController!
var currentMenu: Menu = .trackingTableView
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
let contentVC = DebugTableViewController()
addMainPanel(with: contentVC)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func addMainPanel(with contentVC: UIViewController) {
// Initialize FloatingPanelController
mainPanelVC = FloatingPanelController()
mainPanelVC.delegate = self
// Initialize FloatingPanelController and add the view
mainPanelVC.surfaceView.cornerRadius = 6.0
mainPanelVC.surfaceView.shadowHidden = false
// Set a content view controller
mainPanelVC.set(contentViewController: contentVC)
// Enable tap-to-hide and removal interaction
switch currentMenu {
case .showRemovablePanel, .showIntrinsicView:
mainPanelVC.isRemovalInteractionEnabled = true
let backdropTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
mainPanelVC.backdropView.addGestureRecognizer(backdropTapGesture)
default:
break
}
// Track a scroll view
switch contentVC {
case let consoleVC as DebugTextViewController:
mainPanelVC.track(scrollView: consoleVC.textView)
case let contentVC as DebugTableViewController:
mainPanelVC.track(scrollView: contentVC.tableView)
case let contentVC as NestedScrollViewController:
mainPanelVC.track(scrollView: contentVC.scrollView)
default:
break
}
// Add FloatingPanel to self.view
mainPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
}
@objc func dismissDetailPanelVC() {
detailPanelVC.removePanelFromParent(animated: true, completion: nil)
}
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
mainPanelVC.hide(animated: true, completion: nil)
}
// MARK:- TableViewDatasource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Menu.allCases.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let menu = Menu.allCases[indexPath.row]
cell.textLabel?.text = menu.name
return cell
}
// MARK:- TableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let menu = Menu.allCases[indexPath.row]
let contentVC: UIViewController = {
guard let storyboardID = menu.storyboardID else { return DebugTableViewController() }
guard let vc = self.storyboard?.instantiateViewController(withIdentifier: storyboardID) else { fatalError() }
return vc
}()
self.currentMenu = menu
switch menu {
case .showDetail:
detailPanelVC?.removePanelFromParent(animated: false)
// Initialize FloatingPanelController
detailPanelVC = FloatingPanelController()
// Initialize FloatingPanelController and add the view
detailPanelVC.surfaceView.cornerRadius = 6.0
detailPanelVC.surfaceView.shadowHidden = false
// Set a content view controller
detailPanelVC.set(contentViewController: contentVC)
// Add FloatingPanel to self.view
detailPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
case .showModal, .showTabBar:
let modalVC = contentVC
present(modalVC, animated: true, completion: nil)
case .showFloatingPanelModal:
let fpc = FloatingPanelController()
let contentVC = self.storyboard!.instantiateViewController(withIdentifier: "DetailViewController")
fpc.set(contentViewController: contentVC)
fpc.delegate = self
fpc.surfaceView.cornerRadius = 38.5
fpc.surfaceView.shadowHidden = false
fpc.isRemovalInteractionEnabled = true
self.present(fpc, animated: true, completion: nil)
default:
detailPanelVC?.removePanelFromParent(animated: true, completion: nil)
mainPanelVC?.removePanelFromParent(animated: true) {
self.addMainPanel(with: contentVC)
}
}
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
switch currentMenu {
case .showRemovablePanel:
return newCollection.verticalSizeClass == .compact ? RemovablePanelLandscapeLayout() : RemovablePanelLayout()
case .showIntrinsicView:
return IntrinsicPanelLayout()
case .showFloatingPanelModal:
if vc != mainPanelVC && vc != detailPanelVC {
return ModalPanelLayout()
}
fallthrough
default:
return (newCollection.verticalSizeClass == .compact) ? nil : self
}
}
var initialPosition: FloatingPanelPosition {
return .half
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0
case .half: return 262.0
case .tip: return 69.0
case .hidden: return nil
}
}
}
class IntrinsicPanelLayout: FloatingPanelIntrinsicLayout { }
class RemovablePanelLayout: FloatingPanelIntrinsicLayout {
var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .half]
}
var topInteractionBuffer: CGFloat {
return 200.0
}
var bottomInteractionBuffer: CGFloat {
return 261.0 - 22.0
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .half: return 130.0
default: return nil
}
}
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.3
}
}
class RemovablePanelLandscapeLayout: FloatingPanelIntrinsicLayout {
var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .half]
}
var bottomInteractionBuffer: CGFloat {
return 261.0 - 22.0
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .half: return 261.0
default: return nil
}
}
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.3
}
}
class ModalPanelLayout: FloatingPanelIntrinsicLayout {
var topInteractionBuffer: CGFloat {
return 100.0
}
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.3
}
}
class NestedScrollViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
@IBAction func longPressed(_ sender: Any) {
print("LongPressed!")
}
@IBAction func swipped(_ sender: Any) {
print("Swipped!")
}
@IBAction func tapped(_ sender: Any) {
print("Tapped!")
}
}
class DebugTextViewController: UIViewController, UITextViewDelegate {
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
if #available(iOS 11.0, *) {
textView.contentInsetAdjustmentBehavior = .never
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("TextView --- ", scrollView.contentOffset, scrollView.contentInset)
if #available(iOS 11.0, *) {
print("TextView --- ", scrollView.adjustedContentInset)
}
}
@IBAction func close(sender: UIButton) {
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
dismiss(animated: true, completion: nil)
}
}
class DebugTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
weak var tableView: UITableView!
var items: [String] = []
var itemHeight: CGFloat = 66.0
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView(frame: .zero,
style: .plain)
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leftAnchor.constraint(equalTo: view.leftAnchor),
tableView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
tableView.dataSource = self
tableView.delegate = self
self.tableView = tableView
let stackView = UIStackView()
view.addSubview(stackView)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .trailing
stackView.spacing = 10.0
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 22.0),
stackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -22.0),
])
let button = UIButton()
button.setTitle("Animate Scroll", for: .normal)
button.setTitleColor(view.tintColor, for: .normal)
button.addTarget(self, action: #selector(animateScroll), for: .touchUpInside)
stackView.addArrangedSubview(button)
let button2 = UIButton()
button2.setTitle("Change content size", for: .normal)
button2.setTitleColor(view.tintColor, for: .normal)
button2.addTarget(self, action: #selector(changeContentSize), for: .touchUpInside)
stackView.addArrangedSubview(button2)
for i in 0...100 {
items.append("Items \(i)")
}
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
@objc func animateScroll() {
tableView.scrollToRow(at: IndexPath(row: lround(Double(items.count) / 2.0),
section: 0),
at: .top, animated: true)
}
@objc func changeContentSize() {
let actionSheet = UIAlertController(title: "Change content size", message: "", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Large", style: .default, handler: { (_) in
self.itemHeight = 66.0
self.changeItems(100)
}))
actionSheet.addAction(UIAlertAction(title: "Match", style: .default, handler: { (_) in
switch self.tableView.bounds.height {
case 585: // iPhone 6,7,8
self.itemHeight = self.tableView.bounds.height / 13.0
self.changeItems(13)
case 656: // iPhone {6,7,8} Plus
self.itemHeight = self.tableView.bounds.height / 16.0
self.changeItems(16)
default: // iPhone X family
self.itemHeight = self.tableView.bounds.height / 12.0
self.changeItems(12)
}
}))
actionSheet.addAction(UIAlertAction(title: "Short", style: .default, handler: { (_) in
self.itemHeight = 66.0
self.changeItems(3)
}))
self.present(actionSheet, animated: true, completion: nil)
}
func changeItems(_ count: Int) {
items.removeAll()
for i in 0..<count {
items.append("Items \(i)")
}
tableView.reloadData()
}
@objc func close(sender: UIButton) {
// Remove FloatingPanel from a view
(self.parent as! FloatingPanelController).removePanelFromParent(animated: true, completion: nil)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//print("Content View: viewWillLayoutSubviews")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//print("Content View: viewDidLayoutSubviews")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Content View: viewWillAppear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("Content View: viewDidAppear", view.bounds)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("Content View: viewWillDisappear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("Content View: viewDidDisappear")
}
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
print("Content View: willMove(toParent: \(String(describing: parent))")
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
print("Content View: didMove(toParent: \(String(describing: parent))")
}
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
print("Content View: willTransition(to: \(newCollection), with: \(coordinator))")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return itemHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
return [
UITableViewRowAction(style: .destructive, title: "Delete", handler: { (action, path) in
self.items.remove(at: path.row)
tableView.deleteRows(at: [path], with: .automatic)
}),
]
}
}
class DetailViewController: UIViewController {
@IBOutlet weak var closeButton: UIButton!
@IBAction func close(sender: UIButton) {
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
dismiss(animated: true, completion: nil)
}
@IBAction func buttonPressed(_ sender: UIButton) {
switch sender.titleLabel?.text {
case "Show":
performSegue(withIdentifier: "ShowSegue", sender: self)
case "Present Modally":
performSegue(withIdentifier: "PresentModallySegue", sender: self)
default:
break
}
}
@IBAction func tapped(_ sender: Any) {
print("Detail panel is tapped!")
}
@IBAction func swipped(_ sender: Any) {
print("Detail panel is swipped!")
}
@IBAction func longPressed(_ sender: Any) {
print("Detail panel is longPressed!")
}
}
class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
var fpc: FloatingPanelController!
var consoleVC: DebugTextViewController!
@IBOutlet weak var safeAreaView: UIView!
var isNewlayout: Bool = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.cornerRadius = 6.0
fpc.surfaceView.shadowHidden = false
// Set a content view controller and track the scroll view
let consoleVC = storyboard?.instantiateViewController(withIdentifier: "ConsoleViewController") as! DebugTextViewController
fpc.set(contentViewController: consoleVC)
fpc.track(scrollView: consoleVC.textView)
self.consoleVC = consoleVC
// Add FloatingPanel to self.view
fpc.addPanel(toParent: self, belowView: safeAreaView)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove FloatingPanel from a view
fpc.removePanelFromParent(animated: false)
}
@IBAction func close(sender: UIButton) {
dismiss(animated: true, completion: nil)
}
@IBAction func moveToFull(sender: UIButton) {
fpc.move(to: .full, animated: true)
}
@IBAction func moveToHalf(sender: UIButton) {
fpc.move(to: .half, animated: true)
}
@IBAction func moveToTip(sender: UIButton) {
fpc.move(to: .tip, animated: true)
}
@IBAction func updateLayout(_ sender: Any) {
isNewlayout = !isNewlayout
UIView.animate(withDuration: 0.5) {
self.fpc.updateLayout()
}
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
return (isNewlayout) ? ModalSecondLayout() : nil
}
}
class ModalSecondLayout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 18.0
case .half: return 262.0
case .tip: return 44.0
case .hidden: return nil
}
}
}
class TabBarViewController: UITabBarController {}
class TabBarContentViewController: UIViewController, FloatingPanelControllerDelegate {
var fpc: FloatingPanelController!
var consoleVC: DebugTextViewController!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.cornerRadius = 6.0
fpc.surfaceView.shadowHidden = false
// Set a content view controller and track the scroll view
let consoleVC = storyboard?.instantiateViewController(withIdentifier: "ConsoleViewController") as! DebugTextViewController
fpc.set(contentViewController: consoleVC)
fpc.track(scrollView: consoleVC.textView)
self.consoleVC = consoleVC
// Add FloatingPanel to self.view
fpc.addPanel(toParent: self)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove FloatingPanel from a view
fpc.removePanelFromParent(animated: false)
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
switch self.tabBarItem.tag {
case 0:
return OneTabBarPanelLayout()
case 1:
return TwoTabBarPanel2Layout()
default:
return nil
}
}
@IBAction func close(sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}
extension FloatingPanelLayout {
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
if #available(iOS 11.0, *) {
return [
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
]
} else {
return [
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0),
surfaceView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0),
]
}
}
}
class OneTabBarPanelLayout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .tip
}
var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .tip]
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .tip: return 22.0
default: return nil
}
}
}
class TwoTabBarPanel2Layout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .half]
}
var bottomInteractionBuffer: CGFloat {
return 261.0 - 22.0
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .half: return 261.0
default: return nil
}
}
}
+1 -7
View File
@@ -1,10 +1,4 @@
//
// FloatingModalSampleTests.swift
// FloatingModalSampleTests
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import XCTest
@testable import FloatingPanelSample
+1 -4
View File
@@ -1,7 +1,4 @@
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import XCTest
@@ -0,0 +1,397 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
545BA70621BA3214007F7846 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA70521BA3214007F7846 /* AppDelegate.m */; };
545BA70921BA3214007F7846 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA70821BA3214007F7846 /* MainViewController.m */; };
545BA70C21BA3214007F7846 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545BA70A21BA3214007F7846 /* Main.storyboard */; };
545BA70E21BA3217007F7846 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 545BA70D21BA3217007F7846 /* Assets.xcassets */; };
545BA71121BA3217007F7846 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545BA70F21BA3217007F7846 /* LaunchScreen.storyboard */; };
545BA71421BA3217007F7846 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA71321BA3217007F7846 /* main.m */; };
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; };
545BA72721BA3BAF007F7846 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
545BA72821BA3BAF007F7846 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
545BA72721BA3BAF007F7846 /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
545BA70121BA3214007F7846 /* SamplesObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SamplesObjC.app; sourceTree = BUILT_PRODUCTS_DIR; };
545BA70421BA3214007F7846 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
545BA70521BA3214007F7846 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
545BA70721BA3214007F7846 /* MainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = "<group>"; };
545BA70821BA3214007F7846 /* MainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = "<group>"; };
545BA70B21BA3214007F7846 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
545BA70D21BA3217007F7846 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
545BA71021BA3217007F7846 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
545BA71221BA3217007F7846 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 */
545BA6FE21BA3214007F7846 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
545BA6F821BA3214007F7846 = {
isa = PBXGroup;
children = (
545BA72521BA3BAF007F7846 /* FloatingPanel.framework */,
545BA70321BA3214007F7846 /* SamplesObjC */,
545BA70221BA3214007F7846 /* Products */,
5D82A6AE28D18443006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
545BA70221BA3214007F7846 /* Products */ = {
isa = PBXGroup;
children = (
545BA70121BA3214007F7846 /* SamplesObjC.app */,
);
name = Products;
sourceTree = "<group>";
};
545BA70321BA3214007F7846 /* SamplesObjC */ = {
isa = PBXGroup;
children = (
545BA70D21BA3217007F7846 /* Assets.xcassets */,
545BA70F21BA3217007F7846 /* LaunchScreen.storyboard */,
545BA70A21BA3214007F7846 /* Main.storyboard */,
545BA70421BA3214007F7846 /* AppDelegate.h */,
545BA70521BA3214007F7846 /* AppDelegate.m */,
545BA70721BA3214007F7846 /* MainViewController.h */,
545BA70821BA3214007F7846 /* MainViewController.m */,
545BA71221BA3217007F7846 /* Info.plist */,
545BA71321BA3217007F7846 /* main.m */,
545BA72221BA3867007F7846 /* SamplesObjC-Bridging-Header.h */,
);
path = SamplesObjC;
sourceTree = "<group>";
};
5D82A6AE28D18443006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
545BA70021BA3214007F7846 /* SamplesObjC */ = {
isa = PBXNativeTarget;
buildConfigurationList = 545BA71721BA3217007F7846 /* Build configuration list for PBXNativeTarget "SamplesObjC" */;
buildPhases = (
545BA6FD21BA3214007F7846 /* Sources */,
545BA6FE21BA3214007F7846 /* Frameworks */,
545BA6FF21BA3214007F7846 /* Resources */,
545BA72821BA3BAF007F7846 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = SamplesObjC;
productName = SamplesObjC;
productReference = 545BA70121BA3214007F7846 /* SamplesObjC.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
545BA6F921BA3214007F7846 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "Shin Yamamoto";
TargetAttributes = {
545BA70021BA3214007F7846 = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1010;
};
};
};
buildConfigurationList = 545BA6FC21BA3214007F7846 /* Build configuration list for PBXProject "SamplesObjC" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 545BA6F821BA3214007F7846;
productRefGroup = 545BA70221BA3214007F7846 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
545BA70021BA3214007F7846 /* SamplesObjC */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
545BA6FF21BA3214007F7846 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
545BA71121BA3217007F7846 /* LaunchScreen.storyboard in Resources */,
545BA70E21BA3217007F7846 /* Assets.xcassets in Resources */,
545BA70C21BA3214007F7846 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
545BA6FD21BA3214007F7846 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
545BA70921BA3214007F7846 /* MainViewController.m in Sources */,
545BA71421BA3217007F7846 /* main.m in Sources */,
545BA70621BA3214007F7846 /* AppDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
545BA70A21BA3214007F7846 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
545BA70B21BA3214007F7846 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
545BA70F21BA3217007F7846 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
545BA71021BA3217007F7846 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
545BA71521BA3217007F7846 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
545BA71621BA3217007F7846 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
545BA71821BA3217007F7846 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = NO;
DEVELOPMENT_TEAM = J3D7L9FHSS;
INFOPLIST_FILE = SamplesObjC/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(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";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
545BA71921BA3217007F7846 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = NO;
DEVELOPMENT_TEAM = J3D7L9FHSS;
INFOPLIST_FILE = SamplesObjC/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(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";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
545BA6FC21BA3214007F7846 /* Build configuration list for PBXProject "SamplesObjC" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545BA71521BA3217007F7846 /* Debug */,
545BA71621BA3217007F7846 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
545BA71721BA3217007F7846 /* Build configuration list for PBXNativeTarget "SamplesObjC" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545BA71821BA3217007F7846 /* Debug */,
545BA71921BA3217007F7846 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 545BA6F921BA3214007F7846 /* Project object */;
}
@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:FloatingModalController.xcodeproj">
location = "self:SamplesObjC.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545BA70021BA3214007F7846"
BuildableName = "SamplesObjC.app"
BlueprintName = "SamplesObjC"
ReferencedContainer = "container:SamplesObjC.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545BA70021BA3214007F7846"
BuildableName = "SamplesObjC.app"
BlueprintName = "SamplesObjC"
ReferencedContainer = "container:SamplesObjC.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545BA70021BA3214007F7846"
BuildableName = "SamplesObjC.app"
BlueprintName = "SamplesObjC"
ReferencedContainer = "container:SamplesObjC.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545BA70021BA3214007F7846"
BuildableName = "SamplesObjC.app"
BlueprintName = "SamplesObjC"
ReferencedContainer = "container:SamplesObjC.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,9 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow* window;
@end
@@ -0,0 +1,9 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
@end
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" 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="13104.12"/>
<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"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</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>
@@ -0,0 +1,24 @@
<?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">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Main View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="MainViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
@@ -0,0 +1,45 @@
<?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>UIMainStoryboardFile</key>
<string>Main</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>
@@ -0,0 +1,13 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import <UIKit/UIKit.h>
@import FloatingPanel;
@interface MainViewController : UIViewController
@end
@interface MyFloatingPanelLayout : NSObject <FloatingPanelLayout>
@end
@interface MyFloatingPanelBehavior : NSObject <FloatingPanelBehavior>
@end
@@ -0,0 +1,96 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import "MainViewController.h"
@import FloatingPanel;
// Defining a custom FloatingPanelState
@interface FloatingPanelState(Extended)
+ (FloatingPanelState *)LastQuart;
@end
@implementation FloatingPanelState(Extended)
static FloatingPanelState *_lastQuart;
+ (FloatingPanelState *)LastQuart {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_lastQuart = [[FloatingPanelState alloc] initWithRawValue:@"lastquart" order:750];
});
return _lastQuart;
}
@end
@interface MainViewController()<FloatingPanelControllerDelegate>
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
FloatingPanelController *fpc = [[FloatingPanelController alloc] init];
[fpc setContentViewController:nil];
[fpc setDelegate:self];
[fpc setLayout: [MyFloatingPanelLayout new]];
[fpc setBehavior:[MyFloatingPanelBehavior new]];
[fpc setRemovalInteractionEnabled:NO];
[fpc addPanelToParent:self at:self.view.subviews.count animated:NO completion:nil];
[fpc moveToState:FloatingPanelState.Tip animated:true completion:nil];
[self updateAppearance: fpc];
}
- (id<FloatingPanelLayout>)floatingPanel:(FloatingPanelController *)vc layoutFor:(UITraitCollection *)newCollection {
FloatingPanelBottomLayout *layout = [FloatingPanelBottomLayout new];
return layout;
}
- (void)updateAppearance: (FloatingPanelController*)fpc
{
FloatingPanelSurfaceAppearance *appearance = [[FloatingPanelSurfaceAppearance alloc] init];
appearance.backgroundColor = [UIColor clearColor];
appearance.cornerRadius = 23.0;
if (@available(iOS 13.0, *)) {
fpc.surfaceView.containerView.layer.cornerCurve = kCACornerCurveContinuous;
}
FloatingPanelSurfaceAppearanceShadow *shadow = [[FloatingPanelSurfaceAppearanceShadow alloc] init];
shadow.color = [UIColor redColor];
shadow.radius = 10.0;
shadow.spread = 10.0;
FloatingPanelSurfaceAppearanceShadow *shadow2 = [[FloatingPanelSurfaceAppearanceShadow alloc] init];
shadow2.color = [UIColor blueColor];
shadow2.radius = 10.0;
shadow2.spread = 10.0;
appearance.shadows = @[shadow, shadow2];
fpc.surfaceView.appearance = appearance;
}
@end
@implementation MyFloatingPanelLayout
- (FloatingPanelState *)initialState {
return FloatingPanelState.Half;
}
- (NSDictionary<FloatingPanelState *, id<FloatingPanelLayoutAnchoring>> *)anchors {
return @{
FloatingPanelState.LastQuart: [[FloatingPanelLayoutAnchor alloc] initWithFractionalInset:0.25
edge:FloatingPanelReferenceEdgeTop
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
FloatingPanelState.Half: [[FloatingPanelLayoutAnchor alloc] initWithFractionalInset:0.5
edge:FloatingPanelReferenceEdgeTop
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
FloatingPanelState.Tip: [[FloatingPanelLayoutAnchor alloc] initWithAbsoluteInset:44.0
edge:FloatingPanelReferenceEdgeBottom
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
};
}
- (enum FloatingPanelPosition)position {
return FloatingPanelPositionBottom;
}
@end
@implementation MyFloatingPanelBehavior
@end
@@ -0,0 +1,3 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@@ -7,13 +7,14 @@
objects = {
/* Begin PBXBuildFile section */
5433F24B21717EA300BDAA5D /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5433F24A21717EA300BDAA5D /* FloatingPanel.framework */; };
5433F24C21717EA300BDAA5D /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5433F24A21717EA300BDAA5D /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
548DF95421705BE00041922A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95321705BE00041922A /* AppDelegate.swift */; };
548DF95621705BE00041922A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95521705BE00041922A /* ViewController.swift */; };
548DF95621705BE00041922A /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95521705BE00041922A /* MainViewController.swift */; };
548DF95921705BE00041922A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95721705BE00041922A /* Main.storyboard */; };
548DF95B21705BE10041922A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95A21705BE10041922A /* Assets.xcassets */; };
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 */
@@ -23,7 +24,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
5433F24C21717EA300BDAA5D /* FloatingPanel.framework in Embed Frameworks */,
549D23D0233C77CF008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -31,14 +32,15 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5433F24A21717EA300BDAA5D /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
548DF95021705BE00041922A /* Stocks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stocks.app; sourceTree = BUILT_PRODUCTS_DIR; };
548DF95321705BE00041922A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
548DF95521705BE00041922A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
548DF95521705BE00041922A /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
548DF95821705BE00041922A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
548DF95A21705BE10041922A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
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,7 +48,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5433F24B21717EA300BDAA5D /* FloatingPanel.framework in Frameworks */,
5D82A6AA28D18432006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
549D23CF233C77CF008EF4D7 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -56,9 +59,10 @@
548DF94721705BE00041922A = {
isa = PBXGroup;
children = (
5433F24A21717EA300BDAA5D /* FloatingPanel.framework */,
549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */,
548DF95221705BE00041922A /* Stocks */,
548DF95121705BE00041922A /* Products */,
5D82A6A828D1842A006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -73,16 +77,24 @@
548DF95221705BE00041922A /* Stocks */ = {
isa = PBXGroup;
children = (
548DF95321705BE00041922A /* AppDelegate.swift */,
548DF95521705BE00041922A /* ViewController.swift */,
548DF95721705BE00041922A /* Main.storyboard */,
548DF95A21705BE10041922A /* Assets.xcassets */,
548DF95C21705BE10041922A /* LaunchScreen.storyboard */,
548DF95721705BE00041922A /* Main.storyboard */,
548DF95321705BE00041922A /* AppDelegate.swift */,
548DF95521705BE00041922A /* MainViewController.swift */,
548DF95F21705BE10041922A /* Info.plist */,
);
path = Stocks;
sourceTree = "<group>";
};
5D82A6A828D1842A006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6A928D1842B006A44BA /* libswiftCoreGraphics.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -111,7 +123,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
548DF94F21705BE00041922A = {
@@ -155,7 +167,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
548DF95621705BE00041922A /* ViewController.swift in Sources */,
548DF95621705BE00041922A /* MainViewController.swift in Sources */,
548DF95421705BE00041922A /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -208,6 +220,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -269,6 +282,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -310,9 +324,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -329,9 +347,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)/usr/lib/swift",
);
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
ReferencedContainer = "container:Stocks.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -61,8 +59,6 @@
ReferencedContainer = "container:Stocks.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
+1 -7
View File
@@ -1,10 +1,4 @@
//
// AppDelegate.swift
// Stocks
//
// Created by Shin Yamamoto on 2018/10/12.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
@@ -1,25 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<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-->
<!--Main View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Stocks" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="BYZ-38-t0r" customClass="MainViewController" customModule="Stocks" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uop-sw-I6p">
<rect key="frame" x="0.0" y="85" width="375" height="537.5"/>
<rect key="frame" x="0.0" y="65" width="600" height="490.5"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" placeholderIntrinsicWidth="375" placeholderIntrinsicHeight="625" image="stocks_list" translatesAutoresizingMaskIntoConstraints="NO" id="XJR-iK-fem">
<rect key="frame" x="0.0" y="0.0" width="375" height="625"/>
@@ -34,10 +30,10 @@
</constraints>
</scrollView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dFl-81-6ok">
<rect key="frame" x="0.0" y="622.5" width="375" height="44.5"/>
<rect key="frame" x="0.0" y="555.5" width="600" height="44.5"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="yahoo_bottom_bar" translatesAutoresizingMaskIntoConstraints="NO" id="NKr-gS-mpx">
<rect key="frame" x="0.0" y="0.0" width="375" height="44.5"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="44.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="44.5" id="B5t-ZF-qUj"/>
@@ -53,10 +49,10 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="-8" translatesAutoresizingMaskIntoConstraints="NO" id="f7r-Al-pIN">
<rect key="frame" x="16" y="20" width="153.5" height="57"/>
<rect key="frame" x="16" y="0.0" width="153.5" height="57"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="STOCKS" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCG-Wl-fXa">
<rect key="frame" x="0.0" y="0.0" width="111.5" height="32.5"/>
<rect key="frame" x="0.0" y="0.0" width="111" height="32.5"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="27"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
@@ -70,6 +66,7 @@
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" red="0.11764924973249435" green="0.11764311045408249" blue="0.11764728277921677" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<constraints>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="dFl-81-6ok" secondAttribute="trailing" id="20i-yz-AaQ"/>
@@ -83,7 +80,6 @@
<constraint firstItem="dFl-81-6ok" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="nlX-Ab-1aI"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="NKr-gS-mpx" secondAttribute="bottom" id="yeu-NH-Pmp"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<connections>
<outlet property="bottomToolView" destination="dFl-81-6ok" id="NXn-af-lFv"/>
@@ -122,6 +118,7 @@
</constraints>
</scrollView>
</subviews>
<viewLayoutGuide key="safeArea" id="INo-op-FLO"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="h7M-7T-4k4" secondAttribute="bottom" id="2b8-Wo-tm0"/>
@@ -129,7 +126,6 @@
<constraint firstItem="h7M-7T-4k4" firstAttribute="trailing" secondItem="INo-op-FLO" secondAttribute="trailing" id="WTc-1l-3Ha"/>
<constraint firstItem="h7M-7T-4k4" firstAttribute="leading" secondItem="INo-op-FLO" secondAttribute="leading" id="na8-TO-WiG"/>
</constraints>
<viewLayoutGuide key="safeArea" id="INo-op-FLO"/>
</view>
<size key="freeformSize" width="375" height="600"/>
<connections>
@@ -0,0 +1,121 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
import FloatingPanel
class MainViewController: UIViewController, FloatingPanelControllerDelegate {
@IBOutlet var topBannerView: UIImageView!
@IBOutlet weak var labelStackView: UIStackView!
@IBOutlet weak var bottomToolView: UIView!
var fpc: FloatingPanelController!
var newsVC: NewsViewController!
var initialColor: UIColor = .black
override func viewDidLoad() {
super.viewDidLoad()
initialColor = view.backgroundColor!
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
fpc.behavior = FloatingPanelStocksBehavior()
// Initialize FloatingPanelController and add the view
fpc.surfaceView.backgroundColor = UIColor(displayP3Red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0)
fpc.surfaceView.appearance.cornerRadius = 24.0
fpc.surfaceView.appearance.shadows = []
fpc.surfaceView.appearance.borderWidth = 1.0 / traitCollection.displayScale
fpc.surfaceView.appearance.borderColor = UIColor.black.withAlphaComponent(0.2)
newsVC = storyboard?.instantiateViewController(withIdentifier: "News") as? NewsViewController
// Set a content view controller
fpc.set(contentViewController: newsVC)
fpc.track(scrollView: newsVC.scrollView)
fpc.addPanel(toParent: self, at: view.subviews.firstIndex(of: bottomToolView) ?? -1 , animated: false)
topBannerView.frame = .zero
topBannerView.alpha = 0.0
view.addSubview(topBannerView)
topBannerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topBannerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
topBannerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
])
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
// MARK: FloatingPanelControllerDelegate
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
return FloatingPanelStocksLayout()
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {
if vc.isAttracting == false {
let loc = vc.surfaceLocation
let minY = vc.surfaceLocation(for: .full).y
let maxY = vc.surfaceLocation(for: .tip).y
vc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY))
}
if vc.surfaceLocation.y <= vc.surfaceLocation(for: .full).y + 100 {
showStockTickerBanner()
} else {
hideStockTickerBanner()
}
}
private func showStockTickerBanner() {
// Present top bar with dissolve animation
UIView.animate(withDuration: 0.25) {
self.topBannerView.alpha = 1.0
self.labelStackView.alpha = 0.0
self.view.backgroundColor = .black
}
}
private func hideStockTickerBanner() {
// Dimiss top bar with dissolve animation
UIView.animate(withDuration: 0.25) {
self.topBannerView.alpha = 0.0
self.labelStackView.alpha = 1.0
self.view.backgroundColor = .black
}
}
}
class NewsViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
}
// MARK: - FloatingPanelLayout
class FloatingPanelStocksLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .tip
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
}
}
// MARK: - FloatingPanelBehavior
class FloatingPanelStocksBehavior: FloatingPanelBehavior {
let springDecelerationRate: CGFloat = UIScrollView.DecelerationRate.fast.rawValue
let springResponseTime: CGFloat = 0.25
}
-155
View File
@@ -1,155 +0,0 @@
//
// ViewController.swift
// Stocks
//
// Created by Shin Yamamoto on 2018/10/12.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
import FloatingPanel
class ViewController: UIViewController, FloatingPanelControllerDelegate {
@IBOutlet var topBannerView: UIImageView!
@IBOutlet weak var labelStackView: UIStackView!
@IBOutlet weak var bottomToolView: UIView!
var fpc: FloatingPanelController!
var newsVC: NewsViewController!
var initialColor: UIColor = .black
override func viewDidLoad() {
super.viewDidLoad()
initialColor = view.backgroundColor!
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.backgroundColor = UIColor(displayP3Red: 30.0/255.0, green: 30.0/255.0, blue: 30.0/255.0, alpha: 1.0)
fpc.surfaceView.cornerRadius = 24.0
fpc.surfaceView.shadowHidden = true
fpc.surfaceView.borderWidth = 1.0 / traitCollection.displayScale
fpc.surfaceView.borderColor = UIColor.black.withAlphaComponent(0.2)
newsVC = storyboard?.instantiateViewController(withIdentifier: "News") as? NewsViewController
// Set a content view controller
fpc.set(contentViewController: newsVC)
fpc.track(scrollView: newsVC.scrollView)
fpc.addPanel(toParent: self, belowView: bottomToolView, animated: false)
topBannerView.frame = .zero
topBannerView.alpha = 0.0
view.addSubview(topBannerView)
topBannerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topBannerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
topBannerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
// MARK: FloatingPanelControllerDelegate
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
return FloatingPanelStocksLayout()
}
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
return FloatingPanelStocksBehavior()
}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
if vc.position == .full {
// Dimiss top bar with dissolve animation
UIView.animate(withDuration: 0.25) {
self.topBannerView.alpha = 0.0
self.labelStackView.alpha = 1.0
self.view.backgroundColor = self.initialColor
}
}
}
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {
if targetPosition == .full {
// Present top bar with dissolve animation
UIView.animate(withDuration: 0.25) {
self.topBannerView.alpha = 1.0
self.labelStackView.alpha = 0.0
self.view.backgroundColor = .black
}
}
}
}
class NewsViewController: UIViewController {
@IBOutlet weak var scrollView: UIScrollView!
}
// MARK: My custom layout
class FloatingPanelStocksLayout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .tip
}
var topInteractionBuffer: CGFloat { return 0.0 }
var bottomInteractionBuffer: CGFloat { return 0.0 }
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 56.0
case .half: return 262.0
case .tip: return 85.0 + 44.0 // Visible + ToolView
default: return nil
}
}
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.0
}
}
// MARK: My custom behavior
class FloatingPanelStocksBehavior: FloatingPanelBehavior {
var velocityThreshold: CGFloat {
return 15.0
}
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
let timing = timeingCurve(to: targetPosition, with: velocity)
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
}
private func timeingCurve(to: FloatingPanelPosition, with velocity: CGVector) -> UITimingCurveProvider {
let damping = self.damping(with: velocity)
return UISpringTimingParameters(dampingRatio: damping,
frequencyResponse: 0.4,
initialVelocity: velocity)
}
private func damping(with velocity: CGVector) -> CGFloat {
switch velocity.dy {
case ...(-velocityThreshold):
return 0.7
case velocityThreshold...:
return 0.7
default:
return 1.0
}
}
}
+6 -8
View File
@@ -1,24 +1,22 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.3.0"
s.version = "2.6.0"
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.screenshots = ""
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 => "v#{s.version}" }
s.source_files = "Framework/Sources/*.swift"
s.swift_version = "4.2"
s.pod_target_xcconfig = { 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => s.version.to_s }
s.source_files = "Sources/*.swift"
s.swift_version = '5.0'
s.framework = "UIKit"
s.author = { "Shin Yamamoto" => "shin@scenee.com" }
s.license = { :type => "MIT", :file => "LICENSE" }
s.social_media_url = "https://twitter.com/scenee"
end
+838
View File
@@ -0,0 +1,838 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542753C522C49A6E00D17955 /* LayoutTests.swift */; };
54352E9621A51A2500CBCA08 /* Transitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* Transitioning.swift */; };
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* PassthroughView.swift */; };
5450EEE421646DF500135936 /* Behavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* Behavior.swift */; };
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
545DB9D02151169500CA77B8 /* ControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ControllerTests.swift */; };
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanel.h */; settings = {ATTRIBUTES = (Public, ); }; };
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 /* LayoutProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutProperties.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 */; };
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 */
545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 545DB9B82151169500CA77B8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 545DB9C02151169500CA77B8;
remoteInfo = FloatingModalController;
};
54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 545DB9B82151169500CA77B8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 54E740C9218AFD67005C1A34;
remoteInfo = TestingHost;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
542753C522C49A6E00D17955 /* LayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutTests.swift; sourceTree = "<group>"; };
542753C722C49A8F00D17955 /* TestSupports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSupports.swift; sourceTree = "<group>"; };
54352E9521A51A2500CBCA08 /* Transitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transitioning.swift; sourceTree = "<group>"; };
54352E9721A521CA00CBCA08 /* PassthroughView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassthroughView.swift; sourceTree = "<group>"; };
5450EEE321646DF500135936 /* Behavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Behavior.swift; sourceTree = "<group>"; };
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9C42151169500CA77B8 /* FloatingPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanel.h; sourceTree = "<group>"; };
545DB9C52151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FloatingPanelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9CF2151169500CA77B8 /* ControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerTests.swift; sourceTree = "<group>"; };
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 /* LayoutProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutProperties.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>"; };
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 */
545DB9BE2151169500CA77B8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5D82A6B528D18464006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9C72151169500CA77B8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
54E740C7218AFD67005C1A34 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
545DB9B72151169500CA77B8 = {
isa = PBXGroup;
children = (
545DB9C32151169500CA77B8 /* Sources */,
545DB9CE2151169500CA77B8 /* Tests */,
545DB9C22151169500CA77B8 /* Products */,
5D82A6B328D18460006A44BA /* Frameworks */,
);
sourceTree = "<group>";
};
545DB9C22151169500CA77B8 /* Products */ = {
isa = PBXGroup;
children = (
545DB9C12151169500CA77B8 /* FloatingPanel.framework */,
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */,
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */,
);
name = Products;
sourceTree = "<group>";
};
545DB9C32151169500CA77B8 /* Sources */ = {
isa = PBXGroup;
children = (
545DB9DF21511AC100CA77B8 /* Controller.swift */,
5469F4AD24B30D7E00537F8A /* State.swift */,
5469F4B124B30F1100537F8A /* Position.swift */,
54CFBFC4215CD09C006B5735 /* Core.swift */,
54CFBFC2215CD045006B5735 /* Layout.swift */,
5469F4B324B30F3500537F8A /* LayoutProperties.swift */,
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */,
5450EEE321646DF500135936 /* Behavior.swift */,
54352E9721A521CA00CBCA08 /* PassthroughView.swift */,
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */,
54CDC5D4215B6D8D007D205C /* BackdropView.swift */,
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
54DBA3DB262E938500D75969 /* Extensions.swift */,
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
545DB9C52151169500CA77B8 /* Info.plist */,
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
);
path = Sources;
sourceTree = "<group>";
};
545DB9CE2151169500CA77B8 /* Tests */ = {
isa = PBXGroup;
children = (
5469F49E24B003EF00537F8A /* TestingApp */,
54A6B6B022968B530077F348 /* CoreTests.swift */,
545DB9CF2151169500CA77B8 /* ControllerTests.swift */,
542753C522C49A6E00D17955 /* LayoutTests.swift */,
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */,
549E944422CF295D0050AECF /* StateTests.swift */,
549C371E2361E15D007D8058 /* ExtensionTests.swift */,
542753C722C49A8F00D17955 /* TestSupports.swift */,
545DB9D12151169500CA77B8 /* Info.plist */,
);
path = Tests;
sourceTree = "<group>";
};
5469F49E24B003EF00537F8A /* TestingApp */ = {
isa = PBXGroup;
children = (
5469F49F24B003EF00537F8A /* LaunchScreen.storyboard */,
5469F4A024B003EF00537F8A /* AppDelegate.swift */,
5469F4A124B003EF00537F8A /* Info.plist */,
);
path = TestingApp;
sourceTree = "<group>";
};
5D82A6B328D18460006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
5D82A6B428D18461006A44BA /* libswiftCoreGraphics.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
545DB9BC2151169500CA77B8 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
545DB9C02151169500CA77B8 /* FloatingPanel */ = {
isa = PBXNativeTarget;
buildConfigurationList = 545DB9D52151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanel" */;
buildPhases = (
545DB9BC2151169500CA77B8 /* Headers */,
545DB9BD2151169500CA77B8 /* Sources */,
545DB9BE2151169500CA77B8 /* Frameworks */,
545DB9BF2151169500CA77B8 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FloatingPanel;
productName = FloatingModalController;
productReference = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */;
productType = "com.apple.product-type.framework";
};
545DB9C92151169500CA77B8 /* FloatingPanelTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 545DB9D82151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanelTests" */;
buildPhases = (
545DB9C62151169500CA77B8 /* Sources */,
545DB9C72151169500CA77B8 /* Frameworks */,
545DB9C82151169500CA77B8 /* Resources */,
);
buildRules = (
);
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 */
545DB9B82151169500CA77B8 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
545DB9C02151169500CA77B8 = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1110;
};
545DB9C92151169500CA77B8 = {
CreatedOnToolsVersion = 10.0;
TestTargetID = 54E740C9218AFD67005C1A34;
};
54E740C9218AFD67005C1A34 = {
CreatedOnToolsVersion = 10.1;
};
};
};
buildConfigurationList = 545DB9BB2151169500CA77B8 /* Build configuration list for PBXProject "FloatingPanel" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 545DB9B72151169500CA77B8;
productRefGroup = 545DB9C22151169500CA77B8 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
545DB9C02151169500CA77B8 /* FloatingPanel */,
545DB9C92151169500CA77B8 /* FloatingPanelTests */,
54E740C9218AFD67005C1A34 /* TestingApp */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
545DB9BF2151169500CA77B8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9C82151169500CA77B8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
54E740C8218AFD67005C1A34 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5469F4A224B003EF00537F8A /* LaunchScreen.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
545DB9BD2151169500CA77B8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5469F4B224B30F1100537F8A /* Position.swift in Sources */,
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */,
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */,
54CFBFC3215CD045006B5735 /* Layout.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 */,
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */,
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
545DBA2B2152383100CA77B8 /* GrabberView.swift in Sources */,
54E3992727141F5100A8F9ED /* FloatingPanel.docc in Sources */,
54352E9621A51A2500CBCA08 /* Transitioning.swift in Sources */,
5469F4AE24B30D7E00537F8A /* State.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9C62151169500CA77B8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */,
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */,
545DB9D02151169500CA77B8 /* ControllerTests.swift in Sources */,
549E944522CF295D0050AECF /* StateTests.swift in Sources */,
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 */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
545DB9CD2151169500CA77B8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
};
54E740DD218AFE9F005C1A34 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 54E740C9218AFD67005C1A34 /* TestingApp */;
targetProxy = 54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
545DB9D32151169500CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
545DB9D42151169500CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
545DB9D62151169500CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
545DB9D72151169500CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
545DB9D92151169500CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
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.FloatingModalControllerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
};
name = Debug;
};
545DB9DA2151169500CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
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.FloatingModalControllerTests;
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;
};
54E79ADF224F6C9800717BC6 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Test;
};
54E79AE0224F6C9800717BC6 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG __FP_LOG";
SWIFT_COMPILATION_MODE = singlefile;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Test;
};
54E79AE1224F6C9800717BC6 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
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.FloatingModalControllerTests;
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;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
545DB9BB2151169500CA77B8 /* Build configuration list for PBXProject "FloatingPanel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DB9D32151169500CA77B8 /* Debug */,
54E79ADF224F6C9800717BC6 /* Test */,
545DB9D42151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
545DB9D52151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DB9D62151169500CA77B8 /* Debug */,
54E79AE0224F6C9800717BC6 /* Test */,
545DB9D72151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
545DB9D82151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanelTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DB9D92151169500CA77B8 /* Debug */,
54E79AE1224F6C9800717BC6 /* Test */,
545DB9DA2151169500CA77B8 /* Release */,
);
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 */;
}
@@ -0,0 +1,8 @@
<?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>FILEHEADER</key>
<string> Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.</string>
</dict>
</plist>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,14 +23,33 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545DB9C02151169500CA77B8"
BuildableName = "FloatingPanel.framework"
BlueprintName = "FloatingPanel"
ReferencedContainer = "container:FloatingPanel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545DB9C92151169500CA77B8"
BuildableName = "FloatingPanelTests.xctest"
BlueprintName = "FloatingPanelTests"
ReferencedContainer = "container:FloatingPanel.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -51,8 +70,6 @@
ReferencedContainer = "container:FloatingPanel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
+9 -7
View File
@@ -4,16 +4,15 @@
<FileRef
location = "group:README.md">
</FileRef>
<Group
location = "group:Framework"
name = "Framework">
<FileRef
location = "group:FloatingPanel.xcodeproj">
</FileRef>
</Group>
<FileRef
location = "group:FloatingPanel.xcodeproj">
</FileRef>
<Group
location = "group:Examples"
name = "Examples">
<FileRef
location = "group:Maps-SwiftUI/Maps-SwiftUI.xcodeproj">
</FileRef>
<FileRef
location = "group:Maps/Maps.xcodeproj">
</FileRef>
@@ -23,5 +22,8 @@
<FileRef
location = "group:Samples/Samples.xcodeproj">
</FileRef>
<FileRef
location = "group:SamplesObjC/SamplesObjC.xcodeproj">
</FileRef>
</Group>
</Workspace>
@@ -1,509 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */; };
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ViewTests.swift */; };
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanelController.h */; settings = {ATTRIBUTES = (Public, ); }; };
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DD215118C800CA77B8 /* UIExtensions.swift */; };
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */; };
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */; };
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
54CDC5D3215B6D5A007D205C /* FloatingPanelSurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */; };
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */; };
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */; };
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 545DB9B82151169500CA77B8 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 545DB9C02151169500CA77B8;
remoteInfo = FloatingModalController;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTransitioning.swift; sourceTree = "<group>"; };
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9C42151169500CA77B8 /* FloatingPanelController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanelController.h; sourceTree = "<group>"; };
545DB9C52151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FloatingPanelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9CF2151169500CA77B8 /* ViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTests.swift; sourceTree = "<group>"; };
545DB9D12151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
545DB9DD215118C800CA77B8 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelController.swift; sourceTree = "<group>"; };
545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabberHandleView.swift; sourceTree = "<group>"; };
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelSurfaceView.swift; sourceTree = "<group>"; };
54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBackdropView.swift; sourceTree = "<group>"; };
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelLayout.swift; sourceTree = "<group>"; };
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
545DB9BE2151169500CA77B8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9C72151169500CA77B8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
545DB9B72151169500CA77B8 = {
isa = PBXGroup;
children = (
545DB9C32151169500CA77B8 /* Sources */,
545DB9CE2151169500CA77B8 /* Tests */,
545DB9C22151169500CA77B8 /* Products */,
);
sourceTree = "<group>";
};
545DB9C22151169500CA77B8 /* Products */ = {
isa = PBXGroup;
children = (
545DB9C12151169500CA77B8 /* FloatingPanel.framework */,
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
545DB9C32151169500CA77B8 /* Sources */ = {
isa = PBXGroup;
children = (
545DB9C52151169500CA77B8 /* Info.plist */,
545DB9C42151169500CA77B8 /* FloatingPanelController.h */,
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */,
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */,
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */,
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */,
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */,
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */,
54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */,
54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */,
545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */,
545DB9DD215118C800CA77B8 /* UIExtensions.swift */,
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
);
path = Sources;
sourceTree = "<group>";
};
545DB9CE2151169500CA77B8 /* Tests */ = {
isa = PBXGroup;
children = (
545DB9CF2151169500CA77B8 /* ViewTests.swift */,
545DB9D12151169500CA77B8 /* Info.plist */,
);
path = Tests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
545DB9BC2151169500CA77B8 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
545DB9C02151169500CA77B8 /* FloatingPanel */ = {
isa = PBXNativeTarget;
buildConfigurationList = 545DB9D52151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanel" */;
buildPhases = (
545DB9BC2151169500CA77B8 /* Headers */,
545DB9BD2151169500CA77B8 /* Sources */,
545DB9BE2151169500CA77B8 /* Frameworks */,
545DB9BF2151169500CA77B8 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = FloatingPanel;
productName = FloatingModalController;
productReference = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */;
productType = "com.apple.product-type.framework";
};
545DB9C92151169500CA77B8 /* FloatingPanelTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 545DB9D82151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanelTests" */;
buildPhases = (
545DB9C62151169500CA77B8 /* Sources */,
545DB9C72151169500CA77B8 /* Frameworks */,
545DB9C82151169500CA77B8 /* Resources */,
);
buildRules = (
);
dependencies = (
545DB9CD2151169500CA77B8 /* PBXTargetDependency */,
);
name = FloatingPanelTests;
productName = FloatingModalControllerTests;
productReference = 545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
545DB9B82151169500CA77B8 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
545DB9C02151169500CA77B8 = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1000;
};
545DB9C92151169500CA77B8 = {
CreatedOnToolsVersion = 10.0;
};
};
};
buildConfigurationList = 545DB9BB2151169500CA77B8 /* Build configuration list for PBXProject "FloatingPanel" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 545DB9B72151169500CA77B8;
productRefGroup = 545DB9C22151169500CA77B8 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
545DB9C02151169500CA77B8 /* FloatingPanel */,
545DB9C92151169500CA77B8 /* FloatingPanelTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
545DB9BF2151169500CA77B8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9C82151169500CA77B8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
545DB9BD2151169500CA77B8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
54CDC5D3215B6D5A007D205C /* FloatingPanelSurfaceView.swift in Sources */,
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */,
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */,
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */,
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */,
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */,
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */,
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */,
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */,
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
545DB9C62151169500CA77B8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
545DB9CD2151169500CA77B8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
545DB9D32151169500CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
545DB9D42151169500CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
545DB9D62151169500CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
545DB9D72151169500CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
545DB9D92151169500CA77B8 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
545DB9DA2151169500CA77B8 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = Tests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
545DB9BB2151169500CA77B8 /* Build configuration list for PBXProject "FloatingPanel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DB9D32151169500CA77B8 /* Debug */,
545DB9D42151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
545DB9D52151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DB9D62151169500CA77B8 /* Debug */,
545DB9D72151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
545DB9D82151169500CA77B8 /* Build configuration list for PBXNativeTarget "FloatingPanelTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
545DB9D92151169500CA77B8 /* Debug */,
545DB9DA2151169500CA77B8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 545DB9B82151169500CA77B8 /* Project object */;
}
-847
View File
@@ -1,847 +0,0 @@
//
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
///
/// FloatingPanel presentation model
///
class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate {
/* Cause 'terminating with uncaught exception of type NSException' error on Swift Playground
unowned let view: UIView
*/
let surfaceView: FloatingPanelSurfaceView
let backdropView: FloatingPanelBackdropView
var layoutAdapter: FloatingPanelLayoutAdapter
var behavior: FloatingPanelBehavior
weak var scrollView: UIScrollView? {
didSet {
guard let scrollView = scrollView else { return }
scrollView.panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
scrollBouncable = scrollView.bounces
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
}
}
weak var userScrollViewDelegate: UIScrollViewDelegate?
var safeAreaInsets: UIEdgeInsets! {
get { return layoutAdapter.safeAreaInsets }
set { layoutAdapter.safeAreaInsets = newValue }
}
unowned let viewcontroller: FloatingPanelController
private(set) var state: FloatingPanelPosition = .hidden {
didSet { viewcontroller.delegate?.floatingPanelDidChangePosition(viewcontroller) }
}
private var isBottomState: Bool {
let remains = layoutAdapter.supportedPositions.filter { $0.rawValue > state.rawValue }
return remains.count == 0
}
let panGesture: FloatingPanelPanGestureRecognizer
var isRemovalInteractionEnabled: Bool = false
private var animator: UIViewPropertyAnimator?
private var initialFrame: CGRect = .zero
private var initialScrollOffset: CGPoint = .zero
private var transOffsetY: CGFloat = 0
private var interactionInProgress: Bool = false
// Scroll handling
private var stopScrollDeceleration: Bool = false
private var scrollBouncable = false
private var scrollIndictorVisible = false
// MARK: - Interface
init(_ vc: FloatingPanelController, layout: FloatingPanelLayout, behavior: FloatingPanelBehavior) {
viewcontroller = vc
surfaceView = FloatingPanelSurfaceView()
surfaceView.backgroundColor = .white
backdropView = FloatingPanelBackdropView()
backdropView.backgroundColor = .black
backdropView.alpha = 0.0
self.layoutAdapter = FloatingPanelLayoutAdapter(surfaceView: surfaceView,
backdropView: backdropView,
layout: layout)
self.behavior = behavior
panGesture = FloatingPanelPanGestureRecognizer()
if #available(iOS 11.0, *) {
panGesture.name = "FloatingPanelSurface"
}
super.init()
surfaceView.addGestureRecognizer(panGesture)
panGesture.addTarget(self, action: #selector(handle(panGesture:)))
panGesture.delegate = self
}
func setUpViews(in vc: UIViewController) {
unowned let view = vc.view!
// FloatingPanelSurfaceWrapperView is needed to update the surface's height
// without animation and prevent the backdrop's cut-off on orientation change.
let surfaceWrapperView = FloatingPanelSurfaceWrapperView()
surfaceWrapperView.frame = view.bounds
surfaceWrapperView.backgroundColor = .clear
view.addSubview(surfaceWrapperView)
surfaceWrapperView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
surfaceWrapperView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0),
surfaceWrapperView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0),
surfaceWrapperView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0),
surfaceWrapperView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
])
surfaceWrapperView.addSubview(surfaceView)
view.insertSubview(backdropView, belowSubview: surfaceWrapperView)
backdropView.frame = view.bounds
}
func move(to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
move(from: state, to: to, animated: animated, completion: completion)
}
private func move(from: FloatingPanelPosition, to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
if to != .full {
lockScrollView()
}
if animated {
let animator: UIViewPropertyAnimator
switch (from, to) {
case (.hidden, let to):
animator = behavior.addAnimator(self.viewcontroller, to: to)
case (let from, .hidden):
animator = behavior.removeAnimator(self.viewcontroller, from: from)
case (let from, let to):
animator = behavior.moveAnimator(self.viewcontroller, from: from, to: to)
}
animator.addAnimations { [weak self] in
guard let self = self else { return }
self.updateLayout(to: to)
self.state = to
}
animator.addCompletion { _ in
completion?()
}
animator.startAnimation()
} else {
self.updateLayout(to: to)
self.state = to
completion?()
}
}
// MARK: - Layout update
private func updateLayout(to target: FloatingPanelPosition) {
self.layoutAdapter.activateLayout(of: target)
}
private func getBackdropAlpha(with translation: CGPoint) -> CGFloat {
let currentY = getCurrentY(from: initialFrame, with: translation)
let next = directionalPosition(with: translation)
let pre = redirectionalPosition(with: translation)
let nextY = layoutAdapter.positionY(for: next)
let preY = layoutAdapter.positionY(for: pre)
let nextAlpha = layoutAdapter.layout.backdropAlphaFor(position: next)
let preAlpha = layoutAdapter.layout.backdropAlphaFor(position: pre)
if preY == nextY {
return preAlpha
} else {
return preAlpha + max(min(1.0, 1.0 - (nextY - currentY) / (nextY - preY) ), 0.0) * (nextAlpha - preAlpha)
}
}
// MARK: - UIGestureRecognizerDelegate
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer == panGesture else { return false }
/* log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */
// all gestures of the tracking scroll view should be recognized in parallel
// and handle them in self.handle(panGesture:)
return scrollView?.gestureRecognizers?.contains(otherGestureRecognizer) ?? false
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer == panGesture else { return false }
/* log.debug("shouldBeRequiredToFailBy", otherGestureRecognizer) */
// The tracking scroll view's gestures should begin without waiting for the pan gesture failure.
// `scrollView.gestureRecognizers` can contains the following gestures
// * UIScrollViewDelayedTouchesBeganGestureRecognizer
// * UIScrollViewPanGestureRecognizer (scrollView.panGestureRecognizer)
// * _UIDragAutoScrollGestureRecognizer
// * _UISwipeActionPanGestureRecognizer
// * UISwipeDismissalGestureRecognizer
if let scrollView = scrollView,
let scrollGestureRecognizers = scrollView.gestureRecognizers,
scrollGestureRecognizers.contains(otherGestureRecognizer) {
return false
}
// Long press gesture should begin without waiting for the pan gesture failure.
if otherGestureRecognizer is UILongPressGestureRecognizer {
return false
}
// Do not begin any other gestures until the pan gesture fails.
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer == panGesture else { return false }
log.debug("shouldRequireFailureOf", otherGestureRecognizer)
// Should begin the pan gesture without waiting for the tracking scroll view's gestures.
// `scrollView.gestureRecognizers` can contains the following gestures
// * UIScrollViewDelayedTouchesBeganGestureRecognizer
// * UIScrollViewPanGestureRecognizer (scrollView.panGestureRecognizer)
// * _UIDragAutoScrollGestureRecognizer
// * _UISwipeActionPanGestureRecognizer
// * UISwipeDismissalGestureRecognizer
if let scrollView = scrollView {
// On short contents scroll, `_UISwipeActionPanGestureRecognizer` blocks
// the panel's pan gesture if not returns false
if let scrollGestureRecognizers = scrollView.gestureRecognizers,
scrollGestureRecognizers.contains(otherGestureRecognizer) {
return false
}
}
switch otherGestureRecognizer {
case is UIPanGestureRecognizer,
is UISwipeGestureRecognizer,
is UIRotationGestureRecognizer,
is UIScreenEdgePanGestureRecognizer,
is UIPinchGestureRecognizer:
// Do not begin the pan gesture until these gestures fail
return true
default:
// Should begin the pan gesture witout waiting tap/long press gestures fail
return false
}
}
var grabberAreaFrame: CGRect {
let grabberAreaFrame = CGRect(x: surfaceView.bounds.origin.x,
y: surfaceView.bounds.origin.y,
width: surfaceView.bounds.width,
height: FloatingPanelSurfaceView.topGrabberBarHeight * 2)
return grabberAreaFrame
}
// MARK: - Gesture handling
private let offsetThreshold: CGFloat = 5.0 // Optimal value from testing
@objc func handle(panGesture: UIPanGestureRecognizer) {
log.debug("Gesture >>>>", panGesture)
let velocity = panGesture.velocity(in: panGesture.view)
switch panGesture {
case scrollView?.panGestureRecognizer:
guard let scrollView = scrollView else { return }
log.debug("SrollPanGesture ScrollView.contentOffset >>>", scrollView.contentOffset.y, scrollView.contentSize, scrollView.bounds.size)
// Prevent scoll slip by the top bounce when the scroll view's height is
// less than the content's height
if scrollView.isDecelerating == false, scrollView.contentSize.height > scrollView.bounds.height {
scrollView.bounces = (scrollView.contentOffset.y > offsetThreshold)
}
if surfaceView.frame.minY > layoutAdapter.topY {
switch state {
case .full:
let point = panGesture.location(in: surfaceView)
if grabberAreaFrame.contains(point) {
// Preserve the current content offset in moving from full.
scrollView.contentOffset.y = initialScrollOffset.y
} else {
// Prevent over scrolling in moving from full.
scrollView.contentOffset.y = scrollView.contentOffsetZero.y
}
case .half, .tip:
guard scrollView.isDecelerating == false else {
// Don't fix the scroll offset in animating the panel to half and tip.
// It causes a buggy scrolling deceleration because `state` becomes
// a target position in animating the panel on the interaction from full.
return
}
// Fix the scroll offset in moving the panel from half and tip.
scrollView.contentOffset.y = initialScrollOffset.y
case .hidden:
fatalError("A floating panel hidden must not be used by a user")
}
// Always hide a scroll indicator at the non-top.
if interactionInProgress {
lockScrollView()
}
} else {
// Always show a scroll indicator at the top.
if interactionInProgress {
unlockScrollView()
}
}
case panGesture:
let translation = panGesture.translation(in: panGesture.view!.superview)
let location = panGesture.location(in: panGesture.view)
log.debug(panGesture.state, ">>>", "translation: \(translation.y), velocity: \(velocity.y)")
if shouldScrollViewHandleTouch(scrollView, point: location, velocity: velocity) {
return
}
if let animator = self.animator {
animator.stopAnimation(true)
self.animator = nil
}
switch panGesture.state {
case .began:
panningBegan()
case .changed:
if interactionInProgress == false {
startInteraction(with: translation)
}
panningChange(with: translation)
case .ended, .cancelled, .failed:
panningEnd(with: translation, velocity: velocity)
case .possible:
break
}
default:
return
}
}
private func shouldScrollViewHandleTouch(_ scrollView: UIScrollView?, point: CGPoint, velocity: CGPoint) -> Bool {
// When no scrollView, nothing to handle.
guard let scrollView = scrollView else { return false }
// For _UISwipeActionPanGestureRecognizer
if let scrollGestureRecognizers = scrollView.gestureRecognizers {
for gesture in scrollGestureRecognizers {
guard gesture.state == .began || gesture.state == .changed
else { continue }
if gesture != scrollView.panGestureRecognizer {
return true
}
}
}
guard
state == .full, // When not .full, don't scroll.
interactionInProgress == false, // When interaction already in progress, don't scroll.
scrollView.frame.contains(point), // When point not in scrollView, don't scroll.
!grabberAreaFrame.contains(point) // When point within grabber area, don't scroll.
else {
return false
}
log.debug("ScrollView.contentOffset >>>", scrollView.contentOffset.y)
let offset = scrollView.contentOffset.y - scrollView.contentOffsetZero.y
if abs(offset) > offsetThreshold {
return true
}
if scrollView.isDecelerating {
return true
}
if velocity.y < 0 {
return true
}
return false
}
private func panningBegan() {
// A user interaction does not always start from Began state of the pan gesture
// because it can be recognized in scrolling a content in a content view controller.
// So do nothing here.
log.debug("panningBegan")
}
private func panningChange(with translation: CGPoint) {
log.debug("panningChange")
let currentY = getCurrentY(from: initialFrame, with: translation)
var frame = initialFrame
frame.origin.y = currentY
surfaceView.frame = frame
backdropView.alpha = getBackdropAlpha(with: translation)
viewcontroller.delegate?.floatingPanelDidMove(viewcontroller)
}
private func panningEnd(with translation: CGPoint, velocity: CGPoint) {
log.debug("panningEnd")
if interactionInProgress == false {
initialFrame = surfaceView.frame
}
stopScrollDeceleration = (surfaceView.frame.minY > layoutAdapter.topY) // Projecting the dragging to the scroll dragging or not
let targetPosition = self.targetPosition(with: translation, velocity: velocity)
let distance = self.distance(to: targetPosition, with: translation)
endInteraction(for: targetPosition)
if isRemovalInteractionEnabled, isBottomState {
let velocityVector = (distance != 0) ? CGVector(dx: 0,
dy: max(min(velocity.y/distance, behavior.removalVelocity), 0.0)) : .zero
if shouldStartRemovalAnimation(with: translation, velocityVector: velocityVector) {
viewcontroller.delegate?.floatingPanelDidEndDraggingToRemove(viewcontroller, withVelocity: velocity)
self.startRemovalAnimation(with: velocityVector) { [weak self] in
guard let self = self else { return }
self.viewcontroller.dismiss(animated: false)
self.viewcontroller.delegate?.floatingPanelDidEndRemove(self.viewcontroller)
}
return
}
}
viewcontroller.delegate?.floatingPanelDidEndDragging(viewcontroller, withVelocity: velocity, targetPosition: targetPosition)
viewcontroller.delegate?.floatingPanelWillBeginDecelerating(viewcontroller)
startAnimation(to: targetPosition, at: distance, with: velocity)
}
private func shouldStartRemovalAnimation(with translation: CGPoint, velocityVector: CGVector) -> Bool {
let posY = layoutAdapter.positionY(for: state)
let currentY = getCurrentY(from: initialFrame, with: translation)
let safeAreaBottomY = layoutAdapter.safeAreaBottomY
let vth = behavior.removalVelocity
let pth = max(min(behavior.removalProgress, 1.0), 0.0)
let num = (currentY - posY)
let den = (safeAreaBottomY - posY)
guard num >= 0, den != 0, (num / den >= pth || velocityVector.dy == vth)
else { return false }
return true
}
private func startRemovalAnimation(with velocityVector: CGVector, completion: (() -> Void)?) {
let animator = self.behavior.removalInteractionAnimator(self.viewcontroller, with: velocityVector)
animator.addAnimations { [weak self] in
self?.updateLayout(to: .hidden)
}
animator.addCompletion({ _ in
completion?()
})
animator.startAnimation()
}
private func startInteraction(with translation: CGPoint) {
/* Don't lock a scroll view to show a scroll indicator after hitting the top */
log.debug("startInteraction")
initialFrame = surfaceView.frame
if let scrollView = scrollView {
initialScrollOffset = scrollView.contentOffset
}
transOffsetY = translation.y
viewcontroller.delegate?.floatingPanelWillBeginDragging(viewcontroller)
if layoutAdapter.layout is FloatingPanelIntrinsicLayout {
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
case const.firstAnchor:
(const.secondItem as? UIView)?.disableAutoLayout()
case const.secondAnchor:
(const.firstItem as? UIView)?.disableAutoLayout()
default:
break
}
})
}
interactionInProgress = true
}
private func endInteraction(for targetPosition: FloatingPanelPosition) {
log.debug("endInteraction for \(targetPosition)")
interactionInProgress = false
// Prevent to keep a scoll view indicator visible at the half/tip position
if targetPosition != .full {
lockScrollView()
}
if layoutAdapter.layout is FloatingPanelIntrinsicLayout {
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
case const.firstAnchor:
(const.secondItem as? UIView)?.enableAutoLayout()
case const.secondAnchor:
(const.firstItem as? UIView)?.enableAutoLayout()
default:
break
}
})
}
}
private func getCurrentY(from rect: CGRect, with translation: CGPoint) -> CGFloat {
let dy = translation.y - transOffsetY
let y = rect.offsetBy(dx: 0.0, dy: dy).origin.y
let topY = layoutAdapter.topY
let topBuffer = layoutAdapter.layout.topInteractionBuffer
let bottomY = layoutAdapter.bottomY
let bottomBuffer = layoutAdapter.layout.bottomInteractionBuffer
if let scrollView = scrollView, scrollView.panGestureRecognizer.state == .changed {
let preY = surfaceView.frame.origin.y
if preY > 0 && preY > y {
return max(topY, min(bottomY, y))
}
}
let topMax = layoutAdapter.topMaxY
let bottomMax = layoutAdapter.bottomMaxY
return max(max(topY - topBuffer, topMax), min(min(bottomY + bottomBuffer, bottomMax), y))
}
private func startAnimation(to targetPosition: FloatingPanelPosition, at distance: CGFloat, with velocity: CGPoint) {
let targetY = layoutAdapter.positionY(for: targetPosition)
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: max(min(velocity.y/distance, 30.0), -30.0)) : .zero
let animator = behavior.interactionAnimator(self.viewcontroller, to: targetPosition, with: velocityVector)
animator.addAnimations { [weak self] in
guard let self = self else { return }
if self.state == targetPosition {
self.surfaceView.frame.origin.y = targetY
self.layoutAdapter.setBackdropAlpha(of: targetPosition)
} else {
self.updateLayout(to: targetPosition)
}
self.state = targetPosition
}
animator.addCompletion { [weak self] pos in
guard let self = self else { return }
guard
self.interactionInProgress == false,
animator == self.animator,
pos == .end
else { return }
self.finishAnimation(at: targetPosition)
}
animator.startAnimation()
self.animator = animator
}
private func finishAnimation(at targetPosition: FloatingPanelPosition) {
log.debug("finishAnimation \(targetPosition)")
self.animator = nil
self.viewcontroller.delegate?.floatingPanelDidEndDecelerating(self.viewcontroller)
stopScrollDeceleration = false
// Don't unlock scroll view in animating view when presentation layer != model layer
if targetPosition == .full {
unlockScrollView()
}
}
private func distance(to targetPosition: FloatingPanelPosition, with translation: CGPoint) -> CGFloat {
let topY = layoutAdapter.topY
let middleY = layoutAdapter.middleY
let bottomY = layoutAdapter.bottomY
let currentY = getCurrentY(from: initialFrame, with: translation)
switch targetPosition {
case .full:
return CGFloat(fabs(Double(currentY - topY)))
case .half:
return CGFloat(fabs(Double(currentY - middleY)))
case .tip:
return CGFloat(fabs(Double(currentY - bottomY)))
case .hidden:
fatalError("A floating panel hidden must not be used by a user")
}
}
private func directionalPosition(with translation: CGPoint) -> FloatingPanelPosition {
let currentY = getCurrentY(from: initialFrame, with: translation)
let supportedPositions = layoutAdapter.supportedPositions
if supportedPositions.count == 1 {
return state
}
switch supportedPositions {
case [.full, .half]: return translation.y >= 0 ? .half : .full
case [.half, .tip]: return translation.y >= 0 ? .tip : .half
case [.full, .tip]: return translation.y >= 0 ? .tip : .full
default:
let middleY = layoutAdapter.middleY
switch state {
case .full:
if translation.y <= 0 {
return .full
}
return currentY > middleY ? .tip : .half
case .half:
return translation.y >= 0 ? .tip : .full
case .tip:
if translation.y >= 0 {
return .tip
}
return currentY > middleY ? .half : .full
case .hidden:
fatalError("A floating panel hidden must not be used by a user")
}
}
}
private func redirectionalPosition(with translation: CGPoint) -> FloatingPanelPosition {
let currentY = getCurrentY(from: initialFrame, with: translation)
let supportedPositions = layoutAdapter.supportedPositions
if supportedPositions.count == 1 {
return state
}
switch supportedPositions {
case [.full, .half]: return translation.y >= 0 ? .full : .half
case [.half, .tip]: return translation.y >= 0 ? .half : .tip
case [.full, .tip]: return translation.y >= 0 ? .full : .tip
default:
let middleY = layoutAdapter.middleY
switch state {
case .full:
return currentY > middleY ? .half : .full
case .half:
return .half
case .tip:
return currentY > middleY ? .tip : .half
case .hidden:
fatalError("A floating panel hidden must not be used by a user")
}
}
}
// Distance travelled after decelerating to zero velocity at a constant rate.
// Refer to the slides p176 of [Designing Fluid Interfaces](https://developer.apple.com/videos/play/wwdc2018/803/)
private func project(initialVelocity: CGFloat) -> CGFloat {
let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate)
}
private func targetPosition(with translation: CGPoint, velocity: CGPoint) -> (FloatingPanelPosition) {
let currentY = getCurrentY(from: initialFrame, with: translation)
let supportedPositions = layoutAdapter.supportedPositions
if supportedPositions.count == 1 {
return state
}
switch supportedPositions {
case [.full, .half]:
return targetPosition(from: [.full, .half], at: currentY, velocity: velocity)
case [.half, .tip]:
return targetPosition(from: [.half, .tip], at: currentY, velocity: velocity)
case [.full, .tip]:
return targetPosition(from: [.full, .tip], at: currentY, velocity: velocity)
default:
/*
[topY|full]---[th1]---[middleY|half]---[th2]---[bottomY|tip]
*/
let topY = layoutAdapter.topY
let middleY = layoutAdapter.middleY
let bottomY = layoutAdapter.bottomY
let target: FloatingPanelPosition
let forwardYDirection: Bool
switch state {
case .full:
target = .half
forwardYDirection = true
case .half:
if (currentY < middleY) {
target = .full
forwardYDirection = false
} else {
target = .tip
forwardYDirection = true
}
case .tip:
target = .half
forwardYDirection = false
case .hidden:
fatalError("A floating panel hidden must not be used by a user")
}
let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: target), 1.0), 0.0)
let th1: CGFloat
let th2: CGFloat
if forwardYDirection {
th1 = topY + (middleY - topY) * redirectionalProgress
th2 = middleY + (bottomY - middleY) * redirectionalProgress
} else {
th1 = middleY - (middleY - topY) * redirectionalProgress
th2 = bottomY - (bottomY - middleY) * redirectionalProgress
}
switch currentY {
case ..<th1:
if project(initialVelocity: velocity.y) >= (middleY - currentY) {
return .half
} else {
return .full
}
case ...middleY:
if project(initialVelocity: velocity.y) <= (topY - currentY) {
return .full
} else {
return .half
}
case ..<th2:
if project(initialVelocity: velocity.y) >= (bottomY - currentY) {
return .tip
} else {
return .half
}
default:
if project(initialVelocity: velocity.y) <= (middleY - currentY) {
return .half
} else {
return .tip
}
}
}
}
private func targetPosition(from positions: [FloatingPanelPosition], at currentY: CGFloat, velocity: CGPoint) -> FloatingPanelPosition {
assert(positions.count == 2)
let top = positions[0]
let bottom = positions[1]
let topY = layoutAdapter.positionY(for: top)
let bottomY = layoutAdapter.positionY(for: bottom)
let target = top == state ? bottom : top
let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: target), 1.0), 0.0)
let th = topY + (bottomY - topY) * redirectionalProgress
switch currentY {
case ..<th:
if project(initialVelocity: velocity.y) >= (bottomY - currentY) {
return bottom
} else {
return top
}
default:
if project(initialVelocity: velocity.y) <= (topY - currentY) {
return top
} else {
return bottom
}
}
}
// MARK: - ScrollView handling
private func lockScrollView() {
guard let scrollView = scrollView else { return }
scrollView.isDirectionalLockEnabled = true
scrollView.bounces = false
scrollView.showsVerticalScrollIndicator = false
}
private func unlockScrollView() {
guard let scrollView = scrollView else { return }
scrollView.isDirectionalLockEnabled = false
scrollView.bounces = scrollBouncable
scrollView.showsVerticalScrollIndicator = scrollIndictorVisible
}
// MARK: - UIScrollViewDelegate Intermediation
override func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || userScrollViewDelegate?.responds(to: aSelector) == true
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if userScrollViewDelegate?.responds(to: aSelector) == true {
return userScrollViewDelegate
} else {
return super.forwardingTarget(for: aSelector)
}
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if state != .full {
initialScrollOffset = scrollView.contentOffset
}
userScrollViewDelegate?.scrollViewDidEndScrollingAnimation?(scrollView)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if stopScrollDeceleration {
targetContentOffset.pointee = scrollView.contentOffset
stopScrollDeceleration = false
} else {
userScrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
}
}
class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
override weak var delegate: UIGestureRecognizerDelegate? {
get {
return super.delegate
}
set {
guard newValue is FloatingPanel else {
let exception = NSException(name: .invalidArgumentException,
reason: "FloatingPanelController's built-in pan gesture recognizer must have its controller as its delegate.",
userInfo: nil)
exception.raise()
return
}
super.delegate = newValue
}
}
}
@@ -1,9 +0,0 @@
//
// Created by Shin Yamamoto on 2018/09/26.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
/// A view that presents a backdrop interface behind a floating panel.
public class FloatingPanelBackdropView: UIView { }
@@ -1,108 +0,0 @@
//
// Created by Shin Yamamoto on 2018/10/03.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
public protocol FloatingPanelBehavior {
/// Returns the progress to redirect to the previous position
///
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next posiiton. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat
/// Returns a UIViewPropertyAnimator object to project a floating panel to a position on finger up if the user dragged.
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator
/// Returns a UIViewPropertyAnimator object to add a floating panel to a position.
///
/// Its animator instance will be used to animate the surface view in `FloatingPanelController.addPanel(toParent:belowView:animated:)`.
/// Default is an animator with ease-in-out curve and 0.25 sec duration.
func addAnimator(_ fpc: FloatingPanelController, to: FloatingPanelPosition) -> UIViewPropertyAnimator
/// Returns a UIViewPropertyAnimator object to remove a floating panel from a position.
///
/// Its animator instance will be used to animate the surface view in `FloatingPanelController.removePanelFromParent(animated:completion:)`.
/// Default is an animator with ease-in-out curve and 0.25 sec duration.
func removeAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition) -> UIViewPropertyAnimator
/// Returns a UIViewPropertyAnimator object to move a floating panel from a position to a position.
///
/// Its animator instance will be used to animate the surface view in `FloatingPanelController.move(to:animated:completion:)`.
/// Default is an animator with ease-in-out curve and 0.25 sec duration.
func moveAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> UIViewPropertyAnimator
/// Returns a y-axis velocity to invoke a removal interaction at the bottom position.
///
/// Default is 10.0. This method is called when FloatingPanelController.isRemovalInteractionEnabled is true.
var removalVelocity: CGFloat { get }
/// Returns the threshold of the transition to invoke a removal interaction at the bottom position.
///
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to invoke the removal interaction. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits. This method is called when FloatingPanelController.isRemovalInteractionEnabled is true.
var removalProgress: CGFloat { get }
/// Returns a UIViewPropertyAnimator object to remove a floating panel with a velocity interactively at the bottom position.
///
/// Default is a spring animator with 1.0 damping ratio. This method is called when FloatingPanelController.isRemovalInteractionEnabled is true.
func removalInteractionAnimator(_ fpc: FloatingPanelController, with velocity: CGVector) -> UIViewPropertyAnimator
}
public extension FloatingPanelBehavior {
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat {
return 0.5
}
func addAnimator(_ fpc: FloatingPanelController, to: FloatingPanelPosition) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
}
func removeAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
}
func moveAnimator(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
}
var removalVelocity: CGFloat {
return 10.0
}
var removalProgress: CGFloat {
return 0.5
}
func removalInteractionAnimator(_ fpc: FloatingPanelController, with velocity: CGVector) -> UIViewPropertyAnimator {
log.debug("velocity", velocity)
let timing = UISpringTimingParameters(dampingRatio: 1.0,
frequencyResponse: 0.3,
initialVelocity: velocity)
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
}
}
class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
let timing = timeingCurve(with: velocity)
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
}
private func timeingCurve(with velocity: CGVector) -> UITimingCurveProvider {
log.debug("velocity", velocity)
let damping = self.getDamping(with: velocity)
return UISpringTimingParameters(dampingRatio: damping,
frequencyResponse: 0.3,
initialVelocity: velocity)
}
private let velocityThreshold: CGFloat = 8.0
private func getDamping(with velocity: CGVector) -> CGFloat {
let dy = abs(velocity.dy)
if dy > velocityThreshold {
return 0.7
} else {
return 1.0
}
}
}
@@ -1,9 +0,0 @@
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
#import <UIKit/UIKit.h>
FOUNDATION_EXPORT double FloatingPanelVersionNumber;
FOUNDATION_EXPORT const unsigned char FloatingPanelVersionString[];
@@ -1,454 +0,0 @@
//
// Created by Shin Yamamoto on 2018/09/18.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
public protocol FloatingPanelControllerDelegate: class {
// if it returns nil, FloatingPanelController uses the default layout
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout?
// if it returns nil, FloatingPanelController uses the default behavior
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior?
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) // changed the settled position in the model layer
func floatingPanelDidMove(_ vc: FloatingPanelController) // any offset changes
// called on start of dragging (may require some time and or distance to move)
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController)
// called on finger up if the user dragged. velocity is in points/second.
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition)
func floatingPanelWillBeginDecelerating(_ vc: FloatingPanelController) // called on finger up as we are moving
func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) // called when scroll view grinds to a halt
// called on start of dragging to remove its views from a parent view controller
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint)
// called when its views are removed from a parent view controller
func floatingPanelDidEndRemove(_ vc: FloatingPanelController)
}
public extension FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
return nil
}
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
return nil
}
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {}
func floatingPanelDidMove(_ vc: FloatingPanelController) {}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {}
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {}
func floatingPanelWillBeginDecelerating(_ vc: FloatingPanelController) {}
func floatingPanelDidEndDecelerating(_ vc: FloatingPanelController) {}
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint) {}
func floatingPanelDidEndRemove(_ vc: FloatingPanelController) {}
}
public enum FloatingPanelPosition: Int, CaseIterable {
case full
case half
case tip
case hidden
}
///
/// A container view controller to display a floating panel to present contents in parallel as a user wants.
///
public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
/// Constants indicating how safe area insets are added to the adjusted content inset.
public enum ContentInsetAdjustmentBehavior: Int {
case always
case never
}
/// The delegate of the floating panel controller object.
public weak var delegate: FloatingPanelControllerDelegate?
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
public var surfaceView: FloatingPanelSurfaceView! {
return floatingPanel.surfaceView
}
/// Returns the backdrop view managed by the controller object.
public var backdropView: FloatingPanelBackdropView! {
return floatingPanel.backdropView
}
/// Returns the scroll view that the controller tracks.
public weak var scrollView: UIScrollView? {
return floatingPanel.scrollView
}
// The underlying gesture recognizer for pan gestures
public var panGestureRecognizer: UIPanGestureRecognizer {
return floatingPanel.panGesture
}
/// The current position of the floating panel controller's contents.
public var position: FloatingPanelPosition {
return floatingPanel.state
}
/// The layout object managed by the controller
public var layout: FloatingPanelLayout {
return floatingPanel.layoutAdapter.layout
}
/// The behavior object managed by the controller
public var behavior: FloatingPanelBehavior {
return floatingPanel.behavior
}
/// The content insets of the tracking scroll view derived from this safe area
public var adjustedContentInsets: UIEdgeInsets {
return floatingPanel.layoutAdapter.adjustedContentInsets
}
/// 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.
public var contentInsetAdjustmentBehavior: ContentInsetAdjustmentBehavior = .always
/// A Boolean value that determines whether the removal interaction is enabled.
public var isRemovalInteractionEnabled: Bool {
set { floatingPanel.isRemovalInteractionEnabled = newValue }
get { return floatingPanel.isRemovalInteractionEnabled }
}
/// The view controller responsible for the content portion of the floating panel.
public var contentViewController: UIViewController? {
set { set(contentViewController: newValue) }
get { return _contentViewController }
}
private var _contentViewController: UIViewController?
private var floatingPanel: FloatingPanel!
private var safeAreaInsetsObservation: NSKeyValueObservation?
private let modalTransition = FloatingPanelModalTransition()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
/// Initialize a newly created floating panel controller.
public init() {
super.init(nibName: nil, bundle: nil)
setUp()
}
private func setUp() {
_ = FloatingPanelController.dismissSwizzling
modalPresentationStyle = .custom
transitioningDelegate = modalTransition
floatingPanel = FloatingPanel(self,
layout: fetchLayout(for: self.traitCollection),
behavior: fetchBehavior(for: self.traitCollection))
}
// MARK:- Overrides
/// Creates the view that the controller manages.
override public func loadView() {
assert(self.storyboard == nil, "Storyboard isn't supported")
let view = FloatingPanelPassThroughView()
view.backgroundColor = .clear
self.view = view as UIView
}
public override func viewDidLoad() {
super.viewDidLoad()
floatingPanel.setUpViews(in: self)
}
public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
view.frame.size = size
view.layoutIfNeeded()
floatingPanel.layoutAdapter.checkLayoutConsistance()
}
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
// Change layout for a new trait collection
updateLayout(for: newCollection)
floatingPanel.behavior = fetchBehavior(for: newCollection)
}
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard previousTraitCollection != traitCollection else { return }
self.update(safeAreaInsets: layoutInsets)
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Must track safeAreaInsets/{top,bottom}LayoutGuide of the `self.view`
// to update floatingPanel.safeAreaInsets`. 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.
if #available(iOS 11.0, *) {
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets) { [weak self] (vc, chaneg) in
guard let self = self else { return }
self.update(safeAreaInsets: vc.layoutInsets)
}
} else {
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
// Instead, safeAreaInsets is updated here
self.update(safeAreaInsets: layoutInsets)
}
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
safeAreaInsetsObservation = nil
}
// MARK:- Privates
private func fetchLayout(for traitCollection: UITraitCollection) -> FloatingPanelLayout {
switch traitCollection.verticalSizeClass {
case .compact:
return self.delegate?.floatingPanel(self, layoutFor: traitCollection) ?? FloatingPanelDefaultLandscapeLayout()
default:
return self.delegate?.floatingPanel(self, layoutFor: traitCollection) ?? FloatingPanelDefaultLayout()
}
}
private func fetchBehavior(for traitCollection: UITraitCollection) -> FloatingPanelBehavior {
return self.delegate?.floatingPanel(self, behaviorFor: traitCollection) ?? FloatingPanelDefaultBehavior()
}
private func update(safeAreaInsets: UIEdgeInsets) {
// preserve the current content offset
let contentOffset = scrollView?.contentOffset
floatingPanel.safeAreaInsets = safeAreaInsets
scrollView?.contentOffset = contentOffset ?? .zero
switch contentInsetAdjustmentBehavior {
case .always:
scrollView?.contentInset = adjustedContentInsets
scrollView?.scrollIndicatorInsets = adjustedContentInsets
default:
break
}
}
private func updateLayout(for traitCollection: UITraitCollection) {
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
floatingPanel.layoutAdapter.prepareLayout(in: self)
floatingPanel.layoutAdapter.activateLayout(of: floatingPanel.state)
}
// MARK: - Container view controller interface
/// Shows the surface view at the initial position defined by the current layout
public func show(animated: Bool = false, completion: (() -> Void)? = nil) {
// Must apply the current layout here
updateLayout(for: traitCollection)
move(to: floatingPanel.layoutAdapter.layout.initialPosition,
animated: animated,
completion: completion)
}
/// Hides the surface view to the hidden position
public func hide(animated: Bool = false, completion: (() -> Void)? = nil) {
move(to: .hidden,
animated: animated,
completion: completion)
}
/// Adds the view managed by the controller as a child of the specified view controller.
/// - Parameters:
/// - parent: A parent view controller object that displays FloatingPanelController's view. A container view controller object isn't applicable.
/// - belowView: Insert the surface view managed by the controller below the specified view. By default, the surface view will be added to the end of the parent list of subviews.
/// - animated: Pass true to animate the presentation; otherwise, pass false.
public func addPanel(toParent parent: UIViewController, belowView: UIView? = nil, animated: Bool = false) {
guard self.parent == nil else {
log.warning("Already added to a parent(\(parent))")
return
}
precondition((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.")
precondition((parent is UITabBarController) == false, "UITabBarController displays child view controllers with a radio-style selection interface")
precondition((parent is UISplitViewController) == false, "UISplitViewController manages two child view controllers in a master-detail interface")
precondition((parent is UITableViewController) == false, "UITableViewController should not be the parent because the view is a table view so that a floating panel doens't work well")
precondition((parent is UICollectionViewController) == false, "UICollectionViewController should not be the parent because the view is a collection view so that a floating panel doens't work well")
if let belowView = belowView {
parent.view.insertSubview(self.view, belowSubview: belowView)
} else {
parent.view.addSubview(self.view)
}
view.frame = parent.view.bounds // MUST
parent.addChild(self)
show(animated: true) { [weak self] in
guard let self = self else { return }
self.didMove(toParent: parent)
}
}
/// Removes the controller and the managed view from its parent view controller
/// - Parameters:
/// - animated: Pass true to animate the presentation; otherwise, pass false.
/// - completion: The block to execute after the view controller is dismissed. This block has no return value and takes no parameters. You may specify nil for this parameter.
public func removePanelFromParent(animated: Bool, completion: (() -> Void)? = nil) {
guard self.parent != nil else {
completion?()
return
}
hide(animated: animated) { [weak self] in
guard let self = self else { return }
self.willMove(toParent: nil)
self.view.removeFromSuperview()
self.removeFromParent()
completion?()
}
}
/// Moves the position to the specified position.
/// - Parameters:
/// - to: Pass a FloatingPanelPosition value to move the surface view to the position.
/// - animated: Pass true to animate the presentation; otherwise, pass false.
/// - completion: The block to execute after the view controller has finished moving. This block has no return value and takes no parameters. You may specify nil for this parameter.
public func move(to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
precondition(floatingPanel.layoutAdapter.vc != nil, "Use show(animated:completion)")
floatingPanel.move(to: to, animated: animated, completion: completion)
}
/// Sets the view controller responsible for the content portion of the floating panel..
public func set(contentViewController: UIViewController?) {
if let vc = _contentViewController {
vc.willMove(toParent: nil)
vc.view.removeFromSuperview()
vc.removeFromParent()
}
if let vc = contentViewController {
let surfaceView = floatingPanel.surfaceView
surfaceView.add(childView: vc.view)
addChild(vc)
vc.didMove(toParent: self)
}
_contentViewController = contentViewController
}
@available(*, unavailable, renamed: "set(contentViewController:)")
public override func show(_ vc: UIViewController, sender: Any?) {
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) {
target.show(vc, sender: sender)
}
}
@available(*, unavailable, renamed: "set(contentViewController:)")
public override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.showDetailViewController(_:sender:)), sender: sender) {
target.showDetailViewController(vc, sender: sender)
}
}
// MARK: - Scroll view tracking
/// Tracks the specified scroll view to correspond with the scroll.
///
/// - Attention:
/// The specified scroll view must be already assigned to the delegate property because the controller intermediates between the various delegate methods.
///
public func track(scrollView: UIScrollView) {
floatingPanel.scrollView = scrollView
if scrollView.delegate !== floatingPanel {
floatingPanel.userScrollViewDelegate = scrollView.delegate
scrollView.delegate = floatingPanel
}
switch contentInsetAdjustmentBehavior {
case .always:
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
} else {
children.forEach { (vc) in
vc.automaticallyAdjustsScrollViewInsets = false
}
}
default:
break
}
}
// MARK: - Utilities
/// Updates the layout object from the delegate and lays out the views managed
/// by the controller immediately.
///
/// This method updates the `FloatingPanelLayout` object from the delegate and
/// then it calls `layoutIfNeeded()` of the root view to force the view
/// to update the floating panel's layout immediately. It can be called in an
/// animation block.
public func updateLayout() {
updateLayout(for: view.traitCollection)
floatingPanel.layoutAdapter.checkLayoutConsistance()
}
/// Returns the y-coordinate of the point at the origin of the surface view
public func originYOfSurface(for pos: FloatingPanelPosition) -> CGFloat {
switch pos {
case .full:
return floatingPanel.layoutAdapter.topY
case .half:
return floatingPanel.layoutAdapter.middleY
case .tip:
return floatingPanel.layoutAdapter.bottomY
case .hidden:
return floatingPanel.layoutAdapter.hiddenY
}
}
}
extension FloatingPanelController {
private static let dismissSwizzling: Any? = {
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 {
// switch implementation..
method_exchangeImplementations(originalMethod, swizzledMethod)
}
return nil
}()
}
public extension UIViewController {
@objc public func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
}
@objc public func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if let fpc = parent as? FloatingPanelController, fpc.parent != nil {
fpc.removePanelFromParent(animated: flag, completion: completion)
} else {
self.fp_original_dismiss(animated: flag, completion: completion)
}
}
}
-383
View File
@@ -1,383 +0,0 @@
//
// Created by Shin Yamamoto on 2018/09/27.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
/// FloatingPanelIntrinsicLayout
///
/// - Attention:
/// `insetFor(position:)` must return `nil` for full position because the inset is determined automatically.
/// You can customize insets only for half, tip and hidden positions
/// on FloatingPanelIntrinsicLayout.
public protocol FloatingPanelIntrinsicLayout: FloatingPanelLayout { }
public extension FloatingPanelIntrinsicLayout {
var initialPosition: FloatingPanelPosition {
return .full
}
var supportedPositions: Set<FloatingPanelPosition> {
return [.full]
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
return nil
}
}
public protocol FloatingPanelLayout: class {
/// Returns the initial position of a floating panel.
var initialPosition: FloatingPanelPosition { get }
/// Returns a set of FloatingPanelPosition objects to tell the applicable
/// positions of the floating panel controller.
///
/// By default, it returns all position exepct for `hidden` position. Because
/// it's always supported by `FloatingPanelController` so you don't need to return it.
var supportedPositions: Set<FloatingPanelPosition> { get }
/// Return the interaction buffer to the top from the top position. Default is 6.0.
var topInteractionBuffer: CGFloat { get }
/// Return the interaction buffer to the bottom from the bottom position. Default is 6.0.
var bottomInteractionBuffer: CGFloat { get }
/// Returns a CGFloat value to determine a Y coordinate of a floating panel for each position(full, half, tip and hidden).
///
/// Its returning value indicates a different inset for each positiion.
/// For full position, a top inset from a safe area in `FloatingPanelController.view`.
/// For half or tip position, a bottom inset from the safe area.
/// For hidden position, a bottom inset from `FloatingPanelController.view`.
/// If a position isn't supported or the default value is used, return nil.
func insetFor(position: FloatingPanelPosition) -> CGFloat?
/// Returns X-axis and width layout constraints of the surface view of a floating panel.
/// You must not include any Y-axis and height layout constraints of the surface view
/// because their constraints will be configured by the floating panel controller.
/// By default, the width of a surface view fits a safe area.
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint]
/// Returns a CGFloat value to determine the backdrop view's alpha for a position.
///
/// Default is 0.3 at full position, otherwise 0.0.
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat
}
public extension FloatingPanelLayout {
var topInteractionBuffer: CGFloat { return 6.0 }
var bottomInteractionBuffer: CGFloat { return 6.0 }
var supportedPositions: Set<FloatingPanelPosition> {
return Set([.full, .half, .tip])
}
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
return [
surfaceView.leftAnchor.constraint(equalTo: view.sideLayoutGuide.leftAnchor, constant: 0.0),
surfaceView.rightAnchor.constraint(equalTo: view.sideLayoutGuide.rightAnchor, constant: 0.0),
]
}
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return position == .full ? 0.3 : 0.0
}
}
public class FloatingPanelDefaultLayout: FloatingPanelLayout {
public var contentViewController: UIViewController?
public var initialPosition: FloatingPanelPosition {
return .half
}
public func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 18.0
case .half: return 262.0
case .tip: return 69.0
case .hidden: return nil
}
}
}
public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout {
public var contentViewController: UIViewController?
public var initialPosition: FloatingPanelPosition {
return .tip
}
public var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .tip]
}
public func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 16.0
case .tip: return 69.0
default: return nil
}
}
}
class FloatingPanelLayoutAdapter {
weak var vc: UIViewController!
private weak var surfaceView: FloatingPanelSurfaceView!
private weak var backdropView: FloatingPanelBackdropView!
var layout: FloatingPanelLayout
var safeAreaInsets: UIEdgeInsets = .zero {
didSet {
if oldValue != safeAreaInsets {
updateHeight()
}
}
}
private var heightBuffer: CGFloat = 88.0 // For bounce
private var fixedConstraints: [NSLayoutConstraint] = []
private var fullConstraints: [NSLayoutConstraint] = []
private var halfConstraints: [NSLayoutConstraint] = []
private var tipConstraints: [NSLayoutConstraint] = []
private var offConstraints: [NSLayoutConstraint] = []
private var heightConstraints: [NSLayoutConstraint] = []
private var fullInset: CGFloat {
if layout is FloatingPanelIntrinsicLayout {
return intrinsicHeight
} else {
return layout.insetFor(position: .full) ?? 0.0
}
}
private var halfInset: CGFloat {
return layout.insetFor(position: .half) ?? 0.0
}
private var tipInset: CGFloat {
return layout.insetFor(position: .tip) ?? 0.0
}
private var hiddenInset: CGFloat {
return layout.insetFor(position: .hidden) ?? 0.0
}
var supportedPositions: Set<FloatingPanelPosition> {
var supportedPositions = layout.supportedPositions
supportedPositions.remove(.hidden)
return supportedPositions
}
var topY: CGFloat {
if supportedPositions.contains(.full) {
if layout is FloatingPanelIntrinsicLayout {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + fullInset)
} else {
return (safeAreaInsets.top + fullInset)
}
} else {
return middleY
}
}
var middleY: CGFloat {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
}
var bottomY: CGFloat {
if supportedPositions.contains(.tip) {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset)
} else {
return middleY
}
}
var hiddenY: CGFloat {
return surfaceView.superview!.bounds.height
}
var safeAreaBottomY: CGFloat {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
}
var topMaxY: CGFloat { return -safeAreaInsets.top }
var bottomMaxY: CGFloat { return safeAreaBottomY }
var adjustedContentInsets: UIEdgeInsets {
return UIEdgeInsets(top: 0.0,
left: 0.0,
bottom: safeAreaInsets.bottom,
right: 0.0)
}
func positionY(for pos: FloatingPanelPosition) -> CGFloat {
switch pos {
case .full:
return topY
case .half:
return middleY
case .tip:
return bottomY
case .hidden:
return hiddenY
}
}
var intrinsicHeight: CGFloat = 0.0
init(surfaceView: FloatingPanelSurfaceView, backdropView: FloatingPanelBackdropView, layout: FloatingPanelLayout) {
self.layout = layout
self.surfaceView = surfaceView
self.backdropView = backdropView
}
func updateIntrinsicHeight() {
let fittingSize = UIView.layoutFittingCompressedSize
intrinsicHeight = surfaceView.contentView.systemLayoutSizeFitting(fittingSize).height
}
func prepareLayout(in vc: UIViewController) {
self.vc = vc
surfaceView.translatesAutoresizingMaskIntoConstraints = false
backdropView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints)
// Fixed constraints of surface and backdrop views
let surfaceConstraints = layout.prepareLayout(surfaceView: surfaceView, in: vc.view!)
let backdropConstraints = [
backdropView.topAnchor.constraint(equalTo: vc.view.topAnchor, constant: 0.0),
backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor,constant: 0.0),
backdropView.rightAnchor.constraint(equalTo: vc.view.rightAnchor, constant: 0.0),
backdropView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor, constant: 0.0),
]
fixedConstraints = surfaceConstraints + backdropConstraints
// Flexible surface constarints for full, half, tip and off
if layout is FloatingPanelIntrinsicLayout {
updateIntrinsicHeight()
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
constant: -fullInset),
]
} else {
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
constant: fullInset),
]
}
halfConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
constant: -halfInset),
]
tipConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
constant: -tipInset),
]
offConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.view.bottomAnchor,
constant: -hiddenInset),
]
}
// The method is separated from prepareLayout(to:) for the rotation support
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
func updateHeight() {
guard let vc = vc else { return }
defer {
UIView.performWithoutAnimation {
surfaceView.superview!.layoutIfNeeded()
}
}
NSLayoutConstraint.deactivate(heightConstraints)
let height: CGFloat
if layout is FloatingPanelIntrinsicLayout {
updateIntrinsicHeight()
height = intrinsicHeight + safeAreaInsets.bottom
} else {
// Must use the`vc` height, not the screen height because safe area insets
// of `vc` are relative values. For example, a view controller in
// Navigation controller's safe area insets and frame can be changed whether
// the navigation bar is translucent or not.
height = vc.view.bounds.height - (safeAreaInsets.top + fullInset)
}
heightConstraints = [
surfaceView.heightAnchor.constraint(equalToConstant: height)
]
NSLayoutConstraint.activate(heightConstraints)
surfaceView.set(bottomOverflow: heightBuffer + layout.topInteractionBuffer)
if layout is FloatingPanelIntrinsicLayout {
NSLayoutConstraint.deactivate(fullConstraints)
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
constant: -fullInset),
]
NSLayoutConstraint.activate(fullConstraints)
}
}
func activateLayout(of state: FloatingPanelPosition) {
defer {
surfaceView.superview!.layoutIfNeeded()
}
var state = state
setBackdropAlpha(of: state)
NSLayoutConstraint.activate(fixedConstraints)
if supportedPositions.union([.hidden]).contains(state) == false {
state = layout.initialPosition
}
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
switch state {
case .full:
NSLayoutConstraint.deactivate(halfConstraints + tipConstraints + offConstraints)
NSLayoutConstraint.activate(fullConstraints)
case .half:
NSLayoutConstraint.deactivate(fullConstraints + tipConstraints + offConstraints)
NSLayoutConstraint.activate(halfConstraints)
case .tip:
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + offConstraints)
NSLayoutConstraint.activate(tipConstraints)
case .hidden:
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints)
NSLayoutConstraint.activate(offConstraints)
}
}
func setBackdropAlpha(of target: FloatingPanelPosition) {
if target == .hidden {
self.backdropView.alpha = 0.0
} else {
self.backdropView.alpha = layout.backdropAlphaFor(position: target)
}
}
func checkLayoutConsistance() {
// Verify layout configurations
assert(supportedPositions.count > 0)
assert(supportedPositions.contains(layout.initialPosition),
"Does not include an initial potision(\(layout.initialPosition)) in supportedPositions(\(supportedPositions))")
if layout is FloatingPanelIntrinsicLayout {
assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout")
}
if halfInset > 0 {
assert(halfInset > tipInset, "Invalid half and tip insets")
}
if fullInset > 0 {
assert(middleY > topY, "Invalid insets { topY: \(topY), middleY: \(middleY) }")
assert(bottomY > topY, "Invalid insets { topY: \(topY), bottomY: \(bottomY) }")
}
}
}
@@ -1,178 +0,0 @@
//
// Created by Shin Yamamoto on 2018/09/26.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
class FloatingPanelSurfaceContentView: UIView {}
/// A view that presents a surface interface in a floating panel.
public class FloatingPanelSurfaceView: UIView {
/// A GrabberHandleView object displayed at the top of the surface view.
///
/// To use a custom grabber handle, hide this and then add the custom one
/// to the surface view at appropirate coordinates.
public var grabberHandle: GrabberHandleView!
/// The height of the grabber bar area
public static var topGrabberBarHeight: CGFloat {
return Default.grabberTopPadding * 2 + GrabberHandleView.Default.height // 17.0
}
/// A UIView object that can have the surface view added to it.
public var contentView: UIView!
private var color: UIColor? = .white { didSet { setNeedsLayout() } }
private var bottomOverflow: CGFloat = 0.0 // Must not call setNeedsLayout()
public override var backgroundColor: UIColor? {
get { return color }
set { color = newValue }
}
/// The radius to use when drawing top rounded corners.
///
/// `self.contentView` is masked with the top rounded corners automatically on iOS 11 and later.
/// On iOS 10, they are not automatically masked because of a UIVisualEffectView issue. See https://forums.developer.apple.com/thread/50854
public var cornerRadius: CGFloat = 0.0 { didSet { setNeedsLayout() } }
/// A Boolean indicating whether the surface shadow is displayed.
public var shadowHidden: Bool = false { didSet { setNeedsLayout() } }
/// The color of the surface shadow.
public var shadowColor: UIColor = .black { didSet { setNeedsLayout() } }
/// The offset (in points) of the surface shadow.
public var shadowOffset: CGSize = CGSize(width: 0.0, height: 1.0) { didSet { setNeedsLayout() } }
/// The opacity of the surface shadow.
public var shadowOpacity: Float = 0.2 { didSet { setNeedsLayout() } }
/// The blur radius (in points) used to render the surface shadow.
public var shadowRadius: CGFloat = 3 { didSet { setNeedsLayout() } }
/// The width of the surface border.
public var borderColor: UIColor? { didSet { setNeedsLayout() } }
/// The color of the surface border.
public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
private var backgroundLayer: CAShapeLayer! { didSet { setNeedsLayout() } }
private struct Default {
public static let grabberTopPadding: CGFloat = 6.0
}
override init(frame: CGRect) {
super.init(frame: frame)
render()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
render()
}
private func render() {
super.backgroundColor = .clear
self.clipsToBounds = false
let backgroundLayer = CAShapeLayer()
layer.insertSublayer(backgroundLayer, at: 0)
self.backgroundLayer = backgroundLayer
let contentView = FloatingPanelSurfaceContentView()
addSubview(contentView)
self.contentView = contentView as UIView
contentView.backgroundColor = color
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
contentView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
])
let grabberHandle = GrabberHandleView()
addSubview(grabberHandle)
self.grabberHandle = grabberHandle
grabberHandle.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
grabberHandle.topAnchor.constraint(equalTo: topAnchor, constant: Default.grabberTopPadding),
grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandle.frame.width),
grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandle.frame.height),
grabberHandle.centerXAnchor.constraint(equalTo: centerXAnchor),
])
}
public override func layoutSubviews() {
super.layoutSubviews()
updateLayers()
updateContentViewMask()
contentView.layer.borderColor = borderColor?.cgColor
contentView.layer.borderWidth = borderWidth
contentView.backgroundColor = color
}
private func updateLayers() {
log.debug("SurfaceView bounds", bounds)
var rect = bounds
rect.size.height += bottomOverflow // Expand the height for overflow buffer
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
backgroundLayer.path = path.cgPath
backgroundLayer.fillColor = color?.cgColor
if shadowHidden == false {
layer.shadowColor = shadowColor.cgColor
layer.shadowOffset = shadowOffset
layer.shadowOpacity = shadowOpacity
layer.shadowRadius = shadowRadius
}
}
private func updateContentViewMask() {
if #available(iOS 11, *) {
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyborad of Example/Maps.
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
let maskLayer = CAShapeLayer()
var rect = bounds
rect.size.height += bottomOverflow
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
maskLayer.path = path.cgPath
contentView.layer.mask = maskLayer
} else {
// Don't use `contentView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
// Instead, a user can mask the content view manually in an application.
}
}
func set(bottomOverflow: CGFloat) {
self.bottomOverflow = bottomOverflow
updateLayers()
updateContentViewMask()
}
func add(childView: UIView) {
contentView.addSubview(childView)
childView.frame = contentView.bounds
childView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
childView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
childView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 0.0),
childView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0.0),
childView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
])
}
}
@@ -1,99 +0,0 @@
//
// Created by Shin Yamamoto on 2018/11/21.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
class FloatingPanelModalTransition: NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return FloatingPanelModalPresentTransition()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return FloatingPanelModalDismissTransition()
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return FloatingPanelPresentationController(presentedViewController: presented, presenting: presenting)
}
}
class FloatingPanelPresentationController: UIPresentationController {
override func presentationTransitionDidEnd(_ completed: Bool) {
// For non-animated presentation
if let fpc = presentedViewController as? FloatingPanelController, fpc.position == .hidden {
fpc.show(animated: false, completion: nil)
}
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
// For non-animated dismissal
if let fpc = presentedViewController as? FloatingPanelController, fpc.position != .hidden {
fpc.hide(animated: false, completion: nil)
}
}
override func containerViewWillLayoutSubviews() {
guard
let containerView = self.containerView,
let fpc = presentedViewController as? FloatingPanelController,
let fpView = fpc.view
else { fatalError() }
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
fpc.backdropView.addGestureRecognizer(tapGesture)
containerView.addSubview(fpView)
fpView.frame = containerView.bounds //MUST
}
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
presentedViewController.dismiss(animated: true, completion: nil)
}
}
class FloatingPanelModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
guard
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
else { fatalError()}
let animator = fpc.behavior.addAnimator(fpc, to: fpc.layout.initialPosition)
return TimeInterval(animator.duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
else { fatalError() }
fpc.show(animated: true) {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
class FloatingPanelModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
guard
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
else { fatalError()}
let animator = fpc.behavior.removeAnimator(fpc, from: fpc.position)
return TimeInterval(animator.duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
else { fatalError() }
fpc.hide(animated: true) {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
-30
View File
@@ -1,30 +0,0 @@
//
// Created by Shin Yamamoto on 2018/11/21.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
class FloatingPanelPassThroughView: UIView {
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
switch view {
case is FloatingPanelPassThroughView:
return nil
default:
return view
}
}
}
class FloatingPanelSurfaceWrapperView: UIView {
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
switch view {
case is FloatingPanelSurfaceWrapperView:
return nil
default:
return view
}
}
}
-37
View File
@@ -1,37 +0,0 @@
//
// Created by Shin Yamamoto on 2018/09/19.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import UIKit
public class GrabberHandleView: UIView {
public struct Default {
public static let width: CGFloat = 36.0
public static let height: CGFloat = 5.0
public static let barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
render()
}
init() {
let size = CGSize(width: Default.width,
height: Default.height)
super.init(frame: CGRect(origin: .zero, size: size))
self.backgroundColor = Default.barColor
render()
}
private func render() {
self.layer.masksToBounds = true
self.layer.cornerRadius = frame.size.height * 0.5
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? nil : view
}
}
-106
View File
@@ -1,106 +0,0 @@
//
// Created by Shin Yamamoto on 2018/10/09.
// Copyright © 2018 Shin Yamamoto. All rights reserved.
//
import Foundation
import os.log
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
case fault = 4
var name: String {
switch self {
case .debug: return "DEBUG"
case .info: return "INFO"
case .warning: return "WARNING"
case .error: return "ERROR"
case .fault: return "FAULT"
}
}
var shortName: String {
switch self {
case .debug:
return "D/"
case .info:
return "I/"
case .warning:
return "W/"
case .error:
return "E/"
case .fault:
return "F/"
}
}
@available(iOS 10.0, *)
var osLogType: OSLogType {
switch self {
case .debug: return .debug
case .info: return .info
case .warning: return .info
case .error: return .error
case .fault: return .fault
}
}
public static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
init() {
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
}
private func log(_ level: Level, _ message: Any, _ arguments: [Any], function: String, line: UInt) {
#if __FP_LOG
_ = s.wait(timeout: .now() + 0.033)
defer { s.signal() }
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
let log = "\(level.shortName) \(message) \(extraMessage) (\(function):\(line))"
os_log("%@", log: osLog, type: level.osLogType, log)
#endif
}
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..., function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.debug, log, arguments, function: getPrettyFunction(function, file), line: line)
}
func info(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.info, log, arguments, 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, 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, function: getPrettyFunction(function, file), line: line)
}
func fault(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
self.log(.fault, log, arguments, function: getPrettyFunction(function, file), line: line)
}
}

Some files were not shown because too many files have changed in this diff Show More