Compare commits

...

33 Commits

Author SHA1 Message Date
Shin Yamamoto fe30e60235 Version 2.5.2 2022-02-04 23:59:07 +09:00
Jakub Dudek 6e7c31110f Fix dragging on content placed above the trackedView resets scroll position (#527)
Changed how `initialScrollOffset` is set. This issue was described in #526.
2022-02-04 23:56:46 +09:00
Shin Yamamoto e39f634d34 Unlock a scroll view when untracked 2021-12-04 11:12:11 +09:00
Shin Yamamoto 5851fd77c4 Merge pull request #521 from scenee/release/2.5.1
Release 2.5.1
2021-12-03 18:06:12 +09:00
Shin Yamamoto 0fbbbc8d99 Version 2.5.1 2021-11-29 20:46:32 +09:00
Shin Yamamoto 69078366de Change constraint priorities of the content view on the default content mode (#519)
They were set to `.required - 1` is #359, which fixed #294. However #294
was an issue on `.fitToBounds` content mode. But because of #444 and #515,
if your panel’s content mode is `.static`, their priorities should be set to
`.required` on `.static` content mode.
2021-11-29 20:40:26 +09:00
Shin Yamamoto 084b589a10 Fix invalidateLayout() implementation as following the doc comment (#510)
* Fix FloatingPanelController.invalidateLayout() implementation as following the doc comment
* Revise the doc comments of invalidateLayout() method, layout and behavior properties of FloatingPanelController.
* Add a note in README 
* Fix some grammar errors
2021-11-25 23:19:05 +09:00
Shin Yamamoto 07ae324586 Clean up the example projects and codes (#512)
* Clean up Samples and Maps examples
* Rename the root view controller in Maps/Stocks/SampleObjC examples
* Reorder resource file references
* Rename examples' bundle ids with `example` domain.
* Remove 'Run Script' to modify CFBundleVersion: now this has not been used for testing since unit tests were added
* Revise methods to handle content view controllers in UseCaseController
* Fix a bug on PagePanelController
2021-11-24 20:26:35 +09:00
Shin Yamamoto 819c87c530 Stop changing the content mode whose panel is attracting (#513)
This code causes #511. Unfortunately I don't remember the reason
why the example code do that (I should have added a comment...).
And it's a bit tricky for the example. So I remove it.
2021-11-16 08:29:43 +09:00
Shin Yamamoto 3bb9c4fd0f Apply a part of build settings recommended by Xcode 13 2021-10-30 11:19:52 +09:00
Shin Yamamoto 910a304849 Update README 2021-10-30 11:04:24 +09:00
Shin Yamamoto 2ecdbbae52 Add the documentation catalog 2021-10-11 16:33:16 +09:00
Shin Yamamoto fe2e3173b8 Support DocC symbols 2021-10-09 10:09:14 +09:00
Shin Yamamoto 21c6e3c715 Merge pull request #505 from scenee/release/2.5.0
Release 2.5.0
2021-09-28 20:44:31 +09:00
Shin Yamamoto 29beea0ff9 Version 2.5.0 2021-09-28 08:43:42 +09:00
Shin Yamamoto 1e16d9c1fb Fix a tab bar's appearance in Samples app on iOS 15 2021-09-28 08:43:42 +09:00
milettal 31d7a2a301 Fix a crash in Samples app on iPad (#497)
* Fix a crash when the app shows an action sheet on iPad
* Use the UITableViewCell as the sourceView
2021-09-25 10:11:38 +09:00
Federico Zanetello 5f1d49b0bd Add SwiftUI proof of concept in Maps-SwiftUI example (#481)
Partially solves #281. This adds a new example app which mimics the Maps.app, written in SwiftUI. The code works for iOS 13+, however:
* the project has been created with Xcode 13
* the project uses the SwiftUI lifecycle (iOS 14+)

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

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

Resolve #466.
2021-06-05 14:24:19 +09:00
Shin Yamamoto 895790f697 Update README for the backdrop tap-to-dismiss action 2021-05-28 13:00:30 +09:00
Shin Yamamoto 43c7e8c2a0 Refactor LayoutAdapter
* Remove `edgeMostY`
* Rename the following properties
    * activeStates -> anchorStates
    * orderedStates -> sortedAnchorStates
    * sortedDirectionalStates -> sortedAnchorStatesByCoordinate
    * directionalLeastState -> leastCoordinateState
    * directionalMostState -> mostCoordinateState
    * edgeMostState -> mostExpandedState
    * edgeLeastState -> leastExpandedState
    * offsetFromEdgeMost -> offsetFromMostExpandedAnchor
2021-05-10 22:57:04 +09:00
Shin Yamamoto 904a66115c Add completion handler in addPanel(toParent:) (#402) 2021-05-10 22:14:01 +09:00
Shin Yamamoto d0932e8e37 Rename floatingPanelDidChangePosition to floatingPanelDidChangeState 2021-05-10 22:14:01 +09:00
Shin Yamamoto 22009013eb Merge pull request #461 from scenee/release/2.3.1
Release v2.3.1
2021-05-10 21:37:17 +09:00
71 changed files with 1967 additions and 574 deletions
+37 -14
View File
@@ -10,6 +10,19 @@ on:
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
@@ -24,9 +37,22 @@ jobs:
- name: "Swift 5.3"
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.3 clean build
env:
DEVELOPER_DIR: /Applications/Xcode_12.3.app/Contents/Developer
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
@@ -34,15 +60,15 @@ jobs:
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.3"
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.3,name=iPhone 12 Pro'
- 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.3.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
example:
runs-on: macOS-10.15
runs-on: macOS-11
env:
DEVELOPER_DIR: /Applications/Xcode_12.3.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer
steps:
- uses: actions/checkout@v1
- name: "Build Maps"
@@ -53,26 +79,23 @@ jobs:
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme Samples -sdk iphonesimulator clean build
swiftpm:
runs-on: macOS-10.15
runs-on: macOS-11
env:
DEVELOPER_DIR: /Applications/Xcode_12.3.app/Contents/Developer
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-10.15
env:
# Carthage doesn't fix issues on Xcode 12: https://github.com/Carthage/Carthage/releases/tag/0.36.0
DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer
runs-on: macOS-11
steps:
- uses: actions/checkout@v1
- name: "Carthage build"
run: carthage build --no-skip-current
run: carthage build --use-xcframeworks --no-skip-current
cocoapods:
runs-on: macOS-10.15
runs-on: macOS-11
steps:
- uses: actions/checkout@v1
- name: "CocoaPods: pod lib lint"
-20
View File
@@ -1,20 +0,0 @@
language: objective-c
branches:
only:
- master
env:
global:
- LANG=en_US.UTF-8
- LC_ALL=en_US.UTF-8
jobs:
include:
- 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)"
@@ -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
}
}
+32 -34
View File
@@ -1,28 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" 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="17505"/>
<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"/>
@@ -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"/>
@@ -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"/>
<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>
@@ -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,15 +80,14 @@ 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.setAppearanceForPhone()
detailFpc.setAppearanceForPhone()
[fpc, detailFpc].forEach { $0.setAppearanceForPhone()}
}
}
@@ -116,7 +119,7 @@ extension FloatingPanelController {
// MARK: - UISearchBarDelegate
extension ViewController: UISearchBarDelegate {
extension MainViewController: UISearchBarDelegate {
func activate(searchBar: UISearchBar) {
searchBar.showsCancelButton = true
searchVC.showHeader(animated: true)
@@ -147,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
}
@@ -201,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
}
}
@@ -239,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
}
}
@@ -262,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
}
@@ -335,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
}
@@ -387,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,
@@ -400,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
@@ -30,8 +30,8 @@
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; };
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* Extensions.swift */; };
54CDC5D8215BBE23007D205C /* Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* Components.swift */; };
54EAD35B263A75EB006A36EA /* Layouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD35A263A75EB006A36EA /* Layouts.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 */
@@ -95,8 +95,8 @@
546341AB25C6426500CA0596 /* CustomState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomState.swift; sourceTree = "<group>"; };
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54B51115216AFE5F0033A6F3 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
54CDC5D7215BBE23007D205C /* Components.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Components.swift; sourceTree = "<group>"; };
54EAD35A263A75EB006A36EA /* Layouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layouts.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 */
@@ -126,7 +126,7 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
5442E22225FC519700A26F43 /* ViewControllers */ = {
5442E22225FC519700A26F43 /* ContentViewControllers */ = {
isa = PBXGroup;
children = (
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */,
@@ -140,7 +140,7 @@
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */,
5442E22F25FC525200A26F43 /* TabBarViewController.swift */,
);
path = ViewControllers;
path = ContentViewControllers;
sourceTree = "<group>";
};
545DB9E121511E6300CA77B8 = {
@@ -172,12 +172,12 @@
545DB9F121511E6300CA77B8 /* Main.storyboard */,
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */,
545DB9EF21511E6300CA77B8 /* MainViewController.swift */,
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */,
546341AA25C6421000CA0596 /* UseCases */,
5442E22225FC519700A26F43 /* ViewControllers */,
54EAD35A263A75EB006A36EA /* Layouts.swift */,
5442E22225FC519700A26F43 /* ContentViewControllers */,
54EAD35A263A75EB006A36EA /* PanelLayouts.swift */,
546341AB25C6426500CA0596 /* CustomState.swift */,
54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */,
54B51115216AFE5F0033A6F3 /* Extensions.swift */,
54CDC5D7215BBE23007D205C /* Components.swift */,
545DB9F921511E6400CA77B8 /* Info.plist */,
);
path = Sources;
@@ -205,7 +205,7 @@
isa = PBXGroup;
children = (
546341A025C6415100CA0596 /* UseCase.swift */,
546341AB25C6426500CA0596 /* CustomState.swift */,
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */,
54EAD364263A765F006A36EA /* PagePanelController.swift */,
);
path = UseCases;
@@ -218,7 +218,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 545DBA1221511E6400CA77B8 /* Build configuration list for PBXNativeTarget "Samples" */;
buildPhases = (
54D7209621D4DB970054A255 /* ShellScript */,
545DB9E621511E6300CA77B8 /* Sources */,
545DB9E721511E6300CA77B8 /* Frameworks */,
545DB9E821511E6300CA77B8 /* Resources */,
@@ -276,7 +275,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1000;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
545DB9E921511E6300CA77B8 = {
@@ -339,26 +338,6 @@
};
/* 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;
@@ -366,7 +345,7 @@
files = (
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */,
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */,
54CDC5D8215BBE23007D205C /* Components.swift in Sources */,
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */,
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */,
546341AC25C6426500CA0596 /* CustomState.swift in Sources */,
@@ -379,7 +358,7 @@
545DB9F021511E6300CA77B8 /* MainViewController.swift in Sources */,
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */,
5442E23025FC525200A26F43 /* TabBarViewController.swift in Sources */,
54EAD35B263A75EB006A36EA /* Layouts.swift in Sources */,
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */,
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */,
5442E24425FC538200A26F43 /* InspectorViewController.swift in Sources */,
5442E22C25FC521F00A26F43 /* SettingsViewController.swift in Sources */,
@@ -463,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;
@@ -524,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;
@@ -565,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";
@@ -584,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,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="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>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<deployment identifier="iOS"/>
<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"/>
@@ -796,7 +798,7 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
<color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.35294117647058826" green="0.78431372549019607" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
@@ -53,6 +53,7 @@ class DebugTableViewController: InspectableViewController {
// MARK: - Properties
var kvoObservers: [NSKeyValueObservation] = []
private lazy var items: [String] = {
let items = (0..<100).map { "Items \($0)" }
return Command.replace(items: items)
@@ -87,12 +88,12 @@ class DebugTableViewController: InspectableViewController {
}
}
func execute(for vc: DebugTableViewController) {
func execute(for vc: DebugTableViewController, sourceView: UIView) {
switch self {
case .animateScroll:
vc.animateScroll()
case .changeContentSize:
vc.changeContentSize()
vc.changeContentSize(sourceView: sourceView)
case .moveToFull:
vc.moveToFull()
case .moveToHalf:
@@ -165,8 +166,8 @@ class DebugTableViewController: InspectableViewController {
// MARK: - Actions
private func execute(command: Command) {
command.execute(for: self)
private func execute(command: Command, sourceView: UIView) {
command.execute(for: self, sourceView: sourceView)
}
@objc
@@ -177,7 +178,7 @@ class DebugTableViewController: InspectableViewController {
}
@objc
private func changeContentSize() {
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
@@ -201,6 +202,11 @@ class DebugTableViewController: InspectableViewController {
self.changeItems(3)
}))
if let popoverController = actionSheet.popoverPresentationController {
popoverController.sourceView = sourceView
popoverController.sourceRect = sourceView.bounds
}
self.present(actionSheet, animated: true, completion: nil)
}
@@ -250,7 +256,8 @@ extension DebugTableViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("DebugTableViewController -- select row \(indexPath.row)")
guard let action = Command(rawValue: indexPath.row) else { return }
execute(command: action)
let cell = tableView.cellForRow(at: indexPath)
execute(command: action, sourceView: cell ?? tableView)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
@@ -56,7 +56,6 @@ final class ModalViewController: UIViewController, FloatingPanelControllerDelega
@IBAction func updateLayout(_ sender: Any) {
isNewlayout = !isNewlayout
UIView.animate(withDuration: 0.5) {
self.fpc.layout = (self.isNewlayout) ? ModalSecondLayout() : FloatingPanelBottomLayout()
self.fpc.invalidateLayout()
}
}
@@ -41,6 +41,10 @@ final class TabBarContentViewController: UIViewController {
fpc.addPanel(toParent: self)
if #available(iOS 15, *) {
tabBarController?.tabBar.scrollEdgeAppearance = UITabBarAppearance()
}
switch tabBarItem.tag {
case 1:
fpc.behavior = TwoTabBarPanelBehavior()
@@ -6,9 +6,10 @@ 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
@@ -21,8 +22,6 @@ final class MainViewController: UIViewController {
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.largeTitleDisplayMode = .automatic
} else {
// Fallback on earlier versions
}
var insets = UIEdgeInsets.zero
insets.bottom += 69.0
@@ -47,8 +46,9 @@ final class MainViewController: UIViewController {
super.viewWillDisappear(animated)
observations.removeAll()
}
}
// MARK:- Actions
extension MainViewController {
@IBAction func showDebugMenu(_ sender: UIBarButtonItem) {
useCaseController.setUpSettingsPanel(for: self)
}
@@ -4,6 +4,9 @@ import UIKit
@IBDesignable
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 @@ final 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))
@@ -4,14 +4,10 @@ import UIKit
import FloatingPanel
final class PagePanelController: NSObject {
lazy var 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
})
var pages: [UIViewController] = []
}
extension PagePanelController {
func makePageViewControllerForContent() -> UIPageViewController {
pages = [DebugTableViewController(), DebugTableViewController(), DebugTableViewController()]
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
@@ -22,6 +18,13 @@ final class PagePanelController: NSObject {
}
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)
+38 -24
View File
@@ -1,6 +1,6 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import Foundation
import UIKit
enum UseCase: Int, CaseIterable {
case trackingTableView
@@ -23,7 +23,9 @@ enum UseCase: Int, CaseIterable {
case showAdaptivePanel
case showAdaptivePanelWithCustomGuide
case showCustomStatePanel
}
extension UseCase {
var name: String {
switch self {
case .trackingTableView: return "Scroll tracking(TableView)"
@@ -49,30 +51,42 @@ enum UseCase: Int, CaseIterable {
}
}
var storyboardID: String? {
private enum Content {
case storyboard(String)
case viewController(UIViewController)
}
private var content: Content {
switch self {
case .trackingTableView: return nil
case .trackingTextView: return "ConsoleViewController" // Storyboard only
case .showDetail: return String(describing: DetailViewController.self)
case .showModal: return String(describing: ModalViewController.self)
case .showMultiPanelModal: return nil
case .showPanelInSheetModal: return nil
case .showPanelModal: return nil
case .showTabBar: return String(describing: TabBarViewController.self)
case .showPageView: return nil
case .showPageContentView: return nil
case .showNestedScrollView: return String(describing: NestedScrollViewController.self)
case .showRemovablePanel: return String(describing: DetailViewController.self)
case .showIntrinsicView: return "IntrinsicViewController" // Storyboard only
case .showContentInset: return nil
case .showContainerMargins: return nil
case .showNavigationController: return "RootNavigationController" // Storyboard only
case .showTopPositionedPanel: return nil
case .showAdaptivePanel,
.showAdaptivePanelWithCustomGuide:
return String(describing: ImageViewController.self)
case .showCustomStatePanel:
return nil
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
}
}
}
@@ -5,48 +5,83 @@ import FloatingPanel
final class UseCaseController: NSObject {
unowned let mainVC: MainViewController
private(set) var useCase: UseCase = .trackingTableView
private(set) var useCase: UseCase
fileprivate var mainPanelVC: FloatingPanelController!
private var mainPanelVC: FloatingPanelController!
private var detailPanelVC: FloatingPanelController!
private var settingsPanelVC: FloatingPanelController!
private lazy var pagePanelController = PagePanelController()
private var mainPanelObserves: [NSKeyValueObservation] = []
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!)
detailPanelVC?.removePanelFromParent(animated: true, completion: nil)
detailPanelVC = nil
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:
detailPanelVC?.removePanelFromParent(animated: false)
// Initialize FloatingPanelController
detailPanelVC = FloatingPanelController()
detailPanelVC.delegate = self
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
detailPanelVC.surfaceView.appearance = appearance
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
// Set a content view controller
detailPanelVC.set(contentViewController: contentVC)
detailPanelVC.contentMode = .fitToBounds
fpc.set(contentViewController: contentVC)
fpc.contentMode = .fitToBounds
(contentVC as? DetailViewController)?.intrinsicHeightConstraint.isActive = false
detailPanelVC = fpc
// Add FloatingPanel to self.view
detailPanelVC.addPanel(toParent: mainVC, animated: true)
fpc.addPanel(toParent: mainVC, animated: true)
case .showModal, .showTabBar:
let modalVC = contentVC
modalVC.modalPresentationStyle = .fullScreen
@@ -57,8 +92,50 @@ final class UseCaseController: NSObject {
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()
self.addMainPanel(with: pageVC)
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")
@@ -96,6 +173,7 @@ final class UseCaseController: NSObject {
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
@@ -110,7 +188,6 @@ final class UseCaseController: NSObject {
case .showContainerMargins:
let fpc = FloatingPanelController()
let appearance = SurfaceAppearance()
appearance.cornerRadius = 38.5
fpc.surfaceView.appearance = appearance
@@ -126,81 +203,49 @@ final class UseCaseController: NSObject {
fpc.delegate = self
fpc.isRemovalInteractionEnabled = true
mainVC.present(fpc, animated: true, completion: nil)
default:
self.addMainPanel(with: contentVC)
}
}
private func addMainPanel(with contentVC: UIViewController) {
mainPanelObserves.removeAll()
case .showNavigationController:
let fpc = FloatingPanelController()
fpc.contentInsetAdjustmentBehavior = .never
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
let oldMainPanelVC = mainPanelVC
case .showTopPositionedPanel: // For debug
let fpc = FloatingPanelController()
let contentVC = UIViewController()
contentVC.view.backgroundColor = .red
fpc.set(contentViewController: contentVC)
addMain(panel: fpc)
mainPanelVC = FloatingPanelController()
mainPanelVC.delegate = self
mainPanelVC.contentInsetAdjustmentBehavior = .always
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
mainPanelVC.surfaceView.appearance = appearance
set(contentViewController: contentVC)
useCase.setUpInteraction(for: self)
// Add FloatingPanel to self.view
if let oldMainPanelVC = oldMainPanelVC {
oldMainPanelVC.removePanelFromParent(animated: true, completion: {
self.mainPanelVC.addPanel(toParent: self.mainVC, animated: true)
})
} else {
mainPanelVC.addPanel(toParent: mainVC, animated: true)
}
}
private func set(contentViewController contentVC: UIViewController) {
mainPanelVC.set(contentViewController: contentVC)
// Track a scroll view
switch contentVC {
case let consoleVC as DebugTextViewController:
mainPanelVC.track(scrollView: consoleVC.textView)
case let contentVC as DebugTableViewController:
let ob = contentVC.tableView.observe(\.isEditing) { (tableView, _) in
self.mainPanelVC.panGestureRecognizer.isEnabled = !tableView.isEditing
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)
}
}
mainPanelObserves.append(ob)
mainPanelVC.track(scrollView: contentVC.tableView)
case let contentVC as NestedScrollViewController:
mainPanelVC.track(scrollView: contentVC.scrollView)
case let navVC as UINavigationController:
if let rootVC = (navVC.topViewController as? MainViewController) {
rootVC.loadViewIfNeeded()
mainPanelVC.track(scrollView: rootVC.tableView)
}
case let contentVC as ImageViewController:
if #available(iOS 11.0, *) {
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithCustomGuide) ? .withHeaderFooter : .onlyImage
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
mainPanelVC.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
} else {
mainPanelVC.layout = ImageViewController.PanelLayout(targetGuide: nil)
}
mainPanelVC.delegate = nil
mainPanelVC.isRemovalInteractionEnabled = true
mainPanelVC.track(scrollView: contentVC.scrollView)
default:
break
}
}
addMain(panel: fpc)
@objc
fileprivate func handleSurface(tapGesture: UITapGestureRecognizer) {
switch mainPanelVC.state {
case .full:
mainPanelVC.move(to: .half, animated: true)
default:
mainPanelVC.move(to: .full, animated: true)
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)
}
}
@@ -225,6 +270,28 @@ final class UseCaseController: NSObject {
// 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 {
@@ -274,54 +341,42 @@ extension UseCaseController: FloatingPanelControllerDelegate {
extension UseCaseController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
switch useCase {
case .showNestedScrollView:
if case .showNestedScrollView = useCase {
return true
default:
} else {
return false
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
false
}
}
extension UseCase {
func makeContentViewController(with storyboard: UIStoryboard) -> UIViewController {
guard let storyboardID = self.storyboardID else { return DebugTableViewController() }
return storyboard.instantiateViewController(withIdentifier: storyboardID)
}
private extension FloatingPanelController {
func ext_trackScrollView(in contentVC: UIViewController) {
switch contentVC {
case let consoleVC as DebugTextViewController:
track(scrollView: consoleVC.textView)
func setUpInteraction(for useCaseController: UseCaseController) {
let mainVC = useCaseController.mainVC
let mainPanelVC = useCaseController.mainPanelVC!
// Enable tap-to-hide and removal interaction
switch self {
case .trackingTableView:
let tapGesture = UITapGestureRecognizer(target: useCaseController, 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
mainPanelVC.surfaceView.addGestureRecognizer(tapGesture)
case .showNestedScrollView:
mainPanelVC.panGestureRecognizer.delegateProxy = useCaseController
case .showPageContentView:
if let page = (mainPanelVC.contentViewController as? UIPageViewController)?.viewControllers?.first {
mainPanelVC.track(scrollView: (page as! DebugTableViewController).tableView)
case let contentVC as DebugTableViewController:
let ob = contentVC.tableView.observe(\.isEditing) { [weak self] (tableView, _) in
self?.panGestureRecognizer.isEnabled = !tableView.isEditing
}
case .showRemovablePanel, .showIntrinsicView:
mainPanelVC.isRemovalInteractionEnabled = true
mainPanelVC.backdropView.dismissalTapGestureRecognizer.isEnabled = true
case .showNavigationController:
mainPanelVC.contentInsetAdjustmentBehavior = .never
case .showTopPositionedPanel: // For debug
let contentVC = UIViewController()
contentVC.view.backgroundColor = .red
mainPanelVC.set(contentViewController: contentVC)
mainPanelVC.addPanel(toParent: mainVC, animated: true)
return
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
}
@@ -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,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,6 +1,6 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
#import "ViewController.h"
#import "MainViewController.h"
@import FloatingPanel;
// Defining a custom FloatingPanelState
@@ -19,10 +19,10 @@ static FloatingPanelState *_lastQuart;
}
@end
@interface ViewController()<FloatingPanelControllerDelegate>
@interface MainViewController()<FloatingPanelControllerDelegate>
@end
@implementation ViewController
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
@@ -36,7 +36,7 @@ static FloatingPanelState *_lastQuart;
[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];
@@ -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.3.1"
s.version = "2.5.2"
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.
+8 -1
View File
@@ -33,6 +33,7 @@
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 */
@@ -83,6 +84,7 @@
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 */
@@ -151,6 +153,7 @@
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
545DB9C52151169500CA77B8 /* Info.plist */,
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
);
path = Sources;
sourceTree = "<group>";
@@ -256,7 +259,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = scenee;
TargetAttributes = {
545DB9C02151169500CA77B8 = {
@@ -335,6 +338,7 @@
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
545DBA2B2152383100CA77B8 /* GrabberView.swift in Sources */,
54E3992727141F5100A8F9ED /* FloatingPanel.docc in Sources */,
54352E9621A51A2500CBCA08 /* Transitioning.swift in Sources */,
5469F4AE24B30D7E00537F8A /* State.swift in Sources */,
);
@@ -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>
+15 -2
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)
@@ -55,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)
@@ -287,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
@@ -564,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)
}
```
@@ -658,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!
}
+1 -1
View File
@@ -46,7 +46,7 @@ public protocol FloatingPanelBehavior {
/// Returns the velocity threshold for the default interactive removal gesture.
///
/// In case `floatingPanel:shouldRemoveAt:with` is implemented, this value will not be used. The default value of `FloatingPanelDefaultBehavior` is 5.5
/// 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 }
}
+33 -30
View File
@@ -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
@@ -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
@@ -500,6 +497,7 @@ open class FloatingPanelController: UIViewController {
show(animated: animated) { [weak self] in
guard let self = self else { return }
self.didMove(toParent: parent)
completion?()
}
}
@@ -531,6 +529,7 @@ 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.
@@ -552,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)
}
@@ -607,15 +606,19 @@ open class FloatingPanelController: UIViewController {
// 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)
}
+67 -29
View File
@@ -17,6 +17,19 @@ class Core: NSObject, UIGestureRecognizerDelegate {
didSet {
oldValue?.panGestureRecognizer.removeTarget(self, action: nil)
scrollView?.panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
if let cur = scrollView {
if oldValue == nil {
initialScrollOffset = cur.contentOffset
scrollBounce = cur.bounces
scrollIndictorVisible = cur.showsVerticalScrollIndicator
}
} else {
if let pre = oldValue {
pre.isDirectionalLockEnabled = false
pre.bounces = scrollBounce
pre.showsVerticalScrollIndicator = scrollIndictorVisible
}
}
}
}
@@ -24,7 +37,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
didSet {
log.debug("state changed: \(oldValue) -> \(state)")
if let vc = ownerVC {
vc.delegate?.floatingPanelDidChangePosition?(vc)
vc.delegate?.floatingPanelDidChangeState?(vc)
}
}
}
@@ -107,7 +120,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
completion?()
return
}
if state != layoutAdapter.edgeMostState {
if state != layoutAdapter.mostExpandedState {
lockScrollView()
}
tearDownActiveInteraction()
@@ -117,7 +130,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if animated {
let updateScrollView: () -> Void = { [weak self] in
guard let self = self else { return }
if self.state == self.layoutAdapter.edgeMostState, abs(self.layoutAdapter.offsetFromEdgeMost) <= 1.0 {
if self.state == self.layoutAdapter.mostExpandedState, abs(self.layoutAdapter.offsetFromMostExpandedAnchor) <= 1.0 {
self.unlockScrollView()
} else {
self.lockScrollView()
@@ -173,7 +186,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
} 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()
@@ -186,6 +199,30 @@ 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: target, forceLayout: true)
self.backdropView.alpha = self.getBackdropAlpha(for: target)
@@ -201,8 +238,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
@@ -353,7 +390,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)) -- \
@@ -367,7 +404,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)
@@ -385,7 +422,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if interactionInProgress {
lockScrollView()
} else {
if state == layoutAdapter.edgeMostState, self.transitionAnimator == nil {
if state == layoutAdapter.mostExpandedState, self.transitionAnimator == nil {
switch layoutAdapter.position {
case .top, .left:
if offsetDiff < 0 && velocity > 0 {
@@ -413,14 +450,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) {
@@ -506,15 +543,15 @@ class Core: NSObject, UIGestureRecognizerDelegate {
endAttraction(false)
}
if let animator = self.transitionAnimator {
guard 0 >= layoutAdapter.offsetFromEdgeMost else { return }
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 {
@@ -540,9 +577,9 @@ 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
}
@@ -602,7 +639,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
}
@@ -669,11 +706,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 }
self.stopScrolling(at: self.initialScrollOffset)
}
}
@@ -719,14 +756,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 != layoutAdapter.edgeMostState {
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 != layoutAdapter.edgeMostState,
if let scrollView = scrollView, targetPosition != layoutAdapter.mostExpandedState,
let isScrollEnabled = isScrollEnabled {
scrollView.isScrollEnabled = isScrollEnabled
}
@@ -760,11 +797,11 @@ 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 {
initialScrollOffset = contentOffsetForPinning(of: scrollView)
initialScrollOffset = scrollView.contentOffset
let offsetDiff = scrollView.contentOffset - contentOffsetForPinning(of: scrollView)
switch layoutAdapter.position {
case .top, .left:
@@ -805,7 +842,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()
}
@@ -813,6 +850,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
@@ -882,9 +920,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()
}
}
@@ -911,7 +949,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
@@ -919,7 +957,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
@@ -1015,7 +1053,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:
@@ -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.3.1</string>
<string>2.5.2</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+31 -35
View File
@@ -92,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
}
@@ -104,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
@@ -125,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 {
@@ -265,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)
}
}
@@ -641,7 +637,7 @@ class LayoutAdapter {
return
}
let anchor = layout.anchors[self.edgeMostState]!
let anchor = layout.anchors[self.mostExpandedState]!
let surfaceAnchor = position.mainDimensionAnchor(surfaceView)
switch anchor {
case let anchor as FloatingPanelIntrinsicLayoutAnchor:
@@ -662,11 +658,11 @@ class LayoutAdapter {
default:
switch position {
case .top, .left:
staticConstraint = surfaceAnchor.constraint(equalToConstant: position(for: self.directionalMostState))
staticConstraint = surfaceAnchor.constraint(equalToConstant: position(for: self.mostCoordinateState))
case .bottom, .right:
let rootViewAnchor = position.mainDimensionAnchor(vc.view)
staticConstraint = rootViewAnchor.constraint(equalTo: surfaceAnchor,
constant: position(for: self.directionalLeastState))
constant: position(for: self.leastCoordinateState))
}
}
@@ -687,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
@@ -779,7 +775,7 @@ class LayoutAdapter {
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
@@ -799,7 +795,7 @@ extension LayoutAdapter {
/// |-------|-------|===o===| |-------|===o===|-------|
/// pos: o/x, segment: =
let sortedStates = sortedDirectionalStates
let sortedStates = sortedAnchorStatesByCoordinate
let upperIndex: Int?
if forward {
+10 -5
View File
@@ -85,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()
} }
@@ -408,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)
@@ -424,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
+7
View File
@@ -766,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 {
+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) {