Compare commits

...

16 Commits

Author SHA1 Message Date
Shin Yamamoto 22d46c5260 Version 2.8.3 2024-06-12 09:21:33 +09:00
Will Bishop 466aaf21dd Fix a compilation error in Xcode 16 (#636)
This patch fixed the error at SurfaceView#L63 in Xcode 16 (#635):

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

dbef6a6 commit causes this issue.
2024-02-03 13:36:36 +09:00
Shin Yamamoto 0e0f773df7 Possible fix for #586 2024-02-03 10:27:21 +09:00
Shin Yamamoto be2be99537 Fix a typo 2023-12-02 09:03:31 +09:00
Shin Yamamoto afcf1ced36 ci: support xcode 15.0.1 2023-11-18 11:51:15 +09:00
Shin Yamamoto 5b33d3d5ff Version 2.8.1 2023-11-04 21:17:09 +09:00
Shin Yamamoto dbef6a691a Fix an invalid behavior after switching to a new layout object (#611)
* Added a test for the use case, ControllerTests.test_switching_layout()
2023-11-04 13:25:25 +09:00
23 changed files with 656 additions and 98 deletions
+82 -23
View File
@@ -11,67 +11,83 @@ on:
jobs:
build:
runs-on: ${{ matrix.runsOn }}
runs-on: ${{ matrix.runs-on }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- swift: "5.10"
xcode: "15.4"
runs-on: macos-14
- swift: "5.9"
xcode: "15.2"
runs-on: macos-13
- swift: "5.8"
xcode: "14.3"
runsOn: macos-13
xcode: "14.3.1"
runs-on: macos-13
- swift: "5.7"
xcode: "14.1"
runsOn: macos-12
runs-on: macos-12
- swift: "5.6"
xcode: "13.4.1"
runsOn: macos-12
runs-on: macos-12
- swift: "5.5"
xcode: "13.2.1"
runsOn: macos-11
runs-on: macos-12
- swift: "5.4"
xcode: "12.5.1"
runsOn: macos-11
runs-on: macos-11
- swift: "5.3"
xcode: "12.4"
runsOn: macos-11
runs-on: macos-11
- swift: "5.2"
xcode: "11.7"
runsOn: macos-11
runs-on: macos-11
steps:
- uses: actions/checkout@v3
- name: Building in Swift ${{ matrix.swift }}
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
test:
runs-on: ${{ matrix.runsOn }}
runs-on: ${{ matrix.runs-on }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
- os: "17.5"
xcode: "15.4"
sim: "iPhone 15 Pro"
parallel: NO # Stop random test job failures
runs-on: macos-14
- os: "16.4"
xcode: "14.3.1"
sim: "iPhone 14 Pro"
parallel: NO # Stop random test job failures
runsOn: macos-13
runs-on: macos-13
- os: "15.5"
xcode: "13.4.1"
sim: "iPhone 13 Pro"
parallel: NO # Stop random test job failures
runsOn: macos-12
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- name: Testing in iOS ${{ matrix.os }}
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' -parallel-testing-enabled '${{ matrix.parallel }}'
run: |
xcodebuild clean test \
-workspace FloatingPanel.xcworkspace \
-scheme FloatingPanel \
-destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \
-parallel-testing-enabled '${{ matrix.parallel }}'
timeout-minutes: 20
example:
runs-on: macos-12
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
strategy:
fail-fast: false
matrix:
@@ -83,26 +99,67 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Building ${{ matrix.example }}
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
run: |
xcodebuild clean build \
-workspace FloatingPanel.xcworkspace \
-scheme ${{ matrix.example }} \
-sdk iphonesimulator
swiftpm:
runs-on: macos-12
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
strategy:
fail-fast: false
matrix:
platform: [iphoneos, iphonesimulator]
arch: [x86_64, arm64]
exclude:
- platform: iphoneos
arch: x86_64
include:
# 17.2
- platform: iphoneos
sys: "ios17.2"
- platform: iphonesimulator
sys: "ios17.2-simulator"
steps:
- uses: actions/checkout@v3
- name: "Swift Package Manager build"
run: |
xcrun swift build \
--sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \
-Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}"
swiftpm_old:
runs-on: ${{ matrix.runs-on }}
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
strategy:
fail-fast: false
matrix:
include:
# 16.4
- target: "x86_64-apple-ios16.4-simulator"
xcode: "14.3.1"
runs-on: macos-13
- target: "arm64-apple-ios16.4-simulator"
xcode: "14.3.1"
runs-on: macos-13
# 15.7
- target: "x86_64-apple-ios15.7-simulator"
xcode: "14.1"
runs-on: macos-12
- target: "arm64-apple-ios15.7-simulator"
# 16.1
- target: "x86_64-apple-ios16.1-simulator"
- target: "arm64-apple-ios16.1-simulator"
xcode: "14.1"
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- name: "Swift Package Manager build"
run: swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
run: |
swift build \
-Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" \
-Xswiftc "-target" -Xswiftc "${{ matrix.target }}"
carthage:
runs-on: macos-11
@@ -112,7 +169,9 @@ jobs:
run: carthage build --use-xcframeworks --no-skip-current
cocoapods:
runs-on: macos-12
runs-on: macos-14
env:
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
steps:
- uses: actions/checkout@v3
- name: "CocoaPods: pod lib lint"
@@ -18,7 +18,7 @@
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E24925FC53C100A26F43 /* DebugTextViewController.swift */; };
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5442E25125FC541700A26F43 /* NestedScrollViewController.swift */; };
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54496C58263A7E5A0031E0C8 /* UseCaseController.swift */; };
544BC56826CC918200D0A436 /* AdaptiveLayoutTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */; };
544BC56826CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */; };
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9ED21511E6300CA77B8 /* AppDelegate.swift */; };
545DB9F021511E6300CA77B8 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9EF21511E6300CA77B8 /* MainViewController.swift */; };
545DB9F321511E6300CA77B8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F121511E6300CA77B8 /* Main.storyboard */; };
@@ -30,8 +30,11 @@
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* Extensions.swift */; };
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */; };
54E58CB72BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */; };
54EAD35B263A75EB006A36EA /* PanelLayouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD35A263A75EB006A36EA /* PanelLayouts.swift */; };
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EAD364263A765F006A36EA /* PagePanelController.swift */; };
54F185822BF4AD0000916F57 /* DebugListCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */; };
54F185842BF4B82E00916F57 /* UnavailableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54F185832BF4B82E00916F57 /* UnavailableViewController.swift */; };
5D82A6AD28D1843C006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */; };
/* End PBXBuildFile section */
@@ -61,7 +64,7 @@
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTextViewController.swift; sourceTree = "<group>"; };
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedScrollViewController.swift; sourceTree = "<group>"; };
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCaseController.swift; sourceTree = "<group>"; };
544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveLayoutTestViewController.swift; sourceTree = "<group>"; };
544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewControllerForAdaptiveLayout.swift; sourceTree = "<group>"; };
545DB9EA21511E6300CA77B8 /* Samples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Samples.app; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
545DB9EF21511E6300CA77B8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
@@ -74,8 +77,11 @@
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54B51115216AFE5F0033A6F3 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
54CDC5D7215BBE23007D205C /* SupplementaryViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupplementaryViews.swift; sourceTree = "<group>"; };
54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewControllerForAdaptiveLayout.swift; sourceTree = "<group>"; };
54EAD35A263A75EB006A36EA /* PanelLayouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelLayouts.swift; sourceTree = "<group>"; };
54EAD364263A765F006A36EA /* PagePanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagePanelController.swift; sourceTree = "<group>"; };
54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugListCollectionViewController.swift; sourceTree = "<group>"; };
54F185832BF4B82E00916F57 /* UnavailableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnavailableViewController.swift; sourceTree = "<group>"; };
5D82A6AC28D18438006A44BA /* libswiftCoreGraphics.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftCoreGraphics.tbd; path = usr/lib/swift/libswiftCoreGraphics.tbd; sourceTree = SDKROOT; };
/* End PBXFileReference section */
@@ -95,17 +101,18 @@
5442E22225FC519700A26F43 /* ContentViewControllers */ = {
isa = PBXGroup;
children = (
54E58CB52BF8A88600408EA9 /* AdaptiveLayout */,
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */,
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */,
54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */,
5442E23325FC528400A26F43 /* DetailViewController.swift */,
5442E24325FC538200A26F43 /* InspectorViewController.swift */,
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */,
5442E23925FC52CD00A26F43 /* ModalViewController.swift */,
5442E22725FC51E200A26F43 /* MultiPanelController.swift */,
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */,
5442E22F25FC525200A26F43 /* TabBarViewController.swift */,
544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */,
54F185832BF4B82E00916F57 /* UnavailableViewController.swift */,
);
path = ContentViewControllers;
sourceTree = "<group>";
@@ -157,6 +164,16 @@
path = UseCases;
sourceTree = "<group>";
};
54E58CB52BF8A88600408EA9 /* AdaptiveLayout */ = {
isa = PBXGroup;
children = (
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */,
54E58CB62BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift */,
);
path = AdaptiveLayout;
sourceTree = "<group>";
};
5D82A6AB28D18438006A44BA /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -238,15 +255,17 @@
buildActionMask = 2147483647;
files = (
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */,
54E58CB72BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift in Sources */,
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */,
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */,
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
544BC56826CC918200D0A436 /* AdaptiveLayoutTestViewController.swift in Sources */,
544BC56826CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift in Sources */,
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */,
546341AC25C6426500CA0596 /* CustomState.swift in Sources */,
5442E23A25FC52CD00A26F43 /* ModalViewController.swift in Sources */,
5442E22425FC51AF00A26F43 /* ImageViewController.swift in Sources */,
5442E24025FC533800A26F43 /* DebugTableViewController.swift in Sources */,
54F185822BF4AD0000916F57 /* DebugListCollectionViewController.swift in Sources */,
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */,
5442E22825FC51E200A26F43 /* MultiPanelController.swift in Sources */,
546341A125C6415100CA0596 /* UseCase.swift in Sources */,
@@ -257,6 +276,7 @@
54EAD365263A765F006A36EA /* PagePanelController.swift in Sources */,
5442E24425FC538200A26F43 /* InspectorViewController.swift in Sources */,
5442E22C25FC521F00A26F43 /* SettingsViewController.swift in Sources */,
54F185842BF4B82E00916F57 /* UnavailableViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -801,10 +801,10 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
</objects>
<point key="canvasLocation" x="-2.1739130434782612" y="733.92857142857144"/>
</scene>
<!--Adaptive Layout Test View Controller-->
<!--Table View Controller For Adaptive Layout-->
<scene sceneID="rDI-lU-wEx">
<objects>
<viewController storyboardIdentifier="AdaptiveLayoutTestViewController" id="5nC-6E-bXf" customClass="AdaptiveLayoutTestViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="TableViewControllerForAdaptiveLayout" id="5nC-6E-bXf" customClass="TableViewControllerForAdaptiveLayout" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="jXL-Ss-NCJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -861,13 +861,13 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemOrangeColor">
<color red="1" green="0.58431372549019611" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemPurpleColor">
<color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
@@ -0,0 +1,175 @@
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
import UIKit
import FloatingPanel
@available(iOS 13.0, *)
class CollectionViewControllerForAdaptiveLayout: UIViewController {
class PanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
private unowned var targetGuide: UILayoutGuide
init(targetGuide: UILayoutGuide) {
self.targetGuide = targetGuide
}
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelAdaptiveLayoutAnchor(
absoluteOffset: 0.0,
contentLayout: targetGuide,
referenceGuide: .superview,
contentBoundingGuide: .safeArea
),
.half: FloatingPanelAdaptiveLayoutAnchor(
fractionalOffset: 0.5,
contentLayout: targetGuide,
referenceGuide: .superview,
contentBoundingGuide: .safeArea
),
]
}
}
enum LayoutType {
case flow
case compositional
}
weak var collectionView: UICollectionView!
var layoutType: LayoutType = .flow
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
private func setupCollectionView() {
let collectionViewLayout = {
switch layoutType {
case .flow:
CollectionViewLayoutFactory.flowLayout
case .compositional:
CollectionViewLayoutFactory.compositionalLayout
}
}()
let collectionView = IntrinsicCollectionView(
frame: .zero,
collectionViewLayout: collectionViewLayout
)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = .yellow
collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
view.addSubview(collectionView)
self.collectionView = collectionView
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
@available(iOS 13.0, *)
extension CollectionViewControllerForAdaptiveLayout: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5 // Only three cells needed to fill the space
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as! Cell
cell.configure(text: "Item \(indexPath.row)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width
return CGSize(width: width, height: 100)
}
}
@available(iOS 13.0, *)
extension CollectionViewControllerForAdaptiveLayout {
enum CollectionViewLayoutFactory {
static var flowLayout: UICollectionViewLayout {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 8 // Vertical spacing between rows
return layout
}
@available(iOS 13.0, *)
static var compositionalLayout: UICollectionViewLayout {
UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: itemSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 8 // Spacing between each group/item
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
return section
}
}
}
private final class Cell: UICollectionViewCell {
static let reuseIdentifier = "Cell"
private let label: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.textColor = .white
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
backgroundColor = .systemBlue
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
func configure(text: String) {
label.text = text
}
}
private final class IntrinsicCollectionView: UICollectionView {
override public var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override public var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
}
@@ -3,7 +3,7 @@
import UIKit
import FloatingPanel
final class AdaptiveLayoutTestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
final class TableViewControllerForAdaptiveLayout: UIViewController, UITableViewDataSource, UITableViewDelegate {
class PanelLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .full
@@ -75,7 +75,6 @@ final class AdaptiveLayoutTestViewController: UIViewController, UITableViewDataS
}
class IntrinsicTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
@@ -0,0 +1,77 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
@available(iOS 14, *)
class DebugListCollectionViewController: UIViewController {
enum Section {
case main
}
var dataSource: UICollectionViewDiffableDataSource<Section, Int>! = nil
var collectionView: UICollectionView! = nil
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
configureHierarchy()
configureDataSource()
}
}
@available(iOS 14, *)
extension DebugListCollectionViewController {
/// - Tag: List
private func createLayout() -> UICollectionViewLayout {
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.trailingSwipeActionsConfigurationProvider = { indexPath -> UISwipeActionsConfiguration? in
return UISwipeActionsConfiguration(
actions: [UIContextualAction(
style: .destructive,
title: "Delete",
handler: { _, _, completion in
// Do nothing now
}
)]
)
}
return UICollectionViewCompositionalLayout.list(using: config)
}
}
@available(iOS 14, *)
extension DebugListCollectionViewController {
private func configureHierarchy() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(collectionView)
collectionView.delegate = self
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
content.text = "\(item)"
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
snapshot.appendSections([.main])
snapshot.appendItems(Array(0..<94))
dataSource.apply(snapshot, animatingDifferences: false)
}
}
@available(iOS 14, *)
extension DebugListCollectionViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
}
@@ -0,0 +1,24 @@
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
import UIKit
class UnavailableViewController: UIViewController {
weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Unavailable content"
label.numberOfLines = 0
label.textAlignment = .center
label.frame = view.bounds
label.autoresizingMask = [
.flexibleTopMargin,
.flexibleLeftMargin,
.flexibleBottomMargin,
.flexibleRightMargin
]
view.addSubview(label)
self.label = label
}
}
@@ -5,6 +5,7 @@ import UIKit
enum UseCase: Int, CaseIterable {
case trackingTableView
case trackingTextView
case trackingCollectionViewList
case showDetail
case showModal
case showPanelModal
@@ -22,14 +23,18 @@ enum UseCase: Int, CaseIterable {
case showNavigationController
case showTopPositionedPanel
case showAdaptivePanel
case showAdaptivePanelWithCustomGuide
case showAdaptivePanelWithTableView
case showAdaptivePanelWithCollectionView
case showAdaptivePanelWithCompositionalCollectionView
case showCustomStatePanel
case showCustomBackdrop
}
extension UseCase {
var name: String {
switch self {
case .trackingTableView: return "Scroll tracking(TableView)"
case .trackingCollectionViewList: return "Scroll tracking(List CollectionView)"
case .trackingTextView: return "Scroll tracking(TextView)"
case .showDetail: return "Show Detail Panel"
case .showModal: return "Show Modal"
@@ -48,8 +53,11 @@ extension UseCase {
case .showNavigationController: return "Show Navigation Controller"
case .showTopPositionedPanel: return "Show Top Positioned Panel"
case .showAdaptivePanel: return "Show Adaptive Panel"
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
case .showAdaptivePanelWithTableView: return "Show Adaptive Panel (TableView)"
case .showAdaptivePanelWithCollectionView: return "Show Adaptive Panel (CollectionView)"
case .showAdaptivePanelWithCompositionalCollectionView: return "Show Adaptive Panel (Compositional CollectionView)"
case .showCustomStatePanel: return "Show Panel with Custom state"
case .showCustomBackdrop: return "Show Panel with Custom Backdrop"
}
}
}
@@ -63,6 +71,13 @@ extension UseCase {
private var content: Content {
switch self {
case .trackingTableView: return .viewController(DebugTableViewController())
case .trackingCollectionViewList:
if #available(iOS 14, *) {
return .viewController(DebugListCollectionViewController())
} else {
let msg = "UICollectionLayoutListConfiguration is unavailable.\nBuild this app on iOS 14 and later."
return makeUnavailableViewContent(message: msg)
}
case .trackingTextView: return .storyboard("ConsoleViewController") // Storyboard only
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
case .showModal: return .storyboard(String(describing: ModalViewController.self))
@@ -81,8 +96,19 @@ extension UseCase {
case .showNavigationController: return .storyboard("RootNavigationController") // Storyboard only
case .showTopPositionedPanel: return .viewController(DebugTableViewController())
case .showAdaptivePanel: return .storyboard(String(describing: ImageViewController.self))
case .showAdaptivePanelWithCustomGuide: return .storyboard(String(describing: AdaptiveLayoutTestViewController.self))
case .showAdaptivePanelWithTableView: return .storyboard(String(describing: TableViewControllerForAdaptiveLayout.self))
case .showAdaptivePanelWithCollectionView,
.showAdaptivePanelWithCompositionalCollectionView:
if #available(iOS 13, *) {
let vc = CollectionViewControllerForAdaptiveLayout()
vc.layoutType = self == .showAdaptivePanelWithCollectionView ? .flow : .compositional
return .viewController(vc)
} else {
let msg = "Compositional layout is unavailable.\nBuild this app on iOS 13 and later."
return makeUnavailableViewContent(message: msg)
}
case .showCustomStatePanel: return .viewController(DebugTableViewController())
case .showCustomBackdrop: return .viewController(UIViewController())
}
}
@@ -95,4 +121,11 @@ extension UseCase {
return vc
}
}
private func makeUnavailableViewContent(message: String) -> Content {
let vc = UnavailableViewController()
vc.loadViewIfNeeded()
vc.label.text = message
return .viewController(vc)
}
}
@@ -52,6 +52,30 @@ extension UseCaseController {
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .trackingCollectionViewList:
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(UseCaseController.handleSurface(tapGesture:)))
tapGesture.cancelsTouchesInView = false
tapGesture.numberOfTapsRequired = 2
// Prevents a delay to response a tap in menus of DebugTableViewController.
tapGesture.delaysTouchesEnded = false
fpc.surfaceView.addGestureRecognizer(tapGesture)
fpc.set(contentViewController: contentVC)
if #available(iOS 14, *),
let scrollView = (fpc.contentViewController as? DebugListCollectionViewController)?.collectionView {
fpc.track(scrollView: scrollView)
}
addMain(panel: fpc)
case .trackingTextView:
let fpc = FloatingPanelController()
fpc.delegate = self
@@ -239,13 +263,13 @@ extension UseCaseController {
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
if case let contentVC as ImageViewController = contentVC {
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithCustomGuide) ? .withHeaderFooter : .onlyImage
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithTableView) ? .withHeaderFooter : .onlyImage
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
fpc.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
}
addMain(panel: fpc)
case .showAdaptivePanelWithCustomGuide:
case .showAdaptivePanelWithTableView:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.contentInsetAdjustmentBehavior = .always
@@ -255,10 +279,25 @@ extension UseCaseController {
return appearance
}()
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
fpc.layout = AdaptiveLayoutTestViewController.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
fpc.track(scrollView: (contentVC as! TableViewControllerForAdaptiveLayout).tableView)
fpc.layout = TableViewControllerForAdaptiveLayout.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
addMain(panel: fpc)
case .showAdaptivePanelWithCollectionView, .showAdaptivePanelWithCompositionalCollectionView:
let fpc = FloatingPanelController()
fpc.isRemovalInteractionEnabled = true
fpc.contentInsetAdjustmentBehavior = .always
fpc.surfaceView.appearance = {
let appearance = SurfaceAppearance()
appearance.cornerRadius = 6.0
return appearance
}()
fpc.set(contentViewController: contentVC)
if #available(iOS 13, *) {
fpc.track(scrollView: (contentVC as! CollectionViewControllerForAdaptiveLayout).collectionView)
fpc.layout = CollectionViewControllerForAdaptiveLayout.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
}
addMain(panel: fpc)
case .showCustomStatePanel:
@@ -273,6 +312,52 @@ extension UseCaseController {
fpc.set(contentViewController: contentVC)
fpc.ext_trackScrollView(in: contentVC)
addMain(panel: fpc)
case .showCustomBackdrop:
class BlurBackdropView: BackdropView {
var effectView: UIVisualEffectView!
override var alpha: CGFloat {
set {
effectView.alpha = newValue
}
get {
effectView.alpha
}
}
override init() {
super.init()
let effect = UIBlurEffect(style: .prominent)
let effectView = UIVisualEffectView(effect: effect)
addSubview(effectView)
effectView.frame = bounds
effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.effectView = effectView
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CustomBottomLayout: FloatingPanelBottomLayout {
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
return [
.full: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(fractionalInset: 0.1, edge: .bottom, referenceGuide: .safeArea),
]
}
override func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
return state == .full ? 0.8 : 0.0
}
}
let fpc = FloatingPanelController()
fpc.delegate = self
fpc.set(contentViewController: contentVC)
fpc.backdropView = BlurBackdropView()
fpc.layout = CustomBottomLayout()
addMain(panel: fpc)
}
}
@@ -412,7 +497,7 @@ private extension FloatingPanelController {
case let contentVC as ImageViewController:
track(scrollView: contentVC.scrollView)
case let contentVC as AdaptiveLayoutTestViewController:
case let contentVC as TableViewControllerForAdaptiveLayout:
track(scrollView: contentVC.tableView)
default:
@@ -81,7 +81,7 @@ class MainViewController: UIViewController, FloatingPanelControllerDelegate {
}
private func hideStockTickerBanner() {
// Dimiss top bar with dissolve animation
// Dismiss top bar with dissolve animation
UIView.animate(withDuration: 0.25) {
self.topBannerView.alpha = 0.0
self.labelStackView.alpha = 1.0
+1 -1
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "2.8.0"
s.version = "2.8.3"
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.
+6
View File
@@ -468,6 +468,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -499,6 +500,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -521,6 +523,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -541,6 +544,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -633,6 +637,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
@@ -657,6 +662,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
+1 -1
View File
@@ -9,7 +9,7 @@
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in Apple Maps, Shortcuts and Stocks app.
The user interface displays related content and utilities alongside the main content.
Please see also [the API reference](https://floatingpanel.github.io/2.8.0/documentation/floatingpanel/) for more details.
Please see also [the API reference@SPI](https://swiftpackageindex.com/scenee/FloatingPanel/2.8.3/documentation/floatingpanel) for more details.
![Maps](https://github.com/SCENEE/FloatingPanel/blob/master/assets/maps.gif)
![Stocks](https://github.com/SCENEE/FloatingPanel/blob/master/assets/stocks.gif)
+3 -3
View File
@@ -4,7 +4,7 @@ import UIKit
/// A view that presents a backdrop interface behind a panel.
@objc(FloatingPanelBackdropView)
public class BackdropView: UIView {
open class BackdropView: UIView {
/// The gesture recognizer for tap gestures to dismiss a panel.
///
@@ -12,14 +12,14 @@ public class BackdropView: UIView {
/// To dismiss a panel by tap gestures on the backdrop, `dismissalTapGestureRecognizer.isEnabled` is set to true.
@objc public var dismissalTapGestureRecognizer: UITapGestureRecognizer
init() {
public init() {
dismissalTapGestureRecognizer = UITapGestureRecognizer()
dismissalTapGestureRecognizer.isEnabled = false
super.init(frame: .zero)
addGestureRecognizer(dismissalTapGestureRecognizer)
}
required init?(coder: NSCoder) {
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
+5 -3
View File
@@ -158,14 +158,15 @@ open class FloatingPanelController: UIViewController {
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
@objc
public var surfaceView: SurfaceView! {
public var surfaceView: SurfaceView {
return floatingPanel.surfaceView
}
/// Returns the backdrop view managed by the controller object.
@objc
public var backdropView: BackdropView! {
return floatingPanel.backdropView
public var backdropView: BackdropView {
set { floatingPanel.backdropView = newValue }
get { return floatingPanel.backdropView }
}
/// Returns the scroll view that the controller tracks.
@@ -732,6 +733,7 @@ private var originalDismissImp: IMP?
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
extension FloatingPanelController {
private static let dismissSwizzling: Void = {
guard originalDismissImp == nil else { return }
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
if let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:))),
let swizzledImp = class_getMethodImplementation(aClass, #selector(__swizzled_dismiss(animated:completion:))) {
+46 -25
View File
@@ -10,7 +10,13 @@ class Core: NSObject, UIGestureRecognizerDelegate {
private weak var ownerVC: FloatingPanelController?
let surfaceView: SurfaceView
let backdropView: BackdropView
var backdropView: BackdropView {
didSet {
backdropView.dismissalTapGestureRecognizer
.addTarget(self, action: #selector(handleBackdrop(tapGesture:)))
}
}
let layoutAdapter: LayoutAdapter
let behaviorAdapter: BehaviorAdapter
@@ -24,6 +30,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
scrollBounce = cur.bounces
scrollIndictorVisible = cur.showsVerticalScrollIndicator
}
scrollLocked = false
} else {
if let pre = oldValue {
pre.isDirectionalLockEnabled = false
@@ -68,6 +75,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
private var scrollBounce = false
private var scrollIndictorVisible = false
private var scrollBounceThreshold: CGFloat = -30.0
private var scrollLocked = false
// MARK: - Interface
@@ -209,6 +217,9 @@ class Core: NSObject, UIGestureRecognizerDelegate {
contentOffset = scrollView?.contentOffset
}
if layoutAdapter.validStates.contains(state) == false {
state = layoutAdapter.initialState
}
layoutAdapter.updateStaticConstraint()
layoutAdapter.activateLayout(for: state, forceLayout: forceLayout)
@@ -577,11 +588,16 @@ class Core: NSObject, UIGestureRecognizerDelegate {
// When no scrollView, nothing to handle.
guard let scrollView = scrollView, scrollView.frame.contains(initialLocation) else { return false }
// For _UISwipeActionPanGestureRecognizer
if let scrollGestureRecognizers = scrollView.gestureRecognizers {
// Prevents moving a panel on swipe actions using _UISwipeActionPanGestureRecognizer.
// [Warning] Do not apply this to WKWebView. Since iOS 17.4, WKWebView has an additional pan
// gesture recognizer besides UIScrollViewPanGestureRecognizer. Applying this to WKWebView
// will block panel movements because another pan gesture isn't `scrollView.panGestureRecognizer`.
if let scrollGestureRecognizers = scrollView.gestureRecognizers,
scrollView is UITableView || scrollView is UICollectionView {
for gesture in scrollGestureRecognizers {
guard gesture.state == .began || gesture.state == .changed
else { continue }
guard gesture.state == .began || gesture.state == .changed else {
continue
}
if gesture != scrollView.panGestureRecognizer {
return true
@@ -725,14 +741,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
guard shouldAttract(to: target) else {
self.state = target
self.updateLayout(to: target)
self.unlockScrollView()
// The `floatingPanelDidEndDragging(_:willAttract:)` must be called after the state property changes.
// This allows library users to get the correct state in the delegate method.
if let vc = ownerVC {
vc.delegate?.floatingPanelDidEndDragging?(vc, willAttract: false)
}
self.endWithoutAttraction(target)
return
}
@@ -875,6 +884,17 @@ class Core: NSObject, UIGestureRecognizerDelegate {
return true
}
func endWithoutAttraction(_ target: FloatingPanelState) {
self.state = target
self.updateLayout(to: target)
self.unlockScrollView()
// The `floatingPanelDidEndDragging(_:willAttract:)` must be called after the state property changes.
// This allows library users to get the correct state in the delegate method.
if let vc = ownerVC {
vc.delegate?.floatingPanelDidEndDragging?(vc, willAttract: false)
}
}
private func startAttraction(to state: FloatingPanelState, with velocity: CGPoint, completion: @escaping (() -> Void)) {
os_log(msg, log: devLog, type: .debug, "startAnimation to \(state) -- velocity = \(value(of: velocity))")
guard let vc = ownerVC else { return }
@@ -1049,29 +1069,25 @@ class Core: NSObject, UIGestureRecognizerDelegate {
private func lockScrollView(strict: Bool = false) {
guard let scrollView = scrollView else { return }
if scrollLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll locked")
return
}
scrollBounce = scrollView.bounces
if !strict, shouldLooselyLockScrollView {
if scrollView.isLooselyLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll locked loosely.")
return
}
// Don't change its `bounces` property. If it's changed, it will cause its scroll content offset jump at
// the most expanded anchor position while seamlessly scrolling content. This problem only occurs where its
// content mode is `.fitToBounds` and the tracking scroll content is smaller than the content view size.
// The reason why is because `bounces` prop change leads to the "content frame" change on `.fitToBounds`.
// See also https://github.com/scenee/FloatingPanel/issues/524.
} else {
if scrollView.isLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll locked.")
return
}
scrollBounce = scrollView.bounces
scrollView.bounces = false
}
os_log(msg, log: devLog, type: .debug, "lock scroll view")
scrollView.isDirectionalLockEnabled = true
scrollLocked = true
scrollView.isDirectionalLockEnabled = true
switch layoutAdapter.position {
case .top, .bottom:
scrollIndictorVisible = scrollView.showsVerticalScrollIndicator
@@ -1083,9 +1099,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
}
private func unlockScrollView() {
guard let scrollView = scrollView, scrollView.isLocked else { return }
guard let scrollView = scrollView else { return }
if !scrollLocked {
os_log(msg, log: devLog, type: .debug, "Already scroll unlocked.")
return
}
os_log(msg, log: devLog, type: .debug, "unlock scroll view")
scrollLocked = false
scrollView.bounces = scrollBounce
scrollView.isDirectionalLockEnabled = false
switch layoutAdapter.position {
-6
View File
@@ -109,12 +109,6 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
#endif
extension UIScrollView {
var isLocked: Bool {
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
}
var isLooselyLocked: Bool {
return !showsVerticalScrollIndicator && isDirectionalLockEnabled
}
var fp_contentOffsetMax: CGPoint {
return CGPoint(x: max((contentSize.width + adjustedContentInset.right) - bounds.width, 0.0),
y: max((contentSize.height + adjustedContentInset.bottom) - bounds.height, 0.0))
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.8.0</string>
<string>2.8.3</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
-6
View File
@@ -782,12 +782,6 @@ class LayoutAdapter {
NSLayoutConstraint.activate(constraint: self.fitToBoundsConstraint)
}
var state = state
if validStates.contains(state) == false {
state = layout.initialState
}
// Recalculate the intrinsic size of a content view. This is because
// UIView.systemLayoutSizeFitting() returns a different size between an
// on-screen and off-screen view which includes
+23 -5
View File
@@ -56,11 +56,11 @@ public class SurfaceAppearance: NSObject {
/// Defaults to `.circular`.
@available(iOS 13.0, *)
public var cornerCurve: CALayerCornerCurve {
get { _cornerCurve ?? .circular }
get { _cornerCurve as? CALayerCornerCurve ?? .circular }
set { _cornerCurve = newValue }
}
private var _cornerCurve: CALayerCornerCurve?
private var _cornerCurve: Any?
/// An array of shadows used to create drop shadows underneath a surface view.
public var shadows: [Shadow] = [Shadow()]
@@ -418,12 +418,30 @@ public class SurfaceView: UIView {
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: containerMargins.left + contentPadding.left)
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: containerMargins.right + contentPadding.right)
let bottomConstraint = bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: containerMargins.bottom + contentPadding.bottom)
NSLayoutConstraint.activate([
var constraints = [
topConstraint,
leftConstraint,
rightConstraint,
bottomConstraint,
].map {
bottomConstraint
]
// This constraint is for UICollectionView using UICollectionViewCompositionalLayout.
// It's seemingly obvious, but the UICollectionView doesn't work without setting it. (#628)
switch position {
case .top, .bottom:
constraints += [
heightAnchor.constraint(greaterThanOrEqualToConstant: 1.0)
]
case .left, .right:
constraints += [
widthAnchor.constraint(greaterThanOrEqualToConstant: 1.0)
]
}
NSLayoutConstraint.activate(
constraints
.map {
switch mode {
case .static:
$0.priority = .required
+30
View File
@@ -334,6 +334,36 @@ class ControllerTests: XCTestCase {
fpc.move(to: .tip, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
}
func test_switching_layout() {
final class FirstLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .half
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
]
}
final class SecondLayout: FloatingPanelLayout {
let position: FloatingPanelPosition = .bottom
let initialState: FloatingPanelState = .half
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea)
]
}
let fpc = FloatingPanelController()
fpc.layout = FirstLayout()
fpc.showForTest()
fpc.move(to: .tip, animated: false)
// Switch to another layout
fpc.layout = SecondLayout()
fpc.invalidateLayout()
XCTAssertEqual(fpc.state, .half)
}
}
private class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
+21
View File
@@ -913,6 +913,27 @@ class CoreTests: XCTestCase {
)
}
}
func test_handleGesture_endWithoutAttraction() throws {
class Delegate: FloatingPanelControllerDelegate {
var willAttract: Bool?
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool) {
willAttract = attract
}
}
let fpc = FloatingPanelController()
let scrollView = UIScrollView()
let delegate = Delegate()
fpc.showForTest()
fpc.delegate = delegate
XCTAssertEqual(fpc.state, .half)
fpc.floatingPanel.endWithoutAttraction(.full)
XCTAssertEqual(fpc.state, .full)
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, fpc.surfaceLocation.y)
XCTAssertEqual(delegate.willAttract, false)
}
}
private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {