Compare commits

..

69 Commits

Author SHA1 Message Date
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
87 changed files with 4234 additions and 2000 deletions
+1
View File
@@ -0,0 +1 @@
github: SCENEE
+104
View File
@@ -0,0 +1,104 @@
name: ci
on:
push:
branches:
- master
pull_request:
branches:
- '*'
jobs:
build:
runs-on: macOS-11
steps:
- uses: actions/checkout@v1
- name: "Swift 5.4"
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.4 clean build
env:
DEVELOPER_DIR: /Applications/Xcode_12.5.1.app/Contents/Developer
- name: "Swift 5.5"
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.5 clean build
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
build_compat:
runs-on: macOS-10.15
steps:
- uses: actions/checkout@v1
- name: "Swift 5.1"
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.1 clean build
env:
DEVELOPER_DIR: /Applications/Xcode_11.3.1.app/Contents/Developer
- name: "Swift 5.2"
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.2 clean build
env:
DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer
- name: "Swift 5.3"
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.3 clean build
env:
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
testing:
runs-on: macOS-11
steps:
- uses: actions/checkout@v1
- name: "Testing in iOS 14.5"
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.5,name=iPhone 12 Pro'
env:
DEVELOPER_DIR: /Applications/Xcode_12.5.1.app/Contents/Developer
- name: "Testing in iOS 15.0"
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=15.0,name=iPhone 13 Pro'
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
testing_compat:
runs-on: macOS-10.15
steps:
- uses: actions/checkout@v1
- name: "Testing in iOS 13.7"
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=13.7,name=iPhone 11 Pro'
env:
DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer
- name: "Testing in iOS 14.4"
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.4,name=iPhone 12 Pro'
env:
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
example:
runs-on: macOS-11
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
steps:
- uses: actions/checkout@v1
- name: "Build Maps"
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Maps -sdk iphonesimulator clean build
- name: "Build Stocks"
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Stocks -sdk iphonesimulator clean build
- name: "Build Samples"
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Samples -sdk iphonesimulator clean build
swiftpm:
runs-on: macOS-11
env:
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
steps:
- uses: actions/checkout@v1
- name: "Swift Package build"
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios14.3-simulator"
carthage:
runs-on: macOS-11
steps:
- uses: actions/checkout@v1
- name: "Carthage build"
run: carthage build --use-xcframeworks --no-skip-current
cocoapods:
runs-on: macOS-11
steps:
- uses: actions/checkout@v1
- name: "CocoaPods: pod lib lint"
run: pod lib lint --allow-warnings
- name: "CocoaPods: pod spec lint"
run: pod spec lint --allow-warnings
+1 -1
View File
@@ -1,3 +1,3 @@
--header "// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license."
--disable andOperator,anyObjectProtocol,blankLinesAroundMark,blankLinesAtEndOfScope,blankLinesAtStartOfScope,blankLinesBetweenScopes,braces,consecutiveBlankLines,consecutiveSpaces,duplicateImports,elseOnSameLine,emptyBraces,hoistPatternLet,indent,isEmpty,leadingDelimiters,linebreakAtEndOfFile,linebreaks,numberFormatting,ranges,redundantBackticks,redundantBreak,redundantExtensionACL,redundantFileprivate,redundantGet,redundantInit,redundantLet,redundantLetError,redundantNilInit,redundantObjc,redundantParens,redundantPattern,redundantRawValues,redundantReturn,redundantSelf,redundantVoidReturnType,semicolons,sortedImports,spaceAroundBraces,spaceAroundBrackets,spaceAroundComments,spaceAroundGenerics,spaceAroundOperators,spaceAroundParens,spaceInsideBraces,spaceInsideBrackets,spaceInsideComments,spaceInsideGenerics,spaceInsideParens,specifiers,strongOutlets,strongifiedSelf,todos,trailingClosures,trailingCommas,trailingSpace,typeSugar,unusedArguments,void,wrapArguments,yodaConditions
--disable andOperator,anyObjectProtocol,blankLinesAroundMark,blankLinesAtEndOfScope,blankLinesAtStartOfScope,blankLinesBetweenScopes,braces,consecutiveBlankLines,consecutiveSpaces,duplicateImports,elseOnSameLine,emptyBraces,hoistPatternLet,indent,isEmpty,leadingDelimiters,linebreakAtEndOfFile,linebreaks,numberFormatting,redundantBackticks,redundantBreak,redundantExtensionACL,redundantFileprivate,redundantGet,redundantInit,redundantLet,redundantLetError,redundantNilInit,redundantObjc,redundantParens,redundantPattern,redundantRawValues,redundantReturn,redundantSelf,redundantVoidReturnType,semicolons,sortedImports,spaceAroundBraces,spaceAroundBrackets,spaceAroundComments,spaceAroundGenerics,spaceAroundOperators,spaceAroundParens,spaceInsideBraces,spaceInsideBrackets,spaceInsideComments,spaceInsideGenerics,spaceInsideParens,specifiers,strongOutlets,strongifiedSelf,todos,trailingClosures,trailingCommas,trailingSpace,typeSugar,unusedArguments,void,wrapArguments,yodaConditions
-74
View File
@@ -1,74 +0,0 @@
language: objective-c
branches:
only:
- master
env:
global:
- LANG=en_US.UTF-8
- LC_ALL=en_US.UTF-8
jobs:
include:
- stage: "Builds"
script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.1 SUPPORTS_MACCATALYST=NO clean build
# SUPPORTS_MACCATALYST=NO because Xcode 11 runs on macOS 10.14 in Travis CI
osx_image: xcode11.3
name: "Swift 5.1"
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.2 clean build
osx_image: xcode11.6
name: "Swift 5.2"
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.3 clean build
osx_image: xcode12
name: "Swift 5.3"
- stage: "Tests"
script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=10.3.1,name=iPhone SE (1st generation)'
osx_image: xcode11.6
name: "iPhone SE (iOS 10.3)"
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=11.4,name=iPhone 7'
osx_image: xcode11.6
name: "iPhone 7 (iOS 11.4)"
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=12.4,name=iPhone X'
osx_image: xcode11.6
name: "iPhone X (iOS 12.4)"
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=13.6,name=iPhone 11'
osx_image: xcode11.6
name: "iPhone X (iOS 13.6)"
- stage: "Build examples"
osx_image: xcode11.6
script: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Maps -sdk iphonesimulator clean build
name: "Maps"
- script: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Stocks -sdk iphonesimulator clean build
osx_image: xcode11.6
name: "Stocks"
- script: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Samples -sdk iphonesimulator clean build
osx_image: xcode11.6
name: "Samples"
- script: xcodebuild -workspace FloatingPanel.xcworkspace -scheme SamplesObjC -sdk iphonesimulator clean build
osx_image: xcode11.6
name: "SamplesObjC"
- stage: "Swift Package Manager"
script: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphoneos --show-sdk-path`" -Xswiftc "-target" -Xswiftc "arm64-apple-ios13.0"
osx_image: xcode11.6
name: "iPhone OS"
- script: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios13.0-simulator"
osx_image: xcode11.6
name: "iPhone Simulator"
- stage: "Carthage"
# Carthage doesn't fix the issue with Xcode 12: https://github.com/Carthage/Carthage/releases/tag/0.36.0
osx_image: xcode11.6
before_install:
- brew update
- brew outdated carthage || brew upgrade carthage
script:
- carthage build --no-skip-current
- stage: "CocoaPods"
osx_image: xcode11.6
before_install:
- gem install cocoapods
script:
- pod spec lint --allow-warnings
- pod lib lint --allow-warnings
@@ -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,121 @@
// 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
fpc.set(contentViewController: hostingViewController)
fpc.addPanel(toParent: parentViewController, at: 1, animated: false)
}
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,11 @@
// Copyright 2021 the FloatingPanel authors. All rights reserved. MIT license.
import FloatingPanel
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
}
}
+12 -10
View File
@@ -12,7 +12,7 @@
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 */; };
@@ -40,7 +40,7 @@
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>"; };
@@ -91,14 +91,14 @@
54B51128216C3D840033A6F3 /* Maps */ = {
isa = PBXGroup;
children = (
54B51130216C3D860033A6F3 /* Assets.xcassets */,
54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */,
54B5112D216C3D840033A6F3 /* Main.storyboard */,
54B51129216C3D840033A6F3 /* AppDelegate.swift */,
54B5112B216C3D840033A6F3 /* ViewController.swift */,
54B5112B216C3D840033A6F3 /* MainViewController.swift */,
549A5F58244673FE0025F312 /* SearchViewController.swift */,
54E26CB724A98E310066C720 /* DetailViewController.swift */,
54E26CB524A989090066C720 /* Utils.swift */,
54B5112D216C3D840033A6F3 /* Main.storyboard */,
54B51130216C3D860033A6F3 /* Assets.xcassets */,
54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */,
54B51135216C3D860033A6F3 /* Info.plist */,
);
path = Maps;
@@ -132,7 +132,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
54B51125216C3D840033A6F3 = {
@@ -177,7 +177,7 @@
buildActionMask = 2147483647;
files = (
549A5F59244673FE0025F312 /* SearchViewController.swift in Sources */,
54B5112C216C3D840033A6F3 /* ViewController.swift in Sources */,
54B5112C216C3D840033A6F3 /* MainViewController.swift in Sources */,
54E26CB824A98E310066C720 /* DetailViewController.swift in Sources */,
54B5112A216C3D840033A6F3 /* AppDelegate.swift in Sources */,
54E26CB624A989090066C720 /* Utils.swift in Sources */,
@@ -232,6 +232,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;
@@ -293,6 +294,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;
@@ -334,7 +336,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -353,7 +355,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
PRODUCT_BUNDLE_IDENTIFIER = example.Maps;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -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"
-4
View File
@@ -5,9 +5,5 @@ import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}
+36 -38
View File
@@ -1,33 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<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="16087"/>
<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="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"/>
@@ -39,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"/>
@@ -54,31 +52,31 @@
<objects>
<viewController storyboardIdentifier="SearchViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0S1-Lk-JgE" customClass="SearchViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ncl-E9-yRn">
<rect key="frame" x="0.0" y="0.0" width="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="812"/>
<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="812"/>
<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="746"/>
<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"/>
@@ -88,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"/>
@@ -96,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"/>
@@ -106,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"/>
@@ -116,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"/>
@@ -126,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"/>
@@ -136,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"/>
@@ -146,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"/>
@@ -168,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="70"/>
<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">
@@ -183,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="12" width="303" height="46"/>
<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="22"/>
<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"/>
@@ -233,6 +231,7 @@
<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"/>
@@ -240,7 +239,6 @@
<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"/>
@@ -257,18 +255,18 @@
<objects>
<viewController storyboardIdentifier="DetailViewController" id="Tp2-MF-IFz" customClass="DetailViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="FmO-AT-4Y7">
<rect key="frame" x="0.0" y="0.0" width="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" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c3d-2e-0b1">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<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="375" height="812"/>
<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="375" height="812"/>
<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>
@@ -282,8 +280,8 @@
<blurEffect style="extraLight"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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"/>
@@ -4,7 +4,7 @@ import UIKit
import MapKit
import FloatingPanel
class ViewController: UIViewController {
class MainViewController: UIViewController {
typealias PanelDelegate = FloatingPanelControllerDelegate & UIGestureRecognizerDelegate
// Search Panel
@@ -22,7 +22,9 @@ class ViewController: UIViewController {
storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
@IBOutlet weak var mapView: MKMapView!
}
extension MainViewController {
override func viewDidLoad() {
super.viewDidLoad()
fpc.contentMode = .fitToBounds
@@ -40,7 +42,7 @@ class ViewController: UIViewController {
layoutPanelForPhone()
}
setupMapView()
setUpMapView()
setUpSearchView()
}
@@ -53,9 +55,11 @@ class ViewController: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
teardownMapView()
tearDownMapView()
}
}
extension MainViewController {
func layoutPanelForPad() {
fpc.behavior = SearchPaneliPadBehavior()
fpc.panGestureRecognizer.delegateProxy = fpcDelegate
@@ -76,21 +80,23 @@ class ViewController: UIViewController {
self.didMove(toParent: self)
}
fpc.setAppearanceForPad()
detailFpc.setAppearanceForPad()
[fpc, detailFpc].forEach { $0.setAppearanceForPad() }
}
func layoutPanelForPhone() {
fpc.track(scrollView: searchVC.tableView) // Only track the tabvle view on iPhone
fpc.track(scrollView: searchVC.tableView) // Only track the table view on iPhone
fpc.addPanel(toParent: self, animated: true)
fpc.setApearanceForPhone()
detailFpc.setApearanceForPhone()
[fpc, detailFpc].forEach { $0.setAppearanceForPhone()}
}
}
extension FloatingPanelController {
func setApearanceForPhone() {
func setAppearanceForPhone() {
let appearance = SurfaceAppearance()
if #available(iOS 13.0, *) {
appearance.cornerCurve = .continuous
}
appearance.cornerRadius = 8.0
appearance.backgroundColor = .clear
surfaceView.appearance = appearance
@@ -113,20 +119,28 @@ extension FloatingPanelController {
// MARK: - UISearchBarDelegate
extension ViewController: UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
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) {
searchBar.showsCancelButton = true
searchVC.showHeader(animated: true)
searchVC.tableView.alpha = 1.0
activate(searchBar: searchBar)
UIView.animate(withDuration: 0.25) { [weak self] in
self?.fpc.move(to: .full, animated: false)
}
@@ -136,9 +150,9 @@ extension ViewController: UISearchBarDelegate {
// MARK: - iPhone
class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: ViewController
unowned let owner: MainViewController
init(owner: ViewController) {
init(owner: MainViewController) {
self.owner = owner
}
@@ -190,13 +204,6 @@ class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGes
if targetState.pointee != .full {
owner.searchVC.hideHeader(animated: true)
}
if targetState.pointee == .tip {
vc.contentMode = .static
}
}
func floatingPanelDidEndAttracting(_ fpc: FloatingPanelController) {
fpc.contentMode = .fitToBounds
}
}
@@ -228,9 +235,9 @@ class SearchPanelLandscapeLayout: FloatingPanelLayout {
}
class DetailPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: ViewController
unowned let owner: MainViewController
init(owner: ViewController) {
init(owner: MainViewController) {
self.owner = owner
}
}
@@ -251,9 +258,9 @@ class DetailPanelPhoneLayout: FloatingPanelLayout {
// MARK: - iPad
class SearchPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: ViewController
unowned let owner: MainViewController
init(owner: ViewController) {
init(owner: MainViewController) {
self.owner = owner
}
@@ -324,9 +331,9 @@ class SearchPaneliPadBehavior: FloatingPanelBehavior {
}
class DetailPanelPadDelegate: NSObject, FloatingPanelControllerDelegate, UIGestureRecognizerDelegate {
unowned let owner: ViewController
unowned let owner: MainViewController
init(owner: ViewController) {
init(owner: MainViewController) {
self.owner = owner
}
@@ -376,8 +383,8 @@ class DetailPanelPadRightLayout: FloatingPanelLayout {
// MARK: - MKMapViewDelegate
extension ViewController: MKMapViewDelegate {
func setupMapView() {
extension MainViewController: MKMapViewDelegate {
func setUpMapView() {
let center = CLLocationCoordinate2D(latitude: 37.623198015869235,
longitude: -122.43066818432008)
let span = MKCoordinateSpan(latitudeDelta: 0.4425100023575723,
@@ -389,7 +396,7 @@ extension ViewController: MKMapViewDelegate {
mapView.delegate = self
}
func teardownMapView() {
func tearDownMapView() {
// Prevent a crash
mapView.delegate = nil
mapView = nil
@@ -4,7 +4,7 @@ import UIKit
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
extension MainViewController: UITableViewDelegate {
func setUpSearchView() {
searchVC.loadViewIfNeeded()
searchVC.tableView.delegate = self
@@ -21,6 +21,9 @@ extension ViewController: UITableViewDelegate {
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:
@@ -7,17 +7,32 @@
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 */; };
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 */; };
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 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* UIExtensions.swift */; };
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* UIComponents.swift */; };
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 */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -52,9 +67,20 @@
/* 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>"; };
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 +91,13 @@
545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
545DBA0D21511E6400CA77B8 /* SampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleUITests.swift; sourceTree = "<group>"; };
545DBA0F21511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
546341A025C6415100CA0596 /* UseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCase.swift; sourceTree = "<group>"; };
546341AB25C6426500CA0596 /* CustomState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomState.swift; sourceTree = "<group>"; };
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54B51115216AFE5F0033A6F3 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
54CDC5D7215BBE23007D205C /* UIComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIComponents.swift; sourceTree = "<group>"; };
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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -96,6 +126,23 @@
/* 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 */,
);
path = ContentViewControllers;
sourceTree = "<group>";
};
545DB9E121511E6300CA77B8 = {
isa = PBXGroup;
children = (
@@ -124,9 +171,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;
@@ -150,6 +201,16 @@
path = UITests;
sourceTree = "<group>";
};
546341AA25C6421000CA0596 /* UseCases */ = {
isa = PBXGroup;
children = (
546341A025C6415100CA0596 /* UseCase.swift */,
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */,
54EAD364263A765F006A36EA /* PagePanelController.swift */,
);
path = UseCases;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -157,7 +218,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 545DBA1221511E6400CA77B8 /* Build configuration list for PBXNativeTarget "Samples" */;
buildPhases = (
54D7209621D4DB970054A255 /* ShellScript */,
545DB9E621511E6300CA77B8 /* Sources */,
545DB9E721511E6300CA77B8 /* Frameworks */,
545DB9E821511E6300CA77B8 /* Resources */,
@@ -215,7 +275,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
545DB9E921511E6300CA77B8 = {
@@ -278,35 +338,30 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
54D7209621D4DB970054A255 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $(git rev-parse --abbrev-ref HEAD)($(git rev-parse --short HEAD))\" \"$SRCROOT/$INFOPLIST_FILE\"\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
545DB9E621511E6300CA77B8 /* Sources */ = {
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 */,
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;
};
@@ -387,6 +442,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;
@@ -448,6 +504,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;
@@ -489,7 +546,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -508,7 +565,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
PRODUCT_BUNDLE_IDENTIFIER = example.Samples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -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,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,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<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>
@@ -28,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="600" height="600"/>
<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="812"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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="43.666667938232422"/>
<rect key="frame" x="0.0" y="28" width="600" 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.666667938232422"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="M0G-C8-hAO">
<rect key="frame" x="16" y="0.0" width="343" height="43.666667938232422"/>
<rect key="frame" x="16" y="0.0" width="568" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
@@ -86,23 +87,23 @@
<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.33333333333334"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="197.33000000000001"/>
<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.33333333333334"/>
<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="52.666666666666664"/>
<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.33333333333334" y="0.0" width="74.333333333333343" height="17.333333333333332"/>
<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.333333333333329" y="25.333333333333336" width="154.66666666666669" height="27.333333333333336"/>
<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"/>
@@ -110,19 +111,19 @@
</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="68.666666666666657" width="311" height="80.666666666666657"/>
<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.9999999999999982" width="254" height="20.333333333333329"/>
<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.66666666666665719" width="51" height="31"/>
<rect key="frame" x="262" y="0.5" width="51" height="31"/>
<connections>
<action selector="toggleLargeTitle:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="FJS-Ty-mCY"/>
</connections>
@@ -130,16 +131,16 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="ZtZ-Dz-4cC">
<rect key="frame" x="0.0" y="47.999999999999986" width="311" height="32.666666666666671"/>
<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.333333333333341" width="254" height="20.333333333333329"/>
<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="1" width="51" height="31"/>
<rect key="frame" x="262" y="0.5" width="51" height="31"/>
<connections>
<action selector="toggleTranslucent:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="nL4-3L-9hh"/>
</connections>
@@ -176,11 +177,11 @@
<objects>
<viewController id="RpE-lI-27a" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="JER-jz-KSq">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="RpE-lI-27a" eventType="touchUpInside" id="hj3-Xv-6Gq"/>
@@ -207,11 +208,11 @@
<objects>
<viewController id="pOk-Zm-vD9" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="85d-ub-G8k">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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="44" width="39" height="30"/>
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="pOk-Zm-vD9" eventType="touchUpInside" id="111-PD-Pop"/>
@@ -238,11 +239,11 @@
<objects>
<viewController id="lto-Zc-Vtp" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="ji9-Ez-N7i">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="YL4-GP-ZEZ"/>
@@ -273,7 +274,7 @@
<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="125.66666666666669" y="24" width="124" 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"/>
@@ -293,6 +294,71 @@
</objects>
<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">
<objects>
@@ -317,11 +383,11 @@
<objects>
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
<rect key="frame" x="0.0" y="0.0" width="375" height="724"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<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="724" width="375" height="0.0"/>
<rect key="frame" x="0.0" y="758" width="375" height="0.0"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
@@ -332,7 +398,7 @@
</connections>
</button>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="44" translatesAutoresizingMaskIntoConstraints="NO" id="9p4-06-y2T">
<rect key="frame" x="134.66666666666666" y="88" width="106" height="326"/>
<rect key="frame" x="134.5" y="88" width="106" height="326"/>
<subviews>
<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"/>
@@ -532,11 +598,11 @@
</constraints>
</view>
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="0.0" y="44" width="375" height="734"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<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="44" width="44" height="44"/>
<rect key="frame" x="319" y="0.0" width="44" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="0jg-5D-A1F"/>
<constraint firstAttribute="width" constant="44" id="1Cq-PA-wgW"/>
@@ -546,10 +612,10 @@
</connections>
</button>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="qux-uG-4o2">
<rect key="frame" x="8" y="52" width="148" height="31"/>
<rect key="frame" x="8" y="8" width="148" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="fitToBounds" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7lq-d3-PKi">
<rect key="frame" x="0.0" y="5.3333333333333357" width="91" height="20.333333333333332"/>
<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"/>
@@ -563,7 +629,7 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="tP3-oJ-4EB">
<rect key="frame" x="130.66666666666666" y="88" width="114" height="134"/>
<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"/>
@@ -717,12 +783,22 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
<point key="canvasLocation" x="-1" y="734"/>
</scene>
</scenes>
<designables>
<designable name="noi-1a-5bZ">
<size key="intrinsicContentSize" width="30" height="30"/>
</designable>
</designables>
<inferredMetricsTieBreakers>
<segue reference="r1P-2i-NDe"/>
</inferredMetricsTieBreakers>
<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,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,69 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class ImageViewController: UIViewController {
class PanelLayout: FloatingPanelLayout {
weak var targetGuide: UILayoutGuide?
init(targetGuide: UILayoutGuide?) {
self.targetGuide = targetGuide
}
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
if #available(iOS 11.0, *), let targetGuide = targetGuide {
return [
.full: FloatingPanelAdaptiveLayoutAnchor(absoluteOffset: 0,
contentLayout: targetGuide,
referenceGuide: .superview),
.half: FloatingPanelAdaptiveLayoutAnchor(fractionalOffset: 0.5,
contentLayout: targetGuide,
referenceGuide: .superview)
]
} else {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 500,
edge: .bottom,
referenceGuide: .superview)
]
}
}
}
@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,78 @@
// 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 {
var position: FloatingPanelPosition = .bottom
var initialState: FloatingPanelState { .half }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}
}
@@ -0,0 +1,67 @@
// 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
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.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,36 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
final class SettingsViewController: InspectableViewController {
@IBOutlet weak var largeTitlesSwicth: UISwitch!
@IBOutlet weak var translucentSwicth: 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
largeTitlesSwicth.setOn(prefersLargeTitles, animated: false)
} else {
largeTitlesSwicth.isEnabled = false
}
let isTranslucent = navigationController!.navigationBar.isTranslucent
translucentSwicth.setOn(isTranslucent, animated: false)
}
@IBAction func toggleLargeTitle(_ sender: UISwitch) {
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
}
}
@IBAction func toggleTranslucent(_ sender: UISwitch) {
navigationController?.navigationBar.isTranslucent = sender.isOn
}
}
@@ -0,0 +1,252 @@
// 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 {
var initialState: FloatingPanelState { .half }
var position: FloatingPanelPosition { .bottom }
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 261.0, edge: .bottom, referenceGuide: .safeArea)
]
}
}
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),
]
}
}
@@ -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,90 @@
// 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
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 88.0, edge: .bottom, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .top, referenceGuide: .safeArea)
]
}
}
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
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.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
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.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
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
]
}
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return 0.3
}
}
@@ -3,7 +3,10 @@
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()
@@ -17,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))
@@ -71,7 +71,7 @@ class CloseButton: UIButton {
}
@IBDesignable
class SafeAreaView: UIView {
final class SafeAreaView: UIView {
override func prepareForInterfaceBuilder() {
let label = UILabel()
label.text = "Safe Area"
@@ -86,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"
@@ -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,92 @@
// 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"
}
}
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: ImageViewController.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):
return vc
}
}
}
@@ -0,0 +1,384 @@
// 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()
let contentVC = UIViewController()
contentVC.view.backgroundColor = .red
fpc.set(contentViewController: contentVC)
addMain(panel: fpc)
case .showAdaptivePanel, .showAdaptivePanelWithCustomGuide:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
if case let contentVC as ImageViewController = contentVC {
if #available(iOS 11.0, *) {
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithCustomGuide) ? .withHeaderFooter : .onlyImage
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
fpc.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
} else {
fpc.layout = ImageViewController.PanelLayout(targetGuide: nil)
}
}
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)
default:
break
}
}
}
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@
/* Begin PBXBuildFile section */
545BA70621BA3214007F7846 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA70521BA3214007F7846 /* AppDelegate.m */; };
545BA70921BA3214007F7846 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA70821BA3214007F7846 /* ViewController.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 */; };
@@ -35,8 +35,8 @@
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 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
545BA70821BA3214007F7846 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.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>"; };
@@ -78,13 +78,13 @@
545BA70321BA3214007F7846 /* SamplesObjC */ = {
isa = PBXGroup;
children = (
545BA70421BA3214007F7846 /* AppDelegate.h */,
545BA70521BA3214007F7846 /* AppDelegate.m */,
545BA70721BA3214007F7846 /* ViewController.h */,
545BA70821BA3214007F7846 /* ViewController.m */,
545BA70A21BA3214007F7846 /* Main.storyboard */,
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 */,
@@ -119,7 +119,7 @@
545BA6F921BA3214007F7846 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1110;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "Shin Yamamoto";
TargetAttributes = {
545BA70021BA3214007F7846 = {
@@ -164,7 +164,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
545BA70921BA3214007F7846 /* ViewController.m in Sources */,
545BA70921BA3214007F7846 /* MainViewController.m in Sources */,
545BA71421BA3217007F7846 /* main.m in Sources */,
545BA70621BA3214007F7846 /* AppDelegate.m in Sources */,
);
@@ -218,6 +218,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;
@@ -277,6 +278,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;
@@ -318,7 +320,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.SamplesObjC;
PRODUCT_BUNDLE_IDENTIFIER = example.SamplesObjC;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "SamplesObjC/SamplesObjC-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -340,7 +342,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.SamplesObjC;
PRODUCT_BUNDLE_IDENTIFIER = example.SamplesObjC;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "SamplesObjC/SamplesObjC-Bridging-Header.h";
SWIFT_VERSION = 4.2;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1110"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="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="13104.12"/>
<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" customModuleProvider="" sceneMemberID="viewController">
<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="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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"/>
<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"/>
@@ -3,7 +3,7 @@
#import <UIKit/UIKit.h>
@import FloatingPanel;
@interface ViewController : UIViewController
@interface MainViewController : UIViewController
@end
@interface MyFloatingPanelLayout : NSObject <FloatingPanelLayout>
@@ -1,12 +1,28 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import "ViewController.h"
#import "MainViewController.h"
@import FloatingPanel;
@interface ViewController()<FloatingPanelControllerDelegate>
// Defining a custom FloatingPanelState
@interface FloatingPanelState(Extended)
+ (FloatingPanelState *)LastQuart;
@end
@implementation ViewController
@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];
@@ -20,7 +36,7 @@
[fpc setBehavior:[MyFloatingPanelBehavior new]];
[fpc setRemovalInteractionEnabled:NO];
[fpc addPanelToParent:self at:self.view.subviews.count animated:NO];
[fpc addPanelToParent:self at:self.view.subviews.count animated:NO completion:nil];
[fpc moveToState:FloatingPanelState.Tip animated:true completion:nil];
[self updateAppearance: fpc];
@@ -59,12 +75,15 @@
}
- (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],
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
FloatingPanelState.Tip: [[FloatingPanelLayoutAnchor alloc] initWithAbsoluteInset:44.0
edge:FloatingPanelReferenceEdgeBottom
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
};
}
@@ -8,7 +8,7 @@
/* Begin PBXBuildFile section */
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 */; };
@@ -33,7 +33,7 @@
/* Begin PBXFileReference section */
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>"; };
@@ -73,11 +73,11 @@
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;
@@ -111,7 +111,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
548DF94F21705BE00041922A = {
@@ -155,7 +155,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 +208,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 +270,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,7 +312,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -329,7 +331,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
PRODUCT_BUNDLE_IDENTIFIER = example.Stocks;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -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,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>
@@ -3,7 +3,7 @@
import UIKit
import FloatingPanel
class ViewController: UIViewController, FloatingPanelControllerDelegate {
class MainViewController: UIViewController, FloatingPanelControllerDelegate {
@IBOutlet var topBannerView: UIImageView!
@IBOutlet weak var labelStackView: UIStackView!
@IBOutlet weak var bottomToolView: UIView!
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "2.0.1"
s.version = "2.5.1"
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
s.description = <<-DESC
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
+20 -13
View File
@@ -9,12 +9,11 @@
/* 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 */; };
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, ); }; };
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DD215118C800CA77B8 /* UIExtensions.swift */; };
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 */; };
@@ -24,7 +23,7 @@
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */; };
5469F4B224B30F1100537F8A /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B124B30F1100537F8A /* Position.swift */; };
5469F4B424B30F3500537F8A /* LayoutReferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutReferences.swift */; };
549C371F2361E15E007D8058 /* UtilTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549C371E2361E15D007D8058 /* UtilTests.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 */; };
@@ -33,6 +32,8 @@
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 */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -56,7 +57,7 @@
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>"; };
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>"; };
@@ -64,7 +65,6 @@
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>"; };
545DB9DD215118C800CA77B8 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; 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>"; };
@@ -74,7 +74,7 @@
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAnchoring.swift; sourceTree = "<group>"; };
5469F4B124B30F1100537F8A /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = "<group>"; };
5469F4B324B30F3500537F8A /* LayoutReferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutReferences.swift; sourceTree = "<group>"; };
549C371E2361E15D007D8058 /* UtilTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilTests.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>"; };
@@ -83,6 +83,8 @@
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; };
/* End PBXFileReference section */
@@ -142,15 +144,16 @@
5469F4B324B30F3500537F8A /* LayoutReferences.swift */,
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */,
5450EEE321646DF500135936 /* Behavior.swift */,
54352E9721A521CA00CBCA08 /* PassThroughView.swift */,
54352E9721A521CA00CBCA08 /* PassthroughView.swift */,
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */,
54CDC5D4215B6D8D007D205C /* BackdropView.swift */,
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
545DB9DD215118C800CA77B8 /* UIExtensions.swift */,
54DBA3DB262E938500D75969 /* Extensions.swift */,
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
545DB9C52151169500CA77B8 /* Info.plist */,
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
);
path = Sources;
sourceTree = "<group>";
@@ -164,7 +167,7 @@
542753C522C49A6E00D17955 /* LayoutTests.swift */,
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */,
549E944422CF295D0050AECF /* StateTests.swift */,
549C371E2361E15D007D8058 /* UtilTests.swift */,
549C371E2361E15D007D8058 /* ExtensionTests.swift */,
542753C722C49A8F00D17955 /* TestSupports.swift */,
545DB9D12151169500CA77B8 /* Info.plist */,
);
@@ -256,7 +259,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
545DB9C02151169500CA77B8 = {
@@ -328,14 +331,15 @@
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */,
5469F4B424B30F3500537F8A /* LayoutReferences.swift in Sources */,
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */,
54352E9821A521CA00CBCA08 /* PassThroughView.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 */,
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */,
5469F4AE24B30D7E00537F8A /* State.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -345,7 +349,7 @@
buildActionMask = 2147483647;
files = (
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */,
549C371F2361E15E007D8058 /* UtilTests.swift in Sources */,
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */,
545DB9D02151169500CA77B8 /* ControllerTests.swift in Sources */,
549E944522CF295D0050AECF /* StateTests.swift in Sources */,
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */,
@@ -404,6 +408,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;
@@ -469,6 +474,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;
@@ -666,6 +672,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;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -27,6 +27,15 @@
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"
@@ -41,17 +50,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545DB9C02151169500CA77B8"
BuildableName = "FloatingPanel.framework"
BlueprintName = "FloatingPanel"
ReferencedContainer = "container:FloatingPanel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -72,8 +70,6 @@
ReferencedContainer = "container:FloatingPanel.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
+3
View File
@@ -10,6 +10,9 @@
<Group
location = "group:Examples"
name = "Examples">
<FileRef
location = "group:Maps-SwiftUI/Maps-SwiftUI.xcodeproj">
</FileRef>
<FileRef
location = "group:Maps/Maps.xcodeproj">
</FileRef>
+59 -4
View File
@@ -9,6 +9,8 @@
FloatingPanel is a simple and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
The new interface displays the related contents and utilities in parallel as a user wants.
📝[Here](https://docs.scenee.com/documentation/floatingpanel) is the API references for the latest version powered by [DocC](https://developer.apple.com/documentation/docc).
![Maps](https://github.com/SCENEE/FloatingPanel/blob/master/assets/maps.gif)
![Stocks](https://github.com/SCENEE/FloatingPanel/blob/master/assets/stocks.gif)
@@ -35,6 +37,8 @@ The new interface displays the related contents and utilities in parallel as a u
- [Support your landscape layout](#support-your-landscape-layout)
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
- [Change the backdrop alpha](#change-the-backdrop-alpha)
- [Using custome panel states](#using-custome-panel-states)
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
@@ -53,6 +57,7 @@ The new interface displays the related contents and utilities in parallel as a u
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
- [Move a position with an animation](#move-a-position-with-an-animation)
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
- [Enabling the tap-to-dismiss action of the backdrop view](#enabling-the-tap-to-dismiss-action-of-the-backdrop-view)
- [Notes](#notes)
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
- [UISearchController issue](#uisearchcontroller-issue)
@@ -71,7 +76,7 @@ The new interface displays the related contents and utilities in parallel as a u
- [x] Multi panel support
- [x] Modal presentation
- [x] 4 positioning support(top, left, bottom, right)
- [x] 1~3 magnetic anchors(full, half, tip)
- [x] 1 or more magnetic anchors(full, half, tip and more)
- [x] Layout support for all trait environments(i.e. Landscape orientation)
- [x] Common UI elements: surface, backdrop and grabber handle
- [x] Free from common issues of Auto Layout and gesture handling
@@ -163,7 +168,7 @@ self.present(fpc, animated: true, completion: nil)
You can show a floating panel over UINavigationController from the container view controllers as a modality of `.overCurrentContext` style.
:pencil2: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Framework/Sources/FloatingPanelTransitioning.swift).
:pencil2: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [Transitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Sources/Transitioning.swift).
## View hierarchy
@@ -285,6 +290,8 @@ fpc.layout = MyPanelLayout()
fpc.invalidateLayout() // If needed
```
Note: If you already set the `delegate` property of your `FloatingPanelController` instance, `invalidateLayout()` overrides the layout object of `FloatingPanelController` with one returned by the delegate object.
2. Returns an appropriate layout object in one of 2 `floatingPanel(_:layoutFor:)` delegates.
```swift
@@ -370,6 +377,46 @@ class MyFullScreenLayout: FloatingPanelLayout {
:pencil2: `FloatingPanelFullScreenLayout` is deprecated on v1.
#### Change the backdrop alpha
You can change the backdrop alpha by `FloatingPanelLayout.backdropAlpha(for:)` for each state(`.full`, `.half` and `.tip`).
For instance, if a panel seems like the backdrop view isn't there on `.half` state, it's time to implement the backdropAlpha API and return a value for the state as below.
```swift
class MyPanelLayout: FloatingPanelLayout {
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
switch state {
case .full, .half: return 0.3
default: return 0.0
}
}
}
```
#### Using custome panel states
You're able to define custom panel states and use them as the following example.
```swift
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),
]
}
}
```
### Customize the behavior with `FloatingPanelBehavior` protocol
#### Modify your floating panel's interaction
@@ -522,8 +569,8 @@ override func viewDidLoad() {
surfaceTapGesture.isEnabled = (fpc.position == .tip)
}
// Enable `surfaceTapGesture` only at `tip` position
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
// Enable `surfaceTapGesture` only at `tip` state
func floatingPanelDidChangeState(_ vc: FloatingPanelController) {
surfaceTapGesture.isEnabled = (vc.position == .tip)
}
```
@@ -616,6 +663,14 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
}
```
### Enabling the tap-to-dismiss action of the backdrop view
The tap-to-dismiss action is disabled by default. So it needs to be enabled as below.
```swift
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
```
## Notes
### 'Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller
+1 -1
View File
@@ -7,5 +7,5 @@ import UIKit
public class BackdropView: UIView {
/// The gesture recognizer for tap gestures to dismiss a panel.
public var dismissalTapGestureRecognizer: UITapGestureRecognizer!
@objc public var dismissalTapGestureRecognizer: UITapGestureRecognizer!
}
+20 -19
View File
@@ -43,44 +43,41 @@ public protocol FloatingPanelBehavior {
/// This method allows a panel to activate the rubber band effect to a given edge of the surface view. By default, the effect is disabled.
@objc optional
func allowsRubberBanding(for edge: UIRectEdge) -> Bool
/// Returns the velocity threshold for the default interactive removal gesture.
///
/// In case ``FloatingPanel/FloatingPanelControllerDelegate/floatingPanel(_:shouldRemoveAt:with:)`` is implemented, this value will not be used. The default value of ``FloatingPanelDefaultBehavior`` is 5.5
@objc optional
var removalInteractionVelocityThreshold: CGFloat { get }
}
/// The default behavior object for a panel
///
/// This behavior object is fine-tuned to behave as a search panel(card) in Apple Maps on iPhone portrait orientation.
public class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
public var springDecelerationRate: CGFloat {
open class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
public init() {}
open var springDecelerationRate: CGFloat {
return UIScrollView.DecelerationRate.fast.rawValue + 0.001
}
public var springResponseTime: CGFloat {
open var springResponseTime: CGFloat {
return 0.4
}
public var momentumProjectionRate: CGFloat {
open var momentumProjectionRate: CGFloat {
return UIScrollView.DecelerationRate.normal.rawValue
}
public func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelState, to: FloatingPanelState) -> CGFloat {
open func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelState, to: FloatingPanelState) -> CGFloat {
return 0.5
}
func addPanelAnimator(_ fpc: FloatingPanelController, to: FloatingPanelState) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.0,
timingParameters: UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
frequencyResponse: 0.25))
}
func removePanelAnimator(_ fpc: FloatingPanelController, from: FloatingPanelState, with velocity: CGVector) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.0,
timingParameters: UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
frequencyResponse: 0.25,
initialVelocity: velocity))
}
public func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
open func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
return false
}
open var removalInteractionVelocityThreshold: CGFloat = 5.5
}
class BehaviorAdapter {
@@ -103,6 +100,10 @@ class BehaviorAdapter {
var momentumProjectionRate: CGFloat {
behavior.momentumProjectionRate ?? FloatingPanelDefaultBehavior().momentumProjectionRate
}
var removalInteractionVelocityThreshold: CGFloat {
behavior.removalInteractionVelocityThreshold ?? FloatingPanelDefaultBehavior().removalInteractionVelocityThreshold
}
func redirectionalProgress(from: FloatingPanelState, to: FloatingPanelState) -> CGFloat {
behavior.redirectionalProgress?(vc, from: from, to: to) ?? FloatingPanelDefaultBehavior().redirectionalProgress(vc,from: from, to: to)
+67 -38
View File
@@ -5,7 +5,7 @@ import UIKit
/// A set of methods implemented by the delegate of a panel controller allows the adopting delegate to respond to
/// messages from the FloatingPanelController class and thus respond to, and in some affect, operations such as
/// dragging, attracting a panel, layout of a panel and the content, and transition animations.
@objc public protocol FloatingPanelControllerDelegate: class {
@objc public protocol FloatingPanelControllerDelegate {
/// Returns a FloatingPanelLayout object. If you use the default one, you can use a `FloatingPanelBottomLayout` object.
@objc(floatingPanel:layoutForTraitCollection:) optional
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout
@@ -26,10 +26,12 @@ import UIKit
@objc(floatingPanel:animatorForDismissingWithVelocity:) optional
func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator
/// Called when a panel has changed to a new position. Can be called inside an animation block, so any
/// view properties set inside this function will be automatically animated alongside a panel.
/// Called when a panel has changed to a new state.
///
/// This can be called inside an animation block for presenting, dismissing a panel or moving a panel with your
/// animation. So any view properties set inside this function will be automatically animated alongside a panel.
@objc optional
func floatingPanelDidChangePosition(_ fpc: FloatingPanelController)
func floatingPanelDidChangeState(_ fpc: FloatingPanelController)
/// Asks the delegate if dragging should begin by the pan gesture recognizer.
@objc optional
@@ -63,7 +65,7 @@ import UIKit
/// Asks the delegate whether a panel should be removed when dragging ended at the specified location
///
/// This delegate method is called only where `FloatingPanelController.isRemovalInteractionEnabled` is `true`.
/// This delegate method is called only where ``FloatingPanel/FloatingPanelController/isRemovalInteractionEnabled`` is `true`.
/// The velocity vector is calculated from the distance to a point of the hidden state and the pan gesture's velocity.
@objc(floatingPanel:shouldRemoveAtLocation:withVelocity:)
optional
@@ -155,7 +157,10 @@ open class FloatingPanelController: UIViewController {
return floatingPanel.isAttracting
}
/// The layout object managed by the controller
/// The layout object that the controller manages
///
/// You need to call ``invalidateLayout()`` if you want to apply a new layout object into the panel
/// immediately.
@objc
public var layout: FloatingPanelLayout {
get { _layout }
@@ -167,7 +172,7 @@ open class FloatingPanelController: UIViewController {
}
}
/// The behavior object managed by the controller
/// The behavior object that the controller manages
@objc
public var behavior: FloatingPanelBehavior {
get { _behavior }
@@ -187,7 +192,7 @@ open class FloatingPanelController: UIViewController {
/// The behavior for determining the adjusted content offsets.
///
/// This property specifies how the content area of the tracking scroll view is modified using `adjustedContentInsets`. The default value of this property is FloatingPanelController.ContentInsetAdjustmentBehavior.always.
/// This property specifies how the content area of the tracking scroll view is modified using ``adjustedContentInsets``. The default value of this property is FloatingPanelController.ContentInsetAdjustmentBehavior.always.
@objc
public var contentInsetAdjustmentBehavior: ContentInsetAdjustmentBehavior = .always
@@ -274,7 +279,7 @@ open class FloatingPanelController: UIViewController {
open override func loadView() {
assert(self.storyboard == nil, "Storyboard isn't supported")
let view = PassThroughView()
let view = PassthroughView()
view.backgroundColor = .clear
backdropView.frame = view.bounds
@@ -396,20 +401,8 @@ open class FloatingPanelController: UIViewController {
}
private func activateLayout(forceLayout: Bool = false) {
floatingPanel.layoutAdapter.prepareLayout()
// preserve the current content offset if contentInsetAdjustmentBehavior is `.always`
var contentOffset: CGPoint?
if contentInsetAdjustmentBehavior == .always {
contentOffset = trackingScrollView?.contentOffset
}
floatingPanel.layoutAdapter.updateStaticConstraint()
floatingPanel.layoutAdapter.activateLayout(for: floatingPanel.state, forceLayout: forceLayout)
if let contentOffset = contentOffset {
trackingScrollView?.contentOffset = contentOffset
}
floatingPanel.activateLayout(forceLayout: forceLayout,
contentInsetAdjustmentBehavior: contentInsetAdjustmentBehavior)
}
func remove() {
@@ -427,6 +420,9 @@ open class FloatingPanelController: UIViewController {
// MARK: - Container view controller interface
/// Shows the surface view at the initial position defined by the current layout
/// - Parameters:
/// - animated: Pass true to animate the presentation; otherwise, pass false.
/// - completion: The block to execute after the presentation finishes. This block has no return value and takes no parameters. You may specify nil for this parameter.
@objc(show:completion:)
public func show(animated: Bool = false, completion: (() -> Void)? = nil) {
// Must apply the current layout here
@@ -468,8 +464,9 @@ open class FloatingPanelController: UIViewController {
/// - parent: A parent view controller object that displays FloatingPanelController's view. A container view controller object isn't applicable.
/// - viewIndex: Insert the surface view managed by the controller below the specified view index. 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.
@objc(addPanelToParent:at:animated:)
public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false) {
/// - completion: The block to execute after the presentation finishes. This block has no return value and takes no parameters. You may specify nil for this parameter.
@objc(addPanelToParent:at:animated:completion:)
public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false, completion: (() -> Void)? = nil) {
guard self.parent == nil else {
log.warning("Already added to a parent(\(parent))")
return
@@ -498,8 +495,9 @@ open class FloatingPanelController: UIViewController {
])
show(animated: animated) { [weak self] in
guard let `self` = self else { return }
guard let self = self else { return }
self.didMove(toParent: parent)
completion?()
}
}
@@ -517,7 +515,7 @@ open class FloatingPanelController: UIViewController {
delegate?.floatingPanelWillRemove?(self)
hide(animated: animated) { [weak self] in
guard let `self` = self else { return }
guard let self = self else { return }
self.willMove(toParent: nil)
@@ -531,13 +529,13 @@ open class FloatingPanelController: UIViewController {
}
/// 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.
@objc(moveToState:animated:completion:)
public func move(to: FloatingPanelState, animated: Bool, completion: (() -> Void)? = nil) {
assert(floatingPanel.layoutAdapter.vc != nil, "Use show(animated:completion)")
floatingPanel.move(to: to, animated: animated, completion: completion)
}
@@ -553,7 +551,7 @@ open class FloatingPanelController: UIViewController {
addChild(vc)
let surfaceView = floatingPanel.surfaceView
surfaceView.set(contentView: vc.view)
surfaceView.set(contentView: vc.view, mode: contentMode)
vc.didMove(toParent: self)
}
@@ -598,17 +596,29 @@ open class FloatingPanelController: UIViewController {
}
}
// MARK: - Accessibility
open override func accessibilityPerformEscape() -> Bool {
guard isRemovalInteractionEnabled else { return false }
dismiss(animated: true, completion: nil)
return true
}
// MARK: - Utilities
/// Updates the layout object from the delegate and lays out the views managed
/// by the controller immediately.
/// Invalidates all layout information of the panel and apply the ``layout`` property into it 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 layout immediately. It can be called in an
/// animation block.
/// This lays out subviews of the view that the controller manages with the ``layout`` property by
/// calling the view's `layoutIfNeeded()`. Thus this method can be called in an animation block to
/// animate the panel's changes.
///
/// If the controller has a delegate object, this will lay them out using the layout object returned by
/// `floatingPanel(_:layoutFor:)` delegate method for the current `UITraitCollection`.
@objc
public func invalidateLayout() {
if let newLayout = self.delegate?.floatingPanel?(self, layoutFor: traitCollection) {
layout = newLayout
}
activateLayout(forceLayout: true)
}
@@ -641,10 +651,31 @@ extension FloatingPanelController {
#endif
delegate?.floatingPanelDidMove?(self)
}
func animatorForPresenting(to: FloatingPanelState) -> UIViewPropertyAnimator {
if let animator = delegate?.floatingPanel?(self, animatorForPresentingTo: to) {
return animator
}
let timingParameters = UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
frequencyResponse: 0.25)
return UIViewPropertyAnimator(duration: 0.0,
timingParameters: timingParameters)
}
func animatorForDismissing(with velocity: CGVector) -> UIViewPropertyAnimator {
if let animator = delegate?.floatingPanel?(self, animatorForDismissingWith: velocity) {
return animator
}
let timingParameters = UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
frequencyResponse: 0.25,
initialVelocity: velocity)
return UIViewPropertyAnimator(duration: 0.0,
timingParameters: timingParameters)
}
}
extension FloatingPanelController {
private static let dismissSwizzling: Any? = {
private static let dismissSwizzling: Void = {
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:))) {
@@ -653,10 +684,8 @@ extension FloatingPanelController {
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
}()
}
+116 -58
View File
@@ -24,7 +24,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
didSet {
log.debug("state changed: \(oldValue) -> \(state)")
if let vc = ownerVC {
vc.delegate?.floatingPanelDidChangePosition?(vc)
vc.delegate?.floatingPanelDidChangeState?(vc)
}
}
}
@@ -32,7 +32,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
var isRemovalInteractionEnabled: Bool = false
fileprivate var animator: UIViewPropertyAnimator?
fileprivate var isSuspended: Bool = false // Prevent a memory leak in the modal transition
fileprivate var transitionAnimator: UIViewPropertyAnimator?
fileprivate var moveAnimator: NumericSpringAnimator?
private var initialSurfaceLocation: CGPoint = .zero
@@ -68,11 +69,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
backdropView.backgroundColor = .black
backdropView.alpha = 0.0
self.layoutAdapter = LayoutAdapter(vc: vc,
surfaceView: surfaceView,
backdropView: backdropView,
layout: layout)
self.behaviorAdapter = BehaviorAdapter(vc: vc, behavior: behavior)
layoutAdapter = LayoutAdapter(vc: vc, layout: layout)
behaviorAdapter = BehaviorAdapter(vc: vc, behavior: behavior)
panGestureRecognizer = FloatingPanelPanGestureRecognizer()
@@ -94,6 +92,11 @@ class Core: NSObject, UIGestureRecognizerDelegate {
backdropView.addGestureRecognizer(tapGesture)
}
deinit {
// Release `NumericSpringAnimator.displayLink` from the run loop.
self.moveAnimator?.stopAnimation(false)
}
func move(to: FloatingPanelState, animated: Bool, completion: (() -> Void)? = nil) {
move(from: state, to: to, animated: animated, completion: completion)
}
@@ -104,7 +107,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
completion?()
return
}
if state != layoutAdapter.edgeMostState {
if state != layoutAdapter.mostExpandedState {
lockScrollView()
}
tearDownActiveInteraction()
@@ -112,8 +115,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
interruptAnimationIfNeeded()
if animated {
func updateScrollView() {
if self.state == self.layoutAdapter.edgeMostState, abs(self.layoutAdapter.offsetFromEdgeMost) <= 1.0 {
let updateScrollView: () -> Void = { [weak self] in
guard let self = self else { return }
if self.state == self.layoutAdapter.mostExpandedState, abs(self.layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
self.unlockScrollView()
} else {
self.lockScrollView()
@@ -123,14 +127,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
let animator: UIViewPropertyAnimator
switch (from, to) {
case (.hidden, let to):
animator = vc.delegate?.floatingPanel?(vc, animatorForPresentingTo: to)
?? FloatingPanelDefaultBehavior().addPanelAnimator(vc, to: to)
case (let from, .hidden):
animator = vc.animatorForPresenting(to: to)
case (_, .hidden):
let animationVector = CGVector(dx: abs(removalVector.dx), dy: abs(removalVector.dy))
animator = vc.delegate?.floatingPanel?(vc, animatorForDismissingWith: .zero)
?? FloatingPanelDefaultBehavior().removePanelAnimator(vc, from: from, with: animationVector)
animator = vc.animatorForDismissing(with: animationVector)
default:
move(to: to, with: 0) {
move(to: to, with: 0) { [weak self] in
guard let self = self else { return }
self.moveAnimator = nil
updateScrollView()
completion?()
@@ -143,7 +147,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
&& layoutAdapter.isIntrinsicAnchor(state: to)
animator.addAnimations { [weak self] in
guard let `self` = self else { return }
guard let self = self else { return }
self.state = to
self.updateLayout(to: to)
@@ -154,18 +158,22 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
}
animator.addCompletion { [weak self] _ in
guard let `self` = self else { return }
self.animator = nil
guard let self = self else { return }
self.transitionAnimator = nil
updateScrollView()
self.ownerVC?.notifyDidMove()
completion?()
}
self.animator = animator
self.transitionAnimator = animator
if isSuspended {
return
}
animator.startAnimation()
} else {
self.state = to
self.updateLayout(to: to)
if self.state == self.layoutAdapter.edgeMostState {
if self.state == self.layoutAdapter.mostExpandedState {
self.unlockScrollView()
} else {
self.lockScrollView()
@@ -178,8 +186,37 @@ class Core: NSObject, UIGestureRecognizerDelegate {
// MARK: - Layout update
func activateLayout(forceLayout: Bool = false,
contentInsetAdjustmentBehavior: FloatingPanelController.ContentInsetAdjustmentBehavior) {
layoutAdapter.prepareLayout()
// preserve the current content offset if contentInsetAdjustmentBehavior is `.always`
var contentOffset: CGPoint?
if contentInsetAdjustmentBehavior == .always {
contentOffset = scrollView?.contentOffset
}
layoutAdapter.updateStaticConstraint()
layoutAdapter.activateLayout(for: state, forceLayout: true)
// Update the backdrop alpha only when called in `Controller.show(animated:completion:)`
// Because that prevents a backdrop flicking just before presenting a panel(#466).
if forceLayout {
backdropView.alpha = getBackdropAlpha(for: state)
}
if let contentOffset = contentOffset {
scrollView?.contentOffset = contentOffset
}
}
private func updateLayout(to target: FloatingPanelState) {
self.layoutAdapter.activateLayout(for: state, forceLayout: true)
self.layoutAdapter.activateLayout(for: target, forceLayout: true)
self.backdropView.alpha = self.getBackdropAlpha(for: target)
}
private func getBackdropAlpha(for target: FloatingPanelState) -> CGFloat {
return target == .hidden ? 0.0 : layoutAdapter.backdropAlpha(for: target)
}
func getBackdropAlpha(at cur: CGFloat, with translation: CGFloat) -> CGFloat {
@@ -188,8 +225,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
let segment = layoutAdapter.segment(at: cur, forward: forwardY)
let lowerState = segment.lower ?? layoutAdapter.edgeMostState
let upperState = segment.upper ?? layoutAdapter.edgeLeastState
let lowerState = segment.lower ?? layoutAdapter.mostExpandedState
let upperState = segment.upper ?? layoutAdapter.leastExpandedState
let preState = forwardY ? lowerState : upperState
let nextState = forwardY ? upperState : lowerState
@@ -202,9 +239,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if pre == next {
return preAlpha
} else {
return preAlpha + max(min(1.0, 1.0 - (next - cur) / (next - pre) ), 0.0) * (nextAlpha - preAlpha)
}
return preAlpha + max(min(1.0, 1.0 - (next - cur) / (next - pre) ), 0.0) * (nextAlpha - preAlpha)
}
// MARK: - UIGestureRecognizerDelegate
@@ -341,7 +377,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
let velocity = value(of: panGesture.velocity(in: panGesture.view))
let location = panGesture.location(in: surfaceView)
let belowEdgeMost = 0 > layoutAdapter.offsetFromEdgeMost + (1.0 / surfaceView.fp_displayScale)
let belowEdgeMost = 0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)
log.debug("""
scroll gesture(\(state):\(panGesture.state)) -- \
@@ -355,7 +391,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if belowEdgeMost {
// Scroll offset pinning
if state == layoutAdapter.edgeMostState {
if state == layoutAdapter.mostExpandedState {
if interactionInProgress {
log.debug("settle offset --", value(of: initialScrollOffset))
stopScrolling(at: initialScrollOffset)
@@ -373,7 +409,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if interactionInProgress {
lockScrollView()
} else {
if state == layoutAdapter.edgeMostState, self.animator == nil {
if state == layoutAdapter.mostExpandedState, self.transitionAnimator == nil {
switch layoutAdapter.position {
case .top, .left:
if offsetDiff < 0 && velocity > 0 {
@@ -401,14 +437,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
return
}
}
if state == layoutAdapter.edgeMostState {
if state == layoutAdapter.mostExpandedState {
// Adjust a small gap of the scroll offset just after swiping down starts in the grabber area.
if grabberAreaFrame.contains(location), grabberAreaFrame.contains(initialLocation) {
stopScrolling(at: initialScrollOffset)
}
}
} else {
if state == layoutAdapter.edgeMostState {
if state == layoutAdapter.mostExpandedState {
switch layoutAdapter.position {
case .top, .left:
if velocity < 0, !allowScrollPanGesture(for: scrollView) {
@@ -493,16 +529,16 @@ class Core: NSObject, UIGestureRecognizerDelegate {
animator.stopAnimation(true)
endAttraction(false)
}
if let animator = self.animator {
guard 0 >= layoutAdapter.offsetFromEdgeMost else { return }
if let animator = self.transitionAnimator {
guard 0 >= layoutAdapter.offsetFromMostExpandedAnchor else { return }
log.debug("a panel animation(interruptible: \(animator.isInterruptible)) interrupted!!!")
if animator.isInterruptible {
animator.stopAnimation(false)
// A user can stop a panel at the nearest Y of a target position so this fine-tunes
// the a small gap between the presentation layer frame and model layer frame
// to unlock scroll view properly at finishAnimation(at:)
if abs(layoutAdapter.offsetFromEdgeMost) <= 1.0 {
layoutAdapter.surfaceLocation = layoutAdapter.surfaceLocation(for: layoutAdapter.edgeMostState)
if abs(layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
layoutAdapter.surfaceLocation = layoutAdapter.surfaceLocation(for: layoutAdapter.mostExpandedState)
}
animator.finishAnimation(at: .current)
} else {
@@ -528,22 +564,27 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
guard
state == layoutAdapter.edgeMostState, // When not top most(i.e. .full), don't scroll.
state == layoutAdapter.mostExpandedState, // When not top most(i.e. .full), don't scroll.
interactionInProgress == false, // When interaction already in progress, don't scroll.
0 == layoutAdapter.offsetFromEdgeMost
0 == layoutAdapter.offsetFromMostExpandedAnchor
else {
return false
}
// When the current and initial point within grabber area, do scroll.
// When the current point is within grabber area but the initial point is not, do scroll.
if grabberAreaFrame.contains(point), !grabberAreaFrame.contains(initialLocation) {
return true
}
// When the initial point is within grabber area and the current point is out of surface, don't scroll.
if grabberAreaFrame.contains(initialLocation), !surfaceView.frame.contains(point) {
return false
}
let scrollViewFrame = scrollView.convert(scrollView.bounds, to: surfaceView)
guard
scrollViewFrame.contains(initialLocation), // When initialLocation not in scrollView, don't scroll.
!grabberAreaFrame.contains(point) // When point within grabber area, don't scroll.
scrollViewFrame.contains(initialLocation), // When the initial point not in scrollView, don't scroll.
!grabberAreaFrame.contains(point) // When point within grabber area, don't scroll.
else {
return false
}
@@ -571,6 +612,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if scrollView.isDecelerating {
return true
}
if let tableView = (scrollView as? UITableView), tableView.isEditing {
return true
}
return false
}
@@ -582,7 +626,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
log.debug("panningBegan -- location = \(value(of: location))")
guard let scrollView = scrollView else { return }
if state == layoutAdapter.edgeMostState {
if state == layoutAdapter.mostExpandedState {
if grabberAreaFrame.contains(location) {
initialScrollOffset = scrollView.contentOffset
}
@@ -649,10 +693,11 @@ class Core: NSObject, UIGestureRecognizerDelegate {
return
}
stopScrollDeceleration = (0 > layoutAdapter.offsetFromEdgeMost + (1.0 / surfaceView.fp_displayScale)) // Projecting the dragging to the scroll dragging or not
stopScrollDeceleration = (0 > layoutAdapter.offsetFromMostExpandedAnchor + (1.0 / surfaceView.fp_displayScale)) // Projecting the dragging to the scroll dragging or not
if stopScrollDeceleration {
DispatchQueue.main.async { [weak self] in
guard let `self` = self else { return }
guard let self = self else { return }
self.stopScrolling(at: self.initialScrollOffset)
}
}
@@ -698,14 +743,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
// Workaround: Disable a tracking scroll to prevent bouncing a scroll content in a panel animating
let isScrollEnabled = scrollView?.isScrollEnabled
if let scrollView = scrollView, targetPosition != .full {
if let scrollView = scrollView, targetPosition != layoutAdapter.mostExpandedState {
scrollView.isScrollEnabled = false
}
startAttraction(to: targetPosition, with: velocity)
// Workaround: Reset `self.scrollView.isScrollEnabled`
if let scrollView = scrollView, targetPosition != .full,
if let scrollView = scrollView, targetPosition != layoutAdapter.mostExpandedState,
let isScrollEnabled = isScrollEnabled {
scrollView.isScrollEnabled = isScrollEnabled
}
@@ -718,7 +763,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if let result = vc.delegate?.floatingPanel?(vc, shouldRemoveAt: vc.surfaceLocation, with: velocityVector) {
return result
}
let threshold = CGFloat(5.5)
let threshold = behaviorAdapter.removalInteractionVelocityThreshold
switch layoutAdapter.position {
case .top:
return (velocityVector.dy <= -threshold)
@@ -739,7 +784,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
var offset: CGPoint = .zero
initialSurfaceLocation = layoutAdapter.surfaceLocation
if state == layoutAdapter.edgeMostState, let scrollView = scrollView {
if state == layoutAdapter.mostExpandedState, let scrollView = scrollView {
if grabberAreaFrame.contains(location) {
initialScrollOffset = scrollView.contentOffset
} else {
@@ -784,7 +829,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
interactionInProgress = false
// Prevent to keep a scroll view indicator visible at the half/tip position
if targetPosition != layoutAdapter.edgeMostState {
if targetPosition != layoutAdapter.mostExpandedState {
lockScrollView()
}
@@ -792,6 +837,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
private func tearDownActiveInteraction() {
guard panGestureRecognizer.isEnabled else { return }
// Cancel the pan gesture so that panningEnd(with:velocity:) is called
panGestureRecognizer.isEnabled = false
panGestureRecognizer.isEnabled = true
@@ -825,16 +871,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
decelerationRate: behaviorAdapter.springDecelerationRate,
responseTime: behaviorAdapter.springResponseTime,
update: { [weak self] data in
guard let self = self else { return }
guard let self = self,
let ownerVC = self.ownerVC // Ensure the owner vc is existing for `layoutAdapter.surfaceLocation`
else { return }
animationConstraint.constant = data.value
let current = self.value(of: self.layoutAdapter.surfaceLocation)
let translation = data.value - initialData.value
self.backdropView.alpha = self.getBackdropAlpha(at: current, with: translation)
self.ownerVC?.notifyDidMove()
ownerVC.notifyDidMove()
},
completion: { [weak self] in
guard let self = self else { return }
self.layoutAdapter.activateLayout(for: targetPosition, forceLayout: true)
guard let self = self,
self.ownerVC != nil else { return }
self.updateLayout(to: targetPosition)
completion()
})
moveAnimator?.startAnimation()
@@ -858,9 +907,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
log.debug("""
finishAnimation -- state = \(state) \
surface location = \(layoutAdapter.surfaceLocation) \
edge most position = \(layoutAdapter.surfaceLocation(for: layoutAdapter.edgeMostState))
edge most position = \(layoutAdapter.surfaceLocation(for: layoutAdapter.mostExpandedState))
""")
if finished, state == layoutAdapter.edgeMostState, abs(layoutAdapter.offsetFromEdgeMost) <= 1.0 {
if finished, state == layoutAdapter.mostExpandedState, abs(layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
unlockScrollView()
}
}
@@ -887,7 +936,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
func targetPosition(from currentY: CGFloat, with velocity: CGFloat) -> (FloatingPanelState) {
log.debug("targetPosition -- currentY = \(currentY), velocity = \(velocity)")
let sortedPositions = layoutAdapter.sortedDirectionalStates
let sortedPositions = layoutAdapter.sortedAnchorStatesByCoordinate
guard sortedPositions.count > 1 else {
return state
@@ -895,7 +944,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
// Projection
let decelerationRate = behaviorAdapter.momentumProjectionRate
let baseY = abs(layoutAdapter.position(for: layoutAdapter.edgeLeastState) - layoutAdapter.position(for: layoutAdapter.edgeMostState))
let baseY = abs(layoutAdapter.position(for: layoutAdapter.leastExpandedState) - layoutAdapter.position(for: layoutAdapter.mostExpandedState))
let vecY = velocity / baseY
var pY = project(initialVelocity: vecY, decelerationRate: decelerationRate) * baseY + currentY
@@ -991,7 +1040,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
private func allowScrollPanGesture(for scrollView: UIScrollView) -> Bool {
guard state == layoutAdapter.edgeMostState else { return false }
guard state == layoutAdapter.mostExpandedState else { return false }
var offsetY: CGFloat = 0
switch layoutAdapter.position {
case .top, .left:
@@ -1023,7 +1072,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
initialLocation = touches.first?.location(in: view) ?? .zero
if floatingPanel?.animator != nil || floatingPanel?.moveAnimator != nil {
if floatingPanel?.transitionAnimator != nil || floatingPanel?.moveAnimator != nil {
self.state = .began
}
}
@@ -1184,3 +1233,12 @@ private class NumericSpringAnimator: NSObject {
v = (v + h * o2 * (xt - x)) / det
}
}
extension FloatingPanelController {
func suspendTransitionAnimator(_ suspended: Bool) {
self.floatingPanel.isSuspended = suspended
}
var transitionAnimator: UIViewPropertyAnimator? {
return self.floatingPanel.transitionAnimator
}
}
@@ -2,16 +2,38 @@
import UIKit
internal func displayTrunc(_ v: CGFloat, by s: CGFloat) -> CGFloat {
let base = (1 / s)
let t = v.rounded(.down)
return t + ((v - t) / base).rounded(.toNearestOrAwayFromZero) * base
// MARK: - CoreGraphics
extension CGFloat {
/// Returns this value rounded to an logical pixel value by a display scale
func rounded(by displayScale: CGFloat) -> CGFloat {
return (self * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
}
func isEqual(to: CGFloat, on displayScale: CGFloat) -> Bool {
return self.rounded(by: displayScale) == to.rounded(by: displayScale)
}
}
internal func displayEqual(_ lhs: CGFloat, _ rhs: CGFloat, by displayScale: CGFloat) -> Bool {
return displayTrunc(lhs, by: displayScale) == displayTrunc(rhs, by: displayScale)
extension CGPoint {
static var leastNonzeroMagnitude: CGPoint {
return CGPoint(x: CGFloat.leastNonzeroMagnitude, y: CGFloat.leastNonzeroMagnitude)
}
static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
static prefix func - (point: CGPoint) -> CGPoint {
return CGPoint(x: -point.x, y: -point.y)
}
}
// MARK: - UIKit
protocol LayoutGuideProvider {
var topAnchor: NSLayoutYAxisAnchor { get }
var leftAnchor: NSLayoutXAxisAnchor { get }
@@ -164,24 +186,6 @@ extension UISpringTimingParameters {
}
}
extension CGPoint {
static var leastNonzeroMagnitude: CGPoint {
return CGPoint(x: CGFloat.leastNonzeroMagnitude, y: CGFloat.leastNonzeroMagnitude)
}
static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
static prefix func - (point: CGPoint) -> CGPoint {
return CGPoint(x: -point.x, y: -point.y)
}
}
extension NSLayoutConstraint {
static func activate(constraint: NSLayoutConstraint?) {
guard let constraint = constraint else { return }
@@ -201,3 +205,70 @@ extension UIEdgeInsets {
return self.top + self.bottom
}
}
extension UIBezierPath {
static func path(roundedRect rect: CGRect, appearance: SurfaceAppearance) -> UIBezierPath {
let cornerRadius = appearance.cornerRadius;
if #available(iOS 13.0, *) {
if appearance.cornerCurve == .circular {
let path = UIBezierPath()
let start = CGPoint(x: rect.minX + cornerRadius, y: rect.minY)
path.move(to: start)
path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))
if cornerRadius > 0 {
path .addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius,
y: rect.minY + cornerRadius),
radius: cornerRadius,
startAngle: -0.5 * .pi,
endAngle: 0,
clockwise: true)
}
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
if cornerRadius > 0 {
path.addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius,
y: rect.maxY - cornerRadius),
radius: cornerRadius,
startAngle: 0,
endAngle: .pi * 0.5,
clockwise: true)
}
path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY))
if cornerRadius > 0 {
path.addArc(withCenter: CGPoint(x: rect.minX + cornerRadius,
y: rect.maxY - cornerRadius),
radius: cornerRadius,
startAngle: .pi * 0.5,
endAngle: .pi,
clockwise: true)
}
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
if cornerRadius > 0 {
path.addArc(withCenter: CGPoint(x: rect.minX + cornerRadius,
y: rect.minY + cornerRadius),
radius: cornerRadius,
startAngle: .pi,
endAngle: .pi * 1.5,
clockwise: true)
}
path.addLine(to: start)
path.close()
return path
}
}
return UIBezierPath(roundedRect: rect,
byRoundingCorners: [.allCorners],
cornerRadii: CGSize(width: cornerRadius,
height: cornerRadius))
}
}
@@ -0,0 +1,51 @@
# ``FloatingPanel``
The new interface displays the related contents and utilities in parallel as a user wants.
## Overview
FloatingPanel is a simple and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
The new interface displays the related contents and utilities in parallel as a user wants.
## Topics
### Essentials
- ``FloatingPanelController``
- ``FloatingPanelControllerDelegate``
### Views
- ``SurfaceView``
- ``SurfaceAppearance``
- ``BackdropView``
- ``GrabberView``
### Gestures
- ``FloatingPanelPanGestureRecognizer``
### Layouts and Anchors
- ``FloatingPanelLayout``
- ``FloatingPanelBottomLayout``
- ``FloatingPanelLayoutAnchoring``
- ``FloatingPanelLayoutAnchor``
- ``FloatingPanelAdaptiveLayoutAnchor``
- ``FloatingPanelIntrinsicLayoutAnchor``
### States
- ``FloatingPanelState``
### Positions
- ``FloatingPanelPosition``
- ``FloatingPanelReferenceEdge``
- ``FloatingPanelLayoutReferenceGuide``
### Behaviors
- ``FloatingPanelBehavior``
- ``FloatingPanelDefaultBehavior``
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.1</string>
<string>2.5.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+95 -96
View File
@@ -60,9 +60,7 @@ struct LayoutSegment {
}
class LayoutAdapter {
weak var vc: FloatingPanelController!
private weak var surfaceView: SurfaceView!
private weak var backdropView: BackdropView!
private unowned var vc: FloatingPanelController
private let defaultLayout = FloatingPanelBottomLayout()
fileprivate var layout: FloatingPanelLayout {
@@ -71,16 +69,21 @@ class LayoutAdapter {
}
}
private var surfaceView: SurfaceView {
return vc.surfaceView
}
private var backdropView: BackdropView {
return vc.backdropView
}
private var safeAreaInsets: UIEdgeInsets {
return vc?.fp_safeAreaInsets ?? .zero
return vc.fp_safeAreaInsets
}
private var initialConst: CGFloat = 0.0
private var fixedConstraints: [NSLayoutConstraint] = []
private var fullConstraints: [NSLayoutConstraint] = []
private var halfConstraints: [NSLayoutConstraint] = []
private var tipConstraints: [NSLayoutConstraint] = []
private var stateConstraints: [FloatingPanelState: [NSLayoutConstraint]] = [:]
private var offConstraints: [NSLayoutConstraint] = []
private var fitToBoundsConstraint: NSLayoutConstraint?
@@ -89,10 +92,16 @@ class LayoutAdapter {
private var staticConstraint: NSLayoutConstraint?
private var activeStates: Set<FloatingPanelState> {
private var anchorStates: Set<FloatingPanelState> {
return Set(layout.anchors.keys)
}
private var sortedAnchorStates: [FloatingPanelState] {
return anchorStates.sorted(by: {
return $0.order < $1.order
})
}
var initialState: FloatingPanelState {
layout.initialState
}
@@ -101,18 +110,12 @@ class LayoutAdapter {
layout.position
}
var orderedStates: [FloatingPanelState] {
return activeStates.sorted(by: {
return $0.order < $1.order
})
}
var validStates: Set<FloatingPanelState> {
return activeStates.union([.hidden])
return anchorStates.union([.hidden])
}
var sortedDirectionalStates: [FloatingPanelState] {
return activeStates.sorted(by: {
var sortedAnchorStatesByCoordinate: [FloatingPanelState] {
return anchorStates.sorted(by: {
switch position {
case .top, .left:
return $0.order < $1.order
@@ -122,30 +125,26 @@ class LayoutAdapter {
})
}
private var directionalLeastState: FloatingPanelState {
return sortedDirectionalStates.first ?? .hidden
private var leastCoordinateState: FloatingPanelState {
return sortedAnchorStatesByCoordinate.first ?? .hidden
}
private var directionalMostState: FloatingPanelState {
return sortedDirectionalStates.last ?? .hidden
private var mostCoordinateState: FloatingPanelState {
return sortedAnchorStatesByCoordinate.last ?? .hidden
}
var edgeLeastState: FloatingPanelState {
if orderedStates.count == 1 {
var leastExpandedState: FloatingPanelState {
if sortedAnchorStates.count == 1 {
return .hidden
}
return orderedStates.first ?? .hidden
return sortedAnchorStates.first ?? .hidden
}
var edgeMostState: FloatingPanelState {
if orderedStates.count == 1 {
return orderedStates[0]
var mostExpandedState: FloatingPanelState {
if sortedAnchorStates.count == 1 {
return sortedAnchorStates[0]
}
return orderedStates.last ?? .hidden
}
var edgeMostY: CGFloat {
return position(for: edgeMostState)
return sortedAnchorStates.last ?? .hidden
}
var adjustedContentInsets: UIEdgeInsets {
@@ -220,7 +219,7 @@ class LayoutAdapter {
}
}
} else {
pos = displayTrunc(edgePosition(surfaceView.frame), by: surfaceView.fp_displayScale)
pos = edgePosition(surfaceView.frame).rounded(by: surfaceView.fp_displayScale)
}
switch position {
case .top, .bottom:
@@ -262,12 +261,12 @@ class LayoutAdapter {
}
}
var offsetFromEdgeMost: CGFloat {
var offsetFromMostExpandedAnchor: CGFloat {
switch position {
case .top, .left:
return edgePosition(surfaceView.presentationFrame) - position(for: directionalMostState)
return edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
case .bottom, .right:
return position(for: directionalLeastState) - edgePosition(surfaceView.presentationFrame)
return position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
}
}
@@ -284,18 +283,13 @@ class LayoutAdapter {
}
}
init(vc: FloatingPanelController,
surfaceView: SurfaceView,
backdropView: BackdropView,
layout: FloatingPanelLayout) {
init(vc: FloatingPanelController, layout: FloatingPanelLayout) {
self.vc = vc
self.layout = layout
self.surfaceView = surfaceView
self.backdropView = backdropView
}
func surfaceLocation(for state: FloatingPanelState) -> CGPoint {
let pos = displayTrunc(position(for: state), by: surfaceView.fp_displayScale)
let pos = position(for: state).rounded(by: surfaceView.fp_displayScale)
switch layout.position {
case .top, .bottom:
return CGPoint(x: 0.0, y: pos)
@@ -327,6 +321,22 @@ class LayoutAdapter {
}
return base - intrinsicLength + diff
}
case let anchor as FloatingPanelAdaptiveLayoutAnchor:
let dimension = layout.position.mainDimension(anchor.contentLayoutGuide.layoutFrame.size)
let diff = anchor.distance(from: dimension)
var referenceBoundsLength = layout.position.mainDimension(bounds.size)
switch layout.position {
case .top, .left:
if anchor.referenceGuide == .safeArea {
referenceBoundsLength += position.inset(safeAreaInsets)
}
return dimension - diff
case .bottom, .right:
if anchor.referenceGuide == .safeArea {
referenceBoundsLength -= position.inset(safeAreaInsets)
}
return referenceBoundsLength - dimension + diff
}
case let anchor as FloatingPanelLayoutAnchor:
let referenceBounds = anchor.referenceGuide == .safeArea ? bounds.inset(by: safeAreaInsets) : bounds
let diff = anchor.isAbsolute ? anchor.inset : position.mainDimension(referenceBounds.size) * anchor.inset
@@ -364,7 +374,8 @@ class LayoutAdapter {
private func referenceEdge(of anchor: FloatingPanelLayoutAnchoring) -> FloatingPanelReferenceEdge {
switch anchor {
case is FloatingPanelIntrinsicLayoutAnchor:
case is FloatingPanelIntrinsicLayoutAnchor,
is FloatingPanelAdaptiveLayoutAnchor:
switch position {
case .top: return .top
case .left: return .left
@@ -436,25 +447,13 @@ class LayoutAdapter {
}
private func updateStateConstraints() {
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
if let fullAnchor = layout.anchors[.full] {
fullConstraints = fullAnchor.layoutConstraints(vc, for: position)
fullConstraints.forEach {
$0.identifier = "FloatingPanel-full-constraint"
}
}
if let halfAnchor = layout.anchors[.half] {
halfConstraints = halfAnchor.layoutConstraints(vc, for: position)
halfConstraints.forEach {
$0.identifier = "FloatingPanel-half-constraint"
}
}
if let tipAnchors = layout.anchors[.tip] {
tipConstraints = tipAnchors.layoutConstraints(vc, for: position)
tipConstraints.forEach {
$0.identifier = "FloatingPanel-tip-constraint"
}
let allStateConstraints = stateConstraints.flatMap { $1 }
NSLayoutConstraint.deactivate(allStateConstraints + offConstraints)
stateConstraints.removeAll()
for state in layout.anchors.keys {
stateConstraints[state] = layout.anchors[state]?
.layoutConstraints(vc, for: position)
.map{ $0.identifier = "FloatingPanel-\(state)-constraint"; return $0 }
}
let hiddenAnchor = layout.anchors[.hidden] ?? self.hiddenAnchor
offConstraints = hiddenAnchor.layoutConstraints(vc, for: position)
@@ -471,7 +470,7 @@ class LayoutAdapter {
tearDownAttraction()
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
NSLayoutConstraint.deactivate(stateConstraints.flatMap { $1 } + offConstraints)
initialConst = edgePosition(surfaceView.frame) + offset.y
@@ -487,7 +486,7 @@ class LayoutAdapter {
constraint = surfaceView.leftAnchor.constraint(equalTo: vc.view.leftAnchor, constant: initialConst)
}
constraint.priority = .defaultHigh
constraint.priority = .required
constraint.identifier = "FloatingPanel-interaction"
NSLayoutConstraint.activate([constraint])
@@ -511,7 +510,7 @@ class LayoutAdapter {
let anchor = layout.anchors[state] ?? self.hiddenAnchor
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
NSLayoutConstraint.deactivate(stateConstraints.flatMap { $1 } + offConstraints)
NSLayoutConstraint.deactivate(constraint: interactionConstraint)
interactionConstraint = nil
@@ -630,7 +629,6 @@ class LayoutAdapter {
// The method is separated from prepareLayout(to:) for the rotation support
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
func updateStaticConstraint() {
guard let vc = vc else { return }
NSLayoutConstraint.deactivate(constraint: staticConstraint)
staticConstraint = nil
@@ -639,20 +637,32 @@ class LayoutAdapter {
return
}
let anchor = layout.anchors[self.edgeMostState]!
if anchor is FloatingPanelIntrinsicLayoutAnchor {
var constant = layout.position.mainDimension(surfaceView.intrinsicContentSize)
let anchor = layout.anchors[self.mostExpandedState]!
let surfaceAnchor = position.mainDimensionAnchor(surfaceView)
switch anchor {
case let anchor as FloatingPanelIntrinsicLayoutAnchor:
var constant = position.mainDimension(surfaceView.intrinsicContentSize)
if anchor.referenceGuide == .safeArea {
constant += position.inset(safeAreaInsets)
}
staticConstraint = position.mainDimensionAnchor(surfaceView).constraint(equalToConstant: constant)
} else {
staticConstraint = surfaceAnchor.constraint(equalToConstant: constant)
case let anchor as FloatingPanelAdaptiveLayoutAnchor:
let constant: CGFloat
if anchor.referenceGuide == .safeArea {
constant = position.inset(safeAreaInsets)
} else {
constant = 0.0
}
let baseAnchor = position.mainDimensionAnchor(anchor.contentLayoutGuide)
staticConstraint = surfaceAnchor.constraint(equalTo: baseAnchor, constant: constant)
default:
switch position {
case .top, .left:
staticConstraint = position.mainDimensionAnchor(surfaceView).constraint(equalToConstant: position(for: self.directionalMostState))
staticConstraint = surfaceAnchor.constraint(equalToConstant: position(for: self.mostCoordinateState))
case .bottom, .right:
staticConstraint = position.mainDimensionAnchor(vc.view).constraint(equalTo: position.mainDimensionAnchor(surfaceView),
constant: position(for: self.directionalLeastState))
let rootViewAnchor = position.mainDimensionAnchor(vc.view)
staticConstraint = rootViewAnchor.constraint(equalTo: surfaceAnchor,
constant: position(for: self.leastCoordinateState))
}
}
@@ -673,8 +683,8 @@ class LayoutAdapter {
log.debug("update surface location = \(surfaceLocation)")
}
let minConst: CGFloat = position(for: directionalLeastState)
let maxConst: CGFloat = position(for: directionalMostState)
let minConst: CGFloat = position(for: leastCoordinateState)
let maxConst: CGFloat = position(for: mostCoordinateState)
var const = initialConst + diff
@@ -730,8 +740,6 @@ class LayoutAdapter {
var state = state
setBackdropAlpha(of: state)
if validStates.contains(state) == false {
state = layout.initialState
}
@@ -741,17 +749,16 @@ class LayoutAdapter {
// on-screen and off-screen view which includes
// UIStackView(i.e. Settings view in Samples.app)
updateStateConstraints()
switch state {
case .full:
NSLayoutConstraint.activate(fullConstraints)
case .half:
NSLayoutConstraint.activate(halfConstraints)
case .tip:
NSLayoutConstraint.activate(tipConstraints)
case .hidden:
NSLayoutConstraint.activate(offConstraints)
default:
break
if let constraints = stateConstraints[state] {
NSLayoutConstraint.activate(constraints)
} else {
log.error("Couldn't find any constraints for \(state)")
}
}
}
@@ -762,21 +769,13 @@ class LayoutAdapter {
surfaceView.superview?.layoutIfNeeded()
}
private func setBackdropAlpha(of target: FloatingPanelState) {
if target == .hidden {
self.backdropView.alpha = 0.0
} else {
self.backdropView.alpha = backdropAlpha(for: target)
}
}
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return layout.backdropAlpha?(for: state) ?? defaultLayout.backdropAlpha(for: state)
}
fileprivate func checkLayout() {
// Verify layout configurations
assert(activeStates.count > 0)
assert(anchorStates.count > 0)
assert(validStates.contains(layout.initialState),
"Does not include an initial state (\(layout.initialState)) in (\(validStates))")
/* This assertion does not work in a device rotating
@@ -796,7 +795,7 @@ extension LayoutAdapter {
/// |-------|-------|===o===| |-------|===o===|-------|
/// pos: o/x, segment: =
let sortedStates = sortedDirectionalStates
let sortedStates = sortedAnchorStatesByCoordinate
let upperIndex: Int?
if forward {
+75 -12
View File
@@ -8,22 +8,25 @@ import UIKit
func layoutConstraints(_ fpc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint]
}
/// A layout anchor object that anchors a panel in a state.
/// An object that defines how to settles a panel with insets from an edge of a reference rectangle.
@objc final public class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
/// Initializes and returns a layout anchor object to specify an absolute inset value for the position of a panel.
/// Returns a layout anchor with the specified inset by an absolute value, edge and reference guide for a panel.
///
/// The inset is a distance from the edge of the specified layout guide.
/// The inset is an amount to inset a panel from an edge of the reference guide. The edge refers to a panel
/// positioning.
@objc public init(absoluteInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
self.inset = absoluteInset
self.referenceGuide = referenceGuide
self.referenceEdge = edge
self.isAbsolute = true
}
/// Initializes and returns a layout anchor object to specify a fractional inset value for the position of a panel.
/// Returns a layout anchor with the specified inset by a fractional value, edge and reference guide for a panel.
///
/// The inset is a distance from the edge of the specified layout guide. The value is a floating-point number
/// in the range 0.0 to 1.0, where 0.0 represents zero distance from the edge and 1.0 represents a distance
/// to the opposite edge.
/// The inset is an amount to inset a panel from the edge of the specified reference guide. The value is
/// a floating-point number in the range 0.0 to 1.0, where 0.0 represents zero distance from the edge and
/// 1.0 represents a distance to the opposite edge.
@objc public init(fractionalInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
self.inset = fractionalInset
self.referenceGuide = referenceGuide
@@ -91,19 +94,20 @@ public extension FloatingPanelLayoutAnchor {
}
}
/// A layout anchor object that anchors a panel in a state using the intrinsic size for a content.
/// An object that defines how to settles a panel with the intrinsic size for a content.
@objc final public class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
/// Initializes and returns a layout anchor object to specify an absolute offset value for the position of a panel.
/// Returns a layout anchor with the specified offset by an absolute value and reference guide for a panel.
///
/// The offset is a distance from a position at which a panel displays the entire content.
/// The offset is an amount to offset a position of panel that displays the entire content from an edge of
/// the reference guide. The edge refers to a panel positioning.
@objc public init(absoluteOffset offset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
self.referenceGuide = referenceGuide
self.isAbsolute = true
}
/// Initializes and returns a layout anchor object to specify a fractional offset value for the position of a panel.
/// Returns a layout anchor with the specified offset by a fractional value and reference guide for a panel.
///
/// The offset value is a floating-point number in the range 0.0 to 1.0, where 0.0 represents the full content
/// is displayed and 0.5 represents the half of content is displayed.
@@ -136,3 +140,62 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
}
}
}
/// An object that defines how to settles a panel with a layout guide of a content view.
@objc final public class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
/// Returns a layout anchor with the specified offset by an absolute value, layout guide to display content and reference guide for a panel.
///
/// The offset is an amount to offset a position of panel that displays the entire content of the specified guide from an edge of
/// the reference guide. The edge refers to a panel positioning.
@objc public init(absoluteOffset offset: CGFloat, contentLayout: UILayoutGuide, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
self.contentLayoutGuide = contentLayout
self.referenceGuide = referenceGuide
self.isAbsolute = true
}
/// Returns a layout anchor with the specified offset by a fractional value, layout guide to display content and reference guide for a panel.
///
/// The offset value is a floating-point number in the range 0.0 to 1.0, where 0.0 represents the full content
/// is displayed and 0.5 represents the half of content is displayed.
@objc public init(fractionalOffset offset: CGFloat, contentLayout: UILayoutGuide, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
self.offset = offset
self.contentLayoutGuide = contentLayout
self.referenceGuide = referenceGuide
self.isAbsolute = false
}
fileprivate let offset: CGFloat
fileprivate let isAbsolute: Bool
let contentLayoutGuide: UILayoutGuide
@objc public let referenceGuide: FloatingPanelLayoutReferenceGuide
}
public extension FloatingPanelAdaptiveLayoutAnchor {
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
let offsetAnchor: NSLayoutDimension
switch position {
case .top:
offsetAnchor = layoutGuide.topAnchor.anchorWithOffset(to: vc.surfaceView.bottomAnchor)
case .left:
offsetAnchor = layoutGuide.leftAnchor.anchorWithOffset(to: vc.surfaceView.rightAnchor)
case .bottom:
offsetAnchor = vc.surfaceView.topAnchor.anchorWithOffset(to: layoutGuide.bottomAnchor)
case .right:
offsetAnchor = vc.surfaceView.leftAnchor.anchorWithOffset(to: layoutGuide.rightAnchor)
}
if isAbsolute {
return [offsetAnchor.constraint(equalTo: position.mainDimensionAnchor(contentLayoutGuide), constant: -offset)]
} else {
return [offsetAnchor.constraint(equalTo: position.mainDimensionAnchor(contentLayoutGuide), multiplier: (1 - offset))]
}
}
}
extension FloatingPanelAdaptiveLayoutAnchor {
func distance(from dimension: CGFloat) -> CGFloat {
return isAbsolute ? offset : dimension * offset
}
}
+2 -1
View File
@@ -3,6 +3,7 @@
import Foundation
import os.log
// Must be a variable to use `hook` property in testing
var log = {
return Logger()
}()
@@ -68,7 +69,7 @@ struct Logger {
hook?(log, level)
os_log("%@", log: osLog, type: level.osLogType, log)
os_log("%{public}@", log: osLog, type: level.osLogType, log)
}
private func getPrettyFunction(_ function: String, _ file: String) -> String {
@@ -2,8 +2,8 @@
import UIKit
@objc(FloatingPanelPassThroughView)
class PassThroughView: UIView {
@objc(FloatingPanelPassthroughView)
class PassthroughView: UIView {
public weak var eventForwardingView: UIView?
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
+3 -2
View File
@@ -4,7 +4,7 @@ import Foundation
/// An object that represents the display state of a panel in a screen.
@objc
public class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
open class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
public typealias RawValue = String
required public init?(rawValue: RawValue) {
@@ -13,6 +13,7 @@ public class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
super.init()
}
@objc
public init(rawValue: RawValue, order: Int) {
self.rawValue = rawValue
self.order = order
@@ -33,7 +34,7 @@ public class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
}
public override var debugDescription: String {
return description
return "<FloatingPanel.FloatingPanelState: \(Unmanaged.passUnretained(self).toOpaque())>"
}
/// A panel state indicates the entire panel is shown.
+24 -14
View File
@@ -50,6 +50,12 @@ public class SurfaceAppearance: NSObject {
/// 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
/// Defines the curve used for rendering the rounded corners of the layer.
///
/// Defaults to `.circular`.
@available(iOS 13.0, *)
public lazy var cornerCurve: CALayerCornerCurve = .circular
/// An array of shadows used to create drop shadows underneath a surface view.
public var shadows: [Shadow] = [Shadow()]
@@ -79,7 +85,7 @@ public class SurfaceView: UIView {
/// The grabber handle size
///
/// On left/right positioned panel the width dimension is used as the height of `grabberHandle`, and vice versa.
/// On left/right positioned panel the width dimension is used as the height of ``grabberHandle``, and vice versa.
public var grabberHandleSize: CGSize = CGSize(width: 36.0, height: 5.0) { didSet {
setNeedsUpdateConstraints()
} }
@@ -311,8 +317,8 @@ public class SurfaceView: UIView {
containerView.backgroundColor = appearance.backgroundColor
updateShadow()
updateCornerRadius()
updateShadow()
updateBorder()
grabberHandle.layer.cornerRadius = grabberHandleSize.height / 2
@@ -336,10 +342,9 @@ public class SurfaceView: UIView {
shadowLayer.frame = layer.bounds
let spread = shadow.spread
let shadowPath = UIBezierPath(roundedRect: containerView.frame.insetBy(dx: -spread,
dy: -spread),
byRoundingCorners: [.allCorners],
cornerRadii: CGSize(width: appearance.cornerRadius, height: 0))
let shadowRect = containerView.frame.insetBy(dx: -spread, dy: -spread)
let shadowPath = UIBezierPath.path(roundedRect: shadowRect,
appearance: appearance)
shadowLayer.shadowPath = shadowPath.cgPath
shadowLayer.shadowColor = shadow.color.cgColor
shadowLayer.shadowOffset = shadow.offset
@@ -348,16 +353,16 @@ public class SurfaceView: UIView {
shadowLayer.shadowOpacity = shadow.opacity
let mask = CAShapeLayer()
let path = UIBezierPath(roundedRect: containerView.frame,
byRoundingCorners: [.allCorners],
cornerRadii: CGSize(width: appearance.cornerRadius, height: 0))
let path = UIBezierPath.path(roundedRect: containerView.frame,
appearance: appearance)
let size = window?.bounds.size ?? CGSize(width: 1000.0, height: 1000.0)
path.append(UIBezierPath(rect: layer.bounds.insetBy(dx: -size.width,
dy: -size.height)))
mask.fillRule = .evenOdd
mask.path = path.cgPath
if #available(iOS 13.0, *) {
mask.cornerCurve = containerView.layer.cornerCurve
containerView.layer.cornerCurve = appearance.cornerCurve
mask.cornerCurve = appearance.cornerCurve
}
shadowLayer.mask = mask
}
@@ -403,12 +408,11 @@ public class SurfaceView: UIView {
containerView.layer.borderWidth = appearance.borderWidth
}
func set(contentView: UIView) {
func set(contentView: UIView, mode: FloatingPanelController.ContentMode) {
containerView.addSubview(contentView)
self.contentView = contentView
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
contentView.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor, constant: containerMargins.top + contentPadding.top)
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: containerMargins.left + contentPadding.left)
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: containerMargins.right + contentPadding.right)
@@ -419,9 +423,15 @@ public class SurfaceView: UIView {
rightConstraint,
bottomConstraint,
].map {
$0.priority = .required - 1;
switch mode {
case .static:
$0.priority = .required
// The reason why this priority is set to .required - 1 is #359, which fixed #294.
case .fitToBounds:
$0.priority = .required - 1
}
$0.identifier = "FloatingPanel-surface-content"
return $0;
return $0
})
self.contentViewTopConstraint = topConstraint
self.contentViewLeftConstraint = leftConstraint
+29 -9
View File
@@ -61,7 +61,7 @@ class PresentationController: UIPresentationController {
addFloatingPanel()
// Forward touch events to the presenting view controller
(fpc.view as? PassThroughView)?.eventForwardingView = presentingViewController.view
(fpc.view as? PassthroughView)?.eventForwardingView = presentingViewController.view
}
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
@@ -86,19 +86,29 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
else { fatalError()}
let animator = fpc.delegate?.floatingPanel?(fpc, animatorForPresentingTo: fpc.layout.initialState)
?? FloatingPanelDefaultBehavior().addPanelAnimator(fpc, to: fpc.layout.initialState)
let animator = fpc.animatorForPresenting(to: fpc.layout.initialState)
return TimeInterval(animator.duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
guard
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
else { fatalError() }
fpc.show(animated: true) {
if let animator = fpc.transitionAnimator {
return animator
}
fpc.suspendTransitionAnimator(true)
fpc.show(animated: true) { [weak fpc] in
fpc?.suspendTransitionAnimator(false)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return fpc.transitionAnimator!
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.interruptibleAnimator(using: transitionContext).startAnimation()
}
}
@@ -108,19 +118,29 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
else { fatalError()}
let animator = fpc.delegate?.floatingPanel?(fpc, animatorForDismissingWith: .zero)
?? FloatingPanelDefaultBehavior().removePanelAnimator(fpc, from: fpc.state, with: .zero)
let animator = fpc.animatorForDismissing(with: .zero)
return TimeInterval(animator.duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
guard
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
else { fatalError() }
fpc.hide(animated: true) {
if let animator = fpc.transitionAnimator {
return animator
}
fpc.suspendTransitionAnimator(true)
fpc.hide(animated: true) { [weak fpc] in
fpc?.suspendTransitionAnimator(false)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return fpc.transitionAnimator!
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.interruptibleAnimator(using: transitionContext).startAnimation()
}
}
+58
View File
@@ -185,6 +185,57 @@ class CoreTests: XCTestCase {
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos, with: -1 * distance2), 0.0)
}
func test_updateBackdropAlpha() {
class BackdropTestLayout: FloatingPanelTestLayout {
override var initialState: FloatingPanelState { .hidden }
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
switch state {
case .full: return 0.3
case .half: return 0.0
case .tip: return 0.3
default: return 0.0
}
}
}
let fpc = FloatingPanelController()
fpc.layout = BackdropTestLayout()
fpc.showForTest()
fpc.move(to: .full, animated: false)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.backdropView.alpha, 0.0)
fpc.move(to: .tip, animated: false)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
let exp1 = expectation(description: "move to full with animation")
fpc.move(to: .full, animated: true) {
exp1.fulfill()
}
wait(for: [exp1], timeout: 1.0)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
let exp2 = expectation(description: "move to half with animation")
fpc.move(to: .half, animated: true) {
exp2.fulfill()
}
wait(for: [exp2], timeout: 1.0)
XCTAssertEqual(fpc.backdropView.alpha, 0.0)
let exp3 = expectation(description: "move to tip with animation")
fpc.move(to: .tip, animated: true) {
exp3.fulfill()
}
fpc.contentMode = .fitToBounds
XCTAssertEqual(fpc.backdropView.alpha, 0.0) // Must not affect the backdrop alpha by changing the content mode
wait(for: [exp3], timeout: 1.0)
XCTAssertEqual(floor(fpc.backdropView.alpha * 1000_000) / 1000_000, 0.3)
}
func test_targetPosition_1positions() {
class FloatingPanelLayout1Positions: FloatingPanelLayout {
let initialState: FloatingPanelState = .full
@@ -715,6 +766,13 @@ class CoreTests: XCTestCase {
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .hidden),
])
}
func test_keep_pan_gesture_disabled() {
let fpc = FloatingPanelController()
fpc.panGestureRecognizer.isEnabled = false
fpc.showForTest()
XCTAssertFalse(fpc.panGestureRecognizer.isEnabled)
}
}
private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import XCTest
@testable import FloatingPanel
class ExtensionTests: XCTestCase {
func test_roundedByDisplayScale() {
XCTAssertEqual(CGFloat(333.222).rounded(by: 3), 333.3333333333333)
XCTAssertNotEqual(CGFloat(333.5).rounded(by: 3), 333.66666666666674)
XCTAssertTrue(CGFloat(333.5).isEqual(to: 333.66666666666674, on: 3.0))
}
}
+6 -6
View File
@@ -13,8 +13,8 @@ class LayoutTests: XCTestCase {
override func tearDown() {}
func test_layoutAdapter_topAndBottomMostState() {
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.edgeMostState, .full)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.edgeLeastState, .tip)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.mostExpandedState, .full)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.leastExpandedState, .tip)
class FloatingPanelLayoutWithHidden: FloatingPanelLayout {
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
@@ -38,12 +38,12 @@ class LayoutTests: XCTestCase {
let position: FloatingPanelPosition = .bottom
}
fpc.layout = FloatingPanelLayoutWithHidden()
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.edgeMostState, .full)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.edgeLeastState, .hidden)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.mostExpandedState, .full)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.leastExpandedState, .hidden)
fpc.layout = FloatingPanelLayout2Positions()
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.edgeMostState, .half)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.edgeLeastState, .tip)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.mostExpandedState, .half)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.leastExpandedState, .tip)
}
func test_layoutSegment_3position() {
+48 -47
View File
@@ -48,35 +48,37 @@ class SurfaceViewTests: XCTestCase {
}
func test_surfaceView_contentView() {
XCTContext.runActivity(named: "Bottom sheet") { _ in
for (position, mode, line) in [
(.top, .static, #line),
(.top, .fitToBounds, #line),
(.bottom, .static, #line),
(.bottom, .fitToBounds, #line),
] as [(FloatingPanelPosition, FloatingPanelController.ContentMode, UInt)] {
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
surface.position = position
surface.layoutIfNeeded()
let contentView = UIView()
surface.set(contentView: contentView)
surface.set(contentView: contentView, mode: mode)
let height = surface.bounds.height * 2
surface.containerOverflow = height
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds)
}
XCTContext.runActivity(named: "Top sheet") { _ in
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
surface.position = .top
surface.layoutIfNeeded()
let contentView = UIView()
surface.set(contentView: contentView)
let height = surface.bounds.height * 2
surface.containerOverflow = height
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.containerView.frame, CGRect(x: 0.0, y: -height, width: 320.0, height: 480.0 * 3))
XCTAssertEqual(surface.convert(surface.contentView?.frame ?? .zero, from: surface.containerView),
surface.bounds)
switch position {
case .top:
XCTAssertEqual(surface.containerView.frame,
CGRect(x: 0.0, y: -height, width: 320.0, height: 480.0 * 3),
line: line)
XCTAssertEqual(surface.convert(surface.contentView?.frame ?? .zero, from: surface.containerView),
surface.bounds,
line: line)
case .bottom:
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds, line: line)
default:
break
}
}
}
@@ -140,46 +142,45 @@ class SurfaceViewTests: XCTestCase {
}
func test_surfaceView_contentInsets() {
XCTContext.runActivity(named: "Top sheet") { _ in
for (position, mode, line) in [
(.top, .static, #line),
(.top, .fitToBounds, #line),
(.bottom, .static, #line),
(.bottom, .fitToBounds, #line),
] as [(FloatingPanelPosition, FloatingPanelController.ContentMode, UInt)] {
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
surface.position = .top
surface.position = position
let contentView = UIView()
surface.set(contentView: contentView)
surface.set(contentView: contentView, mode: mode)
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds)
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds, line: line)
surface.contentPadding = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds.inset(by: surface.contentPadding))
}
XCTContext.runActivity(named: "Bottom sheet") { _ in
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
let contentView = UIView()
surface.set(contentView: contentView)
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds)
surface.contentPadding = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds.inset(by: surface.contentPadding))
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds.inset(by: surface.contentPadding), line: line)
}
}
func test_surfaceView_containerMargins_and_contentInsets() {
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
let contentView = UIView()
surface.set(contentView: contentView)
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds)
surface.containerMargins = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.contentPadding = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.containerView.frame, surface.bounds.inset(by: surface.containerMargins))
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.containerView.bounds.inset(by: surface.contentPadding))
for (mode, line) in [
(.static, #line),
(.fitToBounds, #line),
] as [(FloatingPanelController.ContentMode, UInt)] {
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
let contentView = UIView()
surface.set(contentView: contentView, mode: mode)
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.bounds, line: line)
surface.containerMargins = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.contentPadding = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.containerView.frame, surface.bounds.inset(by: surface.containerMargins), line: line)
XCTAssertEqual(surface.contentView?.frame ?? .zero, surface.containerView.bounds.inset(by: surface.contentPadding), line: line)
}
}
func test_surfaceView_cornderRaduis() {
func test_surfaceView_cornerRadius() {
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
XCTAssert(surface.containerView.layer.cornerRadius == 0.0)
XCTAssert(surface.containerView.layer.masksToBounds == false)
+1 -1
View File
@@ -18,7 +18,7 @@ extension FloatingPanelController {
class FloatingPanelTestDelegate: FloatingPanelControllerDelegate {
var position: FloatingPanelState = .hidden
var didMoveCallback: ((FloatingPanelController) -> Void)?
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
func floatingPanelDidChangeState(_ vc: FloatingPanelController) {
position = vc.state
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {
-12
View File
@@ -1,12 +0,0 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import XCTest
@testable import FloatingPanel
class UtilTests: XCTestCase {
func test_displayTrunc() {
XCTAssertEqual(displayTrunc(333.222, by: 3), 333.3333333333333)
XCTAssertNotEqual(displayTrunc(333.5, by: 3), 333.66666666666674)
XCTAssertTrue(displayEqual(333.5, 333.66666666666674, by: 3))
}
}