Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ccabb1914a | |||
| be2be99537 | |||
| afcf1ced36 | |||
| 5b33d3d5ff | |||
| dbef6a691a | |||
| dd238884bf | |||
| 046ed3df5b | |||
| 4996ce1a84 | |||
| b8f7ff825d | |||
| 6fb9a9b3a2 | |||
| 34ebb3bf19 | |||
| dbf665526d | |||
| 6c7f529eff | |||
| c414d3a2a6 | |||
| 8eba647d75 | |||
| c3568067b7 | |||
| 72580f089d | |||
| c508ec892d | |||
| ff2d4a48f1 | |||
| 62364eb6d5 | |||
| ce5469a69d | |||
| 93c31fd71d | |||
| 461f637818 | |||
| 6b3b18b8ed | |||
| b5ca468397 | |||
| 80956bfac6 | |||
| 5d382c440f | |||
| d28c939a4c | |||
| 8bd02145cf | |||
| debeca1fb2 | |||
| 9da54f9fc1 | |||
| a13d053867 | |||
| eda7201fe8 | |||
| b1edef49a4 | |||
| a2db94a8c4 | |||
| 57495cff84 | |||
| eff5cde844 | |||
| c365eadf1e | |||
| 5d02681b05 | |||
| 6e17ff734a | |||
| 02ed923e7b | |||
| 421335d98c | |||
| 2618f49556 | |||
| 328116600f | |||
| a10b1426cd | |||
| 5468856a93 | |||
| 8f3a7de321 | |||
| 2de1fb9ac8 | |||
| 27a2d81a71 | |||
| 85ed3a6ce3 | |||
| b34f1093de | |||
| de1dbe70de | |||
| c593c646ca | |||
| 0cb5307a61 | |||
| 8ab3b7986c | |||
| bbdf3e7c6f | |||
| bdb756b665 | |||
| 6611ec83a2 | |||
| a917d6a626 | |||
| 7511ce577d | |||
| e7d0a72440 | |||
| 44923ef66e | |||
| 2760bc7298 | |||
| d428e96b03 | |||
| 67495961e5 | |||
| 7b88703e43 | |||
| c40e66ef3d | |||
| 53fb131d0e | |||
| 37969f6cb3 |
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
@@ -17,6 +18,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- swift: "5.9"
|
||||
xcode: "15.0.1"
|
||||
runsOn: macos-13
|
||||
- swift: "5.8"
|
||||
xcode: "14.3.1"
|
||||
runsOn: macos-13
|
||||
- swift: "5.7"
|
||||
xcode: "14.1"
|
||||
runsOn: macos-12
|
||||
@@ -48,18 +55,25 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: "16.1"
|
||||
xcode: "14.1"
|
||||
- os: "17.0.1"
|
||||
xcode: "15.0.1"
|
||||
sim: "iPhone 15 Pro"
|
||||
parallel: NO # Stop random test job failures
|
||||
runsOn: macos-13
|
||||
- os: "16.4"
|
||||
xcode: "14.3.1"
|
||||
sim: "iPhone 14 Pro"
|
||||
runsOn: macos-12
|
||||
parallel: NO # Stop random test job failures
|
||||
runsOn: macos-13
|
||||
- os: "15.5"
|
||||
xcode: "13.4.1"
|
||||
sim: "iPhone 13 Pro"
|
||||
parallel: NO # Stop random test job failures
|
||||
runsOn: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Testing in iOS ${{ matrix.os }}
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}'
|
||||
run: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=${{ matrix.os }},name=${{ matrix.sim }}' -parallel-testing-enabled '${{ matrix.parallel }}'
|
||||
timeout-minutes: 20
|
||||
|
||||
example:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
version: 1
|
||||
builder:
|
||||
configs:
|
||||
- documentation_targets: [FloatingPanel]
|
||||
platform: ios
|
||||
@@ -82,7 +82,10 @@ struct FloatingPanelView<Content: View, FloatingPanelContent: View>: UIViewContr
|
||||
/// Responsible to setup the view hierarchy and floating panel.
|
||||
final class Coordinator {
|
||||
private let parent: FloatingPanelView<Content, FloatingPanelContent>
|
||||
private lazy var fpc = FloatingPanelController()
|
||||
private lazy var fpc = {
|
||||
FloatingPanelController.enableDismissToRemove()
|
||||
return FloatingPanelController()
|
||||
}()
|
||||
|
||||
init(parent: FloatingPanelView<Content, FloatingPanelContent>) {
|
||||
self.parent = parent
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
FloatingPanelController.enableDismissToRemove()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -11,16 +13,16 @@
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="MainViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="5Jw-n2-Cpw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
</mapView>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d9i-3g-8Ja">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="59"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="lMa-xa-AVV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="59"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<blurEffect style="prominent"/>
|
||||
@@ -52,31 +54,31 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="SearchViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="0S1-Lk-JgE" customClass="SearchViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ncl-E9-yRn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ye3-uU-bq3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="ED1-gT-FBj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<searchBar contentMode="redraw" searchBarStyle="minimal" translatesAutoresizingMaskIntoConstraints="NO" id="Zcj-SE-gb8">
|
||||
<rect key="frame" x="0.0" y="6" width="600" height="51"/>
|
||||
<rect key="frame" x="0.0" y="6" width="393" height="56"/>
|
||||
<textInputTraits key="textInputTraits"/>
|
||||
</searchBar>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="D7r-re-InH">
|
||||
<rect key="frame" x="0.0" y="61" width="600" height="539"/>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="D7r-re-InH">
|
||||
<rect key="frame" x="0.0" y="66" width="393" height="786"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<view key="tableHeaderView" contentMode="scaleToFill" id="u28-LY-hIh" customClass="SearchHeaderView" customModule="Maps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="116"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="116"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" baselineRelativeArrangement="YES" translatesAutoresizingMaskIntoConstraints="NO" id="era-8w-yA1">
|
||||
<rect key="frame" x="24" y="10.5" width="552" height="97.5"/>
|
||||
<rect key="frame" x="24" y="10.666666666666664" width="345" height="97.333333333333343"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="auI-1v-Yfk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="food" translatesAutoresizingMaskIntoConstraints="NO" id="ErN-bC-qTx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -86,7 +88,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Food & Drinks" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nx2-fW-xAm">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -94,7 +96,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="0vd-sD-XKv">
|
||||
<rect key="frame" x="164" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="95" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="shopping" translatesAutoresizingMaskIntoConstraints="NO" id="xcm-St-HAo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -104,7 +106,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="H7q-q2-ga5">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<string key="text">Shopping
|
||||
</string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
@@ -114,7 +116,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Jd8-YL-b5s">
|
||||
<rect key="frame" x="328" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="190" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="fun" translatesAutoresizingMaskIntoConstraints="NO" id="bMJ-Jn-Gi8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -124,7 +126,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kKh-45-FZ2">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<string key="text">Fun
|
||||
</string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
@@ -134,7 +136,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="dTL-e1-Arz">
|
||||
<rect key="frame" x="492" y="0.0" width="60" height="97.5"/>
|
||||
<rect key="frame" x="285" y="0.0" width="60" height="97.333333333333329"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="travel" translatesAutoresizingMaskIntoConstraints="NO" id="8h3-fo-pC3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
|
||||
@@ -144,7 +146,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WBT-Vj-7QA">
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.5"/>
|
||||
<rect key="frame" x="0.0" y="66" width="60" height="31.333333333333329"/>
|
||||
<string key="text">Travel
|
||||
</string>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
@@ -166,10 +168,10 @@
|
||||
</view>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="Cell" rowHeight="70" id="LzC-B9-Adb" customClass="SearchCell" customModule="Maps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="160.5" width="600" height="70"/>
|
||||
<rect key="frame" x="0.0" y="166" width="393" height="70"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LzC-B9-Adb" id="evr-60-laS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="70"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="70"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="like" translatesAutoresizingMaskIntoConstraints="NO" id="GEk-yE-lLq">
|
||||
@@ -181,16 +183,16 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="Gfl-Oy-rsy">
|
||||
<rect key="frame" x="57" y="12" width="528" height="46"/>
|
||||
<rect key="frame" x="57" y="12" width="321" height="46"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Favorites" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Spf-8L-Ne6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="528" height="22"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="321" height="22"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0 Places" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gyo-3V-7U8">
|
||||
<rect key="frame" x="0.0" y="24" width="528" height="22"/>
|
||||
<rect key="frame" x="0.0" y="24" width="321" height="22"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<color key="textColor" red="0.57647058819999997" green="0.57647058819999997" blue="0.57647058819999997" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -255,18 +257,18 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="DetailViewController" id="Tp2-MF-IFz" customClass="DetailViewController" customModule="Maps" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="FmO-AT-4Y7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="c3d-2e-0b1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="-1" y="-1" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="9fL-a5-0LS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="kP7-56-wlG">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableView>
|
||||
</subviews>
|
||||
|
||||
@@ -124,7 +124,7 @@ extension MainViewController: UISearchBarDelegate {
|
||||
searchBar.showsCancelButton = true
|
||||
searchVC.showHeader(animated: true)
|
||||
searchVC.tableView.alpha = 1.0
|
||||
detailVC.dismiss(animated: true, completion: nil)
|
||||
detailFpc.removePanelFromParent(animated: true)
|
||||
}
|
||||
func deactivate(searchBar: UISearchBar) {
|
||||
searchBar.resignFirstResponder()
|
||||
@@ -156,6 +156,14 @@ class SearchPanelPhoneDelegate: NSObject, FloatingPanelControllerDelegate, UIGes
|
||||
self.owner = owner
|
||||
}
|
||||
|
||||
func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll scrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool {
|
||||
return state == .full || state == .half
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
switch newCollection.verticalSizeClass {
|
||||
case .compact:
|
||||
@@ -215,17 +223,10 @@ class SearchPanelLandscapeLayout: FloatingPanelLayout {
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
if #available(iOS 11.0, *) {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
}
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
}
|
||||
func backdropAlpha(for state: FloatingPanelState) -> CGFloat {
|
||||
return 0.0
|
||||
@@ -319,7 +320,7 @@ class SearchPaneliPadBehavior: FloatingPanelBehavior {
|
||||
var momentumProjectionRate: CGFloat {
|
||||
return UIScrollView.DecelerationRate.fast.rawValue
|
||||
}
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,6 @@ class SearchViewController: UIViewController, UITableViewDataSource {
|
||||
|
||||
var items: [LocationItem] = []
|
||||
|
||||
// For iOS 10 only
|
||||
private lazy var shadowLayer: CAShapeLayer = CAShapeLayer()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.dataSource = self
|
||||
@@ -92,29 +89,6 @@ class SearchViewController: UIViewController, UITableViewDataSource {
|
||||
hideHeader(animated: false)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11, *) {
|
||||
} else {
|
||||
// Exmaple: Add rounding corners on iOS 10
|
||||
visualEffectView.layer.cornerRadius = 9.0
|
||||
visualEffectView.clipsToBounds = true
|
||||
|
||||
// Exmaple: Add shadow manually on iOS 10
|
||||
view.layer.insertSublayer(shadowLayer, at: 0)
|
||||
let rect = visualEffectView.frame
|
||||
let path = UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.topLeft, .topRight],
|
||||
cornerRadii: CGSize(width: 9.0, height: 9.0))
|
||||
shadowLayer.frame = visualEffectView.frame
|
||||
shadowLayer.shadowPath = path.cgPath
|
||||
shadowLayer.shadowColor = UIColor.black.cgColor
|
||||
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0)
|
||||
shadowLayer.shadowOpacity = 0.2
|
||||
shadowLayer.shadowRadius = 3.0
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return items.count
|
||||
}
|
||||
@@ -130,16 +104,16 @@ class SearchViewController: UIViewController, UITableViewDataSource {
|
||||
}
|
||||
|
||||
func showHeader(animated: Bool) {
|
||||
changeHeader(height: 116.0, aniamted: animated)
|
||||
changeHeader(height: 116.0, animated: animated)
|
||||
}
|
||||
|
||||
func hideHeader(animated: Bool) {
|
||||
changeHeader(height: 0.0, aniamted: animated)
|
||||
changeHeader(height: 0.0, animated: animated)
|
||||
}
|
||||
|
||||
private func changeHeader(height: CGFloat, aniamted: Bool) {
|
||||
private func changeHeader(height: CGFloat, animated: Bool) {
|
||||
guard let headerView = tableView.tableHeaderView, headerView.bounds.height != height else { return }
|
||||
if aniamted == false {
|
||||
if animated == false {
|
||||
updateHeader(height: height)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
FloatingPanelController.enableDismissToRemove()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21679"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -383,22 +383,22 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="720"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="768"/>
|
||||
<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="720" width="375" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="768" width="375" height="0.0"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
|
||||
<rect key="frame" x="20" y="48" width="39" height="30"/>
|
||||
<rect key="frame" x="20" y="0.0" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="MSC-ch-YJK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="44" translatesAutoresizingMaskIntoConstraints="NO" id="9p4-06-y2T">
|
||||
<rect key="frame" x="134.5" y="136" width="106" height="326"/>
|
||||
<rect key="frame" x="134.5" y="88" width="106" height="326"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="30"/>
|
||||
@@ -598,7 +598,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="744"/>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
|
||||
@@ -761,14 +761,33 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
<fontDescription key="fontDescription" name="CourierNewPSMT" family="Courier New" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wUo-kb-NIn">
|
||||
<rect key="frame" x="159" y="16" width="200" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Expand top margin" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OC3-od-ldC">
|
||||
<rect key="frame" x="0.0" y="5.5" width="143" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="XFC-sq-pWj">
|
||||
<rect key="frame" x="151" y="0.0" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleTopMargin:" destination="tvD-nO-QUb" eventType="valueChanged" id="XWo-eX-0Jn"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
<constraint firstItem="5ET-zC-lCb" firstAttribute="trailing" secondItem="wUo-kb-NIn" secondAttribute="trailing" constant="16" id="CtG-H5-tAI"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rN1-HL-YHv" secondAttribute="bottom" id="efD-U5-Tet"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="17" id="fiO-LL-nSC"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
<constraint firstItem="wUo-kb-NIn" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="16" id="ogC-1W-upw"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
@@ -780,7 +799,7 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1" y="734"/>
|
||||
<point key="canvasLocation" x="-2.1739130434782612" y="733.92857142857144"/>
|
||||
</scene>
|
||||
<!--Adaptive Layout Test View Controller-->
|
||||
<scene sceneID="rDI-lU-wEx">
|
||||
|
||||
@@ -151,7 +151,7 @@ class DebugTableViewController: InspectableViewController {
|
||||
}
|
||||
}
|
||||
// Set titles
|
||||
tracksScrollView = false
|
||||
tracksScrollView = true
|
||||
followsScrollViewBouncing = false
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,7 @@ final class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
textView.delegate = self
|
||||
print("viewDidLoad: TextView --- ", textView.contentOffset, textView.contentInset)
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
textView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
textView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
@@ -32,8 +30,14 @@ final class DebugTextViewController: UIViewController, UITextViewDelegate {
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
print("TextView --- ", scrollView.contentOffset, scrollView.contentInset)
|
||||
if #available(iOS 11.0, *) {
|
||||
print("TextView --- ", scrollView.adjustedContentInset)
|
||||
print("TextView --- ", scrollView.adjustedContentInset)
|
||||
}
|
||||
|
||||
@IBAction func toggleTopMargin(_ sender: UISwitch) {
|
||||
if sender.isOn {
|
||||
textViewTopConstraint.constant = 160
|
||||
} else {
|
||||
textViewTopConstraint.constant = 16
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ final class ImageViewController: UIViewController {
|
||||
case withHeaderFooter
|
||||
}
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
func layoutGuideFor(mode: Mode) -> UILayoutGuide {
|
||||
switch mode {
|
||||
case .onlyImage:
|
||||
|
||||
@@ -14,21 +14,17 @@ final class SettingsViewController: InspectableViewController {
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {
|
||||
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
|
||||
largeTitlesSwitch.setOn(prefersLargeTitles, animated: false)
|
||||
} else {
|
||||
largeTitlesSwitch.isEnabled = false
|
||||
}
|
||||
let prefersLargeTitles = navigationController!.navigationBar.prefersLargeTitles
|
||||
largeTitlesSwitch.setOn(prefersLargeTitles, animated: false)
|
||||
|
||||
let isTranslucent = navigationController!.navigationBar.isTranslucent
|
||||
translucentSwitch.setOn(isTranslucent, animated: false)
|
||||
}
|
||||
|
||||
@IBAction func toggleLargeTitle(_ sender: UISwitch) {
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
|
||||
}
|
||||
navigationController?.navigationBar.prefersLargeTitles = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleTranslucent(_ sender: UISwitch) {
|
||||
// White non-translucent navigation bar, supports dark appearance
|
||||
if #available(iOS 15, *) {
|
||||
|
||||
@@ -238,13 +238,8 @@ class ThreeTabBarPanelLayout: FloatingPanelLayout {
|
||||
}
|
||||
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
if #available(iOS 11.0, *) {
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
|
||||
} else {
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0)
|
||||
}
|
||||
leftConstraint = surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0)
|
||||
rightConstraint = surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0)
|
||||
return [ leftConstraint, rightConstraint ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,22 +33,10 @@ class CustomLayoutGuide: LayoutGuideProvider {
|
||||
|
||||
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)
|
||||
}
|
||||
return view.safeAreaInsets
|
||||
}
|
||||
|
||||
var layoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view!.safeAreaLayoutGuide
|
||||
} else {
|
||||
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor)
|
||||
}
|
||||
return view.safeAreaLayoutGuide
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,11 @@ extension MainViewController {
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
|
||||
automaticallyAdjustsScrollViewInsets = false
|
||||
|
||||
let searchController = UISearchController(searchResultsController: nil)
|
||||
if #available(iOS 11.0, *) {
|
||||
navigationItem.searchController = searchController
|
||||
navigationItem.hidesSearchBarWhenScrolling = false
|
||||
navigationItem.largeTitleDisplayMode = .automatic
|
||||
}
|
||||
navigationItem.searchController = searchController
|
||||
navigationItem.hidesSearchBarWhenScrolling = false
|
||||
navigationItem.largeTitleDisplayMode = .automatic
|
||||
var insets = UIEdgeInsets.zero
|
||||
insets.bottom += 69.0
|
||||
tableView.contentInset = insets
|
||||
@@ -33,12 +30,10 @@ extension MainViewController {
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
if #available(iOS 11.0, *) {
|
||||
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
|
||||
self.tableView.reloadData()
|
||||
}) {
|
||||
observations.append(observation)
|
||||
}
|
||||
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
|
||||
self.tableView.reloadData()
|
||||
}) {
|
||||
observations.append(observation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +51,8 @@ extension MainViewController {
|
||||
|
||||
extension MainViewController: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if #available(iOS 11.0, *) {
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return UseCase.allCases.count + 30
|
||||
} else {
|
||||
return UseCase.allCases.count
|
||||
}
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return UseCase.allCases.count + 30
|
||||
} else {
|
||||
return UseCase.allCases.count
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ enum UseCase: Int, CaseIterable {
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showOnWindow
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
@@ -34,6 +35,7 @@ extension UseCase {
|
||||
case .showModal: return "Show Modal"
|
||||
case .showPanelModal: return "Show Panel Modal"
|
||||
case .showMultiPanelModal: return "Show Multi Panel Modal"
|
||||
case .showOnWindow: return "Show Panel over Window"
|
||||
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
|
||||
case .showTabBar: return "Show Tab Bar"
|
||||
case .showPageView: return "Show Page View"
|
||||
@@ -65,6 +67,7 @@ extension UseCase {
|
||||
case .showDetail: return .storyboard(String(describing: DetailViewController.self))
|
||||
case .showModal: return .storyboard(String(describing: ModalViewController.self))
|
||||
case .showMultiPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showOnWindow: return .viewController(DebugTableViewController())
|
||||
case .showPanelInSheetModal: return .viewController(DebugTableViewController())
|
||||
case .showPanelModal: return .viewController(DebugTableViewController())
|
||||
case .showTabBar: return .storyboard(String(describing: TabBarViewController.self))
|
||||
|
||||
@@ -11,6 +11,7 @@ final class UseCaseController: NSObject {
|
||||
private var detailPanelVC: FloatingPanelController!
|
||||
private var settingsPanelVC: FloatingPanelController!
|
||||
private lazy var pagePanelController = PagePanelController()
|
||||
private lazy var overWindowPanelVC = FloatingPanelController()
|
||||
|
||||
init(mainVC: MainViewController) {
|
||||
self.mainVC = mainVC
|
||||
@@ -157,6 +158,20 @@ extension UseCaseController {
|
||||
let fpc = MultiPanelController()
|
||||
mainVC.present(fpc, animated: true, completion: nil)
|
||||
|
||||
case .showOnWindow:
|
||||
let fpc = overWindowPanelVC
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
fpc.isRemovalInteractionEnabled = true
|
||||
fpc.set(contentViewController: contentVC)
|
||||
fpc.ext_trackScrollView(in: contentVC)
|
||||
|
||||
guard let window = UIApplication.shared.windows.first else { fatalError("Any window not found") }
|
||||
|
||||
window.addSubview(fpc.view)
|
||||
fpc.view.frame = window.bounds
|
||||
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
fpc.show(animated: true)
|
||||
case .showPanelInSheetModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = UIViewController()
|
||||
@@ -307,8 +322,16 @@ extension UseCaseController {
|
||||
}
|
||||
|
||||
extension UseCaseController: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll scrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool {
|
||||
return state == .full || state == .half
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint {
|
||||
if useCase == .showNavigationController, #available(iOS 11.0, *) {
|
||||
if useCase == .showNavigationController {
|
||||
// 148.0 is the SafeArea's top value for a navigation bar with a large title.
|
||||
return CGPoint(x: 0.0, y: 0.0 - trackingScrollView.contentInset.top - 148.0)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
545BA71421BA3217007F7846 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 545BA71321BA3217007F7846 /* main.m */; };
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; };
|
||||
545BA72721BA3BAF007F7846 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 545BA72521BA3BAF007F7846 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D82A6AF28D18443006A44BA /* libswiftCoreGraphics.tbd */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -53,7 +52,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5D82A6B028D18447006A44BA /* libswiftCoreGraphics.tbd in Frameworks */,
|
||||
545BA72621BA3BAF007F7846 /* FloatingPanel.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
#import "AppDelegate.h"
|
||||
@import FloatingPanel;
|
||||
|
||||
@interface AppDelegate ()
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions
|
||||
{
|
||||
[FloatingPanelController enableDismissToRemove];
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -81,7 +81,7 @@ class MainViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
}
|
||||
|
||||
private func hideStockTickerBanner() {
|
||||
// Dimiss top bar with dissolve animation
|
||||
// Dismiss top bar with dissolve animation
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.topBannerView.alpha = 0.0
|
||||
self.labelStackView.alpha = 1.0
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.6.2"
|
||||
s.version = "2.8.1"
|
||||
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
|
||||
s.description = <<-DESC
|
||||
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
The new interface displays the related contents and utilities in parallel as a user wants.
|
||||
DESC
|
||||
s.homepage = "https://github.com/SCENEE/FloatingPanel"
|
||||
s.homepage = "https://github.com/scenee/FloatingPanel"
|
||||
s.author = "Shin Yamamoto"
|
||||
s.social_media_url = "https://twitter.com/scenee"
|
||||
|
||||
s.platform = :ios, "10.0"
|
||||
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => s.version.to_s }
|
||||
s.platform = :ios, "11.0"
|
||||
s.source = { :git => "https://github.com/scenee/FloatingPanel.git", :tag => s.version.to_s }
|
||||
s.source_files = "Sources/*.swift"
|
||||
s.swift_version = '5.0'
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
5469F4B024B30E1500537F8A /* LayoutAnchoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */; };
|
||||
5469F4B224B30F1100537F8A /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B124B30F1100537F8A /* Position.swift */; };
|
||||
5469F4B424B30F3500537F8A /* LayoutProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5469F4B324B30F3500537F8A /* LayoutProperties.swift */; };
|
||||
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F7A9B2A6E946000303905 /* GestureTests.swift */; };
|
||||
549C371F2361E15E007D8058 /* ExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549C371E2361E15D007D8058 /* ExtensionTests.swift */; };
|
||||
549E944522CF295D0050AECF /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E944422CF295D0050AECF /* StateTests.swift */; };
|
||||
54A6B6B122968B530077F348 /* CoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* CoreTests.swift */; };
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logging.swift */; };
|
||||
54CDC5D3215B6D5A007D205C /* SurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* SurfaceView.swift */; };
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* BackdropView.swift */; };
|
||||
54CFBFC3215CD045006B5735 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* Layout.swift */; };
|
||||
@@ -63,11 +64,12 @@
|
||||
5469F4AF24B30E1500537F8A /* LayoutAnchoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutAnchoring.swift; sourceTree = "<group>"; };
|
||||
5469F4B124B30F1100537F8A /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = "<group>"; };
|
||||
5469F4B324B30F3500537F8A /* LayoutProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutProperties.swift; sourceTree = "<group>"; };
|
||||
547F7A9B2A6E946000303905 /* GestureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureTests.swift; sourceTree = "<group>"; };
|
||||
549C371E2361E15D007D8058 /* ExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionTests.swift; sourceTree = "<group>"; };
|
||||
549E944422CF295D0050AECF /* StateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceViewTests.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
|
||||
54CDC5D2215B6D5A007D205C /* SurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView.swift; sourceTree = "<group>"; };
|
||||
54CDC5D4215B6D8D007D205C /* BackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackdropView.swift; sourceTree = "<group>"; };
|
||||
54CFBFC2215CD045006B5735 /* Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; };
|
||||
@@ -133,7 +135,7 @@
|
||||
545DBA2A2152383100CA77B8 /* GrabberView.swift */,
|
||||
54352E9521A51A2500CBCA08 /* Transitioning.swift */,
|
||||
54DBA3DB262E938500D75969 /* Extensions.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */,
|
||||
54ABD7AE216CCFF7002E6C13 /* Logging.swift */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9C52151169500CA77B8 /* Info.plist */,
|
||||
54E3992627141F5100A8F9ED /* FloatingPanel.docc */,
|
||||
@@ -146,6 +148,7 @@
|
||||
children = (
|
||||
54A6B6B022968B530077F348 /* CoreTests.swift */,
|
||||
545DB9CF2151169500CA77B8 /* ControllerTests.swift */,
|
||||
547F7A9B2A6E946000303905 /* GestureTests.swift */,
|
||||
542753C522C49A6E00D17955 /* LayoutTests.swift */,
|
||||
54A6B6B72296A8520077F348 /* SurfaceViewTests.swift */,
|
||||
549E944422CF295D0050AECF /* StateTests.swift */,
|
||||
@@ -282,7 +285,7 @@
|
||||
54CDC5D5215B6D8D007D205C /* BackdropView.swift in Sources */,
|
||||
54352E9821A521CA00CBCA08 /* PassthroughView.swift in Sources */,
|
||||
54CFBFC5215CD09C006B5735 /* Core.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logging.swift in Sources */,
|
||||
545DB9E021511AC100CA77B8 /* Controller.swift in Sources */,
|
||||
54DBA3DC262E938500D75969 /* Extensions.swift in Sources */,
|
||||
5450EEE421646DF500135936 /* Behavior.swift in Sources */,
|
||||
@@ -304,6 +307,7 @@
|
||||
542753C622C49A6E00D17955 /* LayoutTests.swift in Sources */,
|
||||
54A6B6B82296A8520077F348 /* SurfaceViewTests.swift in Sources */,
|
||||
546055BF2333C4740069F400 /* TestSupports.swift in Sources */,
|
||||
547F7A9C2A6E946000303905 /* GestureTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -458,12 +462,13 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -489,12 +494,13 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
@@ -511,12 +517,13 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -531,12 +538,13 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -623,16 +631,17 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG __FP_LOG";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG FP_LOG";
|
||||
SWIFT_COMPILATION_MODE = singlefile;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -647,12 +656,13 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import PackageDescription
|
||||
let package = Package(
|
||||
name: "FloatingPanel",
|
||||
platforms: [
|
||||
.iOS(.v10)
|
||||
.iOS(.v11)
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in Apple Maps, Shortcuts and Stocks app.
|
||||
The user interface displays related content and utilities alongside the main content.
|
||||
|
||||
Please see also [the API reference](https://floatingpanel.github.io/2.6.2/documentation/floatingpanel/) for more details, powered by [DocC](https://developer.apple.com/documentation/docc).
|
||||
Please see also [the API reference](https://floatingpanel.github.io/2.8.1/documentation/floatingpanel/) for more details.
|
||||
|
||||

|
||||

|
||||
@@ -21,47 +21,47 @@ Please see also [the API reference](https://floatingpanel.github.io/2.6.2/docume
|
||||
- [Features](#features)
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [CocoaPods](#cocoapods)
|
||||
- [Carthage](#carthage)
|
||||
- [Swift Package Manager](#swift-package-manager)
|
||||
- [CocoaPods](#cocoapods)
|
||||
- [Carthage](#carthage)
|
||||
- [Swift Package Manager](#swift-package-manager)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
|
||||
- [Present a floating panel as a modality](#present-a-floating-panel-as-a-modality)
|
||||
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
|
||||
- [Present a floating panel as a modality](#present-a-floating-panel-as-a-modality)
|
||||
- [View hierarchy](#view-hierarchy)
|
||||
- [Usage](#usage)
|
||||
- [Show/Hide a floating panel in a view with your view hierarchy](#showhide-a-floating-panel-in-a-view-with-your-view-hierarchy)
|
||||
- [Scale the content view when the surface position changes](#scale-the-content-view-when-the-surface-position-changes)
|
||||
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
|
||||
- [Change the initial layout](#change-the-initial-layout)
|
||||
- [Update your panel layout](#update-your-panel-layout)
|
||||
- [Support your landscape layout](#support-your-landscape-layout)
|
||||
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
|
||||
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
|
||||
- [Change the backdrop alpha](#change-the-backdrop-alpha)
|
||||
- [Using custome panel states](#using-custome-panel-states)
|
||||
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
|
||||
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
|
||||
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
|
||||
- [Manage the projection of a pan gesture momentum](#manage-the-projection-of-a-pan-gesture-momentum)
|
||||
- [Specify the panel move's boundary](#specify-the-panel-moves-boundary)
|
||||
- [Customize the surface design](#customize-the-surface-design)
|
||||
- [Modify your surface appearance](#modify-your-surface-appearance)
|
||||
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
|
||||
- [Customize layout of the grabber handle](#customize-layout-of-the-grabber-handle)
|
||||
- [Customize content padding from surface edges](#customize-content-padding-from-surface-edges)
|
||||
- [Customize margins of the surface edges](#customize-margins-of-the-surface-edges)
|
||||
- [Customize gestures](#customize-gestures)
|
||||
- [Suppress the panel interaction](#suppress-the-panel-interaction)
|
||||
- [Add tap gestures to the surface view](#add-tap-gestures-to-the-surface-view)
|
||||
- [Interrupt the delegate methods of `FloatingPanelController.panGestureRecognizer`](#interrupt-the-delegate-methods-of-floatingpanelcontrollerpangesturerecognizer)
|
||||
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
|
||||
- [Move a position with an animation](#move-a-position-with-an-animation)
|
||||
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
|
||||
- [Enabling the tap-to-dismiss action of the backdrop view](#enabling-the-tap-to-dismiss-action-of-the-backdrop-view)
|
||||
- [Show/Hide a floating panel in a view with your view hierarchy](#showhide-a-floating-panel-in-a-view-with-your-view-hierarchy)
|
||||
- [Scale the content view when the surface position changes](#scale-the-content-view-when-the-surface-position-changes)
|
||||
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
|
||||
- [Change the initial layout](#change-the-initial-layout)
|
||||
- [Update your panel layout](#update-your-panel-layout)
|
||||
- [Support your landscape layout](#support-your-landscape-layout)
|
||||
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
|
||||
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
|
||||
- [Change the backdrop alpha](#change-the-backdrop-alpha)
|
||||
- [Using custome panel states](#using-custome-panel-states)
|
||||
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
|
||||
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
|
||||
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
|
||||
- [Manage the projection of a pan gesture momentum](#manage-the-projection-of-a-pan-gesture-momentum)
|
||||
- [Specify the panel move's boundary](#specify-the-panel-moves-boundary)
|
||||
- [Customize the surface design](#customize-the-surface-design)
|
||||
- [Modify your surface appearance](#modify-your-surface-appearance)
|
||||
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
|
||||
- [Customize layout of the grabber handle](#customize-layout-of-the-grabber-handle)
|
||||
- [Customize content padding from surface edges](#customize-content-padding-from-surface-edges)
|
||||
- [Customize margins of the surface edges](#customize-margins-of-the-surface-edges)
|
||||
- [Customize gestures](#customize-gestures)
|
||||
- [Suppress the panel interaction](#suppress-the-panel-interaction)
|
||||
- [Add tap gestures to the surface view](#add-tap-gestures-to-the-surface-view)
|
||||
- [Interrupt the delegate methods of `FloatingPanelController.panGestureRecognizer`](#interrupt-the-delegate-methods-of-floatingpanelcontrollerpangesturerecognizer)
|
||||
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
|
||||
- [Move a position with an animation](#move-a-position-with-an-animation)
|
||||
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
|
||||
- [Enabling the tap-to-dismiss action of the backdrop view](#enabling-the-tap-to-dismiss-action-of-the-backdrop-view)
|
||||
- [Allow to scroll content of the tracking scroll view in addition to the most expanded state](#allow-to-scroll-content-of-the-tracking-scroll-view-in-addition-to-the-most-expanded-state)
|
||||
- [Notes](#notes)
|
||||
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
|
||||
- [UISearchController issue](#uisearchcontroller-issue)
|
||||
- [FloatingPanelSurfaceView's issue on iOS 10](#floatingpanelsurfaceviews-issue-on-ios-10)
|
||||
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
|
||||
- [UISearchController issue](#uisearchcontroller-issue)
|
||||
- [Maintainer](#maintainer)
|
||||
- [License](#license)
|
||||
|
||||
@@ -93,10 +93,6 @@ Examples can be found here:
|
||||
|
||||
FloatingPanel is written in Swift 5.0+ and compatible with iOS 11.0+.
|
||||
|
||||
While it still supports iOS 10, it is recommended to use this library on iOS 11+.
|
||||
|
||||
:pencil2: If you'd like to use Swift 4.0, please use FloatingPanel v1.
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods
|
||||
@@ -108,8 +104,6 @@ it, simply add the following line to your Podfile:
|
||||
pod 'FloatingPanel'
|
||||
```
|
||||
|
||||
:pencil2: FloatingPanel v1.7.0 or later requires CocoaPods v1.7.0+ for `swift_versions` support.
|
||||
|
||||
### Carthage
|
||||
|
||||
For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`:
|
||||
@@ -168,7 +162,8 @@ self.present(fpc, animated: true, completion: nil)
|
||||
|
||||
You can show a floating panel over UINavigationController from the container view controllers as a modality of `.overCurrentContext` style.
|
||||
|
||||
:pencil2: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [Transitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Sources/Transitioning.swift).
|
||||
> [!NOTE]
|
||||
> FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [Transitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Sources/Transitioning.swift).
|
||||
|
||||
## View hierarchy
|
||||
|
||||
@@ -252,7 +247,8 @@ fpc.contentMode = .fitToBounds
|
||||
|
||||
Otherwise, `FloatingPanelController` fixes the content by the height of the top most position.
|
||||
|
||||
:pencil2: In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
|
||||
> [!NOTE]
|
||||
> In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
|
||||
|
||||
### Customize the layout with `FloatingPanelLayout` protocol
|
||||
|
||||
@@ -350,7 +346,8 @@ class IntrinsicPanelLayout: FloatingPanelLayout {
|
||||
}
|
||||
```
|
||||
|
||||
:pencil2: `FloatingPanelIntrinsicLayout` is deprecated on v1.
|
||||
> [!WARNING]
|
||||
> `FloatingPanelIntrinsicLayout` is deprecated on v1.
|
||||
|
||||
#### Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame
|
||||
|
||||
@@ -367,7 +364,8 @@ class MyFullScreenLayout: FloatingPanelLayout {
|
||||
}
|
||||
```
|
||||
|
||||
:pencil2: `FloatingPanelFullScreenLayout` is deprecated on v1.
|
||||
> [!WARNING]
|
||||
> `FloatingPanelFullScreenLayout` is deprecated on v1.
|
||||
|
||||
#### Change the backdrop alpha
|
||||
|
||||
@@ -425,13 +423,14 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
class CustomPanelBehavior: FloatingPanelBehavior {
|
||||
let springDecelerationRate = UIScrollView.DecelerationRate.fast.rawValue + 0.02
|
||||
let springResponseTime = 0.4
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:pencil2: `floatingPanel(_ vc:behaviorFor:)` is deprecated on v1.
|
||||
> [!WARNING]
|
||||
> `floatingPanel(_ vc:behaviorFor:)` is deprecated on v1.
|
||||
|
||||
#### Activate the rubber-band effect on panel edges
|
||||
|
||||
@@ -451,7 +450,7 @@ This allows full projectional panel behavior. For example, a user can swipe up a
|
||||
```swift
|
||||
class MyPanelBehavior: FloatingPanelBehavior {
|
||||
...
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelPosition) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelPosition) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -473,7 +472,8 @@ func floatingPanelDidMove(_ vc: FloatingPanelController) {
|
||||
}
|
||||
```
|
||||
|
||||
:pencil2: `{top,bottom}InteractionBuffer` property is removed from `FloatingPanelLayout` since v2.
|
||||
> [!WARNING]
|
||||
> `{top,bottom}InteractionBuffer` property is removed from `FloatingPanelLayout` since v2.
|
||||
|
||||
### Customize the surface design
|
||||
|
||||
@@ -514,7 +514,8 @@ fpc.surfaceView.grabberHandlePadding = 10.0
|
||||
fpc.surfaceView.grabberHandleSize = .init(width: 44.0, height: 12.0)
|
||||
```
|
||||
|
||||
:pencil2: Note that `grabberHandleSize` width and height are reversed in the left/right position.
|
||||
> [!NOTE]
|
||||
> `grabberHandleSize` width and height are reversed in the left/right position.
|
||||
|
||||
#### Customize content padding from surface edges
|
||||
|
||||
@@ -663,6 +664,24 @@ The tap-to-dismiss action is disabled by default. So it needs to be enabled as b
|
||||
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
|
||||
```
|
||||
|
||||
### Allow to scroll content of the tracking scroll view in addition to the most expanded state
|
||||
|
||||
Just define conditions to allow content scrolling in `floatingPanel(:_:shouldAllowToScroll:in)` delegate method. If the returned value is true, the scroll content scrolls when its scroll position is not at the top of the content.
|
||||
|
||||
```swift
|
||||
class MyViewController: FloatingPanelControllerDelegate {
|
||||
...
|
||||
|
||||
func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll trackingScrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool {
|
||||
return state == .full || state == .half
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
### 'Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller
|
||||
@@ -703,21 +722,6 @@ It's a great way to decouple between a floating panel and the content VC.
|
||||
|
||||
Because `UISearchController` automatically presents itself modally when a user interacts with the search bar, and then it swaps the superview of the search bar to the view managed by itself while it displays. As a result, `FloatingPanelController` can't control the search bar when it's active, as you can see from [the screen shot](https://github.com/SCENEE/FloatingPanel/issues/248#issuecomment-521263831).
|
||||
|
||||
### FloatingPanelSurfaceView's issue on iOS 10
|
||||
|
||||
* On iOS 10, `FloatingPanelSurfaceView.cornerRadius` isn't not automatically masked with the top rounded corners because of `UIVisualEffectView` issue. See https://forums.developer.apple.com/thread/50854.
|
||||
So you need to draw top rounding corners of your content. Here is an example in Examples/Maps.
|
||||
```swift
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 10, *) {
|
||||
visualEffectView.layer.cornerRadius = 9.0
|
||||
visualEffectView.clipsToBounds = true
|
||||
}
|
||||
}
|
||||
```
|
||||
* If you sets clear color to `FloatingPanelSurfaceView.backgroundColor`, please note the bottom overflow of your content on bouncing at full position. To prevent it, you need to expand your content. For example, See Example/Maps App's Auto Layout settings of `UIVisualEffectView` in Main.storyboard.
|
||||
|
||||
## Maintainer
|
||||
|
||||
Shin Yamamoto <shin@scenee.com> | [@scenee](https://twitter.com/scenee)
|
||||
|
||||
@@ -25,12 +25,16 @@ public protocol FloatingPanelBehavior {
|
||||
@objc optional
|
||||
var momentumProjectionRate: CGFloat { get }
|
||||
|
||||
/// Asks the behavior if a panel should project a momentum of a user interaction to move the proposed position.
|
||||
/// Asks the behavior if a panel should project a momentum of a user interaction to move the
|
||||
/// proposed state.
|
||||
///
|
||||
/// The default implementation of this method returns true. This method is called for a layout to support all positions(tip, half and full).
|
||||
/// Therefore, `proposedTargetPosition` can only be `FloatingPanelState.tip` or `FloatingPanelState.full`.
|
||||
/// The default implementation of this method returns `false`. This method is called for called
|
||||
/// for all states defined by the current layout object.
|
||||
@objc optional
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool
|
||||
func shouldProjectMomentum(
|
||||
_ fpc: FloatingPanelController,
|
||||
to proposedState: FloatingPanelState
|
||||
) -> Bool
|
||||
|
||||
/// Returns the progress to redirect to the previous position.
|
||||
///
|
||||
|
||||
+119
-73
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// A set of methods implemented by the delegate of a panel controller allows the adopting delegate to respond to
|
||||
/// messages from the FloatingPanelController class and thus respond to, and in some affect, operations such as
|
||||
@@ -51,7 +52,12 @@ import UIKit
|
||||
|
||||
/// Called on finger up if the user dragged.
|
||||
///
|
||||
/// If `attract` is true, it will continue moving afterwards to a nearby state anchor.
|
||||
/// If `attract` is true, the panel continues moving towards the nearby state
|
||||
/// anchor. Otherwise, it stops at the closest state anchor.
|
||||
///
|
||||
/// - Note: If `attract` is false, ``FloatingPanelController.state`` property has
|
||||
/// already changed to the closest anchor's state by the time this delegate method
|
||||
/// is called.
|
||||
@objc optional
|
||||
func floatingPanelDidEndDragging(_ fpc: FloatingPanelController, willAttract attract: Bool)
|
||||
|
||||
@@ -90,6 +96,35 @@ import UIKit
|
||||
@objc(floatingPanel:contentOffsetForPinningScrollView:)
|
||||
optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, contentOffsetForPinning trackingScrollView: UIScrollView) -> CGPoint
|
||||
|
||||
/// Returns a Boolean value that determines whether the tracking scroll view should
|
||||
/// scroll or not
|
||||
///
|
||||
///
|
||||
/// If you return true, the scroll content scrolls when its scroll position is not
|
||||
/// at the top of the content. If the delegate doesn’t implement this method, its
|
||||
/// content can be scrolled only in the most expanded state.
|
||||
///
|
||||
/// Basically, the decision to scroll is based on the `state` property like the
|
||||
/// following code.
|
||||
/// ```swift
|
||||
/// func floatingPanel(
|
||||
/// _ fpc: FloatingPanelController,
|
||||
/// shouldAllowToScroll scrollView: UIScrollView,
|
||||
/// in state: FloatingPanelState
|
||||
/// ) -> Bool {
|
||||
/// return state == .full || state == .half
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Attention: It is recommended that this method always returns the most expanded state(i.e.
|
||||
/// .full). If it excludes the state, the panel might do unexpected behaviors.
|
||||
@objc(floatingPanel:shouldAllowToScroll:in:)
|
||||
optional func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll scrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool
|
||||
}
|
||||
|
||||
///
|
||||
@@ -114,7 +149,7 @@ open class FloatingPanelController: UIViewController {
|
||||
}
|
||||
|
||||
/// The delegate of a panel controller object.
|
||||
@objc
|
||||
@objc
|
||||
public weak var delegate: FloatingPanelControllerDelegate?{
|
||||
didSet{
|
||||
didUpdateDelegate()
|
||||
@@ -167,7 +202,8 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_layout = newValue
|
||||
if let parent = parent, let layout = newValue as? UIViewController, layout == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the layout object. Don't let the parent adopt FloatingPanelLayout.")
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its layout object. Don't allow the parent to adopt FloatingPanelLayout."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +215,8 @@ open class FloatingPanelController: UIViewController {
|
||||
set {
|
||||
_behavior = newValue
|
||||
if let parent = parent, let behavior = newValue as? UIViewController, behavior == parent {
|
||||
log.warning("A memory leak will occur by a retain cycle because \(self) owns the parent view controller(\(parent)) as the behavior object. Don't let the parent adopt FloatingPanelBehavior.")
|
||||
let log = "Warning: A memory leak occurs due to a retain cycle, as \(self) owns the parent view controller(\(parent)) as its behavior object. Don't allow the parent to adopt FloatingPanelBehavior."
|
||||
os_log(msg, log: sysLog, type: .error, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,7 +230,7 @@ open class FloatingPanelController: UIViewController {
|
||||
/// The behavior for determining the adjusted content offsets.
|
||||
///
|
||||
/// This property specifies how the content area of the tracking scroll view is modified using ``adjustedContentInsets``. The default value of this property is FloatingPanelController.ContentInsetAdjustmentBehavior.always.
|
||||
@objc
|
||||
@objc
|
||||
public var contentInsetAdjustmentBehavior: ContentInsetAdjustmentBehavior = .always
|
||||
|
||||
/// A Boolean value that determines whether the removal interaction is enabled.
|
||||
@@ -213,7 +250,7 @@ open class FloatingPanelController: UIViewController {
|
||||
/// The NearbyState determines that finger's nearby state.
|
||||
public var nearbyState: FloatingPanelState {
|
||||
let currentY = surfaceLocation.y
|
||||
return floatingPanel.targetPosition(from: currentY, with: .zero)
|
||||
return floatingPanel.targetState(from: currentY, with: .zero)
|
||||
}
|
||||
|
||||
/// Constants that define how a panel content fills in the surface.
|
||||
@@ -251,8 +288,6 @@ open class FloatingPanelController: UIViewController {
|
||||
}
|
||||
|
||||
private func setUp() {
|
||||
_ = FloatingPanelController.dismissSwizzling
|
||||
|
||||
modalPresentationStyle = .custom
|
||||
transitioningDelegate = modalTransition
|
||||
|
||||
@@ -273,7 +308,7 @@ open class FloatingPanelController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK:- Overrides
|
||||
// MARK: - Overrides
|
||||
|
||||
/// Creates the view that the controller manages.
|
||||
open override func loadView() {
|
||||
@@ -293,19 +328,19 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
open override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {
|
||||
// Ensure to update the static constraint of a panel after rotating a device in static mode
|
||||
if contentMode == .static {
|
||||
floatingPanel.layoutAdapter.updateStaticConstraint()
|
||||
}
|
||||
} else {
|
||||
// Because {top,bottom}LayoutGuide is managed as a view
|
||||
if floatingPanel.isAttracting == false {
|
||||
self.update(safeAreaInsets: fp_safeAreaInsets)
|
||||
}
|
||||
// Ensure to update the static constraint of a panel after rotating a device in static mode
|
||||
if contentMode == .static {
|
||||
floatingPanel.layoutAdapter.updateStaticConstraint()
|
||||
}
|
||||
}
|
||||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
// Need to call this method just after the view appears, as the safe area is not
|
||||
// correctly set before this time, for example, `show(animated:completion:)`.
|
||||
floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
}
|
||||
|
||||
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
@@ -344,7 +379,8 @@ open class FloatingPanelController: UIViewController {
|
||||
safeAreaInsetsObservation = nil
|
||||
}
|
||||
|
||||
// MARK:- Child view controller to consult
|
||||
// MARK: - Child view controller to consult
|
||||
|
||||
open override var childForStatusBarStyle: UIViewController? {
|
||||
return contentViewController
|
||||
}
|
||||
@@ -361,21 +397,21 @@ open class FloatingPanelController: UIViewController {
|
||||
return contentViewController
|
||||
}
|
||||
|
||||
// MARK:- Privates
|
||||
// MARK: - Privates
|
||||
|
||||
private func shouldUpdateLayout(from previous: UITraitCollection, to new: UITraitCollection) -> Bool {
|
||||
return previous.horizontalSizeClass != new.horizontalSizeClass
|
||||
|| previous.verticalSizeClass != new.verticalSizeClass
|
||||
|| previous.preferredContentSizeCategory != new.preferredContentSizeCategory
|
||||
|| previous.layoutDirection != new.layoutDirection
|
||||
|| previous.verticalSizeClass != new.verticalSizeClass
|
||||
|| previous.preferredContentSizeCategory != new.preferredContentSizeCategory
|
||||
|| previous.layoutDirection != new.layoutDirection
|
||||
}
|
||||
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
guard
|
||||
preSafeAreaInsets != safeAreaInsets
|
||||
else { return }
|
||||
else { return }
|
||||
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
os_log(msg, log: devLog, type: .debug, "Update safeAreaInsets = \(safeAreaInsets)")
|
||||
|
||||
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
|
||||
preSafeAreaInsets = safeAreaInsets
|
||||
@@ -387,6 +423,7 @@ open class FloatingPanelController: UIViewController {
|
||||
}
|
||||
|
||||
floatingPanel.layoutAdapter.updateStaticConstraint()
|
||||
floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
|
||||
if let contentOffset = contentOffset {
|
||||
trackingScrollView?.contentOffset = contentOffset
|
||||
@@ -414,8 +451,15 @@ open class FloatingPanelController: UIViewController {
|
||||
guard let self = self else { return }
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
} else {
|
||||
} else if parent != nil {
|
||||
removePanelFromParent(animated: true)
|
||||
} else {
|
||||
delegate?.floatingPanelWillRemove?(self)
|
||||
hide(animated: true) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.view.removeFromSuperview()
|
||||
self.delegate?.floatingPanelDidRemove?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,28 +474,23 @@ open class FloatingPanelController: UIViewController {
|
||||
// Must apply the current layout here
|
||||
activateLayout(forceLayout: true)
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
// Must track the safeAreaInsets of `self.view` to update the layout.
|
||||
// There are 2 reasons.
|
||||
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
safeAreaInsetsObservation = self.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
|
||||
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
|
||||
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
|
||||
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
|
||||
// Must track the safeAreaInsets of `self.view` to update the layout.
|
||||
// There are 2 reasons.
|
||||
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
safeAreaInsetsObservation = self.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
|
||||
// Use `self.view.safeAreaInsets` because `change.newValue` can be nil in particular case when
|
||||
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
|
||||
guard let self = self, change.oldValue != self.view.safeAreaInsets else { return }
|
||||
|
||||
// Sometimes the bounding rectangle of the controlled view becomes invalid when the screen is rotated.
|
||||
// This results in its safeAreaInsets change. In that case, `self.update(safeAreaInsets:)` leads
|
||||
// an unsatisfied constraints error. So this method should not be called with those bounds.
|
||||
guard self.view.bounds.height > 0 && self.view.bounds.width > 0 else { return }
|
||||
// Sometimes the bounding rectangle of the controlled view becomes invalid when the screen is rotated.
|
||||
// This results in its safeAreaInsets change. In that case, `self.update(safeAreaInsets:)` leads
|
||||
// an unsatisfied constraints error. So this method should not be called with those bounds.
|
||||
guard self.view.bounds.height > 0 && self.view.bounds.width > 0 else { return }
|
||||
|
||||
self.update(safeAreaInsets: self.view.safeAreaInsets)
|
||||
}
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
// Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()`
|
||||
self.update(safeAreaInsets: self.view.safeAreaInsets)
|
||||
}
|
||||
|
||||
move(to: floatingPanel.layoutAdapter.initialState,
|
||||
@@ -476,7 +515,7 @@ open class FloatingPanelController: UIViewController {
|
||||
@objc(addPanelToParent:at:animated:completion:)
|
||||
public func addPanel(toParent parent: UIViewController, at viewIndex: Int = -1, animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
guard self.parent == nil else {
|
||||
log.warning("Already added to a parent(\(parent))")
|
||||
os_log(msg, log: sysLog, type: .error, "Warning: already added to a parent(\(parent))")
|
||||
return
|
||||
}
|
||||
assert((parent is UINavigationController) == false, "UINavigationController displays only one child view controller at a time.")
|
||||
@@ -500,7 +539,7 @@ open class FloatingPanelController: UIViewController {
|
||||
self.view.leftAnchor.constraint(equalTo: parent.view.leftAnchor, constant: 0.0),
|
||||
self.view.rightAnchor.constraint(equalTo: parent.view.rightAnchor, constant: 0.0),
|
||||
self.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0),
|
||||
])
|
||||
])
|
||||
|
||||
show(animated: animated) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
@@ -583,18 +622,12 @@ open class FloatingPanelController: UIViewController {
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
if #available(iOS 11.0, *) {
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
} else {
|
||||
children.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
}
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// [Experimental] Allows the panel to move as its tracking scroll view bounces.
|
||||
///
|
||||
/// This method must be called in the delegate method, `UIScrollViewDelegate.scrollViewDidScroll(_:)`,
|
||||
@@ -660,6 +693,18 @@ open class FloatingPanelController: UIViewController {
|
||||
get { floatingPanel.layoutAdapter.surfaceLocation }
|
||||
set { floatingPanel.layoutAdapter.surfaceLocation = newValue }
|
||||
}
|
||||
|
||||
|
||||
/// Calling this will allow to invoke `removePanelFromParent(animated:completion:)` as needed by
|
||||
/// calling UIViewController's `dismiss` method
|
||||
///
|
||||
/// Previously, until v2.8, this was the default behavior. However, from v2.9 onwards, due to
|
||||
/// identified issues when used in conjunction with other libraries, it has been made an opt-in
|
||||
/// feature.
|
||||
@objc
|
||||
public static func enableDismissToRemove() {
|
||||
_ = FloatingPanelController.dismissSwizzling
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatingPanelController {
|
||||
@@ -692,30 +737,31 @@ extension FloatingPanelController {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Swizzling
|
||||
|
||||
private var originalDismissImp: IMP?
|
||||
private typealias DismissFunction = @convention(c) (AnyObject, Selector, Bool, (() -> Void)?) -> Void
|
||||
extension FloatingPanelController {
|
||||
private static let dismissSwizzling: Void = {
|
||||
guard originalDismissImp == nil else { return }
|
||||
let aClass: AnyClass! = UIViewController.self //object_getClass(vc)
|
||||
if let imp = class_getMethodImplementation(aClass, #selector(dismiss(animated:completion:))),
|
||||
let originalAltMethod = class_getInstanceMethod(aClass, #selector(fp_original_dismiss(animated:completion:))) {
|
||||
method_setImplementation(originalAltMethod, imp)
|
||||
}
|
||||
let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:)))
|
||||
let swizzledMethod = class_getInstanceMethod(aClass, #selector(fp_dismiss(animated:completion:)))
|
||||
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
if let originalMethod = class_getInstanceMethod(aClass, #selector(dismiss(animated:completion:))),
|
||||
let swizzledImp = class_getMethodImplementation(aClass, #selector(__swizzled_dismiss(animated:completion:))) {
|
||||
originalDismissImp = method_setImplementation(originalMethod, swizzledImp)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
public extension UIViewController {
|
||||
@objc func fp_original_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
// Implementation will be replaced by IMP of self.dismiss(animated:completion:)
|
||||
}
|
||||
@objc func fp_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
extension UIViewController {
|
||||
@objc
|
||||
fileprivate func __swizzled_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
let dismissImp = unsafeBitCast(originalDismissImp, to: DismissFunction.self)
|
||||
let sel = #selector(UIViewController.dismiss(animated: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)
|
||||
dismissImp(self, sel, flag, completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
@@ -725,7 +771,7 @@ public extension UIViewController {
|
||||
if let fpc = self as? FloatingPanelController {
|
||||
// When a panel is presented modally and it's not a child view controller of the presented view controller.
|
||||
if fpc.presentingViewController != nil, fpc.parent == nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
dismissImp(self, sel, flag, completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
@@ -733,6 +779,6 @@ public extension UIViewController {
|
||||
}
|
||||
|
||||
// For other view controllers
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
dismissImp(self, sel, flag, completion)
|
||||
}
|
||||
}
|
||||
|
||||
+343
-202
File diff suppressed because it is too large
Load Diff
+14
-65
@@ -7,10 +7,12 @@ import UIKit
|
||||
extension CGFloat {
|
||||
/// Returns this value rounded to an logical pixel value by a display scale
|
||||
func rounded(by displayScale: CGFloat) -> CGFloat {
|
||||
return (self * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
|
||||
let p = CGFloat(1.0e9)
|
||||
let v = (self * p).rounded(.towardZero) / p
|
||||
return (v * displayScale).rounded(.toNearestOrAwayFromZero) / displayScale
|
||||
}
|
||||
func isEqual(to: CGFloat, on displayScale: CGFloat) -> Bool {
|
||||
return self.rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
return rounded(by: displayScale) == to.rounded(by: displayScale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,65 +47,16 @@ protocol LayoutGuideProvider {
|
||||
extension UILayoutGuide: LayoutGuideProvider {}
|
||||
extension UIView: LayoutGuideProvider {}
|
||||
|
||||
private class CustomLayoutGuide: LayoutGuideProvider {
|
||||
let topAnchor: NSLayoutYAxisAnchor
|
||||
let leftAnchor: NSLayoutXAxisAnchor
|
||||
let bottomAnchor: NSLayoutYAxisAnchor
|
||||
let rightAnchor: NSLayoutXAxisAnchor
|
||||
let widthAnchor: NSLayoutDimension
|
||||
let heightAnchor: NSLayoutDimension
|
||||
init(topAnchor: NSLayoutYAxisAnchor,
|
||||
leftAnchor: NSLayoutXAxisAnchor,
|
||||
bottomAnchor: NSLayoutYAxisAnchor,
|
||||
rightAnchor: NSLayoutXAxisAnchor,
|
||||
widthAnchor: NSLayoutDimension,
|
||||
heightAnchor: NSLayoutDimension) {
|
||||
self.topAnchor = topAnchor
|
||||
self.leftAnchor = leftAnchor
|
||||
self.bottomAnchor = bottomAnchor
|
||||
self.rightAnchor = rightAnchor
|
||||
self.widthAnchor = widthAnchor
|
||||
self.heightAnchor = heightAnchor
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
/// The proxy property to be used in `LayoutAdapter`
|
||||
///
|
||||
/// This property is to allow the safe area inset to change in unit testing
|
||||
@objc var fp_safeAreaInsets: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets(top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
var fp_safeAreaLayoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view!.safeAreaLayoutGuide
|
||||
} else {
|
||||
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
|
||||
leftAnchor: view.leftAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor,
|
||||
rightAnchor: view.rightAnchor,
|
||||
widthAnchor: view.widthAnchor,
|
||||
heightAnchor: topLayoutGuide.bottomAnchor.anchorWithOffset(to: bottomLayoutGuide.topAnchor))
|
||||
}
|
||||
return view.safeAreaInsets
|
||||
}
|
||||
}
|
||||
|
||||
// The reason why UIView has no extensions of safe area insets and top/bottom guides
|
||||
// is for iOS10 compatibility.
|
||||
extension UIView {
|
||||
var fp_safeAreaLayoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return safeAreaLayoutGuide
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
var presentationFrame: CGRect {
|
||||
return layer.presentation()?.frame ?? frame
|
||||
}
|
||||
@@ -139,7 +92,7 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
#if __FP_LOG
|
||||
#if FP_LOG
|
||||
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
@@ -157,18 +110,14 @@ extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
|
||||
extension UIScrollView {
|
||||
var isLocked: Bool {
|
||||
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
|
||||
}
|
||||
var isLooselyLocked: Bool {
|
||||
return !showsVerticalScrollIndicator && isDirectionalLockEnabled
|
||||
}
|
||||
var fp_contentInset: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return adjustedContentInset
|
||||
} else {
|
||||
return contentInset
|
||||
}
|
||||
}
|
||||
var fp_contentOffsetMax: CGPoint {
|
||||
return CGPoint(x: max((contentSize.width + fp_contentInset.right) - bounds.width, 0.0),
|
||||
y: max((contentSize.height + fp_contentInset.bottom) - bounds.height, 0.0))
|
||||
return CGPoint(x: max((contentSize.width + adjustedContentInset.right) - bounds.width, 0.0),
|
||||
y: max((contentSize.height + adjustedContentInset.bottom) - bounds.height, 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.6.2</string>
|
||||
<string>2.8.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
+27
-19
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An interface for generating layout information for a panel.
|
||||
@objc public protocol FloatingPanelLayout {
|
||||
@@ -44,8 +45,8 @@ open class FloatingPanelBottomLayout: NSObject, FloatingPanelLayout {
|
||||
|
||||
open func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.fp_safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: view.fp_safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -265,12 +266,25 @@ class LayoutAdapter {
|
||||
}
|
||||
|
||||
var offsetFromMostExpandedAnchor: CGFloat {
|
||||
return offset(from: mostExpandedState)
|
||||
}
|
||||
|
||||
/// The distance from the given state position to the current surface location.
|
||||
///
|
||||
/// If the returned value is positive, it indicates that the surface is moving from
|
||||
/// the given state position to closer to the `hidden` state position. In other
|
||||
/// words, the surface is within the given state position. Otherwise, it indicates
|
||||
/// that the surface is outside this position and is moving away from the `hidden`
|
||||
/// state position.
|
||||
func offset(from state: FloatingPanelState) -> CGFloat {
|
||||
let offset: CGFloat
|
||||
switch position {
|
||||
case .top, .left:
|
||||
return edgePosition(surfaceView.presentationFrame) - position(for: mostExpandedState)
|
||||
offset = position(for: state) - edgePosition(surfaceView.frame)
|
||||
case .bottom, .right:
|
||||
return position(for: mostExpandedState) - edgePosition(surfaceView.presentationFrame)
|
||||
offset = edgePosition(surfaceView.frame) - position(for: state)
|
||||
}
|
||||
return offset.rounded(by: surfaceView.fp_displayScale)
|
||||
}
|
||||
|
||||
private var hiddenAnchor: FloatingPanelLayoutAnchoring {
|
||||
@@ -421,13 +435,13 @@ class LayoutAdapter {
|
||||
switch position {
|
||||
case .top, .bottom:
|
||||
surfaceConstraints = [
|
||||
surfaceView.leftAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
surfaceView.leftAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.leftAnchor, constant: 0.0),
|
||||
surfaceView.rightAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.rightAnchor, constant: 0.0),
|
||||
]
|
||||
case .left, .right:
|
||||
surfaceConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.topAnchor, constant: 0.0),
|
||||
surfaceView.bottomAnchor.constraint(equalTo: vc.fp_safeAreaLayoutGuide.bottomAnchor, constant: 0.0),
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.topAnchor, constant: 0.0),
|
||||
surfaceView.bottomAnchor.constraint(equalTo: vc.view.safeAreaLayoutGuide.bottomAnchor, constant: 0.0),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -535,7 +549,7 @@ class LayoutAdapter {
|
||||
let layoutGuideProvider: LayoutGuideProvider
|
||||
switch anchor.referenceGuide {
|
||||
case .safeArea:
|
||||
layoutGuideProvider = vc.fp_safeAreaLayoutGuide
|
||||
layoutGuideProvider = vc.view.safeAreaLayoutGuide
|
||||
case .superview:
|
||||
layoutGuideProvider = vc.view
|
||||
}
|
||||
@@ -710,7 +724,7 @@ class LayoutAdapter {
|
||||
|
||||
func updateInteractiveEdgeConstraint(diff: CGFloat, scrollingContent: Bool, allowsRubberBanding: (UIRectEdge) -> Bool) {
|
||||
defer {
|
||||
log.debug("update surface location = \(surfaceLocation)")
|
||||
os_log(msg, log: devLog, type: .debug, "update surface location = \(surfaceLocation)")
|
||||
}
|
||||
|
||||
let minConst: CGFloat = position(for: leastCoordinateState)
|
||||
@@ -750,9 +764,9 @@ class LayoutAdapter {
|
||||
defer {
|
||||
if forceLayout {
|
||||
layoutSurfaceIfNeeded()
|
||||
log.debug("activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state) -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
|
||||
} else {
|
||||
log.debug("activateLayout for \(state)")
|
||||
os_log(msg, log: devLog, type: .debug, "activateLayout for \(state)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,12 +782,6 @@ class LayoutAdapter {
|
||||
NSLayoutConstraint.activate(constraint: self.fitToBoundsConstraint)
|
||||
}
|
||||
|
||||
var state = state
|
||||
|
||||
if validStates.contains(state) == false {
|
||||
state = layout.initialState
|
||||
}
|
||||
|
||||
// Recalculate the intrinsic size of a content view. This is because
|
||||
// UIView.systemLayoutSizeFitting() returns a different size between an
|
||||
// on-screen and off-screen view which includes
|
||||
@@ -787,7 +795,7 @@ class LayoutAdapter {
|
||||
if let constraints = stateConstraints[state] {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
} else {
|
||||
log.error("Couldn't find any constraints for \(state)")
|
||||
os_log(msg, log: sysLog, type: .fault, "Error: can not find any constraints for \(state)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ extension FloatingPanelLayoutReferenceGuide {
|
||||
func layoutGuide(vc: UIViewController) -> LayoutGuideProvider {
|
||||
switch self {
|
||||
case .safeArea:
|
||||
return vc.fp_safeAreaLayoutGuide
|
||||
return vc.view.safeAreaLayoutGuide
|
||||
case .superview:
|
||||
return vc.view
|
||||
}
|
||||
@@ -57,7 +57,7 @@ extension FloatingPanelLayoutContentBoundingGuide {
|
||||
case .superview:
|
||||
return fpc.view
|
||||
case .safeArea:
|
||||
return fpc.fp_safeAreaLayoutGuide
|
||||
return fpc.view.safeAreaLayoutGuide
|
||||
case .none:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
// Must be a variable to use `hook` property in testing
|
||||
var log = {
|
||||
return Logger()
|
||||
}()
|
||||
|
||||
struct Logger {
|
||||
private let osLog: OSLog
|
||||
private let s = DispatchSemaphore(value: 1)
|
||||
|
||||
enum Level: Int, Comparable {
|
||||
case debug = 0
|
||||
case info = 1
|
||||
case warning = 2
|
||||
case error = 3
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug:
|
||||
return "Debug:"
|
||||
case .info:
|
||||
return "Info:"
|
||||
case .warning:
|
||||
return "Warning:"
|
||||
case .error:
|
||||
return "Error:"
|
||||
}
|
||||
}
|
||||
@available(iOS 10.0, *)
|
||||
var osLogType: OSLogType {
|
||||
switch self {
|
||||
case .debug: return .debug
|
||||
case .info: return .info
|
||||
case .warning: return .default
|
||||
case .error: return .error
|
||||
}
|
||||
}
|
||||
|
||||
static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
typealias Hook = ((String, Level) -> Void)
|
||||
var hook: Hook?
|
||||
|
||||
fileprivate init() {
|
||||
osLog = OSLog(subsystem: "com.scenee.FloatingPanel", category: "FloatingPanel")
|
||||
}
|
||||
|
||||
private func log(_ level: Level, _ message: Any, _ arguments: [Any], tag: String, function: String, line: UInt) {
|
||||
_ = s.wait(timeout: .now() + 0.033)
|
||||
defer { s.signal() }
|
||||
|
||||
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
|
||||
let _tag = tag.isEmpty ? "" : "\(tag):"
|
||||
let log: String = {
|
||||
switch level {
|
||||
case .debug:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage) (\(function):\(line))"
|
||||
default:
|
||||
return "\(level.displayName)\(_tag) \(message) \(extraMessage)"
|
||||
}
|
||||
}()
|
||||
|
||||
hook?(log, level)
|
||||
|
||||
os_log("%{public}@", log: osLog, type: level.osLogType, log)
|
||||
}
|
||||
|
||||
private func getPrettyFunction(_ function: String, _ file: String) -> String {
|
||||
if let filename = file.split(separator: "/").last {
|
||||
return filename + ":" + function
|
||||
} else {
|
||||
return file + ":" + function
|
||||
}
|
||||
}
|
||||
|
||||
func debug(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
#if __FP_LOG
|
||||
self.log(.debug, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
#endif
|
||||
}
|
||||
|
||||
func info(_ log: Any, _ arguments: Any..., tag: String = "", function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.info, log, arguments, tag: tag, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func warning(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.warning, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.error, log, arguments, tag: "", function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import os.log
|
||||
|
||||
let msg = StaticString("%{public}@")
|
||||
let sysLog = OSLog(subsystem: Logging.subsystem, category: Logging.category)
|
||||
#if FP_LOG
|
||||
let devLog = OSLog(subsystem: Logging.subsystem, category: "\(Logging.category):dev")
|
||||
#else
|
||||
let devLog = OSLog.disabled
|
||||
#endif
|
||||
|
||||
struct Logging {
|
||||
static let subsystem = "com.scenee.FloatingPanel"
|
||||
static let category = "FloatingPanel"
|
||||
private init() {}
|
||||
}
|
||||
+18
-22
@@ -1,6 +1,7 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
/// An object for customizing the appearance of a surface view
|
||||
@objc(FloatingPanelSurfaceAppearance)
|
||||
@@ -318,7 +319,7 @@ public class SurfaceView: UIView {
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
log.debug("surface view frame = \(frame)")
|
||||
os_log(msg, log: devLog, type: .debug, "surface view frame = \(frame)")
|
||||
|
||||
containerView.backgroundColor = appearance.backgroundColor
|
||||
|
||||
@@ -382,29 +383,24 @@ public class SurfaceView: UIView {
|
||||
}
|
||||
containerView.layer.masksToBounds = true
|
||||
if position.inset(containerMargins) != 0 {
|
||||
if #available(iOS 11, *) {
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,
|
||||
.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
}
|
||||
containerView.layer.maskedCorners = [
|
||||
.layerMinXMinYCorner, .layerMaxXMinYCorner,
|
||||
.layerMinXMaxYCorner, .layerMaxXMaxYCorner
|
||||
]
|
||||
return
|
||||
}
|
||||
if #available(iOS 11, *) {
|
||||
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
|
||||
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
|
||||
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
|
||||
switch position {
|
||||
case .top:
|
||||
containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
case .left:
|
||||
containerView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
case .bottom:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
case .right:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
}
|
||||
} else {
|
||||
// Can't use `containerView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user should display rounding corners appropriately.
|
||||
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
|
||||
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
|
||||
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
|
||||
switch position {
|
||||
case .top:
|
||||
containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
case .left:
|
||||
containerView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
case .bottom:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
case .right:
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+53
-12
@@ -1,5 +1,6 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import OSLog
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
@@ -7,19 +8,25 @@ class ControllerTests: XCTestCase {
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_warningRetainCycle() {
|
||||
let exp = expectation(description: "Warning retain cycle")
|
||||
exp.expectedFulfillmentCount = 2 // For layout & behavior logs
|
||||
log.hook = {(log, level) in
|
||||
if log.contains("A memory leak will occur by a retain cycle because") {
|
||||
XCTAssert(level == .warning)
|
||||
exp.fulfill()
|
||||
}
|
||||
#if swift(>=5.5) // Avoid the 'No exact matches in call to initializer' build failure for OSLogStore when running this test case on iOS 13.7 using Xcode 12.5.1
|
||||
func test_warningRetainCycle() throws {
|
||||
guard #available(iOS 15.0, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 15 or later")
|
||||
}
|
||||
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
|
||||
myVC.loadViewIfNeeded()
|
||||
wait(for: [exp], timeout: 10)
|
||||
let store = try OSLogStore(scope: .currentProcessIdentifier)
|
||||
let found = try store
|
||||
.getEntries(
|
||||
at: store.position(timeIntervalSinceLatestBoot: 0),
|
||||
matching: .init(format: "subsystem == '\(Logging.subsystem)'")
|
||||
)
|
||||
.contains {
|
||||
$0.composedMessage.contains("A memory leak occurs due to a retain cycle, as")
|
||||
}
|
||||
XCTAssertTrue(found)
|
||||
}
|
||||
#endif
|
||||
|
||||
func test_addPanel() {
|
||||
let rootVC = UIViewController()
|
||||
@@ -34,8 +41,13 @@ class ControllerTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.surfaceLocation.y, fpc.surfaceLocation(for: .tip).y)
|
||||
}
|
||||
|
||||
@available(iOS 12.0, *)
|
||||
func test_updateLayout_willTransition() {
|
||||
func test_updateLayout_willTransition() throws {
|
||||
guard #available(iOS 12, *) else {
|
||||
throw XCTSkip("Unsupported iOS version: this test needs iOS 12 or later")
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
throw XCTSkip("Unsupported iOS version: this test doesn't support iOS 17 or later")
|
||||
}
|
||||
class MyDelegate: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
if newCollection.userInterfaceStyle == .dark {
|
||||
@@ -318,11 +330,40 @@ class ControllerTests: XCTestCase {
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .full).y)
|
||||
fpc.move(to: .half, animated: false)
|
||||
print(1 / fpc.surfaceView.fp_displayScale)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .half).y)
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.surfaceLocation(for: .tip).y)
|
||||
}
|
||||
|
||||
func test_switching_layout() {
|
||||
final class FirstLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
final class SecondLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
let initialState: FloatingPanelState = .half
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 262, edge: .top, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.layout = FirstLayout()
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .tip, animated: false)
|
||||
|
||||
// Switch to another layout
|
||||
fpc.layout = SecondLayout()
|
||||
fpc.invalidateLayout()
|
||||
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
}
|
||||
}
|
||||
|
||||
private class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
|
||||
|
||||
+134
-33
@@ -7,27 +7,32 @@ class CoreTests: XCTestCase {
|
||||
override func setUp() {}
|
||||
override func tearDown() {}
|
||||
|
||||
func test_scrolllock() {
|
||||
func test_scrollLock() {
|
||||
let fpc = FloatingPanelController()
|
||||
|
||||
let contentVC1 = UITableViewController(nibName: nil, bundle: nil)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
fpc.set(contentViewController: contentVC1)
|
||||
fpc.track(scrollView: contentVC1.tableView)
|
||||
fpc.showForTest()
|
||||
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
|
||||
fpc.move(to: .tip, animated: false)
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
|
||||
let exp1 = expectation(description: "move to full with animation")
|
||||
fpc.move(to: .full, animated: true) {
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, true)
|
||||
exp1.fulfill()
|
||||
}
|
||||
wait(for: [exp1], timeout: 1.0)
|
||||
@@ -35,6 +40,7 @@ class CoreTests: XCTestCase {
|
||||
let exp2 = expectation(description: "move to tip with animation")
|
||||
fpc.move(to: .tip, animated: false) {
|
||||
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC1.tableView.bounces, false)
|
||||
exp2.fulfill()
|
||||
}
|
||||
wait(for: [exp2], timeout: 1.0)
|
||||
@@ -42,11 +48,13 @@ class CoreTests: XCTestCase {
|
||||
// Reset the content vc
|
||||
let contentVC2 = UITableViewController(nibName: nil, bundle: nil)
|
||||
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, true)
|
||||
XCTAssertEqual(contentVC2.tableView.bounces, true)
|
||||
fpc.set(contentViewController: contentVC2)
|
||||
fpc.track(scrollView: contentVC2.tableView)
|
||||
fpc.show(animated: false, completion: nil)
|
||||
XCTAssertEqual(fpc.state, .half)
|
||||
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, false)
|
||||
XCTAssertEqual(contentVC2.tableView.bounces, false)
|
||||
}
|
||||
|
||||
func test_getBackdropAlpha_1positions() {
|
||||
@@ -284,7 +292,7 @@ class CoreTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func test_targetPosition_1positions() {
|
||||
func test_targetState_1positions() {
|
||||
class FloatingPanelLayout1Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .full
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -301,7 +309,7 @@ class CoreTests: XCTestCase {
|
||||
let fullPos = fpc.surfaceLocation(for: .full).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full), // redirect
|
||||
@@ -312,7 +320,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_2positions() {
|
||||
func test_targetState_2positions() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .half
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -332,7 +340,7 @@ class CoreTests: XCTestCase {
|
||||
let halfPos = fpc.surfaceLocation(for: .half).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -349,7 +357,7 @@ class CoreTests: XCTestCase {
|
||||
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -367,7 +375,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_2positionsWithHidden() {
|
||||
func test_targetState_2positionsWithHidden() {
|
||||
class FloatingPanelLayout2Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -387,7 +395,7 @@ class CoreTests: XCTestCase {
|
||||
let hiddenPos = fpc.surfaceLocation(for: .hidden).y
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -404,7 +412,7 @@ class CoreTests: XCTestCase {
|
||||
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
|
||||
])
|
||||
fpc.move(to: .hidden, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
|
||||
@@ -422,7 +430,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromFull() {
|
||||
func test_targetState_3positionsFromFull() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -434,7 +442,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
@@ -466,7 +474,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromFull_bottomEdge() {
|
||||
func test_targetState_3positionsFromFull_bottomEdge() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
|
||||
@@ -478,7 +486,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
@@ -510,7 +518,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromHalf() {
|
||||
func test_targetState_3positionsFromHalf() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -522,7 +530,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .half
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
@@ -552,7 +560,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromHalf_bottomEdge() {
|
||||
func test_targetState_3positionsFromHalf_bottomEdge() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
|
||||
@@ -564,7 +572,7 @@ class CoreTests: XCTestCase {
|
||||
let tipPos = fpc.surfaceLocation(for: .tip).y
|
||||
// From .half
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
@@ -594,7 +602,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromTip() {
|
||||
func test_targetState_3positionsFromTip() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -607,7 +615,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
|
||||
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
|
||||
@@ -637,7 +645,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsFromTip_bottomEdge() {
|
||||
func test_targetState_3positionsFromTip_bottomEdge() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3PositionsBottomEdge()
|
||||
@@ -650,7 +658,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from topMostState
|
||||
(#line, tipPos - 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from topMostState
|
||||
@@ -680,7 +688,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsAllProjection() {
|
||||
func test_targetState_3positionsAllProjection() {
|
||||
let delegate = FloatingPanelTestDelegate()
|
||||
let fpc = FloatingPanelController(delegate: delegate)
|
||||
fpc.layout = FloatingPanelLayout3Positions()
|
||||
@@ -694,7 +702,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .full
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
@@ -707,7 +715,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .half
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
|
||||
@@ -716,7 +724,7 @@ class CoreTests: XCTestCase {
|
||||
|
||||
// From .tip
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
|
||||
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
|
||||
@@ -728,7 +736,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsWithHidden() {
|
||||
func test_targetState_3positionsWithHidden() {
|
||||
class FloatingPanelLayout3PositionsWithHidden: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -746,11 +754,11 @@ class CoreTests: XCTestCase {
|
||||
XCTAssertEqual(fpc.state, .hidden)
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .half),
|
||||
])
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -1000.0), .full),
|
||||
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
@@ -758,7 +766,7 @@ class CoreTests: XCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func test_targetPosition_3positionsWithHiddenWithoutFull() {
|
||||
func test_targetState_3positionsWithHiddenWithoutFull() {
|
||||
class FloatingPanelLayout3Positions: FloatingPanelLayout {
|
||||
let initialState: FloatingPanelState = .hidden
|
||||
let position: FloatingPanelPosition = .bottom
|
||||
@@ -782,7 +790,7 @@ class CoreTests: XCTestCase {
|
||||
//let hiddenPos = fpc.surfaceLocation(for: .hidden)
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
|
||||
(#line, halfPos, CGPoint(x: 0.0, y: 385.0), .tip), // projection
|
||||
@@ -798,7 +806,7 @@ class CoreTests: XCTestCase {
|
||||
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
|
||||
])
|
||||
fpc.move(to: .tip, animated: false)
|
||||
assertTargetPosition(fpc.floatingPanel, with: [
|
||||
assertTargetState(fpc.floatingPanel, with: [
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half),
|
||||
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
|
||||
@@ -812,6 +820,99 @@ class CoreTests: XCTestCase {
|
||||
fpc.showForTest()
|
||||
XCTAssertFalse(fpc.panGestureRecognizer.isEnabled)
|
||||
}
|
||||
|
||||
func test_is_scrollable() {
|
||||
class Delegate: FloatingPanelControllerDelegate {
|
||||
var shouldScroll = false
|
||||
func floatingPanel(
|
||||
_ fpc: FloatingPanelController,
|
||||
shouldAllowToScroll scrollView: UIScrollView,
|
||||
in state: FloatingPanelState
|
||||
) -> Bool {
|
||||
return shouldScroll
|
||||
}
|
||||
}
|
||||
|
||||
let fpc = FloatingPanelController()
|
||||
let scrollView = UIScrollView()
|
||||
let delegate = Delegate()
|
||||
fpc.layout = FloatingPanelBottomLayout()
|
||||
fpc.track(scrollView: scrollView)
|
||||
fpc.showForTest()
|
||||
|
||||
XCTAssertTrue(fpc.floatingPanel.isScrollable(state: .full))
|
||||
XCTAssertFalse(fpc.floatingPanel.isScrollable(state: .half))
|
||||
|
||||
fpc.delegate = delegate
|
||||
|
||||
XCTAssertFalse(fpc.floatingPanel.isScrollable(state: .full))
|
||||
XCTAssertFalse(fpc.floatingPanel.isScrollable(state: .half))
|
||||
|
||||
delegate.shouldScroll = true
|
||||
|
||||
XCTAssertTrue(fpc.floatingPanel.isScrollable(state: .full))
|
||||
XCTAssertTrue(fpc.floatingPanel.isScrollable(state: .half))
|
||||
}
|
||||
|
||||
func test_adjustScrollContentInsetIfNeeded() {
|
||||
class CustomScrollView: UIScrollView {
|
||||
var customSafeAreaInsets: UIEdgeInsets = .zero
|
||||
override var safeAreaInsets: UIEdgeInsets {
|
||||
customSafeAreaInsets
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
let scrollView = CustomScrollView()
|
||||
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 34, right: 0)
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.track(scrollView: scrollView)
|
||||
fpc.layout = FloatingPanelBottomLayout()
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.contentMode = .static
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
|
||||
let expect = 34 + (fpc.surfaceLocation(for: .half).y - fpc.surfaceLocation(for: .full).y)
|
||||
XCTAssertEqual(
|
||||
scrollView.contentInset,
|
||||
UIEdgeInsets(top: 0, left: 0, bottom: expect, right: 0)
|
||||
)
|
||||
|
||||
fpc.contentMode = .fitToBounds
|
||||
XCTAssertEqual(
|
||||
scrollView.contentInset,
|
||||
scrollView.customSafeAreaInsets
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
let scrollView = CustomScrollView()
|
||||
scrollView.customSafeAreaInsets = UIEdgeInsets(top: 91, left: 0, bottom: 0, right: 0)
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.track(scrollView: scrollView)
|
||||
fpc.layout = FloatingPanelTopPositionedLayout()
|
||||
fpc.contentInsetAdjustmentBehavior = .always
|
||||
fpc.contentMode = .static
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
fpc.floatingPanel.adjustScrollContentInsetIfNeeded()
|
||||
|
||||
let expect = 91 + (fpc.surfaceLocation(for: .full).y - fpc.surfaceLocation(for: .half).y)
|
||||
XCTAssertEqual(
|
||||
scrollView.contentInset,
|
||||
UIEdgeInsets(top: expect, left: 0, bottom: 0, right: 0)
|
||||
)
|
||||
fpc.contentMode = .fitToBounds
|
||||
XCTAssertEqual(
|
||||
scrollView.contentInset,
|
||||
scrollView.customSafeAreaInsets
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
|
||||
@@ -827,9 +928,9 @@ private class FloatingPanelLayout3PositionsBottomEdge: FloatingPanelTop2BottomTe
|
||||
}
|
||||
|
||||
private typealias TestParameter = (UInt, CGFloat, CGPoint, FloatingPanelState)
|
||||
private func assertTargetPosition(_ floatingPanel: Core, with params: [TestParameter]) {
|
||||
private func assertTargetState(_ floatingPanel: Core, with params: [TestParameter]) {
|
||||
params.forEach { (line, pos, velocity, result) in
|
||||
floatingPanel.surfaceView.frame.origin.y = pos
|
||||
XCTAssertEqual(floatingPanel.targetPosition(from: pos, with: velocity.y), result, line: line)
|
||||
XCTAssertEqual(floatingPanel.targetState(from: pos, with: velocity.y), result, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,20 @@ class ExtensionTests: XCTestCase {
|
||||
XCTAssertNotEqual(CGFloat(333.5).rounded(by: 3), 333.66666666666674)
|
||||
XCTAssertTrue(CGFloat(333.5).isEqual(to: 333.66666666666674, on: 3.0))
|
||||
}
|
||||
|
||||
func test_roundedByDisplayScale_2() {
|
||||
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 3), 0.0)
|
||||
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 3), 0.0)
|
||||
|
||||
XCTAssertEqual(CGFloat(-0.3333333333374246).rounded(by: 3), -0.3333333333333333)
|
||||
XCTAssertEqual(CGFloat(-0.3333333333074246).rounded(by: 3), -0.3333333333333333)
|
||||
XCTAssertEqual(CGFloat(0.33333333333374246).rounded(by: 3), 0.3333333333333333)
|
||||
XCTAssertEqual(CGFloat(0.33333333333074246).rounded(by: 3), 0.3333333333333333)
|
||||
|
||||
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 2), 0.0)
|
||||
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 2), 0.0)
|
||||
|
||||
XCTAssertEqual(CGFloat(-0.16666666666674246).rounded(by: 6), -0.16666666666666666)
|
||||
XCTAssertEqual(CGFloat(0.16666666666674246).rounded(by: 6), 0.16666666666666666)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2018 the FloatingPanel authors. All rights reserved. MIT license.
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
final class GestureTests: XCTestCase {
|
||||
|
||||
func test_delegateProxy_shouldRecognizeSimultaneouslyWith() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldRecognizeSimultaneouslyWith = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldRecognizeSimultaneouslyWith += 1
|
||||
return true
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
// Set a proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
|
||||
|
||||
// Check whether the default delegate method is called when the proxy delegate doesn't implement it.
|
||||
XCTAssertTrue(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldRequireFailureOf: FloatingPanelPanGestureRecognizer()
|
||||
)
|
||||
)
|
||||
|
||||
// Clear the proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRecognizeSimultaneouslyWith: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRecognizeSimultaneouslyWith, 1)
|
||||
}
|
||||
|
||||
func test_delegateProxy_shouldRequireFailureOf() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldRequireFailureOf = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldRequireFailureOf += 1
|
||||
return true
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
// Set a proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRequireFailureOf: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
|
||||
|
||||
// Clear the proxy delegate
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldRequireFailureOf: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldRequireFailureOf, 1)
|
||||
}
|
||||
|
||||
func test_delegateProxy_shouldBeRequiredToFailBy() throws {
|
||||
class GestureDelegateProxy: NSObject, UIGestureRecognizerDelegate {
|
||||
var callsOfShouldBeRequiredToFailBy = 0
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
callsOfShouldBeRequiredToFailBy += 1
|
||||
return false
|
||||
}
|
||||
}
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.showForTest()
|
||||
|
||||
let delegateProxy = GestureDelegateProxy()
|
||||
|
||||
fpc.panGestureRecognizer.delegateProxy = delegateProxy
|
||||
|
||||
_ = fpc.panGestureRecognizer.delegate!.gestureRecognizer?(
|
||||
UIGestureRecognizer(),
|
||||
shouldBeRequiredToFailBy: UIGestureRecognizer()
|
||||
)
|
||||
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 1)
|
||||
|
||||
// Check whether the delegate method of the "proxy" object is called.
|
||||
let otherPanGesture = UIPanGestureRecognizer()
|
||||
otherPanGesture.name = "_UISheetInteractionBackgroundDismissRecognizer"
|
||||
XCTAssertFalse(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldBeRequiredToFailBy: otherPanGesture
|
||||
)
|
||||
)
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
|
||||
|
||||
fpc.panGestureRecognizer.delegateProxy = nil
|
||||
|
||||
// Check whether the delegate method of the "default" object is called.
|
||||
let otherPanGesture2 = UIPanGestureRecognizer()
|
||||
otherPanGesture2.name = "_UISheetInteractionBackgroundDismissRecognizer"
|
||||
XCTAssertTrue(
|
||||
fpc.panGestureRecognizer.delegate!.gestureRecognizer!(
|
||||
fpc.panGestureRecognizer,
|
||||
shouldBeRequiredToFailBy: otherPanGesture2
|
||||
)
|
||||
)
|
||||
XCTAssertEqual(delegateProxy.callsOfShouldBeRequiredToFailBy, 2)
|
||||
}
|
||||
}
|
||||
+59
-34
@@ -411,18 +411,18 @@ class LayoutTests: XCTestCase {
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.bottomAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
@@ -440,7 +440,7 @@ class LayoutTests: XCTestCase {
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
@@ -450,7 +450,7 @@ class LayoutTests: XCTestCase {
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
@@ -459,12 +459,7 @@ class LayoutTests: XCTestCase {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
func test_layoutAnchor_bottomPosition() {
|
||||
@@ -477,9 +472,9 @@ class LayoutTests: XCTestCase {
|
||||
for prop in [
|
||||
// from top edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .top, referenceGuide: .superview),
|
||||
@@ -487,9 +482,9 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
// from bottom edge
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
result: (#line, constant: 100.0, firstAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, constant: 0.0, firstAnchor: fpc.view.bottomAnchor, secondAnchor: fpc.surfaceView.topAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(absoluteInset: 100.0, edge: .bottom, referenceGuide: .superview),
|
||||
@@ -507,7 +502,7 @@ class LayoutTests: XCTestCase {
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .top, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .top, referenceGuide: .superview),
|
||||
@@ -517,7 +512,7 @@ class LayoutTests: XCTestCase {
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.fp_safeAreaLayoutGuide.heightAnchor)),
|
||||
result: (#line, multiplier: 0.5, secondAnchor: fpc.view.safeAreaLayoutGuide.heightAnchor)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.0, edge: .bottom, referenceGuide: .superview),
|
||||
result: (#line, multiplier: 1.0, secondAnchor: nil)),
|
||||
(anchor: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview),
|
||||
@@ -526,12 +521,7 @@ class LayoutTests: XCTestCase {
|
||||
let c = prop.anchor.layoutConstraints(fpc, for: position)[0]
|
||||
XCTAssertEqual(c.multiplier, CGFloat(prop.result.multiplier), line: UInt(prop.result.0))
|
||||
XCTAssertTrue(c.firstAnchor is NSLayoutAnchor<NSLayoutDimension>, line: UInt(prop.result.0))
|
||||
// On iOS 10, `c.secondAnchor` can't be equal object to `prop.result.secondAnchor`
|
||||
// because there is no safe area on iOS 10 and `fp_safeAreaLayoutGuide` emulates it.
|
||||
if #available(iOS 11, *) {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
print(c)
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,20 +547,20 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
for prop in [
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420 - 42, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 210, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.topAnchor)),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: 420, firstAnchor: fpc.surfaceView.bottomAnchor, secondAnchor: fpc.view.topAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
@@ -607,20 +597,20 @@ class LayoutTests: XCTestCase {
|
||||
|
||||
for prop in [
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 42.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420 + 42, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
result: (#line, constant: -210, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 1.0, referenceGuide: .safeArea),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.fp_safeAreaLayoutGuide.bottomAnchor)),
|
||||
result: (#line, constant: 0, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.safeAreaLayoutGuide.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.0, referenceGuide: .superview),
|
||||
result: (#line, constant: -420, firstAnchor: fpc.surfaceView.topAnchor, secondAnchor: fpc.view.bottomAnchor)),
|
||||
(anchor: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .superview),
|
||||
@@ -634,6 +624,41 @@ class LayoutTests: XCTestCase {
|
||||
XCTAssertEqual(c.secondAnchor, prop.result.secondAnchor, line: UInt(prop.result.0))
|
||||
}
|
||||
}
|
||||
|
||||
func test_offsetFromMostExpandedAnchor() {
|
||||
do {
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.layout = FloatingPanelBottomLayout()
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor, 0.0)
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
|
||||
XCTAssertEqual(
|
||||
fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor,
|
||||
(fpc.surfaceLocation(for: .half) - fpc.surfaceLocation(for: .full)).y
|
||||
)
|
||||
}
|
||||
do {
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.layout = FloatingPanelTopPositionedLayout()
|
||||
fpc.showForTest()
|
||||
|
||||
fpc.move(to: .full, animated: false)
|
||||
|
||||
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor, 0.0)
|
||||
|
||||
fpc.move(to: .half, animated: false)
|
||||
|
||||
XCTAssertEqual(
|
||||
fpc.floatingPanel.layoutAdapter.offsetFromMostExpandedAnchor,
|
||||
-(fpc.surfaceLocation(for: .half) - fpc.surfaceLocation(for: .full)).y
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private typealias LayoutSegmentTestParameter = (UInt, pos: CGFloat, forwardY: Bool, lower: FloatingPanelState?, upper: FloatingPanelState?)
|
||||
|
||||
@@ -72,8 +72,18 @@ class FloatingPanelTop2BottomTestLayout: FloatingPanelLayout {
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelTopPositionedLayout: FloatingPanelLayout {
|
||||
let position: FloatingPanelPosition = .top
|
||||
let initialState: FloatingPanelState = .full
|
||||
let anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] = [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 88.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(absoluteInset: 216.0, edge: .top, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .top, referenceGuide: .safeArea)
|
||||
]
|
||||
}
|
||||
|
||||
class FloatingPanelProjectableBehavior: FloatingPanelBehavior {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedTargetPosition: FloatingPanelState) -> Bool {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user