Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac675d505b | |||
| 53bb530537 | |||
| d6d8834423 |
@@ -7,8 +7,15 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=14.5,name=iPhone 12 Pro'
|
||||
test-ios13_7-iPhone_11_Pro:
|
||||
macos:
|
||||
xcode: 12.5.1
|
||||
steps:
|
||||
- checkout
|
||||
- run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=13.7,name=iPhone 11 Pro'
|
||||
|
||||
workflows:
|
||||
test:
|
||||
jobs:
|
||||
- test-ios14_5-iPhone_12_Pro
|
||||
- test-ios13_7-iPhone_11_Pro
|
||||
|
||||
+71
-89
@@ -4,81 +4,99 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
SWIFT_FORMAT: build-tools/.build/release/swift-format
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- xcode: "14.3"
|
||||
runsOn: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache swift-format
|
||||
id: cache_swift-format
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache_swift-format
|
||||
with:
|
||||
path: ${{ env.SWIFT_FORMAT }}
|
||||
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('build-tools/Package.resolved') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('build-tools/Package.resolved') }}
|
||||
- name: Run swift-format
|
||||
run: |
|
||||
[[ -f $SWIFT_FORMAT ]] || xcrun swift build --package-path "build-tools" -c release
|
||||
$SWIFT_FORMAT lint --configuration .swift-format -s -r Sources Tests
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- swift: "5.10"
|
||||
xcode: "15.4"
|
||||
runs-on: macos-14
|
||||
- swift: "5.9"
|
||||
xcode: "15.2"
|
||||
runs-on: macos-13
|
||||
- swift: "5.8"
|
||||
xcode: "14.3.1"
|
||||
runs-on: macos-13
|
||||
xcode: "14.3"
|
||||
runsOn: macos-13
|
||||
- swift: "5.7"
|
||||
xcode: "14.1"
|
||||
runs-on: macos-12
|
||||
runsOn: macos-12
|
||||
- swift: "5.6"
|
||||
xcode: "13.4.1"
|
||||
runs-on: macos-12
|
||||
runsOn: macos-12
|
||||
- swift: "5.5"
|
||||
xcode: "13.2.1"
|
||||
runs-on: macos-12
|
||||
runsOn: macos-11
|
||||
- swift: "5.4"
|
||||
xcode: "12.5.1"
|
||||
runsOn: macos-11
|
||||
- swift: "5.3"
|
||||
xcode: "12.4"
|
||||
runsOn: macos-11
|
||||
- swift: "5.2"
|
||||
xcode: "11.7"
|
||||
runsOn: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Building in Swift ${{ matrix.swift }}
|
||||
run: xcodebuild -scheme FloatingPanel SWIFT_VERSION=${{ matrix.swift }} clean build
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
runs-on: ${{ matrix.runsOn }}
|
||||
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"
|
||||
- os: "16.1"
|
||||
xcode: "14.1"
|
||||
sim: "iPhone 14 Pro"
|
||||
parallel: NO # Stop random test job failures
|
||||
runs-on: macos-13
|
||||
runsOn: macos-12
|
||||
- os: "15.5"
|
||||
xcode: "13.4.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
parallel: NO # Stop random test job failures
|
||||
runs-on: macos-12
|
||||
runsOn: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Testing in iOS ${{ matrix.os }}
|
||||
run: |
|
||||
xcodebuild clean test \
|
||||
-workspace FloatingPanel.xcworkspace \
|
||||
-scheme FloatingPanel \
|
||||
-destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' \
|
||||
-parallel-testing-enabled '${{ matrix.parallel }}'
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}'
|
||||
timeout-minutes: 20
|
||||
|
||||
example:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-12
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -88,76 +106,40 @@ jobs:
|
||||
- example: "Stocks"
|
||||
- example: "Samples"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Building ${{ matrix.example }}
|
||||
run: |
|
||||
xcodebuild clean build \
|
||||
-workspace FloatingPanel.xcworkspace \
|
||||
-scheme ${{ matrix.example }} \
|
||||
-sdk iphonesimulator
|
||||
run: xcodebuild -workspace FloatingPanel.xcworkspace -scheme ${{ matrix.example }} -sdk iphonesimulator clean build
|
||||
|
||||
swiftpm:
|
||||
runs-on: macos-14
|
||||
runs-on: macos-12
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [iphoneos, iphonesimulator]
|
||||
arch: [x86_64, arm64]
|
||||
exclude:
|
||||
- platform: iphoneos
|
||||
arch: x86_64
|
||||
include:
|
||||
# 17.2
|
||||
- platform: iphoneos
|
||||
sys: "ios17.2"
|
||||
- platform: iphonesimulator
|
||||
sys: "ios17.2-simulator"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Swift Package Manager build"
|
||||
run: |
|
||||
xcrun swift build \
|
||||
--sdk "$(xcrun --sdk ${{ matrix.platform }} --show-sdk-path)" \
|
||||
-Xswiftc "-target" -Xswiftc "${{ matrix.arch }}-apple-${{ matrix.sys }}"
|
||||
|
||||
swiftpm_old:
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_14.1.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"
|
||||
xcode: "14.1"
|
||||
runs-on: macos-12
|
||||
# 16.1
|
||||
- target: "x86_64-apple-ios16.1-simulator"
|
||||
- target: "arm64-apple-ios16.1-simulator"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Carthage build"
|
||||
run: carthage build --use-xcframeworks --no-skip-current
|
||||
|
||||
cocoapods:
|
||||
runs-on: macos-14
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: "CocoaPods: pod lib lint"
|
||||
run: pod lib lint --allow-warnings
|
||||
- name: "CocoaPods: pod spec lint"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: [FloatingPanel]
|
||||
platform: ios
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"fileScopedDeclarationPrivacy" : {
|
||||
"accessLevel" : "private"
|
||||
},
|
||||
"indentation" : {
|
||||
"spaces" : 4
|
||||
},
|
||||
"indentConditionalCompilationBlocks" : false,
|
||||
"indentSwitchCaseLabels" : false,
|
||||
"lineBreakAroundMultilineExpressionChainComponents" : false,
|
||||
"lineBreakBeforeControlFlowKeywords" : false,
|
||||
"lineBreakBeforeEachArgument" : false,
|
||||
"lineBreakBeforeEachGenericRequirement" : false,
|
||||
"lineLength" : 240,
|
||||
"maximumBlankLines" : 1,
|
||||
"prioritizeKeepingFunctionOutputTogether" : false,
|
||||
"respectsExistingLineBreaks" : true,
|
||||
"rules" : {
|
||||
"AllPublicDeclarationsHaveDocumentation" : false,
|
||||
"AlwaysUseLowerCamelCase" : false,
|
||||
"AmbiguousTrailingClosureOverload" : true,
|
||||
"BeginDocumentationCommentWithOneLineSummary" : false,
|
||||
"DoNotUseSemicolons" : true,
|
||||
"DontRepeatTypeInStaticProperties" : true,
|
||||
"FileScopedDeclarationPrivacy" : true,
|
||||
"FullyIndirectEnum" : true,
|
||||
"GroupNumericLiterals" : true,
|
||||
"IdentifiersMustBeASCII" : true,
|
||||
"NeverForceUnwrap" : true,
|
||||
"NeverUseForceTry" : true,
|
||||
"NeverUseImplicitlyUnwrappedOptionals" : false,
|
||||
"NoAccessLevelOnExtensionDeclaration" : true,
|
||||
"NoBlockComments" : true,
|
||||
"NoCasesWithOnlyFallthrough" : true,
|
||||
"NoEmptyTrailingClosureParentheses" : true,
|
||||
"NoLabelsInCasePatterns" : true,
|
||||
"NoLeadingUnderscores" : false,
|
||||
"NoParensAroundConditions" : true,
|
||||
"NoVoidReturnOnFunctionSignature" : true,
|
||||
"OneCasePerLine" : true,
|
||||
"OneVariableDeclarationPerLine" : true,
|
||||
"OnlyOneTrailingClosureArgument" : true,
|
||||
"OrderedImports" : true,
|
||||
"ReturnVoidInsteadOfEmptyTuple" : true,
|
||||
"UseEarlyExits" : false,
|
||||
"UseLetInEveryBoundCaseVariable" : true,
|
||||
"UseShorthandTypeNames" : true,
|
||||
"UseSingleLinePropertyGetter" : true,
|
||||
"UseSynthesizedInitializer" : true,
|
||||
"UseTripleSlashForDocumentationComments" : true,
|
||||
"UseWhereClausesInForLoops" : false,
|
||||
"ValidateDocumentationComments" : false
|
||||
},
|
||||
"tabWidth" : 8,
|
||||
"version" : 1
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
<?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="BYZ-38-t0r">
|
||||
<device id="retina6_12" 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="21679"/>
|
||||
<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>
|
||||
@@ -13,16 +11,16 @@
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="MainViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<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="393" height="852"/>
|
||||
<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="393" height="59"/>
|
||||
<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="393" height="59"/>
|
||||
<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="393" height="852"/>
|
||||
<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="393" height="852"/>
|
||||
<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="393" height="852"/>
|
||||
<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="393" 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" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="D7r-re-InH">
|
||||
<rect key="frame" x="0.0" y="66" width="393" height="786"/>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="D7r-re-InH">
|
||||
<rect key="frame" x="0.0" y="61" width="600" height="539"/>
|
||||
<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="393" 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="345" 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 & 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="95" 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="190" 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="285" 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="166" width="393" 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="393" 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="321" 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="321" 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="321" 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="393" height="852"/>
|
||||
<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="-1" y="-1" width="393" height="852"/>
|
||||
<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="393" height="852"/>
|
||||
<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="393" height="852"/>
|
||||
<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>
|
||||
|
||||
@@ -156,14 +156,6 @@ class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGes
|
||||
self.owner = owner
|
||||
}
|
||||
|
||||
func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll scrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool {
|
||||
return state == .full || state == .half
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
switch newCollection.verticalSizeClass {
|
||||
case .compact:
|
||||
@@ -223,10 +215,17 @@ class SearchPanelLandscapeLayout: FloatingPanelLayout {
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
if #available(iOS 11.0, *) {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
}
|
||||
}
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
@@ -320,7 +319,7 @@ class SearchPaneliPadBehavior: FloatingPanelBehavior {
|
||||
var momentumProjectionRate: CGFloat {
|
||||
return UIScrollView.DecelerationRate.fast.rawValue
|
||||
}
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ class SearchViewController: UIViewController, UITableViewDataSource {
|
||||
|
||||
var items: [LocationItem] = []
|
||||
|
||||
// For iOS 10 only
|
||||
private lazy var shadowLayer: CAShapeLayer = CAShapeLayer()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.dataSource = self
|
||||
@@ -89,6 +92,29 @@ class SearchViewController: UIViewController, UITableViewDataSource {
|
||||
hideHeader(animated: false)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11, *) {
|
||||
} else {
|
||||
// Exmaple: Add rounding corners on iOS 10
|
||||
visualEffectView.layer.cornerRadius = 9.0
|
||||
visualEffectView.clipsToBounds = true
|
||||
|
||||
// Exmaple: Add shadow manually on iOS 10
|
||||
view.layer.insertSublayer(shadowLayer, at: 0)
|
||||
let rect = visualEffectView.frame
|
||||
let path = UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.topLeft, .topRight],
|
||||
cornerRadii: CGSize(width: 9.0, height: 9.0))
|
||||
shadowLayer.frame = visualEffectView.frame
|
||||
shadowLayer.shadowPath = path.cgPath
|
||||
shadowLayer.shadowColor = UIColor.black.cgColor
|
||||
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0)
|
||||
shadowLayer.shadowOpacity = 0.2
|
||||
shadowLayer.shadowRadius = 3.0
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return items.count
|
||||
}
|
||||
@@ -104,16 +130,16 @@ class SearchViewController: UIViewController, UITableViewDataSource {
|
||||
}
|
||||
|
||||
func showHeader(animated: Bool) {
|
||||
changeHeader(height: 116.0, animated: animated)
|
||||
changeHeader(height: 116.0, aniamted: animated)
|
||||
}
|
||||
|
||||
func hideHeader(animated: Bool) {
|
||||
changeHeader(height: 0.0, animated: animated)
|
||||
changeHeader(height: 0.0, aniamted: animated)
|
||||
}
|
||||
|
||||
private func changeHeader(height: CGFloat, animated: Bool) {
|
||||
private func changeHeader(height: CGFloat, aniamted: Bool) {
|
||||
guard let headerView = tableView.tableHeaderView, headerView.bounds.height != height else { return }
|
||||
if animated == false {
|
||||
if aniamted == false {
|
||||
updateHeader(height: height)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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 /* TableViewControllerForAdaptiveLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */; };
|
||||
544BC56826CC918200D0A436 /* AdaptiveLayoutTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.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,11 +30,8 @@
|
||||
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 */
|
||||
|
||||
@@ -64,7 +61,7 @@
|
||||
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugTextViewController.swift; sourceTree = "<group>"; };
|
||||
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NestedScrollViewController.swift; sourceTree = "<group>"; };
|
||||
54496C58263A7E5A0031E0C8 /* UseCaseController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCaseController.swift; sourceTree = "<group>"; };
|
||||
544BC56726CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewControllerForAdaptiveLayout.swift; sourceTree = "<group>"; };
|
||||
544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveLayoutTestViewController.swift; sourceTree = "<group>"; };
|
||||
545DB9EA21511E6300CA77B8 /* Samples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Samples.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
545DB9EF21511E6300CA77B8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -77,11 +74,8 @@
|
||||
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 */
|
||||
|
||||
@@ -101,18 +95,17 @@
|
||||
5442E22225FC519700A26F43 /* ContentViewControllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54E58CB52BF8A88600408EA9 /* AdaptiveLayout */,
|
||||
5442E23F25FC533800A26F43 /* DebugTableViewController.swift */,
|
||||
5442E24925FC53C100A26F43 /* DebugTextViewController.swift */,
|
||||
54F185812BF4AD0000916F57 /* DebugListCollectionViewController.swift */,
|
||||
5442E23325FC528400A26F43 /* DetailViewController.swift */,
|
||||
5442E24325FC538200A26F43 /* InspectorViewController.swift */,
|
||||
5442E22325FC51AF00A26F43 /* ImageViewController.swift */,
|
||||
5442E25125FC541700A26F43 /* NestedScrollViewController.swift */,
|
||||
5442E23925FC52CD00A26F43 /* ModalViewController.swift */,
|
||||
5442E22725FC51E200A26F43 /* MultiPanelController.swift */,
|
||||
5442E22B25FC521F00A26F43 /* SettingsViewController.swift */,
|
||||
5442E22F25FC525200A26F43 /* TabBarViewController.swift */,
|
||||
54F185832BF4B82E00916F57 /* UnavailableViewController.swift */,
|
||||
544BC56726CC918200D0A436 /* AdaptiveLayoutTestViewController.swift */,
|
||||
);
|
||||
path = ContentViewControllers;
|
||||
sourceTree = "<group>";
|
||||
@@ -164,16 +157,6 @@
|
||||
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 = (
|
||||
@@ -255,17 +238,15 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5442E23425FC528400A26F43 /* DetailViewController.swift in Sources */,
|
||||
54E58CB72BF8A8D900408EA9 /* CollectionViewControllerForAdaptiveLayout.swift in Sources */,
|
||||
54496C59263A7E5A0031E0C8 /* UseCaseController.swift in Sources */,
|
||||
54CDC5D8215BBE23007D205C /* SupplementaryViews.swift in Sources */,
|
||||
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
|
||||
544BC56826CC918200D0A436 /* TableViewControllerForAdaptiveLayout.swift in Sources */,
|
||||
544BC56826CC918200D0A436 /* AdaptiveLayoutTestViewController.swift in Sources */,
|
||||
5442E24A25FC53C100A26F43 /* DebugTextViewController.swift in Sources */,
|
||||
546341AC25C6426500CA0596 /* CustomState.swift in Sources */,
|
||||
5442E23A25FC52CD00A26F43 /* ModalViewController.swift in Sources */,
|
||||
5442E22425FC51AF00A26F43 /* ImageViewController.swift in Sources */,
|
||||
5442E24025FC533800A26F43 /* DebugTableViewController.swift in Sources */,
|
||||
54F185822BF4AD0000916F57 /* DebugListCollectionViewController.swift in Sources */,
|
||||
5442E25225FC541700A26F43 /* NestedScrollViewController.swift in Sources */,
|
||||
5442E22825FC51E200A26F43 /* MultiPanelController.swift in Sources */,
|
||||
546341A125C6415100CA0596 /* UseCase.swift in Sources */,
|
||||
@@ -276,7 +257,6 @@
|
||||
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="32700.99.1234" 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="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -383,22 +383,22 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="768"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="720"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vut-mK-Y4t" customClass="SafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="768" width="375" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="720" width="375" height="0.0"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
|
||||
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
|
||||
<rect key="frame" x="20" y="48" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="MSC-ch-YJK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="44" translatesAutoresizingMaskIntoConstraints="NO" id="9p4-06-y2T">
|
||||
<rect key="frame" x="134.5" y="88" width="106" height="326"/>
|
||||
<rect key="frame" x="134.5" y="136" width="106" height="326"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="30"/>
|
||||
@@ -598,7 +598,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="744"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
|
||||
@@ -761,33 +761,14 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
<fontDescription key="fontDescription" name="CourierNewPSMT" family="Courier New" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wUo-kb-NIn">
|
||||
<rect key="frame" x="159" y="16" width="200" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Expand top margin" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OC3-od-ldC">
|
||||
<rect key="frame" x="0.0" y="5.5" width="143" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="XFC-sq-pWj">
|
||||
<rect key="frame" x="151" y="0.0" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleTopMargin:" destination="tvD-nO-QUb" eventType="valueChanged" id="XWo-eX-0Jn"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
<constraint firstItem="5ET-zC-lCb" firstAttribute="trailing" secondItem="wUo-kb-NIn" secondAttribute="trailing" constant="16" id="CtG-H5-tAI"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rN1-HL-YHv" secondAttribute="bottom" id="efD-U5-Tet"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="17" id="fiO-LL-nSC"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
<constraint firstItem="wUo-kb-NIn" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="16" id="ogC-1W-upw"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
@@ -799,12 +780,12 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-2.1739130434782612" y="733.92857142857144"/>
|
||||
<point key="canvasLocation" x="-1" y="734"/>
|
||||
</scene>
|
||||
<!--Table View Controller For Adaptive Layout-->
|
||||
<!--Adaptive Layout Test View Controller-->
|
||||
<scene sceneID="rDI-lU-wEx">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="TableViewControllerForAdaptiveLayout" id="5nC-6E-bXf" customClass="TableViewControllerForAdaptiveLayout" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="AdaptiveLayoutTestViewController" id="5nC-6E-bXf" customClass="AdaptiveLayoutTestViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="jXL-Ss-NCJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -861,13 +842,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.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="1" green="0.58431372549019611" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemPurpleColor">
|
||||
<color red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemTealColor">
|
||||
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" 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>
|
||||
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -3,7 +3,7 @@
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
final class TableViewControllerForAdaptiveLayout: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
final class AdaptiveLayoutTestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
class PanelLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .full
|
||||
@@ -75,6 +75,7 @@ final class TableViewControllerForAdaptiveLayout: UIViewController, UITableViewD
|
||||
}
|
||||
|
||||
class IntrinsicTableView: UITableView {
|
||||
|
||||
override var contentSize:CGSize {
|
||||
didSet {
|
||||
invalidateIntrinsicContentSize()
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,9 @@ final class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
textView.delegate = self
|
||||
print("viewDidLoad: TextView --- ", textView.contentOffset, textView.contentInset)
|
||||
|
||||
textView.contentInsetAdjustmentBehavior = .never
|
||||
if #available(iOS 11.0, *) {
|
||||
textView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
@@ -30,14 +32,8 @@ final class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
print("TextView --- ", scrollView.contentOffset, scrollView.contentInset)
|
||||
print("TextView --- ", scrollView.adjustedContentInset)
|
||||
}
|
||||
|
||||
@IBAction func toggleTopMargin(_ sender: UISwitch) {
|
||||
if sender.isOn {
|
||||
textViewTopConstraint.constant = 160
|
||||
} else {
|
||||
textViewTopConstraint.constant = 16
|
||||
if #available(iOS 11.0, *) {
|
||||
print("TextView --- ", scrollView.adjustedContentInset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ final class ImageViewController: UIViewController {
|
||||
case withHeaderFooter
|
||||
}
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
func layoutGuideFor(mode: Mode) -> UILayoutGuide {
|
||||
switch mode {
|
||||
case .onlyImage:
|
||||
@@ -14,17 +14,21 @@ final class SettingsViewController: InspectableViewController {
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
|
||||
largeTitlesSwitch.setOn(prefersLargeTitles, animated: false)
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
|
||||
largeTitlesSwitch.setOn(prefersLargeTitles, animated: false)
|
||||
} else {
|
||||
largeTitlesSwitch.isEnabled = false
|
||||
}
|
||||
let isTranslucent = navigationController!.navigationBar.isTranslucent
|
||||
translucentSwitch.setOn(isTranslucent, animated: false)
|
||||
}
|
||||
|
||||
@IBAction func toggleLargeTitle(_ sender: UISwitch) {
|
||||
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func toggleTranslucent(_ sender: UISwitch) {
|
||||
// White non-translucent navigation bar, supports dark appearance
|
||||
if #available(iOS 15, *) {
|
||||
|
||||
@@ -238,8 +238,13 @@ class ThreeTabBarPanelLayout: FloatingPanelLayout {
|
||||
}
|
||||
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
|
||||
if #available(iOS 11.0, *) {
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
|
||||
} else {
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0)
|
||||
}
|
||||
return [ leftConstraint, rightConstraint ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -33,10 +33,22 @@ class CustomLayoutGuide: LayoutGuideProvider {
|
||||
|
||||
extension UIViewController {
|
||||
var layoutInsets: UIEdgeInsets {
|
||||
return view.safeAreaInsets
|
||||
if #available(iOS 11.0, *) {
|
||||
return view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets(top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
var layoutGuide: LayoutGuideProvider {
|
||||
return view.safeAreaLayoutGuide
|
||||
if #available(iOS 11.0, *) {
|
||||
return view!.safeAreaLayoutGuide
|
||||
} else {
|
||||
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,14 @@ extension MainViewController {
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
|
||||
automaticallyAdjustsScrollViewInsets = false
|
||||
|
||||
let searchController = UISearchController(searchResultsController: nil)
|
||||
navigationItem.searchController = searchController
|
||||
navigationItem.hidesSearchBarWhenScrolling = false
|
||||
navigationItem.largeTitleDisplayMode = .automatic
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationItem.searchController = searchController
|
||||
navigationItem.hidesSearchBarWhenScrolling = false
|
||||
navigationItem.largeTitleDisplayMode = .automatic
|
||||
}
|
||||
var insets = UIEdgeInsets.zero
|
||||
insets.bottom += 69.0
|
||||
tableView.contentInset = insets
|
||||
@@ -30,10 +33,12 @@ extension MainViewController {
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
|
||||
self.tableView.reloadData()
|
||||
}) {
|
||||
observations.append(observation)
|
||||
if #available(iOS 11.0, *) {
|
||||
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
|
||||
self.tableView.reloadData()
|
||||
}) {
|
||||
observations.append(observation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +56,12 @@ extension MainViewController {
|
||||
|
||||
extension MainViewController: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return UseCase.allCases.count + 30
|
||||
if #available(iOS 11.0, *) {
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return UseCase.allCases.count + 30
|
||||
} else {
|
||||
return UseCase.allCases.count
|
||||
}
|
||||
} else {
|
||||
return UseCase.allCases.count
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@ import UIKit
|
||||
enum UseCase: Int, CaseIterable {
|
||||
case trackingTableView
|
||||
case trackingTextView
|
||||
case trackingCollectionViewList
|
||||
case showDetail
|
||||
case showModal
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showOnWindow
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
@@ -23,24 +21,19 @@ enum UseCase: Int, CaseIterable {
|
||||
case showNavigationController
|
||||
case showTopPositionedPanel
|
||||
case showAdaptivePanel
|
||||
case showAdaptivePanelWithTableView
|
||||
case showAdaptivePanelWithCollectionView
|
||||
case showAdaptivePanelWithCompositionalCollectionView
|
||||
case showAdaptivePanelWithCustomGuide
|
||||
case showCustomStatePanel
|
||||
case showCustomBackdrop
|
||||
}
|
||||
|
||||
extension UseCase {
|
||||
var name: String {
|
||||
switch self {
|
||||
case .trackingTableView: return "Scroll tracking(TableView)"
|
||||
case .trackingCollectionViewList: return "Scroll tracking(List CollectionView)"
|
||||
case .trackingTextView: return "Scroll tracking(TextView)"
|
||||
case .showDetail: return "Show Detail Panel"
|
||||
case .showModal: return "Show Modal"
|
||||
case .showPanelModal: return "Show Panel Modal"
|
||||
case .showMultiPanelModal: return "Show Multi Panel Modal"
|
||||
case .showOnWindow: return "Show Panel over Window"
|
||||
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
|
||||
case .showTabBar: return "Show Tab Bar"
|
||||
case .showPageView: return "Show Page View"
|
||||
@@ -53,11 +46,8 @@ extension UseCase {
|
||||
case .showNavigationController: return "Show Navigation Controller"
|
||||
case .showTopPositionedPanel: return "Show Top Positioned Panel"
|
||||
case .showAdaptivePanel: return "Show Adaptive Panel"
|
||||
case .showAdaptivePanelWithTableView: return "Show Adaptive Panel (TableView)"
|
||||
case .showAdaptivePanelWithCollectionView: return "Show Adaptive Panel (CollectionView)"
|
||||
case .showAdaptivePanelWithCompositionalCollectionView: return "Show Adaptive Panel (Compositional CollectionView)"
|
||||
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
|
||||
case .showCustomStatePanel: return "Show Panel with Custom state"
|
||||
case .showCustomBackdrop: return "Show Panel with Custom Backdrop"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,18 +61,10 @@ extension UseCase {
|
||||
private var content: Content {
|
||||
switch self {
|
||||
case .trackingTableView: return .viewController(DebugTableViewController())
|
||||
case .trackingCollectionViewList:
|
||||
if #available(iOS 14, *) {
|
||||
return .viewController(DebugListCollectionViewController())
|
||||
} else {
|
||||
let msg = "UICollectionLayoutListConfiguration is unavailable.\nBuild this app on iOS 14 and later."
|
||||
return makeUnavailableViewContent(message: msg)
|
||||
}
|
||||
case .trackingTextView: return .storyboard("ConsoleViewController") // Storyboard only
|
||||
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showModal: return .storyboard(String(describing: ModalViewController.self))
|
||||
case .showMultiPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showOnWindow: return .viewController(DebugTableViewController())
|
||||
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
|
||||
@@ -96,19 +78,8 @@ extension UseCase {
|
||||
case .showNavigationController: return .storyboard("RootNavigationController") // Storyboard only
|
||||
case .showTopPositionedPanel: return .viewController(DebugTableViewController())
|
||||
case .showAdaptivePanel: return .storyboard(String(describing: ImageViewController.self))
|
||||
case .showAdaptivePanelWithTableView: return .storyboard(String(describing: TableViewControllerForAdaptiveLayout.self))
|
||||
case .showAdaptivePanelWithCollectionView,
|
||||
.showAdaptivePanelWithCompositionalCollectionView:
|
||||
if #available(iOS 13, *) {
|
||||
let vc = CollectionViewControllerForAdaptiveLayout()
|
||||
vc.layoutType = self == .showAdaptivePanelWithCollectionView ? .flow : .compositional
|
||||
return .viewController(vc)
|
||||
} else {
|
||||
let msg = "Compositional layout is unavailable.\nBuild this app on iOS 13 and later."
|
||||
return makeUnavailableViewContent(message: msg)
|
||||
}
|
||||
case .showAdaptivePanelWithCustomGuide: return .storyboard(String(describing: AdaptiveLayoutTestViewController.self))
|
||||
case .showCustomStatePanel: return .viewController(DebugTableViewController())
|
||||
case .showCustomBackdrop: return .viewController(UIViewController())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,11 +92,4 @@ extension UseCase {
|
||||
return vc
|
||||
}
|
||||
}
|
||||
|
||||
private func makeUnavailableViewContent(message: String) -> Content {
|
||||
let vc = UnavailableViewController()
|
||||
vc.loadViewIfNeeded()
|
||||
vc.label.text = message
|
||||
return .viewController(vc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ final class UseCaseController: NSObject {
|
||||
private var detailPanelVC: FloatingPanelController!
|
||||
private var settingsPanelVC: FloatingPanelController!
|
||||
private lazy var pagePanelController = PagePanelController()
|
||||
private lazy var overWindowPanelVC = FloatingPanelController()
|
||||
|
||||
init(mainVC: MainViewController) {
|
||||
self.mainVC = mainVC
|
||||
@@ -52,30 +51,6 @@ 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
|
||||
@@ -182,20 +157,6 @@ extension UseCaseController {
|
||||
let fpc = MultiPanelController()
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showOnWindow:
|
||||
let fpc = overWindowPanelVC
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
|
||||
guard let window = UIApplication.shared.windows.first else { fatalError("Any window not found") }
|
||||
|
||||
window.addSubview(fpc.view)
|
||||
fpc.view.frame = window.bounds
|
||||
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
fpc.show(animated: true)
|
||||
case .showPanelInSheetModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = UIViewController()
|
||||
@@ -263,13 +224,13 @@ extension UseCaseController {
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
if case let contentVC as ImageViewController = contentVC {
|
||||
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithTableView) ? .withHeaderFooter : .onlyImage
|
||||
let mode: ImageViewController.Mode = (useCase == .showAdaptivePanelWithCustomGuide) ? .withHeaderFooter : .onlyImage
|
||||
let layoutGuide = contentVC.layoutGuideFor(mode: mode)
|
||||
fpc.layout = ImageViewController.PanelLayout(targetGuide: layoutGuide)
|
||||
}
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showAdaptivePanelWithTableView:
|
||||
case .showAdaptivePanelWithCustomGuide:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
@@ -279,25 +240,10 @@ extension UseCaseController {
|
||||
return appearance
|
||||
}()
|
||||
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.track(scrollView: (contentVC as! TableViewControllerForAdaptiveLayout).tableView)
|
||||
fpc.layout = TableViewControllerForAdaptiveLayout.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showAdaptivePanelWithCollectionView, .showAdaptivePanelWithCompositionalCollectionView:
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.surfaceView.appearance = {
|
||||
let appearance = SurfaceAppearance()
|
||||
appearance.cornerRadius = 6.0
|
||||
return appearance
|
||||
}()
|
||||
fpc.set(contentViewController: contentVC)
|
||||
if #available(iOS 13, *) {
|
||||
fpc.track(scrollView: (contentVC as! CollectionViewControllerForAdaptiveLayout).collectionView)
|
||||
fpc.layout = CollectionViewControllerForAdaptiveLayout.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
|
||||
}
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
fpc.layout = AdaptiveLayoutTestViewController.PanelLayout(targetGuide: contentVC.view.makeBoundsLayoutGuide())
|
||||
addMain(panel: fpc)
|
||||
|
||||
case .showCustomStatePanel:
|
||||
@@ -312,52 +258,6 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,16 +307,8 @@ extension UseCaseController {
|
||||
}
|
||||
|
||||
extension UseCaseController: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll scrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool {
|
||||
return state == .full || state == .half
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint {
|
||||
if useCase == .showNavigationController {
|
||||
if useCase == .showNavigationController, #available(iOS 11.0, *) {
|
||||
// 148.0 is the SafeArea's top value for a navigation bar with a large title.
|
||||
return CGPoint(x: 0.0, y: 0.0 - trackingScrollView.contentInset.top - 148.0)
|
||||
}
|
||||
@@ -497,7 +389,7 @@ private extension FloatingPanelController {
|
||||
case let contentVC as ImageViewController:
|
||||
track(scrollView: contentVC.scrollView)
|
||||
|
||||
case let contentVC as TableViewControllerForAdaptiveLayout:
|
||||
case let contentVC as AdaptiveLayoutTestViewController:
|
||||
track(scrollView: contentVC.tableView)
|
||||
|
||||
default:
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
545BA71421BA3217007F7846 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA71321BA3217007F7846 /* main.m */; };
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; };
|
||||
545BA72721BA3BAF007F7846 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -52,6 +53,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -81,7 +81,7 @@ class MainViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
}
|
||||
|
||||
private func hideStockTickerBanner() {
|
||||
// Dismiss top bar with dissolve animation
|
||||
// Dimiss top bar with dissolve animation
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.topBannerView.alpha = 0.0
|
||||
self.labelStackView.alpha = 1.0
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.8.6"
|
||||
s.version = "2.6.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.
|
||||
The new interface displays the related contents and utilities in parallel as a user wants.
|
||||
DESC
|
||||
s.homepage = "https://github.com/scenee/FloatingPanel"
|
||||
s.homepage = "https://github.com/SCENEE/FloatingPanel"
|
||||
s.author = "Shin Yamamoto"
|
||||
s.social_media_url = "https://twitter.com/scenee"
|
||||
|
||||
s.platform = :ios, "11.0"
|
||||
s.source = { :git => "https://github.com/scenee/FloatingPanel.git", :tag => s.version.to_s }
|
||||
s.platform = :ios, "10.0"
|
||||
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => s.version.to_s }
|
||||
s.source_files = "Sources/*.swift"
|
||||
s.swift_version = '5.0'
|
||||
|
||||
|
||||
@@ -21,12 +21,11 @@
|
||||
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */; };
|
||||
5469F4B224B30F1100537F8A /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B124B30F1100537F8A /* Position.swift */; };
|
||||
5469F4B424B30F3500537F8A /* LayoutProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutProperties.swift */; };
|
||||
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F7A9B2A6E946000303905 /* GestureTests.swift */; };
|
||||
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549C371E2361E15D007D8058 /* ExtensionTests.swift */; };
|
||||
549E944522CF295D0050AECF /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E944422CF295D0050AECF /* StateTests.swift */; };
|
||||
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* CoreTests.swift */; };
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logging.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
|
||||
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* SurfaceView.swift */; };
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* BackdropView.swift */; };
|
||||
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* Layout.swift */; };
|
||||
@@ -64,12 +63,11 @@
|
||||
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAnchoring.swift; sourceTree = "<group>"; };
|
||||
5469F4B124B30F1100537F8A /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = "<group>"; };
|
||||
5469F4B324B30F3500537F8A /* LayoutProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutProperties.swift; sourceTree = "<group>"; };
|
||||
547F7A9B2A6E946000303905 /* GestureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureTests.swift; sourceTree = "<group>"; };
|
||||
549C371E2361E15D007D8058 /* ExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionTests.swift; sourceTree = "<group>"; };
|
||||
549E944422CF295D0050AECF /* StateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceViewTests.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
||||
54CDC5D4215B6D8D007D205C /* BackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackdropView.swift; sourceTree = "<group>"; };
|
||||
54CFBFC2215CD045006B5735 /* Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; };
|
||||
@@ -135,7 +133,7 @@
|
||||
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
|
||||
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
|
||||
54DBA3DB262E938500D75969 /* Extensions.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9C52151169500CA77B8 /* Info.plist */,
|
||||
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
|
||||
@@ -148,7 +146,6 @@
|
||||
children = (
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */,
|
||||
545DB9CF2151169500CA77B8 /* ControllerTests.swift */,
|
||||
547F7A9B2A6E946000303905 /* GestureTests.swift */,
|
||||
542753C522C49A6E00D17955 /* LayoutTests.swift */,
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */,
|
||||
549E944422CF295D0050AECF /* StateTests.swift */,
|
||||
@@ -193,6 +190,7 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
54B58FC929EB95880009567E /* PBXTargetDependency */,
|
||||
);
|
||||
name = FloatingPanel;
|
||||
productName = FloatingModalController;
|
||||
@@ -245,6 +243,8 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 545DB9B72151169500CA77B8;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 545DB9C22151169500CA77B8 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@@ -285,7 +285,7 @@
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */,
|
||||
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */,
|
||||
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
|
||||
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */,
|
||||
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
|
||||
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
|
||||
@@ -307,7 +307,6 @@
|
||||
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */,
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */,
|
||||
546055BF2333C4740069F400 /* TestSupports.swift in Sources */,
|
||||
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -319,6 +318,10 @@
|
||||
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
|
||||
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
54B58FC929EB95880009567E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 54B58FC829EB95880009567E /* SwiftFormatBuildTool */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -462,13 +465,12 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -477,7 +479,6 @@
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
_EXPERIMENTAL_SWIFT_EXPLICIT_MODULES = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -495,20 +496,18 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
_EXPERIMENTAL_SWIFT_EXPLICIT_MODULES = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -519,13 +518,12 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@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;
|
||||
@@ -540,13 +538,12 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@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,22 +630,20 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG FP_LOG";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG __FP_LOG";
|
||||
SWIFT_COMPILATION_MODE = singlefile;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
_EXPERIMENTAL_SWIFT_EXPLICIT_MODULES = YES;
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
@@ -659,13 +654,12 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@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;
|
||||
@@ -707,6 +701,13 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
54B58FC829EB95880009567E /* SwiftFormatBuildTool */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "plugin:SwiftFormatBuildTool";
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 545DB9B82151169500CA77B8 /* Project object */;
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import PackageDescription
|
||||
let package = Package(
|
||||
name: "FloatingPanel",
|
||||
platforms: [
|
||||
.iOS(.v11)
|
||||
.iOS(.v10)
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||

|
||||
[](https://github.com/Carthage/Carthage)
|
||||
|
||||
# FloatingPanel
|
||||
|
||||
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@SPI](https://swiftpackageindex.com/scenee/FloatingPanel/2.8.6/documentation/floatingpanel) for more details.
|
||||
Please see also [the API reference](https://floatingpanel.github.io/2.6.2/documentation/floatingpanel/) for more details, powered by [DocC](https://developer.apple.com/documentation/docc).
|
||||
|
||||

|
||||

|
||||
@@ -20,46 +21,47 @@ Please see also [the API reference@SPI](https://swiftpackageindex.com/scenee/Flo
|
||||
- [Features](#features)
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [CocoaPods](#cocoapods)
|
||||
- [Swift Package Manager](#swift-package-manager)
|
||||
- [CocoaPods](#cocoapods)
|
||||
- [Carthage](#carthage)
|
||||
- [Swift Package Manager](#swift-package-manager)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
|
||||
- [Present a floating panel as a modality](#present-a-floating-panel-as-a-modality)
|
||||
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
|
||||
- [Present a floating panel as a modality](#present-a-floating-panel-as-a-modality)
|
||||
- [View hierarchy](#view-hierarchy)
|
||||
- [Usage](#usage)
|
||||
- [Show/Hide a floating panel in a view with your view hierarchy](#showhide-a-floating-panel-in-a-view-with-your-view-hierarchy)
|
||||
- [Scale the content view when the surface position changes](#scale-the-content-view-when-the-surface-position-changes)
|
||||
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
|
||||
- [Change the initial layout](#change-the-initial-layout)
|
||||
- [Update your panel layout](#update-your-panel-layout)
|
||||
- [Support your landscape layout](#support-your-landscape-layout)
|
||||
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
|
||||
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
|
||||
- [Change the backdrop alpha](#change-the-backdrop-alpha)
|
||||
- [Using custome panel states](#using-custome-panel-states)
|
||||
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
|
||||
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
|
||||
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
|
||||
- [Manage the projection of a pan gesture momentum](#manage-the-projection-of-a-pan-gesture-momentum)
|
||||
- [Specify the panel move's boundary](#specify-the-panel-moves-boundary)
|
||||
- [Customize the surface design](#customize-the-surface-design)
|
||||
- [Modify your surface appearance](#modify-your-surface-appearance)
|
||||
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
|
||||
- [Customize layout of the grabber handle](#customize-layout-of-the-grabber-handle)
|
||||
- [Customize content padding from surface edges](#customize-content-padding-from-surface-edges)
|
||||
- [Customize margins of the surface edges](#customize-margins-of-the-surface-edges)
|
||||
- [Customize gestures](#customize-gestures)
|
||||
- [Suppress the panel interaction](#suppress-the-panel-interaction)
|
||||
- [Add tap gestures to the surface view](#add-tap-gestures-to-the-surface-view)
|
||||
- [Interrupt the delegate methods of `FloatingPanelController.panGestureRecognizer`](#interrupt-the-delegate-methods-of-floatingpanelcontrollerpangesturerecognizer)
|
||||
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
|
||||
- [Move a position with an animation](#move-a-position-with-an-animation)
|
||||
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
|
||||
- [Enabling the tap-to-dismiss action of the backdrop view](#enabling-the-tap-to-dismiss-action-of-the-backdrop-view)
|
||||
- [Allow to scroll content of the tracking scroll view in addition to the most expanded state](#allow-to-scroll-content-of-the-tracking-scroll-view-in-addition-to-the-most-expanded-state)
|
||||
- [Show/Hide a floating panel in a view with your view hierarchy](#showhide-a-floating-panel-in-a-view-with-your-view-hierarchy)
|
||||
- [Scale the content view when the surface position changes](#scale-the-content-view-when-the-surface-position-changes)
|
||||
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
|
||||
- [Change the initial layout](#change-the-initial-layout)
|
||||
- [Update your panel layout](#update-your-panel-layout)
|
||||
- [Support your landscape layout](#support-your-landscape-layout)
|
||||
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
|
||||
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
|
||||
- [Change the backdrop alpha](#change-the-backdrop-alpha)
|
||||
- [Using custome panel states](#using-custome-panel-states)
|
||||
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
|
||||
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
|
||||
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
|
||||
- [Manage the projection of a pan gesture momentum](#manage-the-projection-of-a-pan-gesture-momentum)
|
||||
- [Specify the panel move's boundary](#specify-the-panel-moves-boundary)
|
||||
- [Customize the surface design](#customize-the-surface-design)
|
||||
- [Modify your surface appearance](#modify-your-surface-appearance)
|
||||
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
|
||||
- [Customize layout of the grabber handle](#customize-layout-of-the-grabber-handle)
|
||||
- [Customize content padding from surface edges](#customize-content-padding-from-surface-edges)
|
||||
- [Customize margins of the surface edges](#customize-margins-of-the-surface-edges)
|
||||
- [Customize gestures](#customize-gestures)
|
||||
- [Suppress the panel interaction](#suppress-the-panel-interaction)
|
||||
- [Add tap gestures to the surface view](#add-tap-gestures-to-the-surface-view)
|
||||
- [Interrupt the delegate methods of `FloatingPanelController.panGestureRecognizer`](#interrupt-the-delegate-methods-of-floatingpanelcontrollerpangesturerecognizer)
|
||||
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
|
||||
- [Move a position with an animation](#move-a-position-with-an-animation)
|
||||
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
|
||||
- [Enabling the tap-to-dismiss action of the backdrop view](#enabling-the-tap-to-dismiss-action-of-the-backdrop-view)
|
||||
- [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)
|
||||
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
|
||||
- [UISearchController issue](#uisearchcontroller-issue)
|
||||
- [FloatingPanelSurfaceView's issue on iOS 10](#floatingpanelsurfaceviews-issue-on-ios-10)
|
||||
- [Maintainer](#maintainer)
|
||||
- [License](#license)
|
||||
|
||||
@@ -91,6 +93,10 @@ Examples can be found here:
|
||||
|
||||
FloatingPanel is written in Swift 5.0+ and compatible with iOS 11.0+.
|
||||
|
||||
While it still supports iOS 10, it is recommended to use this library on iOS 11+.
|
||||
|
||||
:pencil2: If you'd like to use Swift 4.0, please use FloatingPanel v1.
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods
|
||||
@@ -102,6 +108,16 @@ it, simply add the following line to your Podfile:
|
||||
pod 'FloatingPanel'
|
||||
```
|
||||
|
||||
:pencil2: FloatingPanel v1.7.0 or later requires CocoaPods v1.7.0+ for `swift_versions` support.
|
||||
|
||||
### Carthage
|
||||
|
||||
For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`:
|
||||
|
||||
```ogdl
|
||||
github "scenee/FloatingPanel"
|
||||
```
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
Follow [this doc](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).
|
||||
@@ -152,8 +168,7 @@ self.present(fpc, animated: true, completion: nil)
|
||||
|
||||
You can show a floating panel over UINavigationController from the container view controllers as a modality of `.overCurrentContext` style.
|
||||
|
||||
> [!NOTE]
|
||||
> FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [Transitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Sources/Transitioning.swift).
|
||||
:pencil2: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [Transitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Sources/Transitioning.swift).
|
||||
|
||||
## View hierarchy
|
||||
|
||||
@@ -237,8 +252,7 @@ fpc.contentMode = .fitToBounds
|
||||
|
||||
Otherwise, `FloatingPanelController` fixes the content by the height of the top most position.
|
||||
|
||||
> [!NOTE]
|
||||
> In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
|
||||
:pencil2: In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
|
||||
|
||||
### Customize the layout with `FloatingPanelLayout` protocol
|
||||
|
||||
@@ -336,8 +350,7 @@ class IntrinsicPanelLayout: FloatingPanelLayout {
|
||||
}
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> `FloatingPanelIntrinsicLayout` is deprecated on v1.
|
||||
:pencil2: `FloatingPanelIntrinsicLayout` is deprecated on v1.
|
||||
|
||||
#### Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame
|
||||
|
||||
@@ -354,8 +367,7 @@ class MyFullScreenLayout: FloatingPanelLayout {
|
||||
}
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> `FloatingPanelFullScreenLayout` is deprecated on v1.
|
||||
:pencil2: `FloatingPanelFullScreenLayout` is deprecated on v1.
|
||||
|
||||
#### Change the backdrop alpha
|
||||
|
||||
@@ -413,14 +425,13 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
class CustomPanelBehavior: FloatingPanelBehavior {
|
||||
let springDecelerationRate = UIScrollView.DecelerationRate.fast.rawValue + 0.02
|
||||
let springResponseTime = 0.4
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> `floatingPanel(_ vc:behaviorFor:)` is deprecated on v1.
|
||||
:pencil2: `floatingPanel(_ vc:behaviorFor:)` is deprecated on v1.
|
||||
|
||||
#### Activate the rubber-band effect on panel edges
|
||||
|
||||
@@ -440,7 +451,7 @@ This allows full projectional panel behavior. For example, a user can swipe up a
|
||||
```swift
|
||||
class MyPanelBehavior: FloatingPanelBehavior {
|
||||
...
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelPosition) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelPosition) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -462,8 +473,7 @@ func floatingPanelDidMove(_ vc: FloatingPanelController) {
|
||||
}
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> `{top,bottom}InteractionBuffer` property is removed from `FloatingPanelLayout` since v2.
|
||||
:pencil2: `{top,bottom}InteractionBuffer` property is removed from `FloatingPanelLayout` since v2.
|
||||
|
||||
### Customize the surface design
|
||||
|
||||
@@ -504,8 +514,7 @@ fpc.surfaceView.grabberHandlePadding = 10.0
|
||||
fpc.surfaceView.grabberHandleSize = .init(width: 44.0, height: 12.0)
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> `grabberHandleSize` width and height are reversed in the left/right position.
|
||||
:pencil2: Note that `grabberHandleSize` width and height are reversed in the left/right position.
|
||||
|
||||
#### Customize content padding from surface edges
|
||||
|
||||
@@ -654,24 +663,6 @@ The tap-to-dismiss action is disabled by default. So it needs to be enabled as b
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
```
|
||||
|
||||
### Allow to scroll content of the tracking scroll view in addition to the most expanded state
|
||||
|
||||
Just define conditions to allow content scrolling in `floatingPanel(:_:shouldAllowToScroll:in)` delegate method. If the returned value is true, the scroll content scrolls when its scroll position is not at the top of the content.
|
||||
|
||||
```swift
|
||||
class MyViewController: FloatingPanelControllerDelegate {
|
||||
...
|
||||
|
||||
func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll trackingScrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool {
|
||||
return state == .full || state == .half
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
### 'Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller
|
||||
@@ -712,6 +703,21 @@ It's a great way to decouple between a floating panel and the content VC.
|
||||
|
||||
Because `UISearchController` automatically presents itself modally when a user interacts with the search bar, and then it swaps the superview of the search bar to the view managed by itself while it displays. As a result, `FloatingPanelController` can't control the search bar when it's active, as you can see from [the screen shot](https://github.com/SCENEE/FloatingPanel/issues/248#issuecomment-521263831).
|
||||
|
||||
### FloatingPanelSurfaceView's issue on iOS 10
|
||||
|
||||
* On iOS 10, `FloatingPanelSurfaceView.cornerRadius` isn't not automatically masked with the top rounded corners because of `UIVisualEffectView` issue. See https://forums.developer.apple.com/thread/50854.
|
||||
So you need to draw top rounding corners of your content. Here is an example in Examples/Maps.
|
||||
```swift
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 10, *) {
|
||||
visualEffectView.layer.cornerRadius = 9.0
|
||||
visualEffectView.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
```
|
||||
* If you sets clear color to `FloatingPanelSurfaceView.backgroundColor`, please note the bottom overflow of your content on bouncing at full position. To prevent it, you need to expand your content. For example, See Example/Maps App's Auto Layout settings of `UIVisualEffectView` in Main.storyboard.
|
||||
|
||||
## Maintainer
|
||||
|
||||
Shin Yamamoto <shin@scenee.com> | [@scenee](https://twitter.com/scenee)
|
||||
|
||||
@@ -4,7 +4,7 @@ import UIKit
|
||||
|
||||
/// A view that presents a backdrop interface behind a panel.
|
||||
@objc(FloatingPanelBackdropView)
|
||||
open class BackdropView: UIView {
|
||||
public class BackdropView: UIView {
|
||||
|
||||
/// The gesture recognizer for tap gestures to dismiss a panel.
|
||||
///
|
||||
@@ -12,14 +12,14 @@ open class BackdropView: UIView {
|
||||
/// To dismiss a panel by tap gestures on the backdrop, `dismissalTapGestureRecognizer.isEnabled` is set to true.
|
||||
@objc public var dismissalTapGestureRecognizer: UITapGestureRecognizer
|
||||
|
||||
public init() {
|
||||
init() {
|
||||
dismissalTapGestureRecognizer = UITapGestureRecognizer()
|
||||
dismissalTapGestureRecognizer.isEnabled = false
|
||||
super.init(frame: .zero)
|
||||
addGestureRecognizer(dismissalTapGestureRecognizer)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
+22
-26
@@ -12,47 +12,43 @@ public protocol FloatingPanelBehavior {
|
||||
/// When this value is between 0.978 and 1.0, it uses a underdamped spring system with a damping ratio computed by
|
||||
/// this value. You shouldn't return less than 0.979 because the system is overdamped. If the pan gesture's velocity
|
||||
/// is less than 300, this value is ignored and a panel applies a critically damped system.
|
||||
@objc optional
|
||||
var springDecelerationRate: CGFloat { get }
|
||||
@objc
|
||||
optional var springDecelerationRate: CGFloat { get }
|
||||
|
||||
/// A floating-point value that determines the approximate time until a panel stops to an anchor after the user lifts their finger.
|
||||
@objc optional
|
||||
var springResponseTime: CGFloat { get }
|
||||
@objc
|
||||
optional var springResponseTime: CGFloat { get }
|
||||
|
||||
/// Returns a deceleration rate to calculate a target position projected a dragging momentum.
|
||||
///
|
||||
/// The default implementation of this method returns the normal deceleration rate of UIScrollView.
|
||||
@objc optional
|
||||
var momentumProjectionRate: CGFloat { get }
|
||||
@objc
|
||||
optional var momentumProjectionRate: CGFloat { get }
|
||||
|
||||
/// Asks the behavior if a panel should project a momentum of a user interaction to move the
|
||||
/// proposed state.
|
||||
/// Asks the behavior if a panel should project a momentum of a user interaction to move the proposed position.
|
||||
///
|
||||
/// The default implementation of this method returns `false`. This method is called for called
|
||||
/// for all states defined by the current layout object.
|
||||
@objc optional
|
||||
func shouldProjectMomentum(
|
||||
_ fpc: FloatingPanelController,
|
||||
to proposedState: FloatingPanelState
|
||||
) -> Bool
|
||||
/// The default implementation of this method returns true. This method is called for a layout to support all positions(tip, half and full).
|
||||
/// Therefore, `proposedTargetPosition` can only be `FloatingPanelState.tip` or `FloatingPanelState.full`.
|
||||
@objc
|
||||
optional func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool
|
||||
|
||||
/// Returns the progress to redirect to the previous position.
|
||||
///
|
||||
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates a panel is impossible to move to the next position. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
|
||||
@objc optional
|
||||
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelState, to: FloatingPanelState) -> CGFloat
|
||||
@objc
|
||||
optional func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelState, to: FloatingPanelState) -> CGFloat
|
||||
|
||||
/// Asks the behavior whether the rubber band effect is enabled in moving over a given edge of the surface view.
|
||||
///
|
||||
/// This method allows a panel to activate the rubber band effect to a given edge of the surface view. By default, the effect is disabled.
|
||||
@objc optional
|
||||
func allowsRubberBanding(for edge: UIRectEdge) -> Bool
|
||||
|
||||
@objc
|
||||
optional func allowsRubberBanding(for edge: UIRectEdge) -> Bool
|
||||
|
||||
/// Returns the velocity threshold for the default interactive removal gesture.
|
||||
///
|
||||
/// In case ``FloatingPanel/FloatingPanelControllerDelegate/floatingPanel(_:shouldRemoveAt:with:)`` is implemented, this value will not be used. The default value of ``FloatingPanelDefaultBehavior`` is 5.5
|
||||
@objc optional
|
||||
var removalInteractionVelocityThreshold: CGFloat { get }
|
||||
@objc
|
||||
optional var removalInteractionVelocityThreshold: CGFloat { get }
|
||||
}
|
||||
|
||||
/// The default behavior object for a panel
|
||||
@@ -80,7 +76,7 @@ open class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
|
||||
open func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
open var removalInteractionVelocityThreshold: CGFloat = 5.5
|
||||
}
|
||||
|
||||
@@ -104,13 +100,13 @@ class BehaviorAdapter {
|
||||
var momentumProjectionRate: CGFloat {
|
||||
behavior.momentumProjectionRate ?? FloatingPanelDefaultBehavior().momentumProjectionRate
|
||||
}
|
||||
|
||||
|
||||
var removalInteractionVelocityThreshold: CGFloat {
|
||||
behavior.removalInteractionVelocityThreshold ?? FloatingPanelDefaultBehavior().removalInteractionVelocityThreshold
|
||||
}
|
||||
|
||||
func redirectionalProgress(from: FloatingPanelState, to: FloatingPanelState) -> CGFloat {
|
||||
behavior.redirectionalProgress?(vc, from: from, to: to) ?? FloatingPanelDefaultBehavior().redirectionalProgress(vc,from: from, to: to)
|
||||
behavior.redirectionalProgress?(vc, from: from, to: to) ?? FloatingPanelDefaultBehavior().redirectionalProgress(vc, from: from, to: to)
|
||||
}
|
||||
|
||||
func shouldProjectMomentum(to: FloatingPanelState) -> Bool {
|
||||
@@ -125,6 +121,6 @@ class BehaviorAdapter {
|
||||
extension FloatingPanelController {
|
||||
var _behavior: FloatingPanelBehavior {
|
||||
get { floatingPanel.behaviorAdapter.behavior }
|
||||
set { floatingPanel.behaviorAdapter.behavior = newValue}
|
||||
set { floatingPanel.behaviorAdapter.behavior = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
+114
-142
@@ -1,90 +1,82 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// A set of methods implemented by the delegate of a panel controller allows the adopting delegate to respond to
|
||||
/// messages from the FloatingPanelController class and thus respond to, and in some affect, operations such as
|
||||
/// dragging, attracting a panel, layout of a panel and the content, and transition animations.
|
||||
@objc public protocol FloatingPanelControllerDelegate {
|
||||
/// Returns a FloatingPanelLayout object. If you use the default one, you can use a `FloatingPanelBottomLayout` object.
|
||||
@objc(floatingPanel:layoutForTraitCollection:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout
|
||||
@objc(floatingPanel:layoutForTraitCollection:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout
|
||||
|
||||
/// Returns a FloatingPanelLayout object. If you use the default one, you can use a `FloatingPanelBottomLayout` object.
|
||||
@objc(floatingPanel:layoutForSize:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout
|
||||
@objc(floatingPanel:layoutForSize:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to add/present the panel to a position.
|
||||
///
|
||||
/// Default is the spring animation with 0.25 secs.
|
||||
@objc(floatingPanel:animatorForPresentingToState:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, animatorForPresentingTo state: FloatingPanelState) -> UIViewPropertyAnimator
|
||||
@objc(floatingPanel:animatorForPresentingToState:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, animatorForPresentingTo state: FloatingPanelState) -> UIViewPropertyAnimator
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to remove/dismiss a panel from a position.
|
||||
///
|
||||
/// Default is the spring animator with 0.25 secs.
|
||||
@objc(floatingPanel:animatorForDismissingWithVelocity:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator
|
||||
@objc(floatingPanel:animatorForDismissingWithVelocity:)
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator
|
||||
|
||||
/// 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 floatingPanelDidChangeState(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelDidChangeState(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Asks the delegate if dragging should begin by the pan gesture recognizer.
|
||||
@objc optional
|
||||
func floatingPanelShouldBeginDragging(_ fpc: FloatingPanelController) -> Bool
|
||||
@objc
|
||||
optional func floatingPanelShouldBeginDragging(_ fpc: FloatingPanelController) -> Bool
|
||||
|
||||
/// Called while the user drags the surface or the surface moves to a state anchor.
|
||||
@objc optional
|
||||
func floatingPanelDidMove(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelDidMove(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Called on start of dragging (may require some time and or distance to move)
|
||||
@objc optional
|
||||
func floatingPanelWillBeginDragging(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelWillBeginDragging(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Called on finger up if the user dragged. velocity is in points/second.
|
||||
@objc optional
|
||||
func floatingPanelWillEndDragging(_ fpc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>)
|
||||
@objc
|
||||
optional func floatingPanelWillEndDragging(_ fpc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer<FloatingPanelState>)
|
||||
|
||||
/// Called on finger up if the user dragged.
|
||||
///
|
||||
/// If `attract` is true, the panel continues moving towards the nearby state
|
||||
/// anchor. Otherwise, it stops at the closest state anchor.
|
||||
///
|
||||
/// - Note: If `attract` is false, ``FloatingPanelController.state`` property has
|
||||
/// already changed to the closest anchor's state by the time this delegate method
|
||||
/// is called.
|
||||
@objc optional
|
||||
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool)
|
||||
/// If `attract` is true, it will continue moving afterwards to a nearby state anchor.
|
||||
@objc
|
||||
optional func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool)
|
||||
|
||||
/// Called when it is about to be attracted to a state anchor.
|
||||
@objc optional
|
||||
func floatingPanelWillBeginAttracting(_ fpc: FloatingPanelController, to state: FloatingPanelState) // called on finger up as a panel are moving
|
||||
@objc
|
||||
optional func floatingPanelWillBeginAttracting(_ fpc: FloatingPanelController, to state: FloatingPanelState) // called on finger up as a panel are moving
|
||||
|
||||
/// Called when attracting it is completed.
|
||||
@objc optional
|
||||
func floatingPanelDidEndAttracting(_ fpc: FloatingPanelController) // called when a panel stops
|
||||
@objc
|
||||
optional func floatingPanelDidEndAttracting(_ fpc: FloatingPanelController) // called when a panel stops
|
||||
|
||||
/// Asks the delegate whether a panel should be removed when dragging ended at the specified location
|
||||
///
|
||||
/// 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
|
||||
func floatingPanel(_ fpc: FloatingPanelController, shouldRemoveAt location: CGPoint, with velocity: CGVector) -> Bool
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, shouldRemoveAt location: CGPoint, with velocity: CGVector) -> Bool
|
||||
|
||||
/// Called on start to remove its view controller from the parent view controller.
|
||||
@objc(floatingPanelWillRemove:)
|
||||
optional
|
||||
func floatingPanelWillRemove(_ fpc: FloatingPanelController)
|
||||
optional func floatingPanelWillRemove(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Called when a panel is removed from the parent view controller.
|
||||
@objc optional
|
||||
func floatingPanelDidRemove(_ fpc: FloatingPanelController)
|
||||
@objc
|
||||
optional func floatingPanelDidRemove(_ fpc: FloatingPanelController)
|
||||
|
||||
/// Asks the delegate for a content offset of the tracking scroll view to be pinned when a panel moves
|
||||
///
|
||||
@@ -94,37 +86,7 @@ import os.log
|
||||
///
|
||||
/// This method will not be called if the controller doesn't track any scroll view.
|
||||
@objc(floatingPanel:contentOffsetForPinningScrollView:)
|
||||
optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint
|
||||
|
||||
/// Returns a Boolean value that determines whether the tracking scroll view should
|
||||
/// scroll or not
|
||||
///
|
||||
///
|
||||
/// If you return true, the scroll content scrolls when its scroll position is not
|
||||
/// at the top of the content. If the delegate doesn’t implement this method, its
|
||||
/// content can be scrolled only in the most expanded state.
|
||||
///
|
||||
/// Basically, the decision to scroll is based on the `state` property like the
|
||||
/// following code.
|
||||
/// ```swift
|
||||
/// func floatingPanel(
|
||||
/// _ fpc: FloatingPanelController,
|
||||
/// shouldAllowToScroll scrollView: UIScrollView,
|
||||
/// in state: FloatingPanelState
|
||||
/// ) -> Bool {
|
||||
/// return state == .full || state == .half
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Attention: It is recommended that this method always returns the most expanded state(i.e.
|
||||
/// .full). If it excludes the state, the panel might do unexpected behaviors.
|
||||
@objc(floatingPanel:shouldAllowToScroll:in:)
|
||||
optional func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll scrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool
|
||||
optional func floatingPanel(_ fpc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint
|
||||
}
|
||||
|
||||
///
|
||||
@@ -150,23 +112,22 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
/// The delegate of a panel controller object.
|
||||
@objc
|
||||
public weak var delegate: FloatingPanelControllerDelegate?{
|
||||
didSet{
|
||||
public weak var delegate: FloatingPanelControllerDelegate? {
|
||||
didSet {
|
||||
didUpdateDelegate()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
set { floatingPanel.backdropView = newValue }
|
||||
get { return floatingPanel.backdropView }
|
||||
public var backdropView: BackdropView! {
|
||||
return floatingPanel.backdropView
|
||||
}
|
||||
|
||||
/// Returns the scroll view that the controller tracks.
|
||||
@@ -203,8 +164,7 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_layout = newValue
|
||||
if let parent = parent, let layout = newValue as? UIViewController, layout == parent {
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its layout object. Don't allow the parent to adopt FloatingPanelLayout."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the layout object. Don't let the parent adopt FloatingPanelLayout.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,8 +176,7 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_behavior = newValue
|
||||
if let parent = parent, let behavior = newValue as? UIViewController, behavior == parent {
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its behavior object. Don't allow the parent to adopt FloatingPanelBehavior."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the behavior object. Don't let the parent adopt FloatingPanelBehavior.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,7 +210,7 @@ open class FloatingPanelController: UIViewController {
|
||||
/// The NearbyState determines that finger's nearby state.
|
||||
public var nearbyState: FloatingPanelState {
|
||||
let currentY = surfaceLocation.y
|
||||
return floatingPanel.targetState(from: currentY, with: .zero)
|
||||
return floatingPanel.targetPosition(from: currentY, with: .zero)
|
||||
}
|
||||
|
||||
/// Constants that define how a panel content fills in the surface.
|
||||
@@ -266,7 +225,7 @@ open class FloatingPanelController: UIViewController {
|
||||
private var _contentViewController: UIViewController?
|
||||
|
||||
private(set) var floatingPanel: Core!
|
||||
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
|
||||
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
|
||||
private var safeAreaInsetsObservation: NSKeyValueObservation?
|
||||
private let modalTransition = ModalTransition()
|
||||
|
||||
@@ -305,7 +264,7 @@ open class FloatingPanelController: UIViewController {
|
||||
floatingPanel = Core(self, layout: initialLayout, behavior: initialBehavior)
|
||||
}
|
||||
|
||||
private func didUpdateDelegate(){
|
||||
private func didUpdateDelegate() {
|
||||
if let layout = delegate?.floatingPanel?(self, layoutFor: traitCollection) {
|
||||
_layout = layout
|
||||
}
|
||||
@@ -331,19 +290,19 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
open override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
// Ensure to update the static constraint of a panel after rotating a device in static mode
|
||||
if contentMode == .static {
|
||||
floatingPanel.layoutAdapter.updateStaticConstraint()
|
||||
if #available(iOS 11.0, *) {
|
||||
// Ensure to update the static constraint of a panel after rotating a device in static mode
|
||||
if contentMode == .static {
|
||||
floatingPanel.layoutAdapter.updateStaticConstraint()
|
||||
}
|
||||
} else {
|
||||
// Because {top,bottom}LayoutGuide is managed as a view
|
||||
if floatingPanel.isAttracting == false {
|
||||
self.update(safeAreaInsets: fp_safeAreaInsets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
// Need to call this method just after the view appears, as the safe area is not
|
||||
// correctly set before this time, for example, `show(animated:completion:)`.
|
||||
floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
}
|
||||
|
||||
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
@@ -411,9 +370,9 @@ open class FloatingPanelController: UIViewController {
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
guard
|
||||
preSafeAreaInsets != safeAreaInsets
|
||||
else { return }
|
||||
else { return }
|
||||
|
||||
os_log(msg, log: devLog, type: .debug, "Update safeAreaInsets = \(safeAreaInsets)")
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
|
||||
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
|
||||
preSafeAreaInsets = safeAreaInsets
|
||||
@@ -425,7 +384,6 @@ open class FloatingPanelController: UIViewController {
|
||||
}
|
||||
|
||||
floatingPanel.layoutAdapter.updateStaticConstraint()
|
||||
floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
|
||||
if let contentOffset = contentOffset {
|
||||
trackingScrollView?.contentOffset = contentOffset
|
||||
@@ -453,15 +411,8 @@ open class FloatingPanelController: UIViewController {
|
||||
guard let self = self else { return }
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
} else if parent != nil {
|
||||
removePanelFromParent(animated: true)
|
||||
} else {
|
||||
delegate?.floatingPanelWillRemove?(self)
|
||||
hide(animated: true) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.view.removeFromSuperview()
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
removePanelFromParent(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,36 +427,45 @@ open class FloatingPanelController: UIViewController {
|
||||
// Must apply the current layout here
|
||||
activateLayout(forceLayout: true)
|
||||
|
||||
// Must track the safeAreaInsets of `self.view` to update the layout.
|
||||
// There are 2 reasons.
|
||||
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
safeAreaInsetsObservation = self.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
|
||||
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
|
||||
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
|
||||
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
|
||||
if #available(iOS 11.0, *) {
|
||||
// Must track the safeAreaInsets of `self.view` to update the layout.
|
||||
// There are 2 reasons.
|
||||
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
safeAreaInsetsObservation = self.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
|
||||
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
|
||||
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
|
||||
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
|
||||
|
||||
// Sometimes the bounding rectangle of the controlled view becomes invalid when the screen is rotated.
|
||||
// This results in its safeAreaInsets change. In that case, `self.update(safeAreaInsets:)` leads
|
||||
// an unsatisfied constraints error. So this method should not be called with those bounds.
|
||||
guard self.view.bounds.height > 0 && self.view.bounds.width > 0 else { return }
|
||||
// Sometimes the bounding rectangle of the controlled view becomes invalid when the screen is rotated.
|
||||
// This results in its safeAreaInsets change. In that case, `self.update(safeAreaInsets:)` leads
|
||||
// an unsatisfied constraints error. So this method should not be called with those bounds.
|
||||
guard self.view.bounds.height > 0 && self.view.bounds.width > 0 else { return }
|
||||
|
||||
self.update(safeAreaInsets: self.view.safeAreaInsets)
|
||||
self.update(safeAreaInsets: self.view.safeAreaInsets)
|
||||
}
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
// Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()`
|
||||
}
|
||||
|
||||
move(to: floatingPanel.layoutAdapter.initialState,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
move(
|
||||
to: floatingPanel.layoutAdapter.initialState,
|
||||
animated: animated,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/// Hides the surface view to the hidden position
|
||||
@objc(hide:completion:)
|
||||
public func hide(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
move(to: .hidden,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
move(
|
||||
to: .hidden,
|
||||
animated: animated,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/// Adds the view managed by the controller as a child of the specified view controller.
|
||||
@@ -517,7 +477,7 @@ open class FloatingPanelController: UIViewController {
|
||||
@objc(addPanelToParent:at:animated:completion:)
|
||||
public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
guard self.parent == nil else {
|
||||
os_log(msg, log: sysLog, type: .error, "Warning: already added to a parent(\(parent))")
|
||||
log.warning("Already added to a parent(\(parent))")
|
||||
return
|
||||
}
|
||||
assert((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.")
|
||||
@@ -534,14 +494,14 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
parent.addChild(self)
|
||||
|
||||
view.frame = parent.view.bounds // Needed for a correct safe area configuration
|
||||
view.frame = parent.view.bounds // Needed for a correct safe area configuration
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
self.view.topAnchor.constraint(equalTo: parent.view.topAnchor, constant: 0.0),
|
||||
self.view.leftAnchor.constraint(equalTo: parent.view.leftAnchor, constant: 0.0),
|
||||
self.view.rightAnchor.constraint(equalTo: parent.view.rightAnchor, constant: 0.0),
|
||||
self.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0),
|
||||
])
|
||||
])
|
||||
|
||||
show(animated: animated) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
@@ -624,7 +584,13 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
if #available(iOS 11.0, *) {
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
} else {
|
||||
children.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -709,35 +675,41 @@ extension FloatingPanelController {
|
||||
if let animator = delegate?.floatingPanel?(self, animatorForPresentingTo: to) {
|
||||
return animator
|
||||
}
|
||||
let timingParameters = UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25)
|
||||
return UIViewPropertyAnimator(duration: 0.0,
|
||||
timingParameters: timingParameters)
|
||||
let timingParameters = UISpringTimingParameters(
|
||||
decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25)
|
||||
return UIViewPropertyAnimator(
|
||||
duration: 0.0,
|
||||
timingParameters: timingParameters
|
||||
)
|
||||
}
|
||||
|
||||
func animatorForDismissing(with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
if let animator = delegate?.floatingPanel?(self, animatorForDismissingWith: velocity) {
|
||||
return animator
|
||||
}
|
||||
let timingParameters = UISpringTimingParameters(decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25,
|
||||
initialVelocity: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0.0,
|
||||
timingParameters: timingParameters)
|
||||
let timingParameters = UISpringTimingParameters(
|
||||
decelerationRate: UIScrollView.DecelerationRate.fast.rawValue,
|
||||
frequencyResponse: 0.25,
|
||||
initialVelocity: velocity)
|
||||
return UIViewPropertyAnimator(
|
||||
duration: 0.0,
|
||||
timingParameters: timingParameters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Swizzling
|
||||
|
||||
private var originalDismissImp: IMP?
|
||||
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
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)
|
||||
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:))) {
|
||||
originalDismissImp = method_setImplementation(originalMethod, swizzledImp)
|
||||
let swizzledImp = class_getMethodImplementation(aClass, #selector(__swizzled_dismiss(animated:completion:)))
|
||||
{
|
||||
originalDismissImp = method_setImplementation(originalMethod, swizzledImp)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -745,7 +717,7 @@ extension FloatingPanelController {
|
||||
extension UIViewController {
|
||||
@objc
|
||||
fileprivate func __swizzled_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: DismissFunction.self)
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: __dismissFunction.self)
|
||||
let sel = #selector(UIViewController.dismiss(animated:completion:))
|
||||
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
|
||||
+293
-444
File diff suppressed because it is too large
Load Diff
+135
-45
@@ -7,12 +7,10 @@ import UIKit
|
||||
extension CGFloat {
|
||||
/// Returns this value rounded to an logical pixel value by a display scale
|
||||
func rounded(by displayScale: CGFloat) -> CGFloat {
|
||||
let p = CGFloat(1.0e9)
|
||||
let v = (self * p).rounded(.towardZero) / p
|
||||
return (v * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
|
||||
return (self * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
|
||||
}
|
||||
func isEqual(to: CGFloat, on displayScale: CGFloat) -> Bool {
|
||||
return rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
return self.rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,16 +45,71 @@ protocol LayoutGuideProvider {
|
||||
extension UILayoutGuide: LayoutGuideProvider {}
|
||||
extension UIView: LayoutGuideProvider {}
|
||||
|
||||
extension UIViewController {
|
||||
/// The proxy property to be used in `LayoutAdapter`
|
||||
///
|
||||
/// This property is to allow the safe area inset to change in unit testing
|
||||
@objc var fp_safeAreaInsets: UIEdgeInsets {
|
||||
return view.safeAreaInsets
|
||||
private class CustomLayoutGuide: LayoutGuideProvider {
|
||||
let topAnchor: NSLayoutYAxisAnchor
|
||||
let leftAnchor: NSLayoutXAxisAnchor
|
||||
let bottomAnchor: NSLayoutYAxisAnchor
|
||||
let rightAnchor: NSLayoutXAxisAnchor
|
||||
let widthAnchor: NSLayoutDimension
|
||||
let heightAnchor: NSLayoutDimension
|
||||
init(
|
||||
topAnchor: NSLayoutYAxisAnchor,
|
||||
leftAnchor: NSLayoutXAxisAnchor,
|
||||
bottomAnchor: NSLayoutYAxisAnchor,
|
||||
rightAnchor: NSLayoutXAxisAnchor,
|
||||
widthAnchor: NSLayoutDimension,
|
||||
heightAnchor: NSLayoutDimension
|
||||
) {
|
||||
self.topAnchor = topAnchor
|
||||
self.leftAnchor = leftAnchor
|
||||
self.bottomAnchor = bottomAnchor
|
||||
self.rightAnchor = rightAnchor
|
||||
self.widthAnchor = widthAnchor
|
||||
self.heightAnchor = heightAnchor
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
@objc var fp_safeAreaInsets: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets(
|
||||
top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var fp_safeAreaLayoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view?.safeAreaLayoutGuide ?? view
|
||||
} else {
|
||||
return CustomLayoutGuide(
|
||||
topAnchor: topLayoutGuide.bottomAnchor,
|
||||
leftAnchor: view.leftAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor,
|
||||
rightAnchor: view.rightAnchor,
|
||||
widthAnchor: view.widthAnchor,
|
||||
heightAnchor: topLayoutGuide.bottomAnchor.anchorWithOffset(to: bottomLayoutGuide.topAnchor)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The reason why UIView has no extensions of safe area insets and top/bottom guides
|
||||
// is for iOS10 compatibility.
|
||||
extension UIView {
|
||||
var fp_safeAreaLayoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return safeAreaLayoutGuide
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
var presentationFrame: CGRect {
|
||||
return layer.presentation()?.frame ?? frame
|
||||
}
|
||||
@@ -86,13 +139,18 @@ extension UIView {
|
||||
}
|
||||
|
||||
static func performWithLinear(startTime: Double = 0.0, relativeDuration: Double = 1.0, _ animations: @escaping (() -> Void)) {
|
||||
UIView.animateKeyframes(withDuration: 0.0, delay: 0.0, options: [.calculationModeCubic], animations: {
|
||||
UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: relativeDuration, animations: animations)
|
||||
}, completion: nil)
|
||||
UIView.animateKeyframes(
|
||||
withDuration: 0.0,
|
||||
delay: 0.0,
|
||||
options: [.calculationModeCubic],
|
||||
animations: {
|
||||
UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: relativeDuration, animations: animations)
|
||||
},
|
||||
completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
#if FP_LOG
|
||||
#if __FP_LOG
|
||||
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
@@ -109,9 +167,21 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
#endif
|
||||
|
||||
extension UIScrollView {
|
||||
var isLocked: Bool {
|
||||
return !showsVerticalScrollIndicator && isDirectionalLockEnabled
|
||||
}
|
||||
var fp_contentInset: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return adjustedContentInset
|
||||
} else {
|
||||
return contentInset
|
||||
}
|
||||
}
|
||||
var fp_contentOffsetMax: CGPoint {
|
||||
return CGPoint(x: max((contentSize.width + adjustedContentInset.right) - bounds.width, 0.0),
|
||||
y: max((contentSize.height + adjustedContentInset.bottom) - bounds.height, 0.0))
|
||||
return CGPoint(
|
||||
x: max((contentSize.width + fp_contentInset.right) - bounds.width, 0.0),
|
||||
y: max((contentSize.height + fp_contentInset.bottom) - bounds.height, 0.0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +221,7 @@ extension UIEdgeInsets {
|
||||
|
||||
extension UIBezierPath {
|
||||
static func path(roundedRect rect: CGRect, appearance: SurfaceAppearance) -> UIBezierPath {
|
||||
let cornerRadius = appearance.cornerRadius;
|
||||
let cornerRadius = appearance.cornerRadius
|
||||
if #available(iOS 13.0, *) {
|
||||
if appearance.cornerCurve == .circular {
|
||||
let path = UIBezierPath()
|
||||
@@ -161,45 +231,61 @@ extension UIBezierPath {
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))
|
||||
if cornerRadius > 0 {
|
||||
path .addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius,
|
||||
y: rect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: -0.5 * .pi,
|
||||
endAngle: 0,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.maxX - cornerRadius,
|
||||
y: rect.minY + cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: -0.5 * .pi,
|
||||
endAngle: 0,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
|
||||
|
||||
if cornerRadius > 0 {
|
||||
path.addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius,
|
||||
y: rect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: 0,
|
||||
endAngle: .pi * 0.5,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.maxX - cornerRadius,
|
||||
y: rect.maxY - cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: 0,
|
||||
endAngle: .pi * 0.5,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY))
|
||||
|
||||
if cornerRadius > 0 {
|
||||
path.addArc(withCenter: CGPoint(x: rect.minX + cornerRadius,
|
||||
y: rect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi * 0.5,
|
||||
endAngle: .pi,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.minX + cornerRadius,
|
||||
y: rect.maxY - cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi * 0.5,
|
||||
endAngle: .pi,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
|
||||
|
||||
if cornerRadius > 0 {
|
||||
path.addArc(withCenter: CGPoint(x: rect.minX + cornerRadius,
|
||||
y: rect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi,
|
||||
endAngle: .pi * 1.5,
|
||||
clockwise: true)
|
||||
path.addArc(
|
||||
withCenter: CGPoint(
|
||||
x: rect.minX + cornerRadius,
|
||||
y: rect.minY + cornerRadius
|
||||
),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi,
|
||||
endAngle: .pi * 1.5,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: start)
|
||||
@@ -209,9 +295,13 @@ extension UIBezierPath {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.allCorners],
|
||||
cornerRadii: CGSize(width: cornerRadius,
|
||||
height: cornerRadius))
|
||||
return UIBezierPath(
|
||||
roundedRect: rect,
|
||||
byRoundingCorners: [.allCorners],
|
||||
cornerRadii: CGSize(
|
||||
width: cornerRadius,
|
||||
height: cornerRadius
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.8.6</string>
|
||||
<string>2.6.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
+112
-83
@@ -1,7 +1,6 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An interface for generating layout information for a panel.
|
||||
@objc public protocol FloatingPanelLayout {
|
||||
@@ -31,7 +30,7 @@ open class FloatingPanelBottomLayout: NSObject, FloatingPanelLayout {
|
||||
return .half
|
||||
}
|
||||
|
||||
open var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
open var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
@@ -45,8 +44,8 @@ open class FloatingPanelBottomLayout: NSObject, FloatingPanelLayout {
|
||||
|
||||
open func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.fp_safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: view.fp_safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -154,36 +153,42 @@ class LayoutAdapter {
|
||||
var adjustedContentInsets: UIEdgeInsets {
|
||||
switch position {
|
||||
case .top:
|
||||
return UIEdgeInsets(top: safeAreaInsets.top,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: 0.0)
|
||||
return UIEdgeInsets(
|
||||
top: safeAreaInsets.top,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: 0.0
|
||||
)
|
||||
case .left:
|
||||
return UIEdgeInsets(top: 0.0,
|
||||
left: safeAreaInsets.left,
|
||||
bottom: 0.0,
|
||||
right: 0.0)
|
||||
return UIEdgeInsets(
|
||||
top: 0.0,
|
||||
left: safeAreaInsets.left,
|
||||
bottom: 0.0,
|
||||
right: 0.0
|
||||
)
|
||||
case .bottom:
|
||||
return UIEdgeInsets(top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: safeAreaInsets.bottom,
|
||||
right: 0.0)
|
||||
return UIEdgeInsets(
|
||||
top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: safeAreaInsets.bottom,
|
||||
right: 0.0
|
||||
)
|
||||
case .right:
|
||||
return UIEdgeInsets(top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: safeAreaInsets.right)
|
||||
return UIEdgeInsets(
|
||||
top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: safeAreaInsets.right
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a constraint based value in the interaction and animation.
|
||||
|
||||
So that it doesn't need to call `surfaceView.layoutIfNeeded()`
|
||||
after every interaction and animation update. It has an effect on
|
||||
the smooth interaction because the content view doesn't need to update
|
||||
its layout frequently.
|
||||
*/
|
||||
/// Returns a constraint based value in the interaction and animation.
|
||||
///
|
||||
/// So that it doesn't need to call `surfaceView.layoutIfNeeded()`
|
||||
/// after every interaction and animation update. It has an effect on
|
||||
/// the smooth interaction because the content view doesn't need to update
|
||||
/// its layout frequently.
|
||||
var surfaceLocation: CGPoint {
|
||||
get {
|
||||
var pos: CGFloat
|
||||
@@ -266,25 +271,12 @@ class LayoutAdapter {
|
||||
}
|
||||
|
||||
var offsetFromMostExpandedAnchor: CGFloat {
|
||||
return offset(from: mostExpandedState)
|
||||
}
|
||||
|
||||
/// The distance from the given state position to the current surface location.
|
||||
///
|
||||
/// If the returned value is positive, it indicates that the surface is moving from
|
||||
/// the given state position to closer to the `hidden` state position. In other
|
||||
/// words, the surface is within the given state position. Otherwise, it indicates
|
||||
/// that the surface is outside this position and is moving away from the `hidden`
|
||||
/// state position.
|
||||
func offset(from state: FloatingPanelState) -> CGFloat {
|
||||
let offset: CGFloat
|
||||
switch position {
|
||||
case .top, .left:
|
||||
offset = position(for: state) - edgePosition(surfaceView.frame)
|
||||
return edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
|
||||
case .bottom, .right:
|
||||
offset = edgePosition(surfaceView.frame) - position(for: state)
|
||||
return position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
|
||||
}
|
||||
return offset.rounded(by: surfaceView.fp_displayScale)
|
||||
}
|
||||
|
||||
private var hiddenAnchor: FloatingPanelLayoutAnchoring {
|
||||
@@ -435,22 +427,22 @@ class LayoutAdapter {
|
||||
switch position {
|
||||
case .top, .bottom:
|
||||
surfaceConstraints = [
|
||||
surfaceView.leftAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
surfaceView.leftAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
]
|
||||
case .left, .right:
|
||||
surfaceConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
|
||||
surfaceView.bottomAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.bottomAnchor, constant: 0.0),
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.topAnchor, constant: 0.0),
|
||||
surfaceView.bottomAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.bottomAnchor, constant: 0.0),
|
||||
]
|
||||
}
|
||||
}
|
||||
let backdropConstraints = [
|
||||
backdropView.topAnchor.constraint(equalTo: vc.view.topAnchor, constant: 0.0),
|
||||
backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor,constant: 0.0),
|
||||
backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor, constant: 0.0),
|
||||
backdropView.rightAnchor.constraint(equalTo: vc.view.rightAnchor, constant: 0.0),
|
||||
backdropView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor, constant: 0.0),
|
||||
]
|
||||
]
|
||||
|
||||
fixedConstraints = surfaceConstraints + backdropConstraints
|
||||
|
||||
@@ -485,7 +477,10 @@ class LayoutAdapter {
|
||||
for state in layout.anchors.keys {
|
||||
stateConstraints[state] = layout.anchors[state]?
|
||||
.layoutConstraints(vc, for: position)
|
||||
.map{ $0.identifier = "FloatingPanel-\(state)-constraint"; return $0 }
|
||||
.map {
|
||||
$0.identifier = "FloatingPanel-\(state)-constraint"
|
||||
return $0
|
||||
}
|
||||
}
|
||||
let hiddenAnchor = layout.anchors[.hidden] ?? self.hiddenAnchor
|
||||
offConstraints = hiddenAnchor.layoutConstraints(vc, for: position)
|
||||
@@ -549,7 +544,7 @@ class LayoutAdapter {
|
||||
let layoutGuideProvider: LayoutGuideProvider
|
||||
switch anchor.referenceGuide {
|
||||
case .safeArea:
|
||||
layoutGuideProvider = vc.view.safeAreaLayoutGuide
|
||||
layoutGuideProvider = vc.fp_safeAreaLayoutGuide
|
||||
case .superview:
|
||||
layoutGuideProvider = vc.view
|
||||
}
|
||||
@@ -563,8 +558,10 @@ class LayoutAdapter {
|
||||
case .top:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .top:
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.top
|
||||
targetY -= safeAreaInsets.top
|
||||
@@ -572,8 +569,10 @@ class LayoutAdapter {
|
||||
case .bottom:
|
||||
let baseHeight = vc.view.bounds.height
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.bottomAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.bottom
|
||||
targetY += safeAreaInsets.bottom
|
||||
@@ -585,16 +584,20 @@ class LayoutAdapter {
|
||||
case .left:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .left:
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.right
|
||||
targetY -= safeAreaInsets.right
|
||||
}
|
||||
case .right:
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.rightAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.left
|
||||
targetY += safeAreaInsets.left
|
||||
@@ -605,16 +608,20 @@ class LayoutAdapter {
|
||||
case .bottom:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .top:
|
||||
animationConstraint = surfaceView.topAnchor.constraint(equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.topAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.topAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.top
|
||||
targetY -= safeAreaInsets.top
|
||||
}
|
||||
case .bottom:
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.topAnchor.constraint(equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.topAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.bottomAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.bottom
|
||||
targetY += safeAreaInsets.bottom
|
||||
@@ -626,16 +633,20 @@ class LayoutAdapter {
|
||||
case .right:
|
||||
switch referenceEdge(of: anchor) {
|
||||
case .left:
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY)
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.leftAnchor,
|
||||
constant: currentY
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant -= safeAreaInsets.left
|
||||
targetY -= safeAreaInsets.left
|
||||
}
|
||||
case .right:
|
||||
targetY = -(baseHeight - targetY)
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY))
|
||||
animationConstraint = surfaceView.leftAnchor.constraint(
|
||||
equalTo: layoutGuideProvider.rightAnchor,
|
||||
constant: -(baseHeight - currentY)
|
||||
)
|
||||
if anchor.referenceGuide == .safeArea {
|
||||
animationConstraint.constant += safeAreaInsets.right
|
||||
targetY += safeAreaInsets.right
|
||||
@@ -661,7 +672,7 @@ class LayoutAdapter {
|
||||
// The method is separated from prepareLayout(to:) for the rotation support
|
||||
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
|
||||
func updateStaticConstraint() {
|
||||
NSLayoutConstraint.deactivate([staticConstraint, contentBoundingConstraint].compactMap{ $0 })
|
||||
NSLayoutConstraint.deactivate([staticConstraint, contentBoundingConstraint].compactMap { $0 })
|
||||
staticConstraint = nil
|
||||
contentBoundingConstraint = nil
|
||||
|
||||
@@ -670,7 +681,10 @@ class LayoutAdapter {
|
||||
return
|
||||
}
|
||||
|
||||
let anchor = layout.anchors[self.mostExpandedState]!
|
||||
guard let anchor = layout.anchors[mostExpandedState] else {
|
||||
return
|
||||
}
|
||||
|
||||
let surfaceAnchor = position.mainDimensionAnchor(surfaceView)
|
||||
switch anchor {
|
||||
case let anchor as FloatingPanelIntrinsicLayoutAnchor:
|
||||
@@ -689,11 +703,15 @@ class LayoutAdapter {
|
||||
let baseAnchor = position.mainDimensionAnchor(anchor.contentLayoutGuide)
|
||||
if let boundingLayoutGuide = anchor.contentBoundingGuide.layoutGuide(vc) {
|
||||
if anchor.isAbsolute {
|
||||
contentBoundingConstraint = baseAnchor.constraint(lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
constant: anchor.offset)
|
||||
contentBoundingConstraint = baseAnchor.constraint(
|
||||
lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
constant: anchor.offset
|
||||
)
|
||||
} else {
|
||||
contentBoundingConstraint = baseAnchor.constraint(lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
multiplier: anchor.offset)
|
||||
contentBoundingConstraint = baseAnchor.constraint(
|
||||
lessThanOrEqualTo: position.mainDimensionAnchor(boundingLayoutGuide),
|
||||
multiplier: anchor.offset
|
||||
)
|
||||
}
|
||||
staticConstraint = surfaceAnchor.constraint(lessThanOrEqualTo: baseAnchor, constant: constant)
|
||||
} else {
|
||||
@@ -705,8 +723,10 @@ class LayoutAdapter {
|
||||
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.leastCoordinateState))
|
||||
staticConstraint = rootViewAnchor.constraint(
|
||||
equalTo: surfaceAnchor,
|
||||
constant: position(for: self.leastCoordinateState)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,14 +737,14 @@ class LayoutAdapter {
|
||||
staticConstraint?.identifier = "FloatingPanel-static-width"
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([staticConstraint, contentBoundingConstraint].compactMap{ $0 })
|
||||
NSLayoutConstraint.activate([staticConstraint, contentBoundingConstraint].compactMap { $0 })
|
||||
|
||||
surfaceView.containerOverflow = position.mainDimension(vc.view.bounds.size)
|
||||
}
|
||||
|
||||
func updateInteractiveEdgeConstraint(diff: CGFloat, scrollingContent: Bool, allowsRubberBanding: (UIRectEdge) -> Bool) {
|
||||
defer {
|
||||
os_log(msg, log: devLog, type: .debug, "update surface location = \(surfaceLocation)")
|
||||
log.debug("update surface location = \(surfaceLocation)")
|
||||
}
|
||||
|
||||
let minConst: CGFloat = position(for: leastCoordinateState)
|
||||
@@ -764,9 +784,9 @@ class LayoutAdapter {
|
||||
defer {
|
||||
if forceLayout {
|
||||
layoutSurfaceIfNeeded()
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
log.debug("activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
} else {
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state)")
|
||||
log.debug("activateLayout for \(state)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,6 +802,12 @@ 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
|
||||
@@ -795,7 +821,7 @@ class LayoutAdapter {
|
||||
if let constraints = stateConstraints[state] {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
} else {
|
||||
os_log(msg, log: sysLog, type: .fault, "Error: can not find any constraints for \(state)")
|
||||
log.error("Couldn't find any constraints for \(state)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -814,9 +840,12 @@ class LayoutAdapter {
|
||||
fileprivate func checkLayout() {
|
||||
// Verify layout configurations
|
||||
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
|
||||
assert(
|
||||
validStates.contains(layout.initialState),
|
||||
"Does not include an initial state (\(layout.initialState)) in (\(validStates))"
|
||||
)
|
||||
/**
|
||||
// This assertion does not work in a device rotating
|
||||
let statePosOrder = activeStates.sorted(by: { position(for: $0) < position(for: $1) })
|
||||
assert(sortedDirectionalStates == statePosOrder,
|
||||
"Check your layout anchors because the state order(\(statePosOrder)) must be (\(sortedDirectionalStates))).")
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
import UIKit
|
||||
|
||||
/// An interface for implementing custom layout anchor objects.
|
||||
@objc public protocol FloatingPanelLayoutAnchoring {
|
||||
@objc
|
||||
public protocol FloatingPanelLayoutAnchoring {
|
||||
var referenceGuide: FloatingPanelLayoutReferenceGuide { get }
|
||||
func layoutConstraints(_ fpc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint]
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with insets from an edge of a reference rectangle.
|
||||
@objc final public class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
@objc
|
||||
public final class FloatingPanelLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
|
||||
/// Returns a layout anchor with the specified inset by an absolute value, edge and reference guide for a panel.
|
||||
///
|
||||
@@ -17,7 +19,7 @@ import UIKit
|
||||
/// positioning.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - absoluteInset: An absolute distance to attach the panel from the specified edge.
|
||||
/// - absoluteOffset: An absolute offset to attach the panel from the edge.
|
||||
/// - edge: Specify the edge of ``FloatingPanelController``'s view. This is the staring point of the offset.
|
||||
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
|
||||
@objc public init(absoluteInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
|
||||
@@ -34,7 +36,7 @@ import UIKit
|
||||
/// 1.0 represents a distance to the opposite edge.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - fractionalInset: A fractional value of the size of ``FloatingPanelController``'s view to attach the panel from the specified edge.
|
||||
/// - fractionalOffset: A fractional value of the size of ``FloatingPanelController``'s view to attach the panel from the edge.
|
||||
/// - edge: Specify the edge of ``FloatingPanelController``'s view. This is the staring point of the offset.
|
||||
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
|
||||
@objc public init(fractionalInset: CGFloat, edge: FloatingPanelReferenceEdge, referenceGuide: FloatingPanelLayoutReferenceGuide) {
|
||||
@@ -50,8 +52,8 @@ import UIKit
|
||||
@objc let referenceEdge: FloatingPanelReferenceEdge
|
||||
}
|
||||
|
||||
public extension FloatingPanelLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
extension FloatingPanelLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
switch position {
|
||||
case .top:
|
||||
@@ -72,7 +74,7 @@ public extension FloatingPanelLayoutAnchor {
|
||||
return [edgeAnchor.constraint(equalTo: layoutGuide.topAnchor, constant: inset)]
|
||||
}
|
||||
let offsetAnchor = layoutGuide.topAnchor.anchorWithOffset(to: edgeAnchor)
|
||||
return [offsetAnchor.constraint(equalTo:layoutGuide.heightAnchor, multiplier: inset)]
|
||||
return [offsetAnchor.constraint(equalTo: layoutGuide.heightAnchor, multiplier: inset)]
|
||||
case .bottom:
|
||||
if isAbsolute {
|
||||
return [layoutGuide.bottomAnchor.constraint(equalTo: edgeAnchor, constant: inset)]
|
||||
@@ -105,7 +107,8 @@ public extension FloatingPanelLayoutAnchor {
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with the intrinsic size for a content.
|
||||
@objc final public class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
@objc
|
||||
public final class FloatingPanelIntrinsicLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
|
||||
/// Returns a layout anchor with the specified offset by an absolute value and reference guide for a panel.
|
||||
///
|
||||
@@ -115,8 +118,8 @@ public extension FloatingPanelLayoutAnchor {
|
||||
/// - Parameters:
|
||||
/// - absoluteOffset: An absolute offset from the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
|
||||
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
|
||||
@objc public init(absoluteOffset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
|
||||
self.offset = absoluteOffset
|
||||
@objc public init(absoluteOffset offset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
|
||||
self.offset = offset
|
||||
self.referenceGuide = referenceGuide
|
||||
self.isAbsolute = true
|
||||
}
|
||||
@@ -129,8 +132,8 @@ public extension FloatingPanelLayoutAnchor {
|
||||
/// - Parameters:
|
||||
/// - fractionalOffset: A fractional offset of the content size in the main dimension(i.e. y axis for a bottom panel) to attach the panel.
|
||||
/// - referenceGuide: The rectangular area to lay out the content. If it's set to `.safeArea`, the panel content lays out inside the safe area of its ``FloatingPanelController``'s view.
|
||||
@objc public init(fractionalOffset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
|
||||
self.offset = fractionalOffset
|
||||
@objc public init(fractionalOffset offset: CGFloat, referenceGuide: FloatingPanelLayoutReferenceGuide = .safeArea) {
|
||||
self.offset = offset
|
||||
self.referenceGuide = referenceGuide
|
||||
self.isAbsolute = false
|
||||
}
|
||||
@@ -141,8 +144,8 @@ public extension FloatingPanelLayoutAnchor {
|
||||
@objc public let referenceGuide: FloatingPanelLayoutReferenceGuide
|
||||
}
|
||||
|
||||
public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
let surfaceIntrinsicLength = position.mainDimension(vc.surfaceView.intrinsicContentSize)
|
||||
let constant = isAbsolute ? surfaceIntrinsicLength - offset : surfaceIntrinsicLength * (1 - offset)
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
@@ -160,7 +163,8 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
}
|
||||
|
||||
/// An object that defines how to settles a panel with a layout guide of a content view.
|
||||
@objc final public class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring /*, NSCopying */ {
|
||||
@objc
|
||||
public final class FloatingPanelAdaptiveLayoutAnchor: NSObject, FloatingPanelLayoutAnchoring {
|
||||
|
||||
/// Returns a layout anchor with the specified offset by an absolute value to display a panel with its intrinsic content size.
|
||||
///
|
||||
@@ -177,12 +181,12 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
///
|
||||
/// - Warning: If ``contentBoundingGuide`` is set to none, the panel may expand out of the screen size, depending on the intrinsic size of its content.
|
||||
@objc public init(
|
||||
absoluteOffset: CGFloat,
|
||||
absoluteOffset offset: CGFloat,
|
||||
contentLayout: UILayoutGuide,
|
||||
referenceGuide: FloatingPanelLayoutReferenceGuide,
|
||||
contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide = .none
|
||||
) {
|
||||
self.offset = absoluteOffset
|
||||
self.offset = offset
|
||||
self.contentLayoutGuide = contentLayout
|
||||
self.referenceGuide = referenceGuide
|
||||
self.contentBoundingGuide = contentBoundingGuide
|
||||
@@ -204,12 +208,12 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
///
|
||||
/// - Warning: If ``contentBoundingGuide`` is set to none, the panel may expand out of the screen size, depending on the intrinsic size of its content.
|
||||
@objc public init(
|
||||
fractionalOffset: CGFloat,
|
||||
fractionalOffset offset: CGFloat,
|
||||
contentLayout: UILayoutGuide,
|
||||
referenceGuide: FloatingPanelLayoutReferenceGuide,
|
||||
contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide = .none
|
||||
) {
|
||||
self.offset = fractionalOffset
|
||||
self.offset = offset
|
||||
self.contentLayoutGuide = contentLayout
|
||||
self.referenceGuide = referenceGuide
|
||||
self.contentBoundingGuide = contentBoundingGuide
|
||||
@@ -222,8 +226,8 @@ public extension FloatingPanelIntrinsicLayoutAnchor {
|
||||
@objc public let contentBoundingGuide: FloatingPanelLayoutContentBoundingGuide
|
||||
}
|
||||
|
||||
public extension FloatingPanelAdaptiveLayoutAnchor {
|
||||
func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
extension FloatingPanelAdaptiveLayoutAnchor {
|
||||
public func layoutConstraints(_ vc: FloatingPanelController, for position: FloatingPanelPosition) -> [NSLayoutConstraint] {
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
|
||||
let layoutGuide = referenceGuide.layoutGuide(vc: vc)
|
||||
|
||||
@@ -37,7 +37,7 @@ extension FloatingPanelLayoutReferenceGuide {
|
||||
func layoutGuide(vc: UIViewController) -> LayoutGuideProvider {
|
||||
switch self {
|
||||
case .safeArea:
|
||||
return vc.view.safeAreaLayoutGuide
|
||||
return vc.fp_safeAreaLayoutGuide
|
||||
case .superview:
|
||||
return vc.view
|
||||
}
|
||||
@@ -57,7 +57,7 @@ extension FloatingPanelLayoutContentBoundingGuide {
|
||||
case .superview:
|
||||
return fpc.view
|
||||
case .safeArea:
|
||||
return fpc.view.safeAreaLayoutGuide
|
||||
return fpc.fp_safeAreaLayoutGuide
|
||||
case .none:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
// Must be a variable to use `hook` property in testing
|
||||
var log = {
|
||||
return Logger()
|
||||
}()
|
||||
|
||||
struct Logger {
|
||||
private let osLog: OSLog
|
||||
private let s = DispatchSemaphore(value: 1)
|
||||
|
||||
enum Level: Int, Comparable {
|
||||
case debug = 0
|
||||
case info = 1
|
||||
case warning = 2
|
||||
case error = 3
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug:
|
||||
return "Debug:"
|
||||
case .info:
|
||||
return "Info:"
|
||||
case .warning:
|
||||
return "Warning:"
|
||||
case .error:
|
||||
return "Error:"
|
||||
}
|
||||
}
|
||||
@available(iOS 10.0, *)
|
||||
var osLogType: OSLogType {
|
||||
switch self {
|
||||
case .debug: return .debug
|
||||
case .info: return .info
|
||||
case .warning: return .default
|
||||
case .error: return .error
|
||||
}
|
||||
}
|
||||
|
||||
static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
typealias Hook = ((String, Level) -> Void)
|
||||
var hook: Hook?
|
||||
|
||||
fileprivate init() {
|
||||
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
|
||||
}
|
||||
|
||||
private func log(_ level: Level, _ message: Any, _ arguments: [Any], tag: String, function: String, line: UInt) {
|
||||
_ = s.wait(timeout: .now() + 0.033)
|
||||
defer { s.signal() }
|
||||
|
||||
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
|
||||
let _tag = tag.isEmpty ? "" : "\(tag):"
|
||||
let log: String = {
|
||||
switch level {
|
||||
case .debug:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage) (\(function):\(line))"
|
||||
default:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage)"
|
||||
}
|
||||
}()
|
||||
|
||||
hook?(log, level)
|
||||
|
||||
os_log("%{public}@", log: osLog, type: level.osLogType, log)
|
||||
}
|
||||
|
||||
private func getPrettyFunction(_ function: String, _ file: String) -> String {
|
||||
if let filename = file.split(separator: "/").last {
|
||||
return filename + ":" + function
|
||||
} else {
|
||||
return file + ":" + function
|
||||
}
|
||||
}
|
||||
|
||||
func debug(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
#if __FP_LOG
|
||||
self.log(.debug, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
#endif
|
||||
}
|
||||
|
||||
func info(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.info, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func warning(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.warning, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.error, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import os.log
|
||||
|
||||
let msg = StaticString("%{public}@")
|
||||
let sysLog = OSLog(subsystem: Logging.subsystem, category: Logging.category)
|
||||
#if FP_LOG
|
||||
let devLog = OSLog(subsystem: Logging.subsystem, category: "\(Logging.category):dev")
|
||||
#else
|
||||
let devLog = OSLog.disabled
|
||||
#endif
|
||||
|
||||
struct Logging {
|
||||
static let subsystem = "com.scenee.FloatingPanel"
|
||||
static let category = "FloatingPanel"
|
||||
private init() {}
|
||||
}
|
||||
+144
-112
@@ -1,7 +1,6 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An object for customizing the appearance of a surface view
|
||||
@objc(FloatingPanelSurfaceAppearance)
|
||||
@@ -56,11 +55,11 @@ public class SurfaceAppearance: NSObject {
|
||||
/// Defaults to `.circular`.
|
||||
@available(iOS 13.0, *)
|
||||
public var cornerCurve: CALayerCornerCurve {
|
||||
get { _cornerCurve as? CALayerCornerCurve ?? .circular }
|
||||
get { _cornerCurve ?? .circular }
|
||||
set { _cornerCurve = newValue }
|
||||
}
|
||||
|
||||
private var _cornerCurve: Any?
|
||||
private var _cornerCurve: CALayerCornerCurve?
|
||||
|
||||
/// An array of shadows used to create drop shadows underneath a surface view.
|
||||
public var shadows: [Shadow] = [Shadow()]
|
||||
@@ -82,9 +81,11 @@ public class SurfaceView: UIView {
|
||||
public let grabberHandle = GrabberView()
|
||||
|
||||
/// Offset of the grabber handle from the interactive edge.
|
||||
public var grabberHandlePadding: CGFloat = 6.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
public var grabberHandlePadding: CGFloat = 6.0 {
|
||||
didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
/// The offset from the move edge to prevent the content scroll
|
||||
public var grabberAreaOffset: CGFloat = 36.0
|
||||
@@ -92,9 +93,11 @@ 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.
|
||||
public var grabberHandleSize: CGSize = CGSize(width: 36.0, height: 5.0) { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
public var grabberHandleSize: CGSize = CGSize(width: 36.0, height: 5.0) {
|
||||
didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
/// The content view to be assigned a view of the content view controller of `FloatingPanelController`
|
||||
public weak var contentView: UIView?
|
||||
@@ -109,19 +112,26 @@ public class SurfaceView: UIView {
|
||||
|
||||
public override var backgroundColor: UIColor? {
|
||||
get { return appearance.backgroundColor }
|
||||
set { appearance.backgroundColor = newValue; setNeedsLayout() }
|
||||
set {
|
||||
appearance.backgroundColor = newValue
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
/// The appearance settings for a surface view.
|
||||
public var appearance = SurfaceAppearance() { didSet {
|
||||
shadowLayers = appearance.shadows.map { _ in CAShapeLayer() }
|
||||
setNeedsLayout()
|
||||
}}
|
||||
public var appearance = SurfaceAppearance() {
|
||||
didSet {
|
||||
shadowLayers = appearance.shadows.map { _ in CAShapeLayer() }
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
/// The margins to use when laying out the container view wrapping content.
|
||||
public var containerMargins: UIEdgeInsets = .zero { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
public var containerMargins: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
/// The view that displays an actual surface shape.
|
||||
///
|
||||
@@ -131,7 +141,7 @@ public class SurfaceView: UIView {
|
||||
/// content view.
|
||||
public let containerView: UIView = UIView()
|
||||
|
||||
var containerOverflow: CGFloat = 0.0 { // Must not call setNeedsLayout()
|
||||
var containerOverflow: CGFloat = 0.0 { // Must not call setNeedsLayout()
|
||||
didSet {
|
||||
// Calling setNeedsUpdateConstraints() is necessary to fix a layout break
|
||||
// when the contentMode is changed after laying out a panel, for instance,
|
||||
@@ -143,10 +153,12 @@ public class SurfaceView: UIView {
|
||||
var position: FloatingPanelPosition = .bottom {
|
||||
didSet {
|
||||
guard position != oldValue else { return }
|
||||
NSLayoutConstraint.deactivate([grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint])
|
||||
NSLayoutConstraint.deactivate([
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
])
|
||||
switch position {
|
||||
case .top:
|
||||
grabberHandleEdgePaddingConstraint = grabberHandle.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -grabberHandlePadding)
|
||||
@@ -169,10 +181,12 @@ public class SurfaceView: UIView {
|
||||
grabberHandleWidthConstraint = grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandleSize.height)
|
||||
grabberHandleHeightConstraint = grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandleSize.width)
|
||||
}
|
||||
NSLayoutConstraint.activate([grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint])
|
||||
NSLayoutConstraint.activate([
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
])
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
@@ -180,17 +194,25 @@ public class SurfaceView: UIView {
|
||||
var grabberAreaFrame: CGRect {
|
||||
switch position {
|
||||
case .top:
|
||||
return CGRect(origin: .init(x: bounds.minX, y: bounds.maxY - grabberAreaOffset),
|
||||
size: .init(width: bounds.width, height: grabberAreaOffset))
|
||||
return CGRect(
|
||||
origin: .init(x: bounds.minX, y: bounds.maxY - grabberAreaOffset),
|
||||
size: .init(width: bounds.width, height: grabberAreaOffset)
|
||||
)
|
||||
case .left:
|
||||
return CGRect(origin: .init(x: bounds.maxX - grabberAreaOffset, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height))
|
||||
return CGRect(
|
||||
origin: .init(x: bounds.maxX - grabberAreaOffset, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height)
|
||||
)
|
||||
case .bottom:
|
||||
return CGRect(origin: CGPoint(x: bounds.minX, y: bounds.minY),
|
||||
size: CGSize(width: bounds.width, height: grabberAreaOffset))
|
||||
return CGRect(
|
||||
origin: CGPoint(x: bounds.minX, y: bounds.minY),
|
||||
size: CGSize(width: bounds.width, height: grabberAreaOffset)
|
||||
)
|
||||
case .right:
|
||||
return CGRect(origin: .init(x: bounds.minX, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height))
|
||||
return CGRect(
|
||||
origin: .init(x: bounds.minX, y: bounds.minY),
|
||||
size: .init(width: grabberAreaOffset, height: bounds.height)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +232,7 @@ public class SurfaceView: UIView {
|
||||
|
||||
private lazy var grabberHandleWidthConstraint = grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandleSize.width)
|
||||
private lazy var grabberHandleHeightConstraint = grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandleSize.height)
|
||||
private lazy var grabberHandleCenterConstraint = grabberHandle.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
private lazy var grabberHandleCenterConstraint = grabberHandle.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
private lazy var grabberHandleEdgePaddingConstraint = grabberHandle.topAnchor.constraint(equalTo: topAnchor, constant: grabberHandlePadding)
|
||||
|
||||
private var shadowLayers: [CALayer] = [] {
|
||||
@@ -244,27 +266,31 @@ public class SurfaceView: UIView {
|
||||
|
||||
addSubview(containerView)
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
containerViewTopConstraint,
|
||||
containerViewLeftConstraint,
|
||||
containerViewBottomConstraint,
|
||||
containerViewRightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-container"
|
||||
return $0;
|
||||
})
|
||||
NSLayoutConstraint.activate(
|
||||
[
|
||||
containerViewTopConstraint,
|
||||
containerViewLeftConstraint,
|
||||
containerViewBottomConstraint,
|
||||
containerViewRightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-container"
|
||||
return $0
|
||||
}
|
||||
)
|
||||
|
||||
addSubview(grabberHandle)
|
||||
grabberHandle.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-grabber"
|
||||
return $0;
|
||||
})
|
||||
NSLayoutConstraint.activate(
|
||||
[
|
||||
grabberHandleEdgePaddingConstraint,
|
||||
grabberHandleCenterConstraint,
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
].map {
|
||||
$0.identifier = "FloatingPanel-surface-grabber"
|
||||
return $0
|
||||
}
|
||||
)
|
||||
|
||||
shadowLayers = appearance.shadows.map { _ in CALayer() }
|
||||
}
|
||||
@@ -312,14 +338,14 @@ public class SurfaceView: UIView {
|
||||
case .left, .right:
|
||||
grabberHandleWidthConstraint.constant = grabberHandleSize.height
|
||||
grabberHandleHeightConstraint.constant = grabberHandleSize.width
|
||||
}
|
||||
}
|
||||
|
||||
super.updateConstraints()
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
os_log(msg, log: devLog, type: .debug, "surface view frame = \(frame)")
|
||||
log.debug("surface view frame = \(frame)")
|
||||
|
||||
containerView.backgroundColor = appearance.backgroundColor
|
||||
|
||||
@@ -333,8 +359,10 @@ public class SurfaceView: UIView {
|
||||
public override var intrinsicContentSize: CGSize {
|
||||
let fittingSize = UIView.layoutFittingCompressedSize
|
||||
let contentSize = contentView?.systemLayoutSizeFitting(fittingSize) ?? .zero
|
||||
return CGSize(width: containerMargins.horizontalInset + contentPadding.horizontalInset + contentSize.width,
|
||||
height: containerMargins.verticalInset + contentPadding.verticalInset + contentSize.height)
|
||||
return CGSize(
|
||||
width: containerMargins.horizontalInset + contentPadding.horizontalInset + contentSize.width,
|
||||
height: containerMargins.verticalInset + contentPadding.verticalInset + contentSize.height
|
||||
)
|
||||
}
|
||||
|
||||
private func updateShadow() {
|
||||
@@ -349,8 +377,10 @@ public class SurfaceView: UIView {
|
||||
|
||||
let spread = shadow.spread
|
||||
let shadowRect = containerView.frame.insetBy(dx: -spread, dy: -spread)
|
||||
let shadowPath = UIBezierPath.path(roundedRect: shadowRect,
|
||||
appearance: appearance)
|
||||
let shadowPath = UIBezierPath.path(
|
||||
roundedRect: shadowRect,
|
||||
appearance: appearance
|
||||
)
|
||||
shadowLayer.shadowPath = shadowPath.cgPath
|
||||
shadowLayer.shadowColor = shadow.color.cgColor
|
||||
shadowLayer.shadowOffset = shadow.offset
|
||||
@@ -359,11 +389,19 @@ public class SurfaceView: UIView {
|
||||
shadowLayer.shadowOpacity = shadow.opacity
|
||||
|
||||
let mask = CAShapeLayer()
|
||||
let path = UIBezierPath.path(roundedRect: containerView.frame,
|
||||
appearance: appearance)
|
||||
let path = UIBezierPath.path(
|
||||
roundedRect: containerView.frame,
|
||||
appearance: appearance
|
||||
)
|
||||
let size = window?.bounds.size ?? CGSize(width: 1000.0, height: 1000.0)
|
||||
path.append(UIBezierPath(rect: layer.bounds.insetBy(dx: -size.width,
|
||||
dy: -size.height)))
|
||||
path.append(
|
||||
UIBezierPath(
|
||||
rect: layer.bounds.insetBy(
|
||||
dx: -size.width,
|
||||
dy: -size.height
|
||||
)
|
||||
)
|
||||
)
|
||||
mask.fillRule = .evenOdd
|
||||
mask.path = path.cgPath
|
||||
if #available(iOS 13.0, *) {
|
||||
@@ -383,24 +421,31 @@ public class SurfaceView: UIView {
|
||||
}
|
||||
containerView.layer.masksToBounds = true
|
||||
if position.inset(containerMargins) != 0 {
|
||||
containerView.layer.maskedCorners = [
|
||||
.layerMinXMinYCorner, .layerMaxXMinYCorner,
|
||||
.layerMinXMaxYCorner, .layerMaxXMaxYCorner
|
||||
]
|
||||
if #available(iOS 11, *) {
|
||||
containerView.layer.maskedCorners = [
|
||||
.layerMinXMinYCorner, .layerMaxXMinYCorner,
|
||||
.layerMinXMaxYCorner, .layerMaxXMaxYCorner,
|
||||
]
|
||||
}
|
||||
return
|
||||
}
|
||||
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
|
||||
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
|
||||
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
|
||||
switch position {
|
||||
case .top:
|
||||
containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
case .left:
|
||||
containerView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
case .bottom:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
case .right:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
if #available(iOS 11, *) {
|
||||
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
|
||||
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
|
||||
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
|
||||
switch position {
|
||||
case .top:
|
||||
containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
case .left:
|
||||
containerView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
case .bottom:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
case .right:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
}
|
||||
} else {
|
||||
// Can't use `containerView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user should display rounding corners appropriately.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,46 +457,33 @@ public class SurfaceView: 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.
|
||||
/**
|
||||
// MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
contentView.frame = bounds
|
||||
*/
|
||||
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)
|
||||
let bottomConstraint = bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: containerMargins.bottom + contentPadding.bottom)
|
||||
|
||||
var constraints = [
|
||||
topConstraint,
|
||||
leftConstraint,
|
||||
rightConstraint,
|
||||
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
|
||||
// The reason why this priority is set to .required - 1 is #359, which fixed #294.
|
||||
case .fitToBounds:
|
||||
$0.priority = .required - 1
|
||||
[
|
||||
topConstraint,
|
||||
leftConstraint,
|
||||
rightConstraint,
|
||||
bottomConstraint,
|
||||
].map {
|
||||
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
|
||||
}
|
||||
$0.identifier = "FloatingPanel-surface-content"
|
||||
return $0
|
||||
})
|
||||
)
|
||||
self.contentViewTopConstraint = topConstraint
|
||||
self.contentViewLeftConstraint = leftConstraint
|
||||
self.contentViewRightConstraint = rightConstraint
|
||||
|
||||
+16
-12
@@ -3,9 +3,11 @@
|
||||
import UIKit
|
||||
|
||||
class ModalTransition: NSObject, UIViewControllerTransitioningDelegate {
|
||||
func animationController(forPresented presented: UIViewController,
|
||||
presenting: UIViewController,
|
||||
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
func animationController(
|
||||
forPresented presented: UIViewController,
|
||||
presenting: UIViewController,
|
||||
source: UIViewController
|
||||
) -> UIViewControllerAnimatedTransitioning? {
|
||||
return ModalPresentTransition()
|
||||
}
|
||||
|
||||
@@ -52,11 +54,11 @@ class PresentationController: UIPresentationController {
|
||||
view is added unnecessarily.
|
||||
*/
|
||||
fpc.presentedViewController == nil
|
||||
else { return }
|
||||
else { return }
|
||||
|
||||
/*
|
||||
* Layout the views managed by `FloatingPanelController` here for the
|
||||
* sake of the presentation and dismissal modally from the controller.
|
||||
/**
|
||||
Layout the views managed by `FloatingPanelController` here for the
|
||||
sake of the presentation and dismissal modally from the controller.
|
||||
*/
|
||||
addFloatingPanel()
|
||||
|
||||
@@ -72,7 +74,7 @@ class PresentationController: UIPresentationController {
|
||||
guard
|
||||
let containerView = self.containerView,
|
||||
let fpc = presentedViewController as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
else { fatalError() }
|
||||
|
||||
containerView.addSubview(fpc.view)
|
||||
fpc.view.frame = containerView.bounds
|
||||
@@ -84,7 +86,7 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
|
||||
else { return 0.0 }
|
||||
else { fatalError() }
|
||||
|
||||
let animator = fpc.animatorForPresenting(to: fpc.layout.initialState)
|
||||
return TimeInterval(animator.duration)
|
||||
@@ -119,7 +121,7 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
|
||||
else { return 0.0 }
|
||||
else { fatalError() }
|
||||
|
||||
let animator = fpc.animatorForDismissing(with: .zero)
|
||||
return TimeInterval(animator.duration)
|
||||
@@ -139,11 +141,13 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
return fpc.transitionAnimator!
|
||||
guard let transitionAnimator = fpc.transitionAnimator else {
|
||||
fatalError("Something wrong happened in Core.move(from:to:animated:completion)")
|
||||
}
|
||||
return transitionAnimator
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-84
@@ -1,39 +1,32 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import OSLog
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class ControllerTests: XCTestCase {
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
#if swift(>=5.5) // Avoid the 'No exact matches in call to initializer' build failure for OSLogStore when running this test case on iOS 13.7 using Xcode 12.5.1
|
||||
func test_warningRetainCycle() throws {
|
||||
guard #available(iOS 15.0, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 15 or later")
|
||||
func test_warningRetainCycle() {
|
||||
let exp = expectation(description: "Warning retain cycle")
|
||||
exp.expectedFulfillmentCount = 2 // For layout & behavior logs
|
||||
log.hook = { (log, level) in
|
||||
if log.contains("A memory leak will occur by a retain cycle because") {
|
||||
XCTAssert(level == .warning)
|
||||
exp.fulfill()
|
||||
}
|
||||
}
|
||||
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
|
||||
myVC.loadViewIfNeeded()
|
||||
let store = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let found = try store
|
||||
.getEntries(
|
||||
at: store.position(timeIntervalSinceLatestBoot: 0),
|
||||
matching: .init(format: "subsystem == '\(Logging.subsystem)'")
|
||||
)
|
||||
.contains {
|
||||
$0.composedMessage.contains("A memory leak occurs due to a retain cycle, as")
|
||||
}
|
||||
XCTAssertTrue(found)
|
||||
wait(for: [exp], timeout: 10)
|
||||
}
|
||||
#endif
|
||||
|
||||
func test_addPanel() {
|
||||
let rootVC = UIViewController()
|
||||
rootVC.loadViewIfNeeded()
|
||||
rootVC.view.bounds = .init(origin: .zero, size: .init(width: 390, height: 844))
|
||||
|
||||
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.addPanel(toParent: rootVC)
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .half).y)
|
||||
@@ -41,13 +34,8 @@ class ControllerTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .tip).y)
|
||||
}
|
||||
|
||||
func test_updateLayout_willTransition() throws {
|
||||
guard #available(iOS 12, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 12 or later")
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
throw XCTSkip("Unsupported iOS version: this test doesn't support iOS 17 or later")
|
||||
}
|
||||
@available(iOS 12.0, *)
|
||||
func test_updateLayout_willTransition() {
|
||||
class MyDelegate: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
if newCollection.userInterfaceStyle == .dark {
|
||||
@@ -58,13 +46,11 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
let myDelegate = MyDelegate()
|
||||
let fpc = FloatingPanelController(delegate: myDelegate)
|
||||
let traitCollection = UITraitCollection(traitsFrom: [fpc.traitCollection,
|
||||
UITraitCollection(userInterfaceStyle: .dark)])
|
||||
let traitCollection = UITraitCollection(traitsFrom: [fpc.traitCollection, UITraitCollection(userInterfaceStyle: .dark)])
|
||||
XCTAssertEqual(traitCollection.userInterfaceStyle, .dark)
|
||||
}
|
||||
|
||||
func test_moveTo() {
|
||||
let timeout = 3.0
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
@@ -73,7 +59,7 @@ class ControllerTests: XCTestCase {
|
||||
|
||||
fpc.hide()
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.state, .full)
|
||||
XCTAssertEqual(delegate.position, .full)
|
||||
@@ -103,7 +89,7 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
XCTAssertEqual(fpc.state, .full)
|
||||
XCTAssertEqual(delegate.position, .full)
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
}
|
||||
|
||||
XCTContext.runActivity(named: "move to half(animated)") { act in
|
||||
@@ -114,7 +100,7 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(delegate.position, .half)
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
}
|
||||
|
||||
XCTContext.runActivity(named: "move to tip(animated)") { act in
|
||||
@@ -125,7 +111,7 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
XCTAssertEqual(fpc.state, .tip)
|
||||
XCTAssertEqual(delegate.position, .tip)
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
}
|
||||
|
||||
fpc.move(to: .hidden, animated: true)
|
||||
@@ -138,7 +124,6 @@ class ControllerTests: XCTestCase {
|
||||
class MyFloatingPanelTop2BottomLayout: FloatingPanelTop2BottomTestLayout {
|
||||
override var initialState: FloatingPanelState { return .half }
|
||||
}
|
||||
let timeout = 3.0
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = MyFloatingPanelTop2BottomLayout()
|
||||
@@ -177,7 +162,7 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
XCTAssertEqual(fpc.state, .full)
|
||||
XCTAssertEqual(delegate.position, .full)
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
}
|
||||
|
||||
XCTContext.runActivity(named: "move to half(animated)") { act in
|
||||
@@ -188,7 +173,7 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(delegate.position, .half)
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
}
|
||||
|
||||
XCTContext.runActivity(named: "move to tip(animated)") { act in
|
||||
@@ -199,7 +184,7 @@ class ControllerTests: XCTestCase {
|
||||
}
|
||||
XCTAssertEqual(fpc.state, .tip)
|
||||
XCTAssertEqual(delegate.position, .tip)
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
}
|
||||
|
||||
fpc.move(to: .hidden, animated: true)
|
||||
@@ -207,25 +192,24 @@ class ControllerTests: XCTestCase {
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .hidden).y)
|
||||
}
|
||||
|
||||
|
||||
func test_moveWithNearbyPosition() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
fpc.showForTest()
|
||||
|
||||
|
||||
XCTAssertEqual(fpc.nearbyState, .half)
|
||||
|
||||
|
||||
fpc.hide()
|
||||
XCTAssertEqual(fpc.nearbyState, .tip)
|
||||
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.nearbyState, .full)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.surfaceLocation(for: .full).y)
|
||||
}
|
||||
|
||||
func test_moveTo_didMoveDelegate() {
|
||||
let timeout = 3.0
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
XCTAssertEqual(delegate.position, .hidden)
|
||||
@@ -240,7 +224,7 @@ class ControllerTests: XCTestCase {
|
||||
exp.fulfill()
|
||||
}
|
||||
fpc.move(to: .full, animated: false)
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
@@ -256,7 +240,7 @@ class ControllerTests: XCTestCase {
|
||||
fpc.move(to: .half, animated: true) {
|
||||
exp.fulfill()
|
||||
}
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
|
||||
XCTAssertGreaterThan(count, 1)
|
||||
}
|
||||
@@ -273,7 +257,7 @@ class ControllerTests: XCTestCase {
|
||||
exp.fulfill()
|
||||
}
|
||||
}
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
@@ -291,7 +275,7 @@ class ControllerTests: XCTestCase {
|
||||
exp.fulfill()
|
||||
}
|
||||
}
|
||||
wait(for: [exp], timeout: timeout)
|
||||
wait(for: [exp], timeout: 1.0)
|
||||
|
||||
XCTAssertGreaterThan(count, 1)
|
||||
}
|
||||
@@ -333,40 +317,11 @@ class ControllerTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .full).y)
|
||||
fpc.move(to: .half, animated: false)
|
||||
print(1 / fpc.surfaceView.fp_displayScale)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .half).y)
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
|
||||
}
|
||||
|
||||
func test_switching_layout() {
|
||||
final class FirstLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
final class SecondLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.layout = FirstLayout()
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .tip, animated: false)
|
||||
|
||||
// Switch to another layout
|
||||
fpc.layout = SecondLayout()
|
||||
fpc.invalidateLayout()
|
||||
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
}
|
||||
}
|
||||
|
||||
private class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
|
||||
@@ -384,15 +339,21 @@ private class MyZombieViewController: UIViewController, FloatingPanelLayout, Flo
|
||||
return .half
|
||||
}
|
||||
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0,
|
||||
edge: .top,
|
||||
referenceGuide: .superview),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 250.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 60.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview),
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(
|
||||
absoluteInset: UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0,
|
||||
edge: .top,
|
||||
referenceGuide: .superview
|
||||
),
|
||||
.half: FloatingPanelLayoutAnchor(
|
||||
absoluteInset: 250.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview
|
||||
),
|
||||
.tip: FloatingPanelLayoutAnchor(
|
||||
absoluteInset: 60.0,
|
||||
edge: .bottom,
|
||||
referenceGuide: .superview
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
+363
-445
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class ExtensionTests: XCTestCase {
|
||||
@@ -9,20 +10,4 @@ class ExtensionTests: XCTestCase {
|
||||
XCTAssertNotEqual(CGFloat(333.5).rounded(by: 3), 333.66666666666674)
|
||||
XCTAssertTrue(CGFloat(333.5).isEqual(to: 333.66666666666674, on: 3.0))
|
||||
}
|
||||
|
||||
func test_roundedByDisplayScale_2() {
|
||||
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 3), 0.0)
|
||||
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 3), 0.0)
|
||||
|
||||
XCTAssertEqual(CGFloat(-0.3333333333374246).rounded(by: 3), -0.3333333333333333)
|
||||
XCTAssertEqual(CGFloat(-0.3333333333074246).rounded(by: 3), -0.3333333333333333)
|
||||
XCTAssertEqual(CGFloat(0.33333333333374246).rounded(by: 3), 0.3333333333333333)
|
||||
XCTAssertEqual(CGFloat(0.33333333333074246).rounded(by: 3), 0.3333333333333333)
|
||||
|
||||
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 2), 0.0)
|
||||
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 2), 0.0)
|
||||
|
||||
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 6), -0.16666666666666666)
|
||||
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 6), 0.16666666666666666)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
final class GestureTests: XCTestCase {
|
||||
|
||||
func test_delegateProxy_shouldRecognizeSimultaneouslyWith() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldRecognizeSimultaneouslyWith = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldRecognizeSimultaneouslyWith += 1
|
||||
return true
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
// Set a proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
|
||||
|
||||
// Check whether the default delegate method is called when the proxy delegate doesn't implement it.
|
||||
XCTAssertTrue(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldRequireFailureOf: FloatingPanelPanGestureRecognizer()
|
||||
)
|
||||
)
|
||||
|
||||
// Clear the proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
|
||||
}
|
||||
|
||||
func test_delegateProxy_shouldRequireFailureOf() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldRequireFailureOf = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldRequireFailureOf += 1
|
||||
return true
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
// Set a proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRequireFailureOf: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
|
||||
|
||||
// Clear the proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRequireFailureOf: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
|
||||
}
|
||||
|
||||
func test_delegateProxy_shouldBeRequiredToFailBy() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldBeRequiredToFailBy = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldBeRequiredToFailBy += 1
|
||||
return false
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldBeRequiredToFailBy: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 1)
|
||||
|
||||
// Check whether the delegate method of the "proxy" object is called.
|
||||
let otherPanGesture = UIPanGestureRecognizer()
|
||||
otherPanGesture.name = "_UISheetInteractionBackgroundDismissRecognizer"
|
||||
XCTAssertFalse(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldBeRequiredToFailBy: otherPanGesture
|
||||
)
|
||||
)
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
|
||||
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
// Check whether the delegate method of the "default" object is called.
|
||||
let otherPanGesture2 = UIPanGestureRecognizer()
|
||||
otherPanGesture2.name = "_UISheetInteractionBackgroundDismissRecognizer"
|
||||
XCTAssertTrue(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldBeRequiredToFailBy: otherPanGesture2
|
||||
)
|
||||
)
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
|
||||
}
|
||||
}
|
||||
+397
-270
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class LayoutTests: XCTestCase {
|
||||
@@ -20,7 +21,7 @@ class LayoutTests: XCTestCase {
|
||||
let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 18.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview)
|
||||
.hidden: FloatingPanelLayoutAnchor(absoluteInset: 0, edge: .bottom, referenceGuide: .superview),
|
||||
]
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -44,7 +45,7 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
func test_layoutSegment_3position() {
|
||||
class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
}
|
||||
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -56,25 +57,29 @@ class LayoutTests: XCTestCase {
|
||||
let minPos = CGFloat.leastNormalMagnitude
|
||||
let maxPos = CGFloat.greatestFiniteMagnitude
|
||||
|
||||
assertLayoutSegment(fpc.floatingPanel, with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: .tip),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: tipPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: tipPos, forwardY: false, lower: .half, upper: .tip),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .tip, upper: nil),
|
||||
])
|
||||
assertLayoutSegment(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: .tip),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: tipPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: tipPos, forwardY: false, lower: .half, upper: .tip),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .tip, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .tip, upper: nil),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_layoutSegment_2positions() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
|
||||
{ super.anchors.filter { (key, _) in key != .tip } }
|
||||
override var initialState: FloatingPanelState { .half }
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
super.anchors.filter { (key, _) in key != .tip }
|
||||
}
|
||||
}
|
||||
|
||||
fpc.layout = FloatingPanelLayout2Positions()
|
||||
@@ -85,23 +90,27 @@ class LayoutTests: XCTestCase {
|
||||
let minPos = CGFloat.leastNormalMagnitude
|
||||
let maxPos = CGFloat.greatestFiniteMagnitude
|
||||
|
||||
assertLayoutSegment(fpc.floatingPanel, with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .half, upper: nil),
|
||||
])
|
||||
assertLayoutSegment(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: halfPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .half, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .half, upper: nil),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_layoutSegment_1positions() {
|
||||
class FloatingPanelLayout1Positions: FloatingPanelTestLayout {
|
||||
override var initialState: FloatingPanelState { .full }
|
||||
override var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring]
|
||||
{ super.anchors.filter { (key, _) in key == .full } }
|
||||
override var initialState: FloatingPanelState { .full }
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
super.anchors.filter { (key, _) in key == .full }
|
||||
}
|
||||
}
|
||||
|
||||
fpc.layout = FloatingPanelLayout1Positions()
|
||||
@@ -111,14 +120,17 @@ class LayoutTests: XCTestCase {
|
||||
let minPos = CGFloat.leastNormalMagnitude
|
||||
let maxPos = CGFloat.greatestFiniteMagnitude
|
||||
|
||||
assertLayoutSegment(fpc.floatingPanel, with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .full, upper: nil),
|
||||
])
|
||||
assertLayoutSegment(
|
||||
fpc.floatingPanel,
|
||||
with: [
|
||||
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
|
||||
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: fullPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
|
||||
(#line, pos: maxPos, forwardY: true, lower: .full, upper: nil),
|
||||
(#line, pos: maxPos, forwardY: false, lower: .full, upper: nil),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func test_updateInteractiveEdgeConstraint() {
|
||||
@@ -126,40 +138,50 @@ class LayoutTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state)
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100.0)
|
||||
|
||||
@@ -171,11 +193,10 @@ class LayoutTests: XCTestCase {
|
||||
fpc.showForTest()
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame, CGRect(x: 0.0, y: -667.0 + 60.0, width: 375.0, height: 667))
|
||||
XCTAssertEqual(fpc.surfaceView.containerView.frame, CGRect(x: 0.0, y: -667.0,
|
||||
width: 375.0, height: 667 * 2.0))
|
||||
XCTAssertEqual(fpc.surfaceView.containerView.frame, CGRect(x: 0.0, y: -667.0, width: 375.0, height: 667 * 2.0))
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state)
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.state) // Should be ignore
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.interactionConstraint?.constant, 60.0)
|
||||
|
||||
@@ -185,33 +206,43 @@ class LayoutTests: XCTestCase {
|
||||
var pre: CGFloat
|
||||
var next: CGFloat
|
||||
pre = fpc.surfaceLocation.y
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, pre)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: fullPos - tipPos,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos + 100,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: fullPos - tipPos + 100,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: fullPos - tipPos + 100,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: fullPos - tipPos + 100,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
|
||||
@@ -238,21 +269,27 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos - 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, hiddenPos + 100.0)
|
||||
|
||||
@@ -279,21 +316,27 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
var next: CGFloat
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos + 100.0)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: hiddenPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, hiddenPos + 100.0)
|
||||
|
||||
@@ -317,21 +360,27 @@ class LayoutTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
|
||||
var next: CGFloat
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: true,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: -100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, fullPos - 100)
|
||||
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:))
|
||||
fpc.floatingPanel.layoutAdapter.updateInteractiveEdgeConstraint(
|
||||
diff: tipPos - fullPos + 100.0,
|
||||
scrollingContent: false,
|
||||
allowsRubberBanding: fpc.floatingPanel.behaviorAdapter.allowsRubberBanding(for:)
|
||||
)
|
||||
next = fpc.surfaceLocation.y
|
||||
XCTAssertEqual(next, tipPos + 100)
|
||||
|
||||
@@ -366,7 +415,7 @@ class LayoutTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.layout.anchors.filter({ $0.value.referenceGuide != .safeArea }).count, 0)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, myLayout.fullInset + fpc.fp_safeAreaInsets.top)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .half).y, bounds.height - myLayout.halfInset + fpc.fp_safeAreaInsets.bottom)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, bounds.height - myLayout.tipInset + fpc.fp_safeAreaInsets.bottom)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, bounds.height - myLayout.tipInset + fpc.fp_safeAreaInsets.bottom)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .hidden).y, bounds.height + 100.0)
|
||||
}
|
||||
|
||||
@@ -375,7 +424,7 @@ class LayoutTests: XCTestCase {
|
||||
fpc.loadViewIfNeeded()
|
||||
fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
|
||||
|
||||
class MyFloatingPanelFullLayout: FloatingPanelTop2BottomTestLayout { }
|
||||
class MyFloatingPanelFullLayout: FloatingPanelTop2BottomTestLayout {}
|
||||
class MyFloatingPanelSafeAreaLayout: FloatingPanelTop2BottomTestLayout {
|
||||
override var referenceGuide: FloatingPanelLayoutReferenceGuide {
|
||||
return .safeArea
|
||||
@@ -389,10 +438,9 @@ class LayoutTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.layout.anchors.filter({ $0.value.referenceGuide != .superview }).count, 0)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .full).y, bounds.height - myLayout.fullInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .half).y, myLayout.halfInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, myLayout.tipInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .tip).y, myLayout.tipInset)
|
||||
XCTAssertEqual(fpc.surfaceLocation(for: .hidden).y, -100.0)
|
||||
|
||||
|
||||
fpc.layout = MyFloatingPanelSafeAreaLayout()
|
||||
|
||||
XCTAssertEqual(fpc.layout.anchors.filter({ $0.value.referenceGuide != .safeArea }).count, 0)
|
||||
@@ -410,56 +458,93 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
|
||||
// fractional
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
}
|
||||
}
|
||||
func test_layoutAnchor_bottomPosition() {
|
||||
@@ -471,57 +556,94 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
|
||||
// fractional
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.heightAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,32 +668,52 @@ class LayoutTests: XCTestCase {
|
||||
fpc.set(contentViewController: contentVC)
|
||||
|
||||
for prop in [
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,67 +738,52 @@ class LayoutTests: XCTestCase {
|
||||
fpc.set(contentViewController: contentVC)
|
||||
|
||||
for prop in [
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
|
||||
func test_offsetFromMostExpandedAnchor() {
|
||||
do {
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.layout = FloatingPanelBottomLayout()
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor, 0.0)
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
|
||||
XCTAssertEqual(
|
||||
fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor,
|
||||
(fpc.surfaceLocation(for: .half) - fpc.surfaceLocation(for: .full)).y
|
||||
)
|
||||
}
|
||||
do {
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.layout = FloatingPanelTopPositionedLayout()
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor, 0.0)
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
|
||||
XCTAssertEqual(
|
||||
fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor,
|
||||
-(fpc.surfaceLocation(for: .half) - fpc.surfaceLocation(for: .full)).y
|
||||
)
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
(
|
||||
anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)
|
||||
),
|
||||
] {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.constant, CGFloat(prop.result.constant), line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.firstAnchor, prop.result.firstAnchor, line: UInt(prop.result.0))
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class StateTests: XCTestCase {
|
||||
override func setUp() { }
|
||||
override func tearDown() { }
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_nextAndPre() {
|
||||
var positions: [FloatingPanelState]
|
||||
positions = [.full, .half, .tip, .hidden]
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .half)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .half)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.pre(in: positions), .tip)
|
||||
|
||||
positions = [.full, .hidden]
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.full.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.full.pre(in: positions), .full)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.next(in: positions), .hidden)
|
||||
XCTAssertEqual(FloatingPanelState.hidden.pre(in: positions), .full)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
class SurfaceViewTests: XCTestCase {
|
||||
@@ -68,12 +69,14 @@ class SurfaceViewTests: XCTestCase {
|
||||
|
||||
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)
|
||||
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:
|
||||
@@ -82,14 +85,13 @@ class SurfaceViewTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func test_surfaceView_grabberHandle() {
|
||||
XCTContext.runActivity(named: "Bottom sheet") { _ in
|
||||
let surface = SurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
|
||||
XCTAssertNil(surface.contentView)
|
||||
surface.layoutIfNeeded()
|
||||
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, 6.0)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, 6.0)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.width, surface.grabberHandleSize.width)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.height, surface.grabberHandleSize.height)
|
||||
|
||||
@@ -97,7 +99,7 @@ class SurfaceViewTests: XCTestCase {
|
||||
surface.grabberHandleSize = CGSize(width: 44.0, height: 12.0)
|
||||
surface.setNeedsLayout()
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, surface.grabberHandlePadding)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.minY, surface.grabberHandlePadding)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.width, surface.grabberHandleSize.width)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.height, surface.grabberHandleSize.height)
|
||||
}
|
||||
@@ -115,7 +117,7 @@ class SurfaceViewTests: XCTestCase {
|
||||
surface.grabberHandlePadding = 10.0
|
||||
surface.setNeedsLayout()
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssertEqual(surface.grabberHandle.frame.maxY, surface.bounds.maxY - surface.grabberHandlePadding)
|
||||
XCTAssertEqual(surface.grabberHandle.frame.maxY, surface.bounds.maxY - surface.grabberHandlePadding)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +206,7 @@ class SurfaceViewTests: XCTestCase {
|
||||
XCTAssert(surface.containerView.layer.cornerRadius == 0.0)
|
||||
XCTAssert(surface.containerView.layer.masksToBounds == false)
|
||||
|
||||
surface.containerView.layer.cornerRadius = 12.0 // Don't change it directly
|
||||
surface.containerView.layer.cornerRadius = 12.0 // Don't change it directly
|
||||
XCTAssert(surface.containerView.layer.cornerRadius == 12.0)
|
||||
XCTAssertFalse(surface.containerView.layer.masksToBounds == true)
|
||||
|
||||
@@ -231,49 +233,65 @@ class SurfaceViewTests: XCTestCase {
|
||||
|
||||
func test_surfaceView_grabberArea() {
|
||||
let sv = SurfaceView()
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
sv.grabberAreaOffset = 44.0
|
||||
// Top
|
||||
do {
|
||||
sv.position = .top
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: 0,
|
||||
y: sv.bounds.height - sv.grabberAreaOffset,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: 0,
|
||||
y: sv.bounds.height - sv.grabberAreaOffset,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset
|
||||
)
|
||||
)
|
||||
}
|
||||
// Bottom
|
||||
do {
|
||||
sv.position = .bottom
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: 0,
|
||||
y: 0,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: sv.bounds.width,
|
||||
height: sv.grabberAreaOffset
|
||||
)
|
||||
)
|
||||
}
|
||||
// Left
|
||||
do {
|
||||
sv.position = .left
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: sv.bounds.width - sv.grabberAreaOffset,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: sv.bounds.width - sv.grabberAreaOffset,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height
|
||||
)
|
||||
)
|
||||
}
|
||||
// Right
|
||||
do {
|
||||
sv.position = .right
|
||||
XCTAssertEqual(sv.grabberAreaFrame,
|
||||
.init(x: 0,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height))
|
||||
XCTAssertEqual(
|
||||
sv.grabberAreaFrame,
|
||||
.init(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: sv.grabberAreaOffset,
|
||||
height: sv.bounds.height
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func test_surfaceView_grabberAreaDetection() {
|
||||
let sv = SurfaceView()
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
sv.bounds = .init(x: 0, y: 0, width: 375, height: 500)
|
||||
// Top
|
||||
do {
|
||||
sv.position = .top
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import FloatingPanel
|
||||
|
||||
func waitRunLoop(secs: TimeInterval = 0) {
|
||||
@@ -40,7 +41,7 @@ class FloatingPanelTestLayout: FloatingPanelLayout {
|
||||
var referenceGuide: FloatingPanelLayoutReferenceGuide {
|
||||
return .superview
|
||||
}
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: fullInset, edge: .top, referenceGuide: referenceGuide),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: halfInset, edge: .bottom, referenceGuide: referenceGuide),
|
||||
@@ -63,7 +64,7 @@ class FloatingPanelTop2BottomTestLayout: FloatingPanelLayout {
|
||||
var referenceGuide: FloatingPanelLayoutReferenceGuide {
|
||||
return .superview
|
||||
}
|
||||
var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] {
|
||||
var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: fullInset, edge: .bottom, referenceGuide: referenceGuide),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: halfInset, edge: .top, referenceGuide: referenceGuide),
|
||||
@@ -72,18 +73,8 @@ class FloatingPanelTop2BottomTestLayout: FloatingPanelLayout {
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelTopPositionedLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .top
|
||||
let initialState: FloatingPanelState = .full
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 88.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .top, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
|
||||
class FloatingPanelProjectableBehavior: FloatingPanelBehavior {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -108,4 +99,3 @@ class MockTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator
|
||||
var containerView: UIView { UIView() }
|
||||
var targetTransform: CGAffineTransform = .identity
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||
"state" : {
|
||||
"revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1",
|
||||
"version" : "1.1.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-format",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-format.git",
|
||||
"state" : {
|
||||
"branch" : "508.0.1",
|
||||
"revision" : "fbfe1869527923dd9f9b2edac148baccfce0dce7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-syntax",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-syntax.git",
|
||||
"state" : {
|
||||
"revision" : "cd793adf5680e138bf2bcbaacc292490175d0dcd",
|
||||
"version" : "508.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-system",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-system.git",
|
||||
"state" : {
|
||||
"revision" : "836bc4557b74fe6d2660218d56e3ce96aff76574",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-tools-support-core",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-tools-support-core.git",
|
||||
"state" : {
|
||||
"revision" : "93784c59434dbca8e8a9e4b700d0d6d94551da6a",
|
||||
"version" : "0.5.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// swift-tools-version: 5.8
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "build-tools",
|
||||
platforms: [.macOS(.v12)],
|
||||
products: [
|
||||
.plugin(name: "SwiftFormatCommand", targets: ["SwiftFormatCommand"]),
|
||||
.plugin(name: "SwiftFormatBuildTool", targets: ["SwiftFormatBuildTool"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-format.git", branch: "508.0.1")
|
||||
],
|
||||
targets: [
|
||||
.plugin(
|
||||
name: "SwiftFormatCommand",
|
||||
capability: .command(
|
||||
intent: .sourceCodeFormatting(),
|
||||
permissions: [
|
||||
.writeToPackageDirectory(reason: "")
|
||||
]
|
||||
),
|
||||
dependencies: [
|
||||
.product(name: "swift-format", package: "swift-format")
|
||||
]
|
||||
),
|
||||
.plugin(
|
||||
name: "SwiftFormatBuildTool",
|
||||
capability: .buildTool(),
|
||||
dependencies: [
|
||||
.product(name: "swift-format", package: "swift-format")
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
import PackagePlugin
|
||||
|
||||
@main
|
||||
struct SwiftFormatBuildTool: BuildToolPlugin {
|
||||
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
|
||||
debugPrint("BuildToolPlugin -> \(context.package.directory)")
|
||||
let configFile = context.package.directory.appending(".swift-format")
|
||||
return [
|
||||
.prebuildCommand(
|
||||
displayName: "Run swift-format",
|
||||
executable: try context.tool(named: "swift-format").path,
|
||||
arguments: [
|
||||
"lint",
|
||||
"--configuration",
|
||||
configFile.string,
|
||||
"-r",
|
||||
"\(context.pluginWorkDirectory.string)",
|
||||
],
|
||||
outputFilesDirectory: context.package.directory
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(XcodeProjectPlugin)
|
||||
import XcodeProjectPlugin
|
||||
extension SwiftFormatBuildTool: XcodeBuildToolPlugin {
|
||||
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
|
||||
debugPrint("XcodeBuildToolPlugin -> \(context.xcodeProject.directory.string)")
|
||||
let configFile = context.xcodeProject.directory.appending(".swift-format")
|
||||
let sourceFiles = context.xcodeProject.directory.appending("Sources")
|
||||
let testFiles = context.xcodeProject.directory.appending("Tests")
|
||||
return [
|
||||
.buildCommand(
|
||||
displayName: "Run swift-format(xcode)",
|
||||
executable: try context.tool(named: "swift-format").path,
|
||||
arguments: [
|
||||
"lint",
|
||||
"--configuration",
|
||||
configFile.string,
|
||||
"-r",
|
||||
"\(sourceFiles.string)",
|
||||
"\(testFiles.string)",
|
||||
],
|
||||
inputFiles: [],
|
||||
outputFiles: []
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
import Foundation
|
||||
import PackagePlugin
|
||||
|
||||
@main
|
||||
struct SwiftFormatCommand: CommandPlugin {
|
||||
func performCommand(context: PluginContext, arguments: [String]) async throws {
|
||||
debugPrint("CommandPlugin start: \(context)")
|
||||
|
||||
let swiftFormatTool = try context.tool(named: "swift-format")
|
||||
debugPrint("XcodeCommandPlugin: swift-format: \(swiftFormatTool.path)")
|
||||
|
||||
let configFile = context.package.directory.appending(".swift-format")
|
||||
debugPrint("CommandPlugin using config: \(configFile)")
|
||||
|
||||
var argExtractor = ArgumentExtractor(arguments)
|
||||
let targetNames = argExtractor.extractOption(named: "target")
|
||||
let targetsToFormat = try context.package.targets(named: targetNames)
|
||||
|
||||
let sourceCodeTargets = targetsToFormat.compactMap { $0 as? SourceModuleTarget }
|
||||
|
||||
try runCommand(swiftFormatTool, configFile: configFile, filePaths: sourceCodeTargets.map(\.directory))
|
||||
}
|
||||
|
||||
func runCommand(_ swiftFormatTool: PackagePlugin.PluginContext.Tool, configFile: Path, filePaths: [Path]) throws {
|
||||
// Invoke `swift-format` on the target directory, passing a configuration
|
||||
// file from the package directory.
|
||||
let swiftFormatExec = URL(fileURLWithPath: swiftFormatTool.path.string)
|
||||
let swiftFormatArgs =
|
||||
[
|
||||
"--configuration",
|
||||
"\(configFile.string)",
|
||||
"--in-place",
|
||||
"--recursive",
|
||||
] + filePaths.map(\.string)
|
||||
let process = try Process.run(swiftFormatExec, arguments: swiftFormatArgs)
|
||||
process.waitUntilExit()
|
||||
|
||||
debugPrint("result: \(process.terminationStatus)")
|
||||
|
||||
if process.terminationReason == .exit && process.terminationStatus == 0 {
|
||||
print("success.")
|
||||
} else {
|
||||
let problem = "\(process.terminationReason):\(process.terminationStatus)"
|
||||
Diagnostics.error("failed: \(problem)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(XcodeProjectPlugin)
|
||||
import XcodeProjectPlugin
|
||||
extension SwiftFormatCommand: XcodeCommandPlugin {
|
||||
func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws {
|
||||
debugPrint("start")
|
||||
|
||||
let swiftFormatTool = try context.tool(named: "swift-format")
|
||||
debugPrint("swift-format executable = \(swiftFormatTool.path)")
|
||||
|
||||
let configFile = context.xcodeProject.directory.appending(".swift-format")
|
||||
debugPrint("config file = \(swiftFormatTool.path)")
|
||||
|
||||
var argExtractor = ArgumentExtractor(arguments)
|
||||
let targetNames = argExtractor.extractOption(named: "target")
|
||||
let xcodeTargets = context.xcodeProject.targets.filter { targetNames.contains($0.product?.name ?? "") }
|
||||
|
||||
let filePaths = xcodeTargets.flatMap(\.inputFiles).filter { $0.type == .source }.map(\.path)
|
||||
|
||||
debugPrint("files to format = \(filePaths.map(\.lastComponent))")
|
||||
|
||||
try runCommand(swiftFormatTool, configFile: configFile, filePaths: filePaths)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user