Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 402b9bd8dc | |||
| c39cc9d93b | |||
| aad56ab0a7 | |||
| fe18e493a9 | |||
| 5d14166508 | |||
| e1a745e3b5 | |||
| a0cac28ed0 | |||
| c205dc8672 | |||
| 5c0ed4cf7d | |||
| 780472a17f | |||
| 0264db3d54 | |||
| c117594669 | |||
| 5214bd8936 | |||
| a1195be08e | |||
| b69d366538 | |||
| c9de6f0dc3 | |||
| 21be693a9a | |||
| 129362fcd0 | |||
| 75e2fcc3ce | |||
| 4f56b57b0e | |||
| f9bbdf3427 | |||
| fcf200e169 | |||
| 7d668c8525 | |||
| ebd4a32bfc | |||
| 6aa739231d | |||
| 8877d32ced | |||
| 7f025ae845 | |||
| 1b2dae2135 | |||
| 6e4e9df616 | |||
| 49e868a505 | |||
| f1b70e0367 | |||
| 1d0e747578 | |||
| 2c72d07cab | |||
| 31c057f9f8 | |||
| d3033df9da | |||
| 459d82b1c6 | |||
| 85d7ca640e | |||
| c1b7f2f092 | |||
| b7a7e0d4ad | |||
| dc7f6d58f9 | |||
| a2a10bd0d3 | |||
| b54c8ee6ee | |||
| 08d275690a | |||
| 1c307f751e | |||
| 6f06a0f7fc | |||
| a095ace30e | |||
| a486f61f5f | |||
| 14e0abc240 | |||
| 32203c48bd | |||
| 9d6024f603 | |||
| a4dd4e48e7 | |||
| e6f7456a0f | |||
| fca79c9b0c | |||
| 4ad7f11e93 | |||
| 0412bdc996 | |||
| 31faeaada3 | |||
| 72539ca973 | |||
| ef94630aa1 | |||
| cb696f9992 | |||
| aa23e404e1 | |||
| 4c0749640f | |||
| ee5661f304 | |||
| ddefdc4f34 | |||
| f2a0af1646 | |||
| 7ded61c2bc | |||
| 08aabcf6dd | |||
| e19af7e67d | |||
| 62aa07e28e | |||
| 03b0bf747e | |||
| 8dc75aed55 | |||
| d5f5e99010 | |||
| 18c46d191e | |||
| e9f92430b2 | |||
| 3106865449 | |||
| 375e7a59e2 | |||
| bc4a2def42 | |||
| 8204a6cf27 | |||
| 797292dbe5 | |||
| 6e7a33b3a1 | |||
| 56557f0092 | |||
| 1e6cb7b1ad | |||
| 8ba4ce36a1 | |||
| cf60b09225 | |||
| 427ec45d42 |
@@ -0,0 +1,27 @@
|
||||
> Please fill out this template appropriately when filing a bug report.
|
||||
>
|
||||
> Please remove this line and everything above it before submitting.
|
||||
|
||||
### Short description
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
**Code example that reproduces the issue**
|
||||
|
||||
### Environment
|
||||
|
||||
**Library version**
|
||||
|
||||
**Installation method**
|
||||
|
||||
- [ ] CocoaPods
|
||||
- [ ] Carthage
|
||||
- [ ] Git submodules
|
||||
|
||||
**iOS version(s)**
|
||||
|
||||
**Xcode version**
|
||||
+17
-7
@@ -5,10 +5,10 @@ branches:
|
||||
- next
|
||||
cache:
|
||||
directories:
|
||||
- build
|
||||
- vendor
|
||||
- /usr/local/Homebrew
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
before_cache:
|
||||
- brew cleanup
|
||||
env:
|
||||
global:
|
||||
- LANG=en_US.UTF-8
|
||||
@@ -16,7 +16,17 @@ env:
|
||||
skip_cleanup: true
|
||||
jobs:
|
||||
include:
|
||||
- stage: carthage
|
||||
- stage: Build framework(swift 4.1)
|
||||
osx_image: xcode9.4
|
||||
script:
|
||||
- xcodebuild -scheme FloatingPanel clean build
|
||||
|
||||
- stage: Build framework(swift 4.2)
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme FloatingPanel clean build
|
||||
|
||||
- stage: Carthage
|
||||
osx_image: xcode10
|
||||
before_install:
|
||||
- brew update
|
||||
@@ -24,22 +34,22 @@ jobs:
|
||||
script:
|
||||
- carthage build --no-skip-current
|
||||
|
||||
- stage: podspec
|
||||
- stage: Podspec
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- pod spec lint
|
||||
|
||||
- stage: check Maps example
|
||||
- stage: Build maps example
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme Maps -sdk iphonesimulator clean build
|
||||
|
||||
- stage: check Stocks example
|
||||
- stage: Build stocks example
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme Stocks -sdk iphonesimulator clean build
|
||||
|
||||
- stage: check Samples example
|
||||
- stage: Build samples example
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme Samples -sdk iphonesimulator clean build
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 545DBA1221511E6400CA77B8 /* Build configuration list for PBXNativeTarget "Samples" */;
|
||||
buildPhases = (
|
||||
54D7209621D4DB970054A255 /* ShellScript */,
|
||||
545DB9E621511E6300CA77B8 /* Sources */,
|
||||
545DB9E721511E6300CA77B8 /* Frameworks */,
|
||||
545DB9E821511E6300CA77B8 /* Resources */,
|
||||
@@ -285,6 +286,26 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
54D7209621D4DB970054A255 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $(git rev-parse --abbrev-ref HEAD)($(git rev-parse --short HEAD))\" $SRCROOT/$INFOPLIST_FILE\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
545DB9E621511E6300CA77B8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<scene sceneID="Cjh-iX-VQw">
|
||||
<objects>
|
||||
<navigationController id="RoN-h0-uBD" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="hNW-5m-Omi">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="44"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="hNW-5m-Omi">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
@@ -61,13 +61,19 @@
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="top" secondItem="Smh-Bd-AAc" secondAttribute="top" id="6yd-jv-ey3"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="leading" secondItem="TkN-Oh-wF8" secondAttribute="leading" id="Z6Y-Dc-cei"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="TkN-Oh-wF8" secondAttribute="bottom" id="fNW-DP-lhV"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="trailing" secondItem="TkN-Oh-wF8" secondAttribute="trailing" id="vfY-Rc-FOI"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="leading" secondItem="39L-Nq-qfp" secondAttribute="leading" id="Z6Y-Dc-cei"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="39L-Nq-qfp" secondAttribute="bottom" id="fNW-DP-lhV"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="trailing" secondItem="39L-Nq-qfp" secondAttribute="trailing" id="vfY-Rc-FOI"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="TkN-Oh-wF8"/>
|
||||
<viewLayoutGuide key="safeArea" id="39L-Nq-qfp"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Samples" id="wCF-su-7up"/>
|
||||
<navigationItem key="navigationItem" title="Samples" id="wCF-su-7up">
|
||||
<barButtonItem key="rightBarButtonItem" title="Settings" id="rbH-U3-XyA">
|
||||
<connections>
|
||||
<action selector="showDebugMenu:" destination="jF4-A0-Eq6" id="j02-db-ZM5"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="7IS-PU-x0P" id="YFM-9W-eP4"/>
|
||||
</connections>
|
||||
@@ -76,7 +82,87 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="57" y="27"/>
|
||||
</scene>
|
||||
<!--Item 2-->
|
||||
<!--Settings View Controller-->
|
||||
<scene sceneID="Bd0-D2-agO">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="SettingsViewController" id="C1X-9Z-TyQ" customClass="SettingsViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="af9-Zr-Ppc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="197.33000000000001"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="n93-ZL-fmC">
|
||||
<rect key="frame" x="32" y="16" width="311" height="147.33333333333334"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version: 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WmC-Tq-NDN">
|
||||
<rect key="frame" x="118.33333333333334" y="0.0" width="74.333333333333343" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UINavigationBar" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ulg-gS-ah0">
|
||||
<rect key="frame" x="90.666666666666686" y="33" width="130" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="126" translatesAutoresizingMaskIntoConstraints="NO" id="uEf-g4-CeU">
|
||||
<rect key="frame" x="23.333333333333343" y="69.333333333333329" width="264.33333333333326" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Large Titles" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogl-S5-4tJ">
|
||||
<rect key="frame" x="0.0" y="5.3333333333333428" width="89.333333333333329" height="20.333333333333332"/>
|
||||
<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="js8-Qv-lUC">
|
||||
<rect key="frame" x="215.33333333333334" y="0.0" width="51.000000000000028" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleLargeTitle:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="FJS-Ty-mCY"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="126" translatesAutoresizingMaskIntoConstraints="NO" id="ZtZ-Dz-4cC">
|
||||
<rect key="frame" x="23.333333333333343" y="116.33333333333334" width="264.66666666666663" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Translucent" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Z5i-rm-QgL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="89.666666666666671" height="31"/>
|
||||
<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="s6b-j9-8Kw">
|
||||
<rect key="frame" x="215.66666666666666" y="0.0" width="50.999999999999972" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleTranslucent:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="nL4-3L-9hh"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="0hr-ty-yWm" firstAttribute="bottom" secondItem="n93-ZL-fmC" secondAttribute="bottom" id="2Ey-ou-E1M"/>
|
||||
<constraint firstAttribute="trailing" secondItem="n93-ZL-fmC" secondAttribute="trailing" constant="32" id="DdZ-eB-F5s"/>
|
||||
<constraint firstItem="n93-ZL-fmC" firstAttribute="leading" secondItem="af9-Zr-Ppc" secondAttribute="leading" constant="32" id="TyK-GP-Ari"/>
|
||||
<constraint firstItem="n93-ZL-fmC" firstAttribute="top" secondItem="af9-Zr-Ppc" secondAttribute="topMargin" constant="16" id="mbC-6H-z9M"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="0hr-ty-yWm"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="197.33000000000001"/>
|
||||
<connections>
|
||||
<outlet property="largeTitlesSwicth" destination="js8-Qv-lUC" id="FOm-6k-ffi"/>
|
||||
<outlet property="translucentSwicth" destination="s6b-j9-8Kw" id="jmf-WH-bzZ"/>
|
||||
<outlet property="versionLabel" destination="WmC-Tq-NDN" id="Woh-kK-U0m"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="M9h-4V-3M0" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="708" y="-200"/>
|
||||
</scene>
|
||||
<!--Layout 2-->
|
||||
<scene sceneID="lRc-OZ-sL4">
|
||||
<objects>
|
||||
<viewController id="RpE-lI-27a" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
@@ -84,12 +170,6 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AiP-dx-mFn">
|
||||
<rect key="frame" x="163.66666666666666" y="395.66666666666669" width="48" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
@@ -101,20 +181,50 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="2Cd-km-qEk" secondAttribute="top" id="18k-sV-PgT"/>
|
||||
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerY" secondItem="JER-jz-KSq" secondAttribute="centerY" id="NUc-tM-0dN"/>
|
||||
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerX" secondItem="JER-jz-KSq" secondAttribute="centerX" id="hwP-mu-Vmz"/>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="2Cd-km-qEk" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="954-Dk-zvc" secondAttribute="top" id="18k-sV-PgT"/>
|
||||
<constraint firstItem="954-Dk-zvc" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="IvG-yp-yzI" secondAttribute="trailing" id="mpr-u5-MZu"/>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="954-Dk-zvc" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="2Cd-km-qEk"/>
|
||||
<viewLayoutGuide key="safeArea" id="954-Dk-zvc"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" tag="1" title="Item 2" id="qb3-RB-B28"/>
|
||||
<tabBarItem key="tabBarItem" tag="1" title="Layout 2" id="qb3-RB-B28"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="NhZ-u5-Beh" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="326" y="1575"/>
|
||||
<point key="canvasLocation" x="-308" y="1546"/>
|
||||
</scene>
|
||||
<!--Item 1-->
|
||||
<!--Layout 3-->
|
||||
<scene sceneID="r9h-Ql-gIv">
|
||||
<objects>
|
||||
<viewController id="pOk-Zm-vD9" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="85d-ub-G8k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NbG-e8-HdI">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="pOk-Zm-vD9" eventType="touchUpInside" id="111-PD-Pop"/>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="1Rg-YG-TtU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="0ao-SI-QZW" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="NbG-e8-HdI" secondAttribute="trailing" id="K9F-6x-KWn"/>
|
||||
<constraint firstItem="NbG-e8-HdI" firstAttribute="top" secondItem="0ao-SI-QZW" secondAttribute="top" id="nsE-so-rTl"/>
|
||||
<constraint firstItem="NbG-e8-HdI" firstAttribute="leading" secondItem="0ao-SI-QZW" secondAttribute="leading" constant="20" id="sF4-Dm-aoY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="0ao-SI-QZW"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" tag="2" title="Layout 3" id="RJD-TF-Sdh"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Oe3-FT-q1C" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="332" y="1546"/>
|
||||
</scene>
|
||||
<!--Layout 1-->
|
||||
<scene sceneID="m6X-j6-yBM">
|
||||
<objects>
|
||||
<viewController id="lto-Zc-Vtp" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
@@ -122,12 +232,6 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uoW-c8-9wx">
|
||||
<rect key="frame" x="164.66666666666666" y="395.66666666666669" width="46" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
@@ -139,29 +243,28 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="f88-U8-Vja" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
|
||||
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerY" secondItem="ji9-Ez-N7i" secondAttribute="centerY" id="Nyw-Wt-78z"/>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="f88-U8-Vja" secondAttribute="top" id="hUV-3a-XkY"/>
|
||||
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerX" secondItem="ji9-Ez-N7i" secondAttribute="centerX" id="wDv-OH-7PX"/>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="5Ns-4l-Ufg" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
|
||||
<constraint firstItem="5Ns-4l-Ufg" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="eFN-tN-4Ct" secondAttribute="trailing" id="OzZ-Dz-RNF"/>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="5Ns-4l-Ufg" secondAttribute="top" id="hUV-3a-XkY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="f88-U8-Vja"/>
|
||||
<viewLayoutGuide key="safeArea" id="5Ns-4l-Ufg"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" title="Item 1" id="HEV-kf-jxH"/>
|
||||
<tabBarItem key="tabBarItem" title="Layout 1" id="HEV-kf-jxH"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bkL-bc-hZC" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-380" y="1576"/>
|
||||
<point key="canvasLocation" x="-962" y="1546"/>
|
||||
</scene>
|
||||
<!--Intrinsic View Controller-->
|
||||
<scene sceneID="wtJ-qZ-aCl">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="IntrinsicViewController" title="Intrinsic View Controller" id="aK0-kv-mTu" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="eLM-xc-d9e">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" text="Change this text" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ge4-RW-Gmz">
|
||||
<rect key="frame" x="24" y="68" width="327" height="20.333333333333329"/>
|
||||
<rect key="frame" x="24" y="24" width="327" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -169,17 +272,18 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="vtu-Jb-oOn" firstAttribute="trailing" secondItem="ge4-RW-Gmz" secondAttribute="trailing" constant="24" id="V59-MD-Lcg"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ge4-RW-Gmz" secondAttribute="trailing" constant="24" id="V59-MD-Lcg"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="leading" secondItem="eLM-xc-d9e" secondAttribute="leading" constant="24" id="hAO-P0-7Kw"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="top" secondItem="vtu-Jb-oOn" secondAttribute="top" constant="24" id="j0s-fd-MYj"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="top" secondItem="eLM-xc-d9e" secondAttribute="top" constant="24" id="j0s-fd-MYj"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ge4-RW-Gmz" secondAttribute="bottom" constant="24" id="tEn-PO-nVD"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="vtu-Jb-oOn"/>
|
||||
<viewLayoutGuide key="safeArea" id="ouu-g9-OiX"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="DfE-fL-zy5" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-940" y="805"/>
|
||||
<point key="canvasLocation" x="2753" y="734"/>
|
||||
</scene>
|
||||
<!--Tab Bar View Controller-->
|
||||
<scene sceneID="nQ5-PV-qFw">
|
||||
@@ -193,22 +297,23 @@
|
||||
<connections>
|
||||
<segue destination="lto-Zc-Vtp" kind="relationship" relationship="viewControllers" id="6hP-AH-YiH"/>
|
||||
<segue destination="RpE-lI-27a" kind="relationship" relationship="viewControllers" id="g6X-Sq-uSW"/>
|
||||
<segue destination="pOk-Zm-vD9" kind="relationship" relationship="viewControllers" id="OPp-iO-iDK"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Z9x-EI-p2b" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-183" y="806"/>
|
||||
<point key="canvasLocation" x="-706" y="749"/>
|
||||
</scene>
|
||||
<!--Modal View Controller-->
|
||||
<scene sceneID="C9P-Ns-Qrq">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vut-mK-Y4t" customClass="SafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="778" width="375" height="34"/>
|
||||
<rect key="frame" x="0.0" y="744" width="375" height="34"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
|
||||
@@ -254,35 +359,36 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="top" id="3VR-hj-zeQ"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="top" constant="88" id="41n-Fn-hi3"/>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="top" id="3VR-hj-zeQ"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="top" constant="88" id="41n-Fn-hi3"/>
|
||||
<constraint firstAttribute="bottom" secondItem="vut-mK-Y4t" secondAttribute="bottom" id="6eq-Kt-heZ"/>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="leading" secondItem="GBa-yx-8to" secondAttribute="leading" constant="20" id="T2G-1L-PRs"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="leading" secondItem="qwo-GK-p1U" secondAttribute="leading" id="gVC-jv-VJX"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="trailing" secondItem="GBa-yx-8to" secondAttribute="trailing" id="jkq-p2-lUm"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="centerX" secondItem="qwo-GK-p1U" secondAttribute="centerX" id="l8t-p3-ETf"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="bottom" id="rMy-JT-t4B"/>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="leading" secondItem="kjr-TP-fcM" secondAttribute="leading" constant="20" id="T2G-1L-PRs"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="leading" secondItem="kjr-TP-fcM" secondAttribute="leading" id="gVC-jv-VJX"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="trailing" secondItem="kjr-TP-fcM" secondAttribute="trailing" id="jkq-p2-lUm"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="centerX" secondItem="kjr-TP-fcM" secondAttribute="centerX" id="l8t-p3-ETf"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="bottom" id="rMy-JT-t4B"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="GBa-yx-8to"/>
|
||||
<viewLayoutGuide key="safeArea" id="kjr-TP-fcM"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="safeAreaView" destination="vut-mK-Y4t" id="r9P-XF-wLd"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="fbi-LZ-M4Y" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="561" y="806"/>
|
||||
<point key="canvasLocation" x="1375" y="734"/>
|
||||
</scene>
|
||||
<!--Nested Scroll View Controller-->
|
||||
<scene sceneID="TfC-A3-4R0">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="NestedScrollViewController" id="LAe-jm-k6f" customClass="NestedScrollViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="414-Wy-0t1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sBe-tN-uMi">
|
||||
<rect key="frame" x="0.0" y="32" width="375" height="635"/>
|
||||
<rect key="frame" x="0.0" y="32" width="375" height="746"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="lFR-Sp-Sj1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="968"/>
|
||||
@@ -358,20 +464,21 @@
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="sBe-tN-uMi" firstAttribute="leading" secondItem="414-Wy-0t1" secondAttribute="leading" id="8Qd-my-knA"/>
|
||||
<constraint firstItem="sBe-tN-uMi" firstAttribute="leading" secondItem="ufS-Rf-F2F" secondAttribute="leading" id="8Qd-my-knA"/>
|
||||
<constraint firstItem="sBe-tN-uMi" firstAttribute="top" secondItem="414-Wy-0t1" secondAttribute="top" constant="32" id="9Js-LU-lNr"/>
|
||||
<constraint firstAttribute="bottom" secondItem="sBe-tN-uMi" secondAttribute="bottom" id="jzB-47-P7e"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sBe-tN-uMi" secondAttribute="trailing" id="nHG-wg-pLP"/>
|
||||
<constraint firstItem="ufS-Rf-F2F" firstAttribute="trailing" secondItem="sBe-tN-uMi" secondAttribute="trailing" id="nHG-wg-pLP"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="sL5-d5-za2"/>
|
||||
<viewLayoutGuide key="safeArea" id="ufS-Rf-F2F"/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="tOa-bf-zGz" appends="YES" id="zle-Sz-M3U"/>
|
||||
<outletCollection property="gestureRecognizers" destination="SCk-hG-weZ" appends="YES" id="OcK-FK-Lac"/>
|
||||
<outletCollection property="gestureRecognizers" destination="Fvp-Z6-eVc" appends="YES" id="Fds-J5-YCg"/>
|
||||
</connections>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="667"/>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="nestedScrollView" destination="xba-kG-VQ2" id="ddV-kf-37A"/>
|
||||
<outlet property="scrollView" destination="sBe-tN-uMi" id="h4S-Zl-cLO"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
@@ -392,7 +499,7 @@
|
||||
</connections>
|
||||
</swipeGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1311" y="806"/>
|
||||
<point key="canvasLocation" x="2097" y="734"/>
|
||||
</scene>
|
||||
<!--Detail View Controller-->
|
||||
<scene sceneID="b6k-zi-3wn">
|
||||
@@ -402,8 +509,19 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8yw-OC-Ubk">
|
||||
<rect key="frame" x="0.0" y="690" width="375" height="88"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="88" id="jwV-YU-tXG"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="700"/>
|
||||
<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="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="319" y="12" width="44" height="44"/>
|
||||
<rect key="frame" x="319" y="44" width="44" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="0jg-5D-A1F"/>
|
||||
<constraint firstAttribute="width" constant="44" id="1Cq-PA-wgW"/>
|
||||
@@ -412,22 +530,8 @@
|
||||
<action selector="closeWithSender:" destination="YC8-ae-15L" eventType="touchUpInside" id="Z2v-19-S5k"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8yw-OC-Ubk">
|
||||
<rect key="frame" x="0.0" y="690" width="375" height="88"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="88" id="jwV-YU-tXG"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="700" width="375" height="44"/>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="DQJ-cY-cKx"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="tP3-oJ-4EB">
|
||||
<rect key="frame" x="130.66666666666666" y="132" width="114" height="82"/>
|
||||
<rect key="frame" x="130.66666666666666" y="132" width="114" height="134"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c5r-jU-haj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="114" height="30"/>
|
||||
@@ -444,25 +548,33 @@
|
||||
<segue destination="bYI-y3-Rzb" kind="presentation" identifier="PresentModallySegue" id="3yq-HE-Tgn"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="01L-lp-oy6">
|
||||
<rect key="frame" x="0.0" y="104" width="114" height="30"/>
|
||||
<state key="normal" title="Update Layout"/>
|
||||
<connections>
|
||||
<action selector="buttonPressed:" destination="YC8-ae-15L" eventType="touchUpInside" id="zTb-sq-B6f"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="g7l-kO-y7q" secondAttribute="top" constant="12" id="EQy-cr-F2Y"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="centerX" secondItem="g7l-kO-y7q" secondAttribute="centerX" id="EsD-Vf-dNZ"/>
|
||||
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="EQy-cr-F2Y"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="centerX" secondItem="aOK-7l-cA6" secondAttribute="centerX" id="EsD-Vf-dNZ"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="Fff-HL-4mo"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="bottom" secondItem="g7l-kO-y7q" secondAttribute="bottom" id="JOL-wC-w74"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="leading" secondItem="tAi-nk-rDB" secondAttribute="leading" id="RiJ-Hb-OOZ"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="trailing" secondItem="tAi-nk-rDB" secondAttribute="trailing" id="Sof-yL-mwK"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="tAi-nk-rDB" secondAttribute="top" constant="88" id="Zhb-Ss-epe"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="trailing" secondItem="tAi-nk-rDB" secondAttribute="trailing" id="kkp-Yo-FQW"/>
|
||||
<constraint firstItem="tAi-nk-rDB" firstAttribute="trailing" secondItem="noi-1a-5bZ" secondAttribute="trailing" constant="12" id="lv9-Nf-HNB"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="leading" secondItem="tAi-nk-rDB" secondAttribute="leading" id="oVC-i1-TwS"/>
|
||||
<constraint firstItem="tAi-nk-rDB" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" id="rW2-mF-5DR"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="top" relation="greaterThanOrEqual" secondItem="tP3-oJ-4EB" secondAttribute="bottom" constant="8" symbolic="YES" id="vKQ-h9-uKt"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="RiJ-Hb-OOZ"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="Sof-yL-mwK"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" constant="88" id="Zhb-Ss-epe"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="kkp-Yo-FQW"/>
|
||||
<constraint firstItem="aOK-7l-cA6" firstAttribute="trailing" secondItem="noi-1a-5bZ" secondAttribute="trailing" constant="12" id="lv9-Nf-HNB"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="oVC-i1-TwS"/>
|
||||
<constraint firstItem="aOK-7l-cA6" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" id="rW2-mF-5DR"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="top" relation="greaterThanOrEqual" secondItem="tP3-oJ-4EB" secondAttribute="bottom" constant="88" id="vKQ-h9-uKt"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="tAi-nk-rDB"/>
|
||||
<viewLayoutGuide key="safeArea" id="aOK-7l-cA6"/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="6Ca-p8-7uF" appends="YES" id="xOy-f1-NZE"/>
|
||||
<outletCollection property="gestureRecognizers" destination="SPY-Vr-XDT" appends="YES" id="vgS-Am-jhQ"/>
|
||||
@@ -492,7 +604,7 @@
|
||||
</connections>
|
||||
</pongPressGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1440.8" y="-23.388305847076463"/>
|
||||
<point key="canvasLocation" x="655" y="734"/>
|
||||
</scene>
|
||||
<!--Debug Text View Controller-->
|
||||
<scene sceneID="Bkq-O7-q4A">
|
||||
@@ -550,24 +662,26 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="ix0-2W-gQN" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rN1-HL-YHv" secondAttribute="bottom" id="efD-U5-Tet"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="17" id="fiO-LL-nSC"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="ix0-2W-gQN" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="ix0-2W-gQN"/>
|
||||
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="textView" destination="rN1-HL-YHv" id="gmr-Uf-jd8"/>
|
||||
<outlet property="textViewTopConstraint" destination="fiO-LL-nSC" id="Rum-TN-c2e"/>
|
||||
<outlet property="view" destination="9YG-0j-Zzg" id="jhb-eT-nEn"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="729" y="-23"/>
|
||||
<point key="canvasLocation" x="-1" y="734"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="3yq-HE-Tgn"/>
|
||||
<segue reference="r1P-2i-NDe"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
||||
@@ -5,21 +5,39 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
var layoutInsets: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return safeAreaInsets
|
||||
} else {
|
||||
return layoutMargins
|
||||
}
|
||||
}
|
||||
protocol LayoutGuideProvider {
|
||||
var topAnchor: NSLayoutYAxisAnchor { get }
|
||||
var bottomAnchor: NSLayoutYAxisAnchor { get }
|
||||
}
|
||||
extension UILayoutGuide: LayoutGuideProvider {}
|
||||
|
||||
var layoutGuide: UILayoutGuide {
|
||||
if #available(iOS 11.0, *) {
|
||||
return safeAreaLayoutGuide
|
||||
} else {
|
||||
return layoutMarginsGuide
|
||||
}
|
||||
class CustomLayoutGuide: LayoutGuideProvider {
|
||||
let topAnchor: NSLayoutYAxisAnchor
|
||||
let bottomAnchor: NSLayoutYAxisAnchor
|
||||
init(topAnchor: NSLayoutYAxisAnchor, bottomAnchor: NSLayoutYAxisAnchor) {
|
||||
self.topAnchor = topAnchor
|
||||
self.bottomAnchor = bottomAnchor
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
var layoutInsets: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets(top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
var layoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view!.safeAreaLayoutGuide
|
||||
} else {
|
||||
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,14 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}
|
||||
}
|
||||
|
||||
var currentMenu: Menu = .trackingTableView
|
||||
|
||||
var mainPanelVC: FloatingPanelController!
|
||||
var detailPanelVC: FloatingPanelController!
|
||||
var currentMenu: Menu = .trackingTableView
|
||||
var settingsPanelVC: FloatingPanelController!
|
||||
|
||||
var mainPanelObserves: [NSKeyValueObservation] = []
|
||||
var settingsObserves: [NSKeyValueObservation] = []
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -62,15 +67,39 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
tableView.delegate = self
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
|
||||
|
||||
let searchController = UISearchController(searchResultsController: nil)
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationItem.searchController = searchController
|
||||
navigationItem.hidesSearchBarWhenScrolling = false
|
||||
navigationItem.largeTitleDisplayMode = .automatic
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
|
||||
let contentVC = DebugTableViewController()
|
||||
addMainPanel(with: contentVC)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
|
||||
self.tableView.reloadData()
|
||||
}) {
|
||||
settingsObserves.append(observation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
settingsObserves.removeAll()
|
||||
}
|
||||
|
||||
func addMainPanel(with contentVC: UIViewController) {
|
||||
mainPanelObserves.removeAll()
|
||||
|
||||
// Initialize FloatingPanelController
|
||||
mainPanelVC = FloatingPanelController()
|
||||
mainPanelVC.delegate = self
|
||||
@@ -99,6 +128,10 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
mainPanelVC.track(scrollView: consoleVC.textView)
|
||||
|
||||
case let contentVC as DebugTableViewController:
|
||||
let ob = contentVC.tableView.observe(\.isEditing) { (tableView, _) in
|
||||
self.mainPanelVC.panGestureRecognizer.isEnabled = !tableView.isEditing
|
||||
}
|
||||
mainPanelObserves.append(ob)
|
||||
mainPanelVC.track(scrollView: contentVC.tableView)
|
||||
case let contentVC as NestedScrollViewController:
|
||||
mainPanelVC.track(scrollView: contentVC.scrollView)
|
||||
@@ -115,25 +148,71 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}
|
||||
|
||||
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
|
||||
mainPanelVC.hide(animated: true, completion: nil)
|
||||
switch tapGesture.view {
|
||||
case mainPanelVC.backdropView:
|
||||
mainPanelVC.hide(animated: true, completion: nil)
|
||||
case settingsPanelVC.backdropView:
|
||||
settingsPanelVC.removePanelFromParent(animated: true)
|
||||
settingsPanelVC = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MARK:- TableViewDatasource
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return Menu.allCases.count
|
||||
if #available(iOS 11.0, *) {
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return Menu.allCases.count + 30
|
||||
} else {
|
||||
return Menu.allCases.count
|
||||
}
|
||||
} else {
|
||||
return Menu.allCases.count
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
let menu = Menu.allCases[indexPath.row]
|
||||
cell.textLabel?.text = menu.name
|
||||
if Menu.allCases.count > indexPath.row {
|
||||
let menu = Menu.allCases[indexPath.row]
|
||||
cell.textLabel?.text = menu.name
|
||||
} else {
|
||||
cell.textLabel?.text = "\(indexPath.row) row"
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK:- Actions
|
||||
@IBAction func showDebugMenu(_ sender: UIBarButtonItem) {
|
||||
guard settingsPanelVC == nil else { return }
|
||||
// Initialize FloatingPanelController
|
||||
settingsPanelVC = FloatingPanelController()
|
||||
|
||||
// Initialize FloatingPanelController and add the view
|
||||
settingsPanelVC.surfaceView.cornerRadius = 6.0
|
||||
settingsPanelVC.surfaceView.shadowHidden = false
|
||||
settingsPanelVC.isRemovalInteractionEnabled = true
|
||||
|
||||
let backdropTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
|
||||
settingsPanelVC.backdropView.addGestureRecognizer(backdropTapGesture)
|
||||
|
||||
settingsPanelVC.delegate = self
|
||||
|
||||
let contentVC = storyboard?.instantiateViewController(withIdentifier: "SettingsViewController")
|
||||
|
||||
// Set a content view controller
|
||||
settingsPanelVC.set(contentViewController: contentVC)
|
||||
|
||||
// Add FloatingPanel to self.view
|
||||
settingsPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
|
||||
}
|
||||
|
||||
// MARK:- TableViewDelegate
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard Menu.allCases.count > indexPath.row else { return }
|
||||
let menu = Menu.allCases[indexPath.row]
|
||||
let contentVC: UIViewController = {
|
||||
guard let storyboardID = menu.storyboardID else { return DebugTableViewController() }
|
||||
@@ -146,7 +225,7 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
switch menu {
|
||||
case .showDetail:
|
||||
detailPanelVC?.removePanelFromParent(animated: false)
|
||||
|
||||
|
||||
// Initialize FloatingPanelController
|
||||
detailPanelVC = FloatingPanelController()
|
||||
|
||||
@@ -183,6 +262,10 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
if vc == settingsPanelVC {
|
||||
return IntrinsicPanelLayout()
|
||||
}
|
||||
|
||||
switch currentMenu {
|
||||
case .showRemovablePanel:
|
||||
return newCollection.verticalSizeClass == .compact ? RemovablePanelLandscapeLayout() : RemovablePanelLayout()
|
||||
@@ -198,6 +281,24 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
switch currentMenu {
|
||||
case .showNestedScrollView:
|
||||
return (vc.contentViewController as? NestedScrollViewController)?.nestedScrollView.gestureRecognizers?.contains(gestureRecognizer) ?? false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelDidEndRemove(_ vc: FloatingPanelController) {
|
||||
switch vc {
|
||||
case settingsPanelVC:
|
||||
settingsPanelVC = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
@@ -218,6 +319,9 @@ class RemovablePanelLayout: FloatingPanelIntrinsicLayout {
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return [.full, .half]
|
||||
}
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
var topInteractionBuffer: CGFloat {
|
||||
return 200.0
|
||||
}
|
||||
@@ -265,6 +369,7 @@ class ModalPanelLayout: FloatingPanelIntrinsicLayout {
|
||||
|
||||
class NestedScrollViewController: UIViewController {
|
||||
@IBOutlet weak var scrollView: UIScrollView!
|
||||
@IBOutlet weak var nestedScrollView: UIScrollView!
|
||||
|
||||
@IBAction func longPressed(_ sender: Any) {
|
||||
print("LongPressed!")
|
||||
@@ -279,16 +384,32 @@ class NestedScrollViewController: UIViewController {
|
||||
|
||||
class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
@IBOutlet weak var textView: UITextView!
|
||||
@IBOutlet weak var textViewTopConstraint: NSLayoutConstraint!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
textView.delegate = self
|
||||
print("viewDidLoad: TextView --- ", textView.contentOffset, textView.contentInset)
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
textView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
print("viewWillLayoutSubviews: TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
print("viewDidLayoutSubviews: TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
print("TextView --- ", textView.contentOffset, textView.contentInset, textView.frame)
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
print("TextView --- ", scrollView.contentOffset, scrollView.contentInset)
|
||||
if #available(iOS 11.0, *) {
|
||||
@@ -302,10 +423,63 @@ class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
class DebugTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
class InspectableViewController: UIViewController {
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
print(">>> Content View: viewWillLayoutSubviews", layoutInsets)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
print(">>> Content View: viewDidLayoutSubviews", layoutInsets)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
print(">>> Content View: viewWillAppear", layoutInsets)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
print(">>> Content View: viewDidAppear", view.bounds, layoutInsets)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
print(">>> Content View: viewWillDisappear")
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
print(">>> Content View: viewDidDisappear")
|
||||
}
|
||||
|
||||
override func willMove(toParent parent: UIViewController?) {
|
||||
super.willMove(toParent: parent)
|
||||
print(">>> Content View: willMove(toParent: \(String(describing: parent))")
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
print(">>> Content View: didMove(toParent: \(String(describing: parent))")
|
||||
}
|
||||
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
print(">>> Content View: willTransition(to: \(newCollection), with: \(coordinator))", layoutInsets)
|
||||
}
|
||||
}
|
||||
|
||||
class DebugTableViewController: InspectableViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
weak var tableView: UITableView!
|
||||
var items: [String] = []
|
||||
var itemHeight: CGFloat = 66.0
|
||||
|
||||
enum Menu: String, CaseIterable {
|
||||
case animateScroll = "Animate Scroll"
|
||||
case changeContentSize = "Change content size"
|
||||
case reorder = "Reorder"
|
||||
}
|
||||
|
||||
var reorderButton: UIButton!
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
@@ -335,17 +509,21 @@ class DebugTableViewController: UIViewController, UITableViewDataSource, UITable
|
||||
stackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -22.0),
|
||||
])
|
||||
|
||||
let button = UIButton()
|
||||
button.setTitle("Animate Scroll", for: .normal)
|
||||
button.setTitleColor(view.tintColor, for: .normal)
|
||||
button.addTarget(self, action: #selector(animateScroll), for: .touchUpInside)
|
||||
stackView.addArrangedSubview(button)
|
||||
|
||||
let button2 = UIButton()
|
||||
button2.setTitle("Change content size", for: .normal)
|
||||
button2.setTitleColor(view.tintColor, for: .normal)
|
||||
button2.addTarget(self, action: #selector(changeContentSize), for: .touchUpInside)
|
||||
stackView.addArrangedSubview(button2)
|
||||
for menu in Menu.allCases {
|
||||
let button = UIButton()
|
||||
button.setTitle(menu.rawValue, for: .normal)
|
||||
button.setTitleColor(view.tintColor, for: .normal)
|
||||
switch menu {
|
||||
case .animateScroll:
|
||||
button.addTarget(self, action: #selector(animateScroll), for: .touchUpInside)
|
||||
case .changeContentSize:
|
||||
button.addTarget(self, action: #selector(changeContentSize), for: .touchUpInside)
|
||||
case .reorder:
|
||||
button.addTarget(self, action: #selector(reorderItems), for: .touchUpInside)
|
||||
reorderButton = button
|
||||
}
|
||||
stackView.addArrangedSubview(button)
|
||||
}
|
||||
|
||||
for i in 0...100 {
|
||||
items.append("Items \(i)")
|
||||
@@ -386,6 +564,16 @@ class DebugTableViewController: UIViewController, UITableViewDataSource, UITable
|
||||
self.present(actionSheet, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func reorderItems() {
|
||||
if reorderButton.titleLabel?.text == Menu.reorder.rawValue {
|
||||
tableView.isEditing = true
|
||||
reorderButton.setTitle("Cancel", for: .normal)
|
||||
} else {
|
||||
tableView.isEditing = false
|
||||
reorderButton.setTitle(Menu.reorder.rawValue, for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
func changeItems(_ count: Int) {
|
||||
items.removeAll()
|
||||
for i in 0..<count {
|
||||
@@ -399,50 +587,6 @@ class DebugTableViewController: UIViewController, UITableViewDataSource, UITable
|
||||
(self.parent as! FloatingPanelController).removePanelFromParent(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
//print("Content View: viewWillLayoutSubviews")
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
//print("Content View: viewDidLayoutSubviews")
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
print("Content View: viewWillAppear")
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
print("Content View: viewDidAppear", view.bounds)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
print("Content View: viewWillDisappear")
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
print("Content View: viewDidDisappear")
|
||||
}
|
||||
|
||||
override func willMove(toParent parent: UIViewController?) {
|
||||
super.willMove(toParent: parent)
|
||||
print("Content View: willMove(toParent: \(String(describing: parent))")
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
print("Content View: didMove(toParent: \(String(describing: parent))")
|
||||
}
|
||||
|
||||
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
print("Content View: willTransition(to: \(newCollection), with: \(coordinator))")
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return items.count
|
||||
}
|
||||
@@ -465,9 +609,17 @@ class DebugTableViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
|
||||
items.insert(items.remove(at: sourceIndexPath.row), at: destinationIndexPath.row)
|
||||
}
|
||||
}
|
||||
|
||||
class DetailViewController: UIViewController {
|
||||
class DetailViewController: InspectableViewController {
|
||||
@IBOutlet weak var closeButton: UIButton!
|
||||
@IBAction func close(sender: UIButton) {
|
||||
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
|
||||
@@ -609,12 +761,60 @@ class TabBarContentViewController: UIViewController, FloatingPanelControllerDele
|
||||
case 0:
|
||||
return OneTabBarPanelLayout()
|
||||
case 1:
|
||||
return TwoTabBarPanel2Layout()
|
||||
return TwoTabBarPanelLayout()
|
||||
case 2:
|
||||
return ThreeTabBarPanelLayout()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) {
|
||||
guard self.tabBarItem.tag == 2 else { return }
|
||||
|
||||
/* Solution 1: Manipulate scoll content inset */
|
||||
/*
|
||||
guard let scrollView = consoleVC.textView else { return }
|
||||
var insets = vc.adjustedContentInsets
|
||||
if vc.surfaceView.frame.minY < vc.layoutInsets.top {
|
||||
insets.top = vc.layoutInsets.top - vc.surfaceView.frame.minY
|
||||
} else {
|
||||
insets.top = 0.0
|
||||
}
|
||||
scrollView.contentInset = insets
|
||||
*/
|
||||
|
||||
// Solution 2: Manipulate top constraint
|
||||
assert(consoleVC.textViewTopConstraint != nil)
|
||||
if vc.surfaceView.frame.minY + 17.0 < vc.layoutInsets.top {
|
||||
consoleVC.textViewTopConstraint?.constant = vc.layoutInsets.top - vc.surfaceView.frame.minY
|
||||
} else {
|
||||
consoleVC.textViewTopConstraint?.constant = 17.0
|
||||
}
|
||||
consoleVC.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
|
||||
guard self.tabBarItem.tag == 2 else { return }
|
||||
|
||||
/* Solution 1: Manipulate scoll content inset */
|
||||
/*
|
||||
guard let scrollView = consoleVC.textView else { return }
|
||||
var insets = vc.adjustedContentInsets
|
||||
insets.top = (vc.position == .full) ? vc.layoutInsets.top : 0.0
|
||||
scrollView.contentInset = insets
|
||||
if scrollView.contentOffset.y - scrollView.contentInset.top < 0.0 {
|
||||
scrollView.contentOffset = CGPoint(x: 0.0,
|
||||
y: 0.0 - scrollView.contentInset.top)
|
||||
}
|
||||
*/
|
||||
|
||||
// Solution 2: Manipulate top constraint
|
||||
assert(consoleVC.textViewTopConstraint != nil)
|
||||
consoleVC.textViewTopConstraint?.constant = (vc.position == .full) ? vc.layoutInsets.top : 17.0
|
||||
consoleVC.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
@IBAction func close(sender: UIButton) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
@@ -653,7 +853,7 @@ class OneTabBarPanelLayout: FloatingPanelLayout {
|
||||
}
|
||||
}
|
||||
|
||||
class TwoTabBarPanel2Layout: FloatingPanelLayout {
|
||||
class TwoTabBarPanelLayout: FloatingPanelLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
@@ -672,3 +872,53 @@ class TwoTabBarPanel2Layout: FloatingPanelLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ThreeTabBarPanelLayout: FloatingPanelFullScreenLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return [.full, .half]
|
||||
}
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return 0.0
|
||||
case .half: return 261.0
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
|
||||
return 0.3
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsViewController: InspectableViewController {
|
||||
@IBOutlet weak var largeTitlesSwicth: UISwitch!
|
||||
@IBOutlet weak var translucentSwicth: UISwitch!
|
||||
@IBOutlet weak var versionLabel: UILabel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
versionLabel.text = "Version: \(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "--")"
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {
|
||||
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
|
||||
largeTitlesSwicth.setOn(prefersLargeTitles, animated: false)
|
||||
} else {
|
||||
largeTitlesSwicth.isEnabled = false
|
||||
}
|
||||
let isTranslucent = navigationController!.navigationBar.isTranslucent
|
||||
translucentSwicth.setOn(isTranslucent, animated: false)
|
||||
}
|
||||
|
||||
@IBAction func toggleLargeTitle(_ sender: UISwitch) {
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
|
||||
}
|
||||
}
|
||||
@IBAction func toggleTranslucent(_ sender: UISwitch) {
|
||||
navigationController?.navigationBar.isTranslucent = sender.isOn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "1.3.0"
|
||||
s.version = "1.3.5"
|
||||
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.
|
||||
@@ -13,7 +13,7 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
s.platform = :ios, "10.0"
|
||||
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => "v#{s.version}" }
|
||||
s.source_files = "Framework/Sources/*.swift"
|
||||
s.swift_version = "4.2"
|
||||
s.swift_version = "4.0"
|
||||
s.pod_target_xcconfig = { 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
|
||||
|
||||
s.framework = "UIKit"
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
|
||||
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */; };
|
||||
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
|
||||
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
|
||||
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
|
||||
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ViewTests.swift */; };
|
||||
@@ -34,8 +34,8 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
|
||||
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTransitioning.swift; sourceTree = "<group>"; };
|
||||
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
|
||||
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
|
||||
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9C42151169500CA77B8 /* FloatingPanelController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanelController.h; sourceTree = "<group>"; };
|
||||
@@ -406,7 +406,7 @@
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -432,7 +432,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import UIKit.UIGestureRecognizerSubclass // For Xcode 9.4.1
|
||||
|
||||
///
|
||||
/// FloatingPanel presentation model
|
||||
///
|
||||
class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate {
|
||||
/* Cause 'terminating with uncaught exception of type NSException' error on Swift Playground
|
||||
unowned let view: UIView
|
||||
*/
|
||||
// MUST be a weak reference to prevent UI freeze on the presentaion modally
|
||||
weak var viewcontroller: FloatingPanelController!
|
||||
|
||||
let surfaceView: FloatingPanelSurfaceView
|
||||
let backdropView: FloatingPanelBackdropView
|
||||
var layoutAdapter: FloatingPanelLayoutAdapter
|
||||
@@ -26,13 +27,6 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
weak var userScrollViewDelegate: UIScrollViewDelegate?
|
||||
|
||||
var safeAreaInsets: UIEdgeInsets! {
|
||||
get { return layoutAdapter.safeAreaInsets }
|
||||
set { layoutAdapter.safeAreaInsets = newValue }
|
||||
}
|
||||
|
||||
unowned let viewcontroller: FloatingPanelController
|
||||
|
||||
private(set) var state: FloatingPanelPosition = .hidden {
|
||||
didSet { viewcontroller.delegate?.floatingPanelDidChangePosition(viewcontroller) }
|
||||
}
|
||||
@@ -42,14 +36,17 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
return remains.count == 0
|
||||
}
|
||||
|
||||
let panGesture: FloatingPanelPanGestureRecognizer
|
||||
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
|
||||
var isRemovalInteractionEnabled: Bool = false
|
||||
|
||||
private var animator: UIViewPropertyAnimator?
|
||||
fileprivate var animator: UIViewPropertyAnimator?
|
||||
private var initialFrame: CGRect = .zero
|
||||
private var initialScrollOffset: CGPoint = .zero
|
||||
private var initialScrollInset: UIEdgeInsets = .zero
|
||||
private var transOffsetY: CGFloat = 0
|
||||
private var interactionInProgress: Bool = false
|
||||
|
||||
var interactionInProgress: Bool = false
|
||||
var isDecelerating: Bool = false
|
||||
|
||||
// Scroll handling
|
||||
private var stopScrollDeceleration: Bool = false
|
||||
@@ -73,42 +70,19 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
layout: layout)
|
||||
self.behavior = behavior
|
||||
|
||||
panGesture = FloatingPanelPanGestureRecognizer()
|
||||
panGestureRecognizer = FloatingPanelPanGestureRecognizer()
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
panGesture.name = "FloatingPanelSurface"
|
||||
panGestureRecognizer.name = "FloatingPanelSurface"
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
surfaceView.addGestureRecognizer(panGesture)
|
||||
panGesture.addTarget(self, action: #selector(handle(panGesture:)))
|
||||
panGesture.delegate = self
|
||||
}
|
||||
panGestureRecognizer.floatingPanel = self
|
||||
|
||||
func setUpViews(in vc: UIViewController) {
|
||||
unowned let view = vc.view!
|
||||
|
||||
// FloatingPanelSurfaceWrapperView is needed to update the surface's height
|
||||
// without animation and prevent the backdrop's cut-off on orientation change.
|
||||
let surfaceWrapperView = FloatingPanelSurfaceWrapperView()
|
||||
surfaceWrapperView.frame = view.bounds
|
||||
surfaceWrapperView.backgroundColor = .clear
|
||||
|
||||
view.addSubview(surfaceWrapperView)
|
||||
|
||||
surfaceWrapperView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
surfaceWrapperView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0),
|
||||
surfaceWrapperView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0),
|
||||
surfaceWrapperView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0),
|
||||
surfaceWrapperView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
|
||||
])
|
||||
|
||||
surfaceWrapperView.addSubview(surfaceView)
|
||||
|
||||
view.insertSubview(backdropView, belowSubview: surfaceWrapperView)
|
||||
backdropView.frame = view.bounds
|
||||
surfaceView.addGestureRecognizer(panGestureRecognizer)
|
||||
panGestureRecognizer.addTarget(self, action: #selector(handle(panGesture:)))
|
||||
panGestureRecognizer.delegate = self
|
||||
}
|
||||
|
||||
func move(to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
@@ -132,14 +106,17 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
animator.addAnimations { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard let `self` = self else { return }
|
||||
|
||||
self.updateLayout(to: to)
|
||||
self.state = to
|
||||
}
|
||||
animator.addCompletion { _ in
|
||||
animator.addCompletion { [weak self] _ in
|
||||
guard let `self` = self else { return }
|
||||
self.animator = nil
|
||||
completion?()
|
||||
}
|
||||
self.animator = animator
|
||||
animator.startAnimation()
|
||||
} else {
|
||||
self.updateLayout(to: to)
|
||||
@@ -157,8 +134,8 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
private func getBackdropAlpha(with translation: CGPoint) -> CGFloat {
|
||||
let currentY = getCurrentY(from: initialFrame, with: translation)
|
||||
|
||||
let next = directionalPosition(with: translation)
|
||||
let pre = redirectionalPosition(with: translation)
|
||||
let next = directionalPosition(at: currentY, with: translation)
|
||||
let pre = redirectionalPosition(at: currentY, with: translation)
|
||||
let nextY = layoutAdapter.positionY(for: next)
|
||||
let preY = layoutAdapter.positionY(for: pre)
|
||||
|
||||
@@ -176,46 +153,39 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard gestureRecognizer == panGesture else { return false }
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
|
||||
/* log.debug("shouldRecognizeSimultaneouslyWith", otherGestureRecognizer) */
|
||||
|
||||
// all gestures of the tracking scroll view should be recognized in parallel
|
||||
// and handle them in self.handle(panGesture:)
|
||||
return scrollView?.gestureRecognizers?.contains(otherGestureRecognizer) ?? false
|
||||
if viewcontroller.delegate?.floatingPanel(viewcontroller, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false {
|
||||
return true
|
||||
}
|
||||
|
||||
switch otherGestureRecognizer {
|
||||
case is UIPanGestureRecognizer,
|
||||
is UISwipeGestureRecognizer,
|
||||
is UIRotationGestureRecognizer,
|
||||
is UIScreenEdgePanGestureRecognizer,
|
||||
is UIPinchGestureRecognizer:
|
||||
// all gestures of the tracking scroll view should be recognized in parallel
|
||||
// and handle them in self.handle(panGesture:)
|
||||
return scrollView?.gestureRecognizers?.contains(otherGestureRecognizer) ?? false
|
||||
default:
|
||||
// Should always recognize tap/long press gestures in parallel
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard gestureRecognizer == panGesture else { return false }
|
||||
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
/* log.debug("shouldBeRequiredToFailBy", otherGestureRecognizer) */
|
||||
|
||||
// The tracking scroll view's gestures should begin without waiting for the pan gesture failure.
|
||||
// `scrollView.gestureRecognizers` can contains the following gestures
|
||||
// * UIScrollViewDelayedTouchesBeganGestureRecognizer
|
||||
// * UIScrollViewPanGestureRecognizer (scrollView.panGestureRecognizer)
|
||||
// * _UIDragAutoScrollGestureRecognizer
|
||||
// * _UISwipeActionPanGestureRecognizer
|
||||
// * UISwipeDismissalGestureRecognizer
|
||||
if let scrollView = scrollView,
|
||||
let scrollGestureRecognizers = scrollView.gestureRecognizers,
|
||||
scrollGestureRecognizers.contains(otherGestureRecognizer) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Long press gesture should begin without waiting for the pan gesture failure.
|
||||
if otherGestureRecognizer is UILongPressGestureRecognizer {
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not begin any other gestures until the pan gesture fails.
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard gestureRecognizer == panGesture else { return false }
|
||||
guard gestureRecognizer == panGestureRecognizer else { return false }
|
||||
|
||||
log.debug("shouldRequireFailureOf", otherGestureRecognizer)
|
||||
/* log.debug("shouldRequireFailureOf", otherGestureRecognizer) */
|
||||
|
||||
// Should begin the pan gesture without waiting for the tracking scroll view's gestures.
|
||||
// `scrollView.gestureRecognizers` can contains the following gestures
|
||||
@@ -233,6 +203,11 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
}
|
||||
|
||||
if viewcontroller.delegate?.floatingPanel(viewcontroller, shouldRecognizeSimultaneouslyWith: otherGestureRecognizer) ?? false {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
switch otherGestureRecognizer {
|
||||
case is UIPanGestureRecognizer,
|
||||
is UISwipeGestureRecognizer,
|
||||
@@ -292,9 +267,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
return
|
||||
}
|
||||
// Fix the scroll offset in moving the panel from half and tip.
|
||||
scrollView.contentOffset.y = initialScrollOffset.y
|
||||
scrollView.contentOffset.y = initialScrollOffset.y + (initialScrollInset.top - scrollView.contentInset.top)
|
||||
case .hidden:
|
||||
fatalError("A floating panel hidden must not be used by a user")
|
||||
fatalError("Now .hidden must not be used for a user interaction")
|
||||
}
|
||||
|
||||
// Always hide a scroll indicator at the non-top.
|
||||
@@ -307,7 +282,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
unlockScrollView()
|
||||
}
|
||||
}
|
||||
case panGesture:
|
||||
case panGestureRecognizer:
|
||||
let translation = panGesture.translation(in: panGesture.view!.superview)
|
||||
let location = panGesture.location(in: panGesture.view)
|
||||
|
||||
@@ -318,7 +293,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
if let animator = self.animator {
|
||||
animator.stopAnimation(true)
|
||||
if animator.isInterruptible {
|
||||
animator.stopAnimation(true)
|
||||
}
|
||||
self.animator = nil
|
||||
}
|
||||
|
||||
@@ -403,6 +380,12 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
private func panningEnd(with translation: CGPoint, velocity: CGPoint) {
|
||||
log.debug("panningEnd")
|
||||
|
||||
guard state != .hidden else {
|
||||
log.debug("Already hidden")
|
||||
return
|
||||
}
|
||||
|
||||
if interactionInProgress == false {
|
||||
initialFrame = surfaceView.frame
|
||||
}
|
||||
@@ -416,15 +399,19 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
if isRemovalInteractionEnabled, isBottomState {
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0,
|
||||
dy: max(min(velocity.y/distance, behavior.removalVelocity), 0.0)) : .zero
|
||||
dy: min(fabs(velocity.y)/distance, behavior.removalVelocity)) : .zero
|
||||
|
||||
|
||||
|
||||
if shouldStartRemovalAnimation(with: translation, velocityVector: velocityVector) {
|
||||
|
||||
viewcontroller.delegate?.floatingPanelDidEndDraggingToRemove(viewcontroller, withVelocity: velocity)
|
||||
self.startRemovalAnimation(with: velocityVector) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.viewcontroller.dismiss(animated: false)
|
||||
self.viewcontroller.delegate?.floatingPanelDidEndRemove(self.viewcontroller)
|
||||
guard let `self` = self else { return }
|
||||
self.viewcontroller.dismiss(animated: false, completion: { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.viewcontroller.delegate?.floatingPanelDidEndRemove(self.viewcontroller)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -459,8 +446,10 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
self?.updateLayout(to: .hidden)
|
||||
}
|
||||
animator.addCompletion({ _ in
|
||||
self.animator = nil
|
||||
completion?()
|
||||
})
|
||||
self.animator = animator
|
||||
animator.startAnimation()
|
||||
}
|
||||
|
||||
@@ -470,18 +459,21 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
initialFrame = surfaceView.frame
|
||||
if let scrollView = scrollView {
|
||||
initialScrollOffset = scrollView.contentOffset
|
||||
initialScrollInset = scrollView.contentInset
|
||||
}
|
||||
transOffsetY = translation.y
|
||||
|
||||
viewcontroller.delegate?.floatingPanelWillBeginDragging(viewcontroller)
|
||||
|
||||
if layoutAdapter.layout is FloatingPanelIntrinsicLayout {
|
||||
if state == .full {
|
||||
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
|
||||
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
|
||||
case const.firstAnchor:
|
||||
(const.secondItem as? UIView)?.disableAutoLayout()
|
||||
const.isActive = false
|
||||
case const.secondAnchor:
|
||||
(const.firstItem as? UIView)?.disableAutoLayout()
|
||||
const.isActive = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -500,13 +492,15 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
lockScrollView()
|
||||
}
|
||||
|
||||
if layoutAdapter.layout is FloatingPanelIntrinsicLayout {
|
||||
if state == .full {
|
||||
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
|
||||
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
|
||||
case const.firstAnchor:
|
||||
(const.secondItem as? UIView)?.enableAutoLayout()
|
||||
const.isActive = true
|
||||
case const.secondAnchor:
|
||||
(const.firstItem as? UIView)?.enableAutoLayout()
|
||||
const.isActive = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -522,24 +516,28 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
let topBuffer = layoutAdapter.layout.topInteractionBuffer
|
||||
let bottomY = layoutAdapter.bottomY
|
||||
let bottomBuffer = layoutAdapter.layout.bottomInteractionBuffer
|
||||
|
||||
let topMax = layoutAdapter.topMaxY
|
||||
let bottomMax = layoutAdapter.bottomMaxY
|
||||
|
||||
if let scrollView = scrollView, scrollView.panGestureRecognizer.state == .changed {
|
||||
let preY = surfaceView.frame.origin.y
|
||||
if preY > 0 && preY > y {
|
||||
return max(topY, min(bottomY, y))
|
||||
return max(max(topY, topMax), min(min(bottomY + bottomBuffer, bottomMax), y))
|
||||
}
|
||||
}
|
||||
let topMax = layoutAdapter.topMaxY
|
||||
let bottomMax = layoutAdapter.bottomMaxY
|
||||
return max(max(topY - topBuffer, topMax), min(min(bottomY + bottomBuffer, bottomMax), y))
|
||||
}
|
||||
|
||||
private func startAnimation(to targetPosition: FloatingPanelPosition, at distance: CGFloat, with velocity: CGPoint) {
|
||||
log.debug("startAnimation", targetPosition, distance, velocity)
|
||||
let targetY = layoutAdapter.positionY(for: targetPosition)
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: max(min(velocity.y/distance, 30.0), -30.0)) : .zero
|
||||
|
||||
isDecelerating = true
|
||||
|
||||
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: min(fabs(velocity.y)/distance, 30.0)) : .zero
|
||||
let animator = behavior.interactionAnimator(self.viewcontroller, to: targetPosition, with: velocityVector)
|
||||
animator.addAnimations { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard let `self` = self else { return }
|
||||
if self.state == targetPosition {
|
||||
self.surfaceView.frame.origin.y = targetY
|
||||
self.layoutAdapter.setBackdropAlpha(of: targetPosition)
|
||||
@@ -549,21 +547,22 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
self.state = targetPosition
|
||||
}
|
||||
animator.addCompletion { [weak self] pos in
|
||||
guard let self = self else { return }
|
||||
guard let `self` = self else { return }
|
||||
self.isDecelerating = false
|
||||
guard
|
||||
self.interactionInProgress == false,
|
||||
animator == self.animator,
|
||||
pos == .end
|
||||
else { return }
|
||||
self.animator = nil
|
||||
self.finishAnimation(at: targetPosition)
|
||||
}
|
||||
animator.startAnimation()
|
||||
self.animator = animator
|
||||
animator.startAnimation()
|
||||
}
|
||||
|
||||
private func finishAnimation(at targetPosition: FloatingPanelPosition) {
|
||||
log.debug("finishAnimation \(targetPosition)")
|
||||
self.animator = nil
|
||||
self.viewcontroller.delegate?.floatingPanelDidEndDecelerating(self.viewcontroller)
|
||||
|
||||
stopScrollDeceleration = false
|
||||
@@ -581,76 +580,43 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
switch targetPosition {
|
||||
case .full:
|
||||
return CGFloat(fabs(Double(currentY - topY)))
|
||||
return CGFloat(fabs(currentY - topY))
|
||||
case .half:
|
||||
return CGFloat(fabs(Double(currentY - middleY)))
|
||||
return CGFloat(fabs(currentY - middleY))
|
||||
case .tip:
|
||||
return CGFloat(fabs(Double(currentY - bottomY)))
|
||||
return CGFloat(fabs(currentY - bottomY))
|
||||
case .hidden:
|
||||
fatalError("A floating panel hidden must not be used by a user")
|
||||
fatalError("Now .hidden must not be used for a user interaction")
|
||||
}
|
||||
}
|
||||
|
||||
private func directionalPosition(with translation: CGPoint) -> FloatingPanelPosition {
|
||||
let currentY = getCurrentY(from: initialFrame, with: translation)
|
||||
|
||||
let supportedPositions = layoutAdapter.supportedPositions
|
||||
|
||||
if supportedPositions.count == 1 {
|
||||
return state
|
||||
}
|
||||
|
||||
switch supportedPositions {
|
||||
case [.full, .half]: return translation.y >= 0 ? .half : .full
|
||||
case [.half, .tip]: return translation.y >= 0 ? .tip : .half
|
||||
case [.full, .tip]: return translation.y >= 0 ? .tip : .full
|
||||
default:
|
||||
let middleY = layoutAdapter.middleY
|
||||
|
||||
switch state {
|
||||
case .full:
|
||||
if translation.y <= 0 {
|
||||
return .full
|
||||
}
|
||||
return currentY > middleY ? .tip : .half
|
||||
case .half:
|
||||
return translation.y >= 0 ? .tip : .full
|
||||
case .tip:
|
||||
if translation.y >= 0 {
|
||||
return .tip
|
||||
}
|
||||
return currentY > middleY ? .half : .full
|
||||
case .hidden:
|
||||
fatalError("A floating panel hidden must not be used by a user")
|
||||
}
|
||||
}
|
||||
private func directionalPosition(at currentY: CGFloat, with translation: CGPoint) -> FloatingPanelPosition {
|
||||
return getPosition(at: currentY, with: translation, directional: true)
|
||||
}
|
||||
|
||||
private func redirectionalPosition(with translation: CGPoint) -> FloatingPanelPosition {
|
||||
let currentY = getCurrentY(from: initialFrame, with: translation)
|
||||
|
||||
let supportedPositions = layoutAdapter.supportedPositions
|
||||
private func redirectionalPosition(at currentY: CGFloat, with translation: CGPoint) -> FloatingPanelPosition {
|
||||
return getPosition(at: currentY, with: translation, directional: false)
|
||||
}
|
||||
|
||||
private func getPosition(at currentY: CGFloat, with translation: CGPoint, directional: Bool) -> FloatingPanelPosition {
|
||||
let supportedPositions: Set = layoutAdapter.supportedPositions
|
||||
if supportedPositions.count == 1 {
|
||||
return state
|
||||
}
|
||||
|
||||
let isForwardYAxis = (translation.y >= 0)
|
||||
switch supportedPositions {
|
||||
case [.full, .half]: return translation.y >= 0 ? .full : .half
|
||||
case [.half, .tip]: return translation.y >= 0 ? .half : .tip
|
||||
case [.full, .tip]: return translation.y >= 0 ? .full : .tip
|
||||
case [.full, .half]:
|
||||
return (isForwardYAxis == directional) ? .half : .full
|
||||
case [.half, .tip]:
|
||||
return (isForwardYAxis == directional) ? .tip : .half
|
||||
case [.full, .tip]:
|
||||
return (isForwardYAxis == directional) ? .tip : .full
|
||||
default:
|
||||
let middleY = layoutAdapter.middleY
|
||||
|
||||
switch state {
|
||||
case .full:
|
||||
return currentY > middleY ? .half : .full
|
||||
case .half:
|
||||
return .half
|
||||
case .tip:
|
||||
return currentY > middleY ? .tip : .half
|
||||
case .hidden:
|
||||
fatalError("A floating panel hidden must not be used by a user")
|
||||
if currentY > middleY {
|
||||
return (isForwardYAxis == directional) ? .tip : .half
|
||||
} else {
|
||||
return (isForwardYAxis == directional) ? .half : .full
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -658,7 +624,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
// Distance travelled after decelerating to zero velocity at a constant rate.
|
||||
// Refer to the slides p176 of [Designing Fluid Interfaces](https://developer.apple.com/videos/play/wwdc2018/803/)
|
||||
private func project(initialVelocity: CGFloat) -> CGFloat {
|
||||
let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
|
||||
let decelerationRate = UIScrollViewDecelerationRateNormal
|
||||
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate)
|
||||
}
|
||||
|
||||
@@ -685,29 +651,27 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
let middleY = layoutAdapter.middleY
|
||||
let bottomY = layoutAdapter.bottomY
|
||||
|
||||
let target: FloatingPanelPosition
|
||||
let nextState: FloatingPanelPosition
|
||||
let forwardYDirection: Bool
|
||||
|
||||
/*
|
||||
full <-> half <-> tip
|
||||
*/
|
||||
switch state {
|
||||
case .full:
|
||||
target = .half
|
||||
nextState = .half
|
||||
forwardYDirection = true
|
||||
case .half:
|
||||
if (currentY < middleY) {
|
||||
target = .full
|
||||
forwardYDirection = false
|
||||
} else {
|
||||
target = .tip
|
||||
forwardYDirection = true
|
||||
}
|
||||
nextState = (currentY > middleY) ? .tip : .full
|
||||
forwardYDirection = (currentY > middleY)
|
||||
case .tip:
|
||||
target = .half
|
||||
nextState = .half
|
||||
forwardYDirection = false
|
||||
case .hidden:
|
||||
fatalError("A floating panel hidden must not be used by a user")
|
||||
fatalError("Now .hidden must not be used for a user interaction")
|
||||
}
|
||||
|
||||
let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: target), 1.0), 0.0)
|
||||
let redirectionalProgress = max(min(behavior.redirectionalProgress(viewcontroller, from: state, to: nextState), 1.0), 0.0)
|
||||
|
||||
let th1: CGFloat
|
||||
let th2: CGFloat
|
||||
@@ -823,12 +787,24 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
targetContentOffset.pointee = scrollView.contentOffset
|
||||
stopScrollDeceleration = false
|
||||
} else {
|
||||
let targetOffset = targetContentOffset.pointee
|
||||
userScrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
|
||||
// Stop scrolling on tip and half
|
||||
if state != .full, targetOffset == targetContentOffset.pointee {
|
||||
targetContentOffset.pointee.y = scrollView.contentOffset.y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
fileprivate var floatingPanel: FloatingPanel?
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
if floatingPanel?.animator != nil {
|
||||
self.state = .began
|
||||
}
|
||||
}
|
||||
override weak var delegate: UIGestureRecognizerDelegate? {
|
||||
get {
|
||||
return super.delegate
|
||||
|
||||
@@ -85,7 +85,9 @@ public extension FloatingPanelBehavior {
|
||||
class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
let timing = timeingCurve(with: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
animator.isInterruptible = false
|
||||
return animator
|
||||
}
|
||||
|
||||
private func timeingCurve(with velocity: CGVector) -> UITimingCurveProvider {
|
||||
|
||||
@@ -27,6 +27,8 @@ public protocol FloatingPanelControllerDelegate: class {
|
||||
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint)
|
||||
// called when its views are removed from a parent view controller
|
||||
func floatingPanelDidEndRemove(_ vc: FloatingPanelController)
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool
|
||||
}
|
||||
|
||||
public extension FloatingPanelControllerDelegate {
|
||||
@@ -45,9 +47,12 @@ public extension FloatingPanelControllerDelegate {
|
||||
|
||||
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint) {}
|
||||
func floatingPanelDidEndRemove(_ vc: FloatingPanelController) {}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool { return false }
|
||||
}
|
||||
|
||||
public enum FloatingPanelPosition: Int, CaseIterable {
|
||||
|
||||
public enum FloatingPanelPosition: Int {
|
||||
case full
|
||||
case half
|
||||
case tip
|
||||
@@ -65,7 +70,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
|
||||
/// The delegate of the floating panel controller object.
|
||||
public weak var delegate: FloatingPanelControllerDelegate?
|
||||
public weak var delegate: FloatingPanelControllerDelegate?{
|
||||
didSet{
|
||||
didUpdateDelegate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
|
||||
public var surfaceView: FloatingPanelSurfaceView! {
|
||||
@@ -84,7 +93,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
|
||||
// The underlying gesture recognizer for pan gestures
|
||||
public var panGestureRecognizer: UIPanGestureRecognizer {
|
||||
return floatingPanel.panGesture
|
||||
return floatingPanel.panGestureRecognizer
|
||||
}
|
||||
|
||||
/// The current position of the floating panel controller's contents.
|
||||
@@ -129,14 +138,15 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
private var safeAreaInsetsObservation: NSKeyValueObservation?
|
||||
private let modalTransition = FloatingPanelModalTransition()
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setUp()
|
||||
}
|
||||
|
||||
/// Initialize a newly created floating panel controller.
|
||||
public init() {
|
||||
public init(delegate: FloatingPanelControllerDelegate? = nil) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.delegate = delegate
|
||||
setUp()
|
||||
}
|
||||
|
||||
@@ -151,6 +161,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
behavior: fetchBehavior(for: self.traitCollection))
|
||||
}
|
||||
|
||||
private func didUpdateDelegate(){
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.behavior = fetchBehavior(for: self.traitCollection)
|
||||
}
|
||||
|
||||
// MARK:- Overrides
|
||||
|
||||
/// Creates the view that the controller manages.
|
||||
@@ -160,59 +175,43 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
let view = FloatingPanelPassThroughView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
backdropView.frame = view.bounds
|
||||
view.addSubview(backdropView)
|
||||
|
||||
surfaceView.frame = view.bounds
|
||||
view.addSubview(surfaceView)
|
||||
|
||||
self.view = view as UIView
|
||||
}
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
floatingPanel.setUpViews(in: self)
|
||||
public override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {}
|
||||
else {
|
||||
// Because {top,bottom}LayoutGuide is managed as a view
|
||||
self.update(safeAreaInsets: layoutInsets)
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
view.frame.size = size
|
||||
view.layoutIfNeeded()
|
||||
|
||||
floatingPanel.layoutAdapter.checkLayoutConsistance()
|
||||
if view.translatesAutoresizingMaskIntoConstraints {
|
||||
view.frame.size = size
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.willTransition(to: newCollection, with: coordinator)
|
||||
|
||||
// Change layout for a new trait collection
|
||||
updateLayout(for: newCollection)
|
||||
reloadLayout(for: newCollection)
|
||||
setUpLayout()
|
||||
|
||||
floatingPanel.behavior = fetchBehavior(for: newCollection)
|
||||
}
|
||||
|
||||
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
guard previousTraitCollection != traitCollection else { return }
|
||||
|
||||
self.update(safeAreaInsets: layoutInsets)
|
||||
}
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
// Must track safeAreaInsets/{top,bottom}LayoutGuide of the `self.view`
|
||||
// to update floatingPanel.safeAreaInsets`. There are 2 reasons.
|
||||
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
if #available(iOS 11.0, *) {
|
||||
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets) { [weak self] (vc, chaneg) in
|
||||
guard let self = self else { return }
|
||||
self.update(safeAreaInsets: vc.layoutInsets)
|
||||
}
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
// Instead, safeAreaInsets is updated here
|
||||
self.update(safeAreaInsets: layoutInsets)
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
safeAreaInsetsObservation = nil
|
||||
@@ -234,12 +233,18 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
// preserve the current content offset
|
||||
let contentOffset = scrollView?.contentOffset
|
||||
// Don't re-layout the surface on SafeArea.Bottom enabled/disabled in interaction progress
|
||||
guard
|
||||
floatingPanel.layoutAdapter.safeAreaInsets != safeAreaInsets,
|
||||
self.floatingPanel.interactionInProgress == false,
|
||||
self.floatingPanel.isDecelerating == false
|
||||
else { return }
|
||||
|
||||
floatingPanel.safeAreaInsets = safeAreaInsets
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
|
||||
scrollView?.contentOffset = contentOffset ?? .zero
|
||||
floatingPanel.layoutAdapter.safeAreaInsets = safeAreaInsets
|
||||
|
||||
setUpLayout()
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
@@ -250,10 +255,19 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
}
|
||||
|
||||
private func updateLayout(for traitCollection: UITraitCollection) {
|
||||
private func reloadLayout(for traitCollection: UITraitCollection) {
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.layoutAdapter.prepareLayout(in: self)
|
||||
}
|
||||
|
||||
private func setUpLayout() {
|
||||
// preserve the current content offset
|
||||
let contentOffset = scrollView?.contentOffset
|
||||
|
||||
floatingPanel.layoutAdapter.updateHeight()
|
||||
floatingPanel.layoutAdapter.activateLayout(of: floatingPanel.state)
|
||||
|
||||
scrollView?.contentOffset = contentOffset ?? .zero
|
||||
}
|
||||
|
||||
// MARK: - Container view controller interface
|
||||
@@ -261,7 +275,25 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
/// Shows the surface view at the initial position defined by the current layout
|
||||
public func show(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
// Must apply the current layout here
|
||||
updateLayout(for: traitCollection)
|
||||
reloadLayout(for: traitCollection)
|
||||
setUpLayout()
|
||||
|
||||
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.observe(\.view.safeAreaInsets) { [weak self] (vc, chaneg) in
|
||||
guard let `self` = self else { return }
|
||||
self.update(safeAreaInsets: vc.layoutInsets)
|
||||
}
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
// Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()`
|
||||
}
|
||||
|
||||
move(to: floatingPanel.layoutAdapter.layout.initialPosition,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
@@ -269,6 +301,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
|
||||
/// Hides the surface view to the hidden position
|
||||
public func hide(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
safeAreaInsetsObservation = nil
|
||||
move(to: .hidden,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
@@ -296,13 +329,20 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
parent.view.addSubview(self.view)
|
||||
}
|
||||
|
||||
view.frame = parent.view.bounds // MUST
|
||||
parent.addChildViewController(self)
|
||||
|
||||
parent.addChild(self)
|
||||
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: true) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.didMove(toParent: parent)
|
||||
show(animated: animated) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.didMove(toParentViewController: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,10 +357,10 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
|
||||
hide(animated: animated) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.willMove(toParent: nil)
|
||||
guard let `self` = self else { return }
|
||||
self.willMove(toParentViewController: nil)
|
||||
self.view.removeFromSuperview()
|
||||
self.removeFromParent()
|
||||
self.removeFromParentViewController()
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
@@ -338,21 +378,27 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
/// Sets the view controller responsible for the content portion of the floating panel..
|
||||
public func set(contentViewController: UIViewController?) {
|
||||
if let vc = _contentViewController {
|
||||
vc.willMove(toParent: nil)
|
||||
vc.willMove(toParentViewController: nil)
|
||||
vc.view.removeFromSuperview()
|
||||
vc.removeFromParent()
|
||||
vc.removeFromParentViewController()
|
||||
|
||||
if let scrollView = floatingPanel.scrollView,
|
||||
let delegate = floatingPanel.userScrollViewDelegate,
|
||||
vc.view.subviews.contains(scrollView) {
|
||||
scrollView.delegate = delegate
|
||||
}
|
||||
}
|
||||
|
||||
if let vc = contentViewController {
|
||||
addChildViewController(vc)
|
||||
let surfaceView = floatingPanel.surfaceView
|
||||
surfaceView.add(childView: vc.view)
|
||||
addChild(vc)
|
||||
vc.didMove(toParent: self)
|
||||
surfaceView.add(contentView: vc.view)
|
||||
vc.didMove(toParentViewController: self)
|
||||
}
|
||||
|
||||
_contentViewController = contentViewController
|
||||
}
|
||||
|
||||
|
||||
@available(*, unavailable, renamed: "set(contentViewController:)")
|
||||
public override func show(_ vc: UIViewController, sender: Any?) {
|
||||
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) {
|
||||
@@ -385,7 +431,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
if #available(iOS 11.0, *) {
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
} else {
|
||||
children.forEach { (vc) in
|
||||
childViewControllers.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
}
|
||||
@@ -404,8 +450,8 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
/// to update the floating panel's layout immediately. It can be called in an
|
||||
/// animation block.
|
||||
public func updateLayout() {
|
||||
updateLayout(for: view.traitCollection)
|
||||
floatingPanel.layoutAdapter.checkLayoutConsistance()
|
||||
reloadLayout(for: traitCollection)
|
||||
setUpLayout()
|
||||
}
|
||||
|
||||
/// Returns the y-coordinate of the point at the origin of the surface view
|
||||
@@ -445,10 +491,26 @@ public extension UIViewController {
|
||||
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
|
||||
}
|
||||
@objc public func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
if let fpc = parent as? FloatingPanelController, fpc.parent != nil {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
} else {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
if let fpc = parent as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Call dismiss(animated:completion:) to FloatingPanelController directly
|
||||
if let fpc = self as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// For other view controllers
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,21 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
/// FloatingPanelFullScreenLayout
|
||||
///
|
||||
/// Use the layout protocol if you want to configure a full inset from Superview.Top, not SafeArea.Top.
|
||||
/// It can't be used with FloatingPanelIntrinsicLayout.
|
||||
public protocol FloatingPanelFullScreenLayout: FloatingPanelLayout { }
|
||||
|
||||
/// FloatingPanelIntrinsicLayout
|
||||
///
|
||||
/// Use the layout protocol if you want to layout a panel using the intrinsic height.
|
||||
/// It can't be used with FloatingPanelFullScreenLayout.
|
||||
///
|
||||
/// - Attention:
|
||||
/// `insetFor(position:)` must return `nil` for full position because the inset is determined automatically.
|
||||
/// You can customize insets only for half, tip and hidden positions
|
||||
/// on FloatingPanelIntrinsicLayout.
|
||||
/// `insetFor(position:)` must return `nil` for the full position. Because
|
||||
/// the inset is determined automatically by the intrinsic height.
|
||||
/// You can customize insets only for the half, tip and hidden positions.
|
||||
public protocol FloatingPanelIntrinsicLayout: FloatingPanelLayout { }
|
||||
|
||||
public extension FloatingPanelIntrinsicLayout {
|
||||
@@ -34,7 +43,7 @@ public protocol FloatingPanelLayout: class {
|
||||
/// Returns a set of FloatingPanelPosition objects to tell the applicable
|
||||
/// positions of the floating panel controller.
|
||||
///
|
||||
/// By default, it returns all position exepct for `hidden` position. Because
|
||||
/// By default, it returns all position except for `hidden` position. Because
|
||||
/// it's always supported by `FloatingPanelController` so you don't need to return it.
|
||||
var supportedPositions: Set<FloatingPanelPosition> { get }
|
||||
|
||||
@@ -46,7 +55,7 @@ public protocol FloatingPanelLayout: class {
|
||||
|
||||
/// Returns a CGFloat value to determine a Y coordinate of a floating panel for each position(full, half, tip and hidden).
|
||||
///
|
||||
/// Its returning value indicates a different inset for each positiion.
|
||||
/// Its returning value indicates a different inset for each position.
|
||||
/// For full position, a top inset from a safe area in `FloatingPanelController.view`.
|
||||
/// For half or tip position, a bottom inset from the safe area.
|
||||
/// For hidden position, a bottom inset from `FloatingPanelController.view`.
|
||||
@@ -72,7 +81,7 @@ public extension FloatingPanelLayout {
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return Set([.full, .half, .tip])
|
||||
}
|
||||
|
||||
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.sideLayoutGuide.leftAnchor, constant: 0.0),
|
||||
@@ -86,8 +95,6 @@ public extension FloatingPanelLayout {
|
||||
}
|
||||
|
||||
public class FloatingPanelDefaultLayout: FloatingPanelLayout {
|
||||
public var contentViewController: UIViewController?
|
||||
|
||||
public var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
@@ -103,8 +110,6 @@ public class FloatingPanelDefaultLayout: FloatingPanelLayout {
|
||||
}
|
||||
|
||||
public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout {
|
||||
public var contentViewController: UIViewController?
|
||||
|
||||
public var initialPosition: FloatingPanelPosition {
|
||||
return .tip
|
||||
}
|
||||
@@ -127,17 +132,14 @@ class FloatingPanelLayoutAdapter {
|
||||
private weak var surfaceView: FloatingPanelSurfaceView!
|
||||
private weak var backdropView: FloatingPanelBackdropView!
|
||||
|
||||
var layout: FloatingPanelLayout
|
||||
|
||||
var safeAreaInsets: UIEdgeInsets = .zero {
|
||||
var layout: FloatingPanelLayout {
|
||||
didSet {
|
||||
if oldValue != safeAreaInsets {
|
||||
updateHeight()
|
||||
}
|
||||
checkLayoutConsistance()
|
||||
}
|
||||
}
|
||||
|
||||
private var heightBuffer: CGFloat = 88.0 // For bounce
|
||||
var safeAreaInsets: UIEdgeInsets = .zero
|
||||
|
||||
private var fixedConstraints: [NSLayoutConstraint] = []
|
||||
private var fullConstraints: [NSLayoutConstraint] = []
|
||||
private var halfConstraints: [NSLayoutConstraint] = []
|
||||
@@ -170,9 +172,12 @@ class FloatingPanelLayoutAdapter {
|
||||
|
||||
var topY: CGFloat {
|
||||
if supportedPositions.contains(.full) {
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + fullInset)
|
||||
} else {
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
return surfaceView.superview!.bounds.height - surfaceView.bounds.height
|
||||
case is FloatingPanelFullScreenLayout:
|
||||
return fullInset
|
||||
default:
|
||||
return (safeAreaInsets.top + fullInset)
|
||||
}
|
||||
} else {
|
||||
@@ -200,7 +205,9 @@ class FloatingPanelLayoutAdapter {
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
|
||||
}
|
||||
|
||||
var topMaxY: CGFloat { return -safeAreaInsets.top }
|
||||
var topMaxY: CGFloat {
|
||||
return layout is FloatingPanelFullScreenLayout ? 0.0 : safeAreaInsets.top
|
||||
}
|
||||
var bottomMaxY: CGFloat { return safeAreaBottomY }
|
||||
|
||||
var adjustedContentInsets: UIEdgeInsets {
|
||||
@@ -232,18 +239,31 @@ class FloatingPanelLayoutAdapter {
|
||||
}
|
||||
|
||||
func updateIntrinsicHeight() {
|
||||
let fittingSize = UIView.layoutFittingCompressedSize
|
||||
intrinsicHeight = surfaceView.contentView.systemLayoutSizeFitting(fittingSize).height
|
||||
let fittingSize = UILayoutFittingCompressedSize
|
||||
var intrinsicHeight = surfaceView.contentView?.systemLayoutSizeFitting(fittingSize).height ?? 0.0
|
||||
var safeAreaBottom: CGFloat = 0.0
|
||||
if #available(iOS 11.0, *) {
|
||||
safeAreaBottom = surfaceView.contentView?.safeAreaInsets.bottom ?? 0.0
|
||||
if safeAreaBottom > 0 {
|
||||
intrinsicHeight -= safeAreaInsets.bottom
|
||||
}
|
||||
}
|
||||
self.intrinsicHeight = max(intrinsicHeight, 0.0)
|
||||
|
||||
log.debug("Update intrinsic height =", intrinsicHeight,
|
||||
", surface(height) =", surfaceView.frame.height,
|
||||
", content(height) =", surfaceView.contentView?.frame.height ?? 0.0,
|
||||
", content safe area(bottom) =", safeAreaBottom)
|
||||
}
|
||||
|
||||
func prepareLayout(in vc: UIViewController) {
|
||||
self.vc = vc
|
||||
|
||||
NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
surfaceView.translatesAutoresizingMaskIntoConstraints = false
|
||||
backdropView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
// Fixed constraints of surface and backdrop views
|
||||
let surfaceConstraints = layout.prepareLayout(surfaceView: surfaceView, in: vc.view!)
|
||||
let backdropConstraints = [
|
||||
@@ -256,18 +276,22 @@ class FloatingPanelLayoutAdapter {
|
||||
fixedConstraints = surfaceConstraints + backdropConstraints
|
||||
|
||||
// Flexible surface constarints for full, half, tip and off
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
updateIntrinsicHeight()
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
// Set up on updateHeight()
|
||||
break
|
||||
case is FloatingPanelFullScreenLayout:
|
||||
fullConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
|
||||
constant: -fullInset),
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.view.topAnchor,
|
||||
constant: fullInset),
|
||||
]
|
||||
} else {
|
||||
default:
|
||||
fullConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
|
||||
constant: fullInset),
|
||||
]
|
||||
}
|
||||
|
||||
halfConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
|
||||
constant: -halfInset),
|
||||
@@ -286,31 +310,30 @@ class FloatingPanelLayoutAdapter {
|
||||
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
|
||||
func updateHeight() {
|
||||
guard let vc = vc else { return }
|
||||
defer {
|
||||
UIView.performWithoutAnimation {
|
||||
surfaceView.superview!.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(heightConstraints)
|
||||
|
||||
let height: CGFloat
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
updateIntrinsicHeight()
|
||||
height = intrinsicHeight + safeAreaInsets.bottom
|
||||
} else {
|
||||
// Must use the`vc` height, not the screen height because safe area insets
|
||||
// of `vc` are relative values. For example, a view controller in
|
||||
// Navigation controller's safe area insets and frame can be changed whether
|
||||
// the navigation bar is translucent or not.
|
||||
height = vc.view.bounds.height - (safeAreaInsets.top + fullInset)
|
||||
heightConstraints = [
|
||||
surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom),
|
||||
]
|
||||
case is FloatingPanelFullScreenLayout:
|
||||
heightConstraints = [
|
||||
surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
|
||||
constant: -fullInset),
|
||||
]
|
||||
default:
|
||||
heightConstraints = [
|
||||
surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
|
||||
constant: -(safeAreaInsets.top + fullInset)),
|
||||
]
|
||||
}
|
||||
|
||||
heightConstraints = [
|
||||
surfaceView.heightAnchor.constraint(equalToConstant: height)
|
||||
]
|
||||
NSLayoutConstraint.activate(heightConstraints)
|
||||
surfaceView.set(bottomOverflow: heightBuffer + layout.topInteractionBuffer)
|
||||
|
||||
surfaceView.bottomOverflow = vc.view.bounds.height + layout.topInteractionBuffer
|
||||
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
NSLayoutConstraint.deactivate(fullConstraints)
|
||||
@@ -318,7 +341,6 @@ class FloatingPanelLayoutAdapter {
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
|
||||
constant: -fullInset),
|
||||
]
|
||||
NSLayoutConstraint.activate(fullConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,16 +362,12 @@ class FloatingPanelLayoutAdapter {
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
switch state {
|
||||
case .full:
|
||||
NSLayoutConstraint.deactivate(halfConstraints + tipConstraints + offConstraints)
|
||||
NSLayoutConstraint.activate(fullConstraints)
|
||||
case .half:
|
||||
NSLayoutConstraint.deactivate(fullConstraints + tipConstraints + offConstraints)
|
||||
NSLayoutConstraint.activate(halfConstraints)
|
||||
case .tip:
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + offConstraints)
|
||||
NSLayoutConstraint.activate(tipConstraints)
|
||||
case .hidden:
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints)
|
||||
NSLayoutConstraint.activate(offConstraints)
|
||||
}
|
||||
}
|
||||
@@ -362,7 +380,7 @@ class FloatingPanelLayoutAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
func checkLayoutConsistance() {
|
||||
private func checkLayoutConsistance() {
|
||||
// Verify layout configurations
|
||||
assert(supportedPositions.count > 0)
|
||||
assert(supportedPositions.contains(layout.initialPosition),
|
||||
@@ -375,9 +393,13 @@ class FloatingPanelLayoutAdapter {
|
||||
if halfInset > 0 {
|
||||
assert(halfInset > tipInset, "Invalid half and tip insets")
|
||||
}
|
||||
if fullInset > 0 {
|
||||
// The verification isn't working on orientation change(portrait -> landscape)
|
||||
// of a floating panel in tab bar. Because the `safeAreaInsets.bottom` is
|
||||
// updated in delay so that it can be 83.0(not 53.0) even after the surface
|
||||
// and the super view's frame is fit to landscape already.
|
||||
/*if fullInset > 0 {
|
||||
assert(middleY > topY, "Invalid insets { topY: \(topY), middleY: \(middleY) }")
|
||||
assert(bottomY > topY, "Invalid insets { topY: \(topY), bottomY: \(bottomY) }")
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
return Default.grabberTopPadding * 2 + GrabberHandleView.Default.height // 17.0
|
||||
}
|
||||
|
||||
/// A UIView object that can have the surface view added to it.
|
||||
public var contentView: UIView!
|
||||
/// A root view of a content view controller
|
||||
public weak var contentView: UIView!
|
||||
|
||||
private var color: UIColor? = .white { didSet { setNeedsLayout() } }
|
||||
private var bottomOverflow: CGFloat = 0.0 // Must not call setNeedsLayout()
|
||||
var bottomOverflow: CGFloat = 0.0 // Must not call setNeedsLayout()
|
||||
|
||||
public override var backgroundColor: UIColor? {
|
||||
get { return color }
|
||||
@@ -70,7 +70,7 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
render()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
render()
|
||||
}
|
||||
@@ -83,18 +83,6 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
layer.insertSublayer(backgroundLayer, at: 0)
|
||||
self.backgroundLayer = backgroundLayer
|
||||
|
||||
let contentView = FloatingPanelSurfaceContentView()
|
||||
addSubview(contentView)
|
||||
self.contentView = contentView as UIView
|
||||
contentView.backgroundColor = color
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
|
||||
contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
contentView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
|
||||
])
|
||||
|
||||
let grabberHandle = GrabberHandleView()
|
||||
addSubview(grabberHandle)
|
||||
self.grabberHandle = grabberHandle
|
||||
@@ -110,13 +98,14 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
log.debug("SurfaceView frame", frame)
|
||||
|
||||
updateLayers()
|
||||
updateContentViewMask()
|
||||
|
||||
contentView.layer.borderColor = borderColor?.cgColor
|
||||
contentView.layer.borderWidth = borderWidth
|
||||
contentView.backgroundColor = color
|
||||
contentView?.layer.borderColor = borderColor?.cgColor
|
||||
contentView?.layer.borderWidth = borderWidth
|
||||
contentView?.frame = bounds
|
||||
}
|
||||
|
||||
private func updateLayers() {
|
||||
@@ -150,29 +139,23 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
byRoundingCorners: [.topLeft, .topRight],
|
||||
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
|
||||
maskLayer.path = path.cgPath
|
||||
contentView.layer.mask = maskLayer
|
||||
contentView?.layer.mask = maskLayer
|
||||
} else {
|
||||
// Don't use `contentView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user can mask the content view manually in an application.
|
||||
}
|
||||
}
|
||||
|
||||
func set(bottomOverflow: CGFloat) {
|
||||
self.bottomOverflow = bottomOverflow
|
||||
updateLayers()
|
||||
updateContentViewMask()
|
||||
}
|
||||
|
||||
|
||||
func add(childView: UIView) {
|
||||
contentView.addSubview(childView)
|
||||
childView.frame = contentView.bounds
|
||||
childView.translatesAutoresizingMaskIntoConstraints = false
|
||||
func add(contentView: UIView) {
|
||||
insertSubview(contentView, belowSubview: grabberHandle)
|
||||
self.contentView = contentView
|
||||
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
childView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
|
||||
childView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 0.0),
|
||||
childView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0.0),
|
||||
childView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
|
||||
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
|
||||
contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
contentView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,12 @@ class FloatingPanelModalTransition: NSObject, UIViewControllerTransitioningDeleg
|
||||
}
|
||||
|
||||
class FloatingPanelPresentationController: UIPresentationController {
|
||||
override func presentationTransitionWillBegin() {
|
||||
// Must call here even if duplicating on in containerViewWillLayoutSubviews()
|
||||
// Because it let the floating panel present correclty with the presentation animation
|
||||
addFloatingPanel()
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
// For non-animated presentation
|
||||
if let fpc = presentedViewController as? FloatingPanelController, fpc.position == .hidden {
|
||||
@@ -30,29 +36,48 @@ class FloatingPanelPresentationController: UIPresentationController {
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
// For non-animated dismissal
|
||||
if let fpc = presentedViewController as? FloatingPanelController, fpc.position != .hidden {
|
||||
fpc.hide(animated: false, completion: nil)
|
||||
if let fpc = presentedViewController as? FloatingPanelController {
|
||||
// For non-animated dismissal
|
||||
if fpc.position != .hidden {
|
||||
fpc.hide(animated: false, completion: nil)
|
||||
}
|
||||
fpc.view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
guard
|
||||
let containerView = self.containerView,
|
||||
let fpc = presentedViewController as? FloatingPanelController,
|
||||
let fpView = fpc.view
|
||||
let fpc = presentedViewController as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
/*
|
||||
* Layout the views managed by `FloatingPanelController` here for the
|
||||
* sake of the presentation and disimissal modally from the controller.
|
||||
*/
|
||||
addFloatingPanel()
|
||||
|
||||
// Forward touch events to the presenting view controller
|
||||
(fpc.view as? FloatingPanelPassThroughView)?.eventForwardingView = presentingViewController.view
|
||||
|
||||
// Set tap-to-dimiss in the backdrop view
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
|
||||
fpc.backdropView.addGestureRecognizer(tapGesture)
|
||||
|
||||
containerView.addSubview(fpView)
|
||||
fpView.frame = containerView.bounds //MUST
|
||||
}
|
||||
|
||||
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
|
||||
presentedViewController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func addFloatingPanel() {
|
||||
guard
|
||||
let containerView = self.containerView,
|
||||
let fpc = presentedViewController as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
containerView.addSubview(fpc.view)
|
||||
fpc.view.frame = containerView.bounds
|
||||
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
|
||||
@@ -6,25 +6,14 @@
|
||||
import UIKit
|
||||
|
||||
class FloatingPanelPassThroughView: UIView {
|
||||
public weak var eventForwardingView: UIView?
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let view = super.hitTest(point, with: event)
|
||||
switch view {
|
||||
case is FloatingPanelPassThroughView:
|
||||
return nil
|
||||
let hitView = super.hitTest(point, with: event)
|
||||
switch hitView {
|
||||
case self:
|
||||
return eventForwardingView?.hitTest(self.convert(point, to: eventForwardingView), with: event)
|
||||
default:
|
||||
return view
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelSurfaceWrapperView: UIView {
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let view = super.hitTest(point, with: event)
|
||||
switch view {
|
||||
case is FloatingPanelSurfaceWrapperView:
|
||||
return nil
|
||||
default:
|
||||
return view
|
||||
return hitView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public class GrabberHandleView: UIView {
|
||||
public static let barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
render()
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
|
||||
extension UIGestureRecognizerState: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .began: return "Began"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://swift.org/)
|
||||
[](https://swift.org/)
|
||||
|
||||
# FloatingPanel
|
||||
@@ -25,6 +26,7 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
- [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)
|
||||
- [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)
|
||||
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
|
||||
@@ -65,7 +67,7 @@ Examples are here.
|
||||
|
||||
## Requirements
|
||||
|
||||
FloatingPanel is written in Swift 4.2. Compatible with iOS 10.0+
|
||||
FloatingPanel is written in Swift. It can be built by Xcode 9.4.1 or later. Compatible with iOS 10.0+.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -86,7 +88,6 @@ For [Carthage](https://github.com/Carthage/Carthage), add the following to your
|
||||
github "scenee/FloatingPanel"
|
||||
```
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Add a floating panel as a child view controller
|
||||
@@ -138,9 +139,21 @@ fpc.isRemovalInteractionEnabled = true // Optional: Let it removable by a swipe-
|
||||
self.present(fpc, animated: true, completion: nil)
|
||||
```
|
||||
|
||||
You can show a floating panel over UINavigationController from the containnee view controllers as a modality of `.overCurrentContext` style.
|
||||
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 [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/feat-modality/Framework/Sources/FloatingPanelTransitioning.swift).
|
||||
NOTE: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Framework/Sources/FloatingPanelTransitioning.swift).
|
||||
|
||||
## View hierarchy
|
||||
|
||||
`FloatingPanelController` manages the views as the following view hierarchy.
|
||||
|
||||
```
|
||||
FloatingPanelController.view (FloatingPanelPassThroughView)
|
||||
├─ .backdropView (FloatingPanelBackdropView)
|
||||
└─ .surfaceView (FloatingPanelSurfaceView)
|
||||
├─ .contentView == FloatingPanelController.contentViewController.view
|
||||
└─ .grabberHandle (GrabberHandleView)
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -150,7 +163,15 @@ NOTE: FloatingPanelController has the custom presentation controller. If you wou
|
||||
// Add the controller and the managed views to a view controller.
|
||||
// From the second time, just call `show(animated:completion)`.
|
||||
view.addSubview(fpc.view)
|
||||
|
||||
fpc.view.frame = view.bounds // MUST
|
||||
// In addition, Auto Layout constraints are highly recommended.
|
||||
// Because it makes the layout more robust on trait collection change.
|
||||
//
|
||||
// fpc.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
// NSLayoutConstraint.activate([...])
|
||||
//
|
||||
|
||||
parent.addChild(fpc)
|
||||
|
||||
// Show a floating panel to the initial position defined in your `FloatingPanelLayout` object.
|
||||
|
||||
Reference in New Issue
Block a user