Compare commits
265 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b05ea8d92 | |||
| d255e1ea4a | |||
| 6fcb817fb8 | |||
| e2ebfd01df | |||
| cf70929204 | |||
| 624e3f7553 | |||
| 3cc8538db3 | |||
| a9a65436bb | |||
| 353dabfc47 | |||
| 1bdf0f5b78 | |||
| 6696d7f71d | |||
| 59a6c7e576 | |||
| 0b0148635e | |||
| c354d8ea92 | |||
| 9562cdaccb | |||
| bcfff8a33a | |||
| f5c409ba90 | |||
| 2f23520330 | |||
| a95694cbfc | |||
| 6cfba6495f | |||
| b9f3de1c64 | |||
| c67b56e7af | |||
| bf39f07691 | |||
| a9e46f0de6 | |||
| 05478fa8fa | |||
| d123afc3f7 | |||
| b1b3c15300 | |||
| 49bae50739 | |||
| 9b5459af8e | |||
| 96d2ea57f5 | |||
| b78c5f4ece | |||
| 341522ccaa | |||
| 833628e42f | |||
| 50c1c6fdc9 | |||
| 213386e822 | |||
| 17317ed274 | |||
| 652ae8c967 | |||
| ec0e8cbdaf | |||
| c15d4c9035 | |||
| 39dfdd0ef0 | |||
| d25bc58249 | |||
| 194a197e83 | |||
| bd02f34bcf | |||
| 7d5f03bb6e | |||
| 60f41e168f | |||
| 680b16aa25 | |||
| 2394c03dca | |||
| c8f211f2bf | |||
| 9076ba8933 | |||
| 08e79bfc5c | |||
| e4808516aa | |||
| 5888104e98 | |||
| 2b8d29759a | |||
| 8e4b56ff17 | |||
| 23c5761c14 | |||
| 835ec0c3a0 | |||
| d2dce0b6f8 | |||
| 3f8628af01 | |||
| 40c6fae07c | |||
| e1185fda93 | |||
| 458ed903c5 | |||
| 4b640f4f01 | |||
| 8743c5efd0 | |||
| 9bd9d31d40 | |||
| 7e3d720720 | |||
| 7a512191ab | |||
| af767863bb | |||
| 1b233f4f87 | |||
| 3a840df79e | |||
| 6851e3b072 | |||
| 5a2b079872 | |||
| f683f987d8 | |||
| 3626621e87 | |||
| cc2d1eb002 | |||
| 5ba19bcf8b | |||
| 8391686e28 | |||
| 38327c917f | |||
| fca0f399b2 | |||
| e3b7ac0e99 | |||
| 04cd357f68 | |||
| 68f48f714d | |||
| fa586c494f | |||
| b886a0da64 | |||
| 0616aec3d2 | |||
| 5df36a6601 | |||
| 7d6f295e72 | |||
| 1c952b6dcb | |||
| 7e7c2a0fd7 | |||
| a15444d237 | |||
| 5b100f3b22 | |||
| 9fa8a48c56 | |||
| cb54a2a7e1 | |||
| ed02713ccc | |||
| 68e3fd2093 | |||
| a43f73d7b1 | |||
| 61e0c4ed0a | |||
| 53e0629b1d | |||
| 2b1d6a3d8a | |||
| 5ba1fb3d95 | |||
| 1b7c15cdb5 | |||
| 81fd85e993 | |||
| 8f4c08d5b3 | |||
| 61b6429851 | |||
| bbc6b39c08 | |||
| 3f812f4d6d | |||
| c9b15e4239 | |||
| 5fbdb3d481 | |||
| c59e1cd7fc | |||
| ce891e47da | |||
| 7160e4a42e | |||
| eba857a285 | |||
| a1dd02c780 | |||
| 87ff5d629b | |||
| 6b75523428 | |||
| 773434d4f6 | |||
| ad6dcd0314 | |||
| b9e29ad87d | |||
| 32b965ba87 | |||
| f1b315c9ea | |||
| 459fc75af3 | |||
| 9b0cd3511f | |||
| af9b988507 | |||
| 36f297c35b | |||
| ff959f71a7 | |||
| 0a4312ada6 | |||
| 5411cdc07a | |||
| a8c6fba3c1 | |||
| 11b115b47b | |||
| 22edf5ce46 | |||
| f43f7df7f3 | |||
| 3a2633d818 | |||
| 04a62bcf74 | |||
| 6c1320168c | |||
| 8657c91002 | |||
| bafe492009 | |||
| c6197ef6a3 | |||
| 1b3f16bcd5 | |||
| 28712fdeca | |||
| 0c30b68a9e | |||
| 30c4bee432 | |||
| ece9ced085 | |||
| f231105752 | |||
| 91dfc1e086 | |||
| b2c59c17aa | |||
| 10d1a920f0 | |||
| 4cb79a14fc | |||
| 402b9bd8dc | |||
| c39cc9d93b | |||
| aad56ab0a7 | |||
| fe18e493a9 | |||
| 5d14166508 | |||
| e1a745e3b5 | |||
| a0cac28ed0 | |||
| c205dc8672 | |||
| 5c0ed4cf7d | |||
| 780472a17f | |||
| 0264db3d54 | |||
| c117594669 | |||
| 5214bd8936 | |||
| a1195be08e | |||
| b69d366538 | |||
| c9de6f0dc3 | |||
| 21be693a9a | |||
| 129362fcd0 | |||
| 75e2fcc3ce | |||
| 4f56b57b0e | |||
| f9bbdf3427 | |||
| fcf200e169 | |||
| 7d668c8525 | |||
| ebd4a32bfc | |||
| 6aa739231d | |||
| 8877d32ced | |||
| 7f025ae845 | |||
| 1b2dae2135 | |||
| 6e4e9df616 | |||
| 49e868a505 | |||
| f1b70e0367 | |||
| 1d0e747578 | |||
| 2c72d07cab | |||
| 31c057f9f8 | |||
| d3033df9da | |||
| 459d82b1c6 | |||
| 85d7ca640e | |||
| c1b7f2f092 | |||
| b7a7e0d4ad | |||
| dc7f6d58f9 | |||
| a2a10bd0d3 | |||
| b54c8ee6ee | |||
| 08d275690a | |||
| 1c307f751e | |||
| 6f06a0f7fc | |||
| a095ace30e | |||
| a486f61f5f | |||
| 14e0abc240 | |||
| 32203c48bd | |||
| 9d6024f603 | |||
| a4dd4e48e7 | |||
| e6f7456a0f | |||
| fca79c9b0c | |||
| 4ad7f11e93 | |||
| 0412bdc996 | |||
| 31faeaada3 | |||
| 72539ca973 | |||
| ef94630aa1 | |||
| cb696f9992 | |||
| aa23e404e1 | |||
| 4c0749640f | |||
| ee5661f304 | |||
| ddefdc4f34 | |||
| f2a0af1646 | |||
| 7ded61c2bc | |||
| 08aabcf6dd | |||
| e19af7e67d | |||
| 62aa07e28e | |||
| 03b0bf747e | |||
| 8dc75aed55 | |||
| d5f5e99010 | |||
| 18c46d191e | |||
| e9f92430b2 | |||
| 3106865449 | |||
| 375e7a59e2 | |||
| bc4a2def42 | |||
| 8204a6cf27 | |||
| 797292dbe5 | |||
| 6e7a33b3a1 | |||
| 2bfea00c72 | |||
| 81441a724b | |||
| 82c8d8dd9a | |||
| 8751a9fe53 | |||
| 9cf561fcf1 | |||
| 332559c67d | |||
| 56557f0092 | |||
| 1e6cb7b1ad | |||
| 7f419b7e78 | |||
| 973a90e071 | |||
| 66a8ca36e4 | |||
| 3715007156 | |||
| 5158685c02 | |||
| 61d3371ea5 | |||
| 0491507e67 | |||
| fcd4ad874a | |||
| e2668fcdf2 | |||
| f3ac2b2cec | |||
| 8b84391e36 | |||
| 1b3ca347f5 | |||
| 2dced7bfbf | |||
| 8ba4ce36a1 | |||
| cf60b09225 | |||
| 427ec45d42 | |||
| ac9f8fe89c | |||
| 6817990555 | |||
| d395cde316 | |||
| 20272eccb8 | |||
| 091ae8abff | |||
| 6e87690649 | |||
| d5a1bd3859 | |||
| a1e4643a25 | |||
| 71c0450614 | |||
| d469caad69 | |||
| 5cc3d4fbfb | |||
| a8691ee3a5 | |||
| 91d7941921 | |||
| 0bc7a0953e | |||
| c60bea5952 | |||
| 4db648ad25 |
@@ -0,0 +1,27 @@
|
||||
> Please fill out this template appropriately when filing a bug report.
|
||||
>
|
||||
> Please remove this line and everything above it before submitting.
|
||||
|
||||
### Short description
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
**Code example that reproduces the issue**
|
||||
|
||||
### Environment
|
||||
|
||||
**Library version**
|
||||
|
||||
**Installation method**
|
||||
|
||||
- [ ] CocoaPods
|
||||
- [ ] Carthage
|
||||
- [ ] Git submodules
|
||||
|
||||
**iOS version(s)**
|
||||
|
||||
**Xcode version**
|
||||
@@ -22,6 +22,7 @@ xcuserdata/
|
||||
*.moved-aside
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
*.xcsettings
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
+39
-21
@@ -1,44 +1,62 @@
|
||||
language: swift
|
||||
language: objective-c
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
cache:
|
||||
directories:
|
||||
- build
|
||||
- vendor
|
||||
- /usr/local/Homebrew
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
before_cache:
|
||||
- brew cleanup
|
||||
env:
|
||||
global:
|
||||
- LANG=en_US.UTF-8
|
||||
- LC_ALL=en_US.UTF-8
|
||||
skip_cleanup: true
|
||||
jobs:
|
||||
include:
|
||||
- stage: carthage
|
||||
- stage: "Builds"
|
||||
osx_image: xcode9.4
|
||||
script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.1 clean build
|
||||
name: "Swift 4.1"
|
||||
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=4.2 clean build
|
||||
osx_image: xcode10
|
||||
name: "Swift 4.2"
|
||||
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.0 clean build
|
||||
osx_image: xcode10.2
|
||||
name: "Swift 5.0"
|
||||
|
||||
- stage: "Tests"
|
||||
osx_image: xcode10.2
|
||||
script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=10.3.1,name=iPhone SE'
|
||||
name: "iPhone SE (iOS 10.3)"
|
||||
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=11.4,name=iPhone 7'
|
||||
osx_image: xcode10.2
|
||||
name: "iPhone 7 (iOS 11.4)"
|
||||
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=12.2,name=iPhone X'
|
||||
osx_image: xcode10.2
|
||||
name: "iPhone X (iOS 12.2)"
|
||||
|
||||
- stage: Carthage
|
||||
osx_image: xcode10.2
|
||||
before_install:
|
||||
- brew update
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
script:
|
||||
- carthage build --no-skip-current
|
||||
|
||||
- stage: podspec
|
||||
osx_image: xcode10
|
||||
- stage: CocoaPods
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- pod spec lint
|
||||
- pod lib lint
|
||||
|
||||
- stage: check Maps example
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme Maps -sdk iphonesimulator clean build
|
||||
|
||||
- stage: check Stocks example
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme Stocks -sdk iphonesimulator clean build
|
||||
|
||||
- stage: check Samples example
|
||||
osx_image: xcode10
|
||||
script:
|
||||
- xcodebuild -scheme Samples -sdk iphonesimulator clean build
|
||||
- stage: Build examples
|
||||
osx_image: xcode10.2
|
||||
script: xcodebuild -scheme Maps -sdk iphonesimulator clean build
|
||||
name: "Maps"
|
||||
- script: xcodebuild -scheme Stocks -sdk iphonesimulator clean build
|
||||
osx_image: xcode10.2
|
||||
name: "Stocks"
|
||||
- script: xcodebuild -scheme Samples -sdk iphonesimulator clean build
|
||||
osx_image: xcode10.2
|
||||
name: "Samples"
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -331,7 +331,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Maps;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -21,7 +21,11 @@ class ViewController: UIViewController, MKMapViewDelegate, UISearchBarDelegate,
|
||||
|
||||
// Initialize FloatingPanelController and add the view
|
||||
fpc.surfaceView.backgroundColor = .clear
|
||||
fpc.surfaceView.cornerRadius = 9.0
|
||||
if #available(iOS 11, *) {
|
||||
fpc.surfaceView.cornerRadius = 9.0
|
||||
} else {
|
||||
fpc.surfaceView.cornerRadius = 0.0
|
||||
}
|
||||
fpc.surfaceView.shadowHidden = false
|
||||
|
||||
searchVC = storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as? SearchPanelViewController
|
||||
@@ -135,7 +139,10 @@ class SearchPanelViewController: UIViewController, UITableViewDataSource, UITabl
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var searchBar: UISearchBar!
|
||||
@IBOutlet weak var visualEffectView: UIVisualEffectView!
|
||||
|
||||
|
||||
// For iOS 10 only
|
||||
private lazy var shadowLayer: CAShapeLayer = CAShapeLayer()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.dataSource = self
|
||||
@@ -150,9 +157,24 @@ class SearchPanelViewController: UIViewController, UITableViewDataSource, UITabl
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 10, *) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 545DBA1221511E6400CA77B8 /* Build configuration list for PBXNativeTarget "Samples" */;
|
||||
buildPhases = (
|
||||
54D7209621D4DB970054A255 /* ShellScript */,
|
||||
545DB9E621511E6300CA77B8 /* Sources */,
|
||||
545DB9E721511E6300CA77B8 /* Frameworks */,
|
||||
545DB9E821511E6300CA77B8 /* Resources */,
|
||||
@@ -285,6 +286,26 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
54D7209621D4DB970054A255 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $(git rev-parse --abbrev-ref HEAD)($(git rev-parse --short HEAD))\" $SRCROOT/$INFOPLIST_FILE\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
545DB9E621511E6300CA77B8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@@ -478,7 +499,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -497,7 +518,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<device id="retina5_9" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
@@ -14,8 +14,8 @@
|
||||
<scene sceneID="Cjh-iX-VQw">
|
||||
<objects>
|
||||
<navigationController id="RoN-h0-uBD" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="hNW-5m-Omi">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="hNW-5m-Omi">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
@@ -31,22 +31,22 @@
|
||||
<objects>
|
||||
<viewController id="jF4-A0-Eq6" customClass="SampleListViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Smh-Bd-AAc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7IS-PU-x0P">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="M0G-C8-hAO" style="IBUITableViewCellStyleDefault" id="ySY-oA-g81">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ySY-oA-g81" id="sXB-nH-2g2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="M0G-C8-hAO">
|
||||
<rect key="frame" x="15" y="0.0" width="345" height="43.5"/>
|
||||
<rect key="frame" x="15" y="0.0" width="345" height="43.666666666666664"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
@@ -61,13 +61,19 @@
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="top" secondItem="Smh-Bd-AAc" secondAttribute="top" id="6yd-jv-ey3"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="leading" secondItem="TkN-Oh-wF8" secondAttribute="leading" id="Z6Y-Dc-cei"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="TkN-Oh-wF8" secondAttribute="bottom" id="fNW-DP-lhV"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="trailing" secondItem="TkN-Oh-wF8" secondAttribute="trailing" id="vfY-Rc-FOI"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="leading" secondItem="39L-Nq-qfp" secondAttribute="leading" id="Z6Y-Dc-cei"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="39L-Nq-qfp" secondAttribute="bottom" id="fNW-DP-lhV"/>
|
||||
<constraint firstItem="7IS-PU-x0P" firstAttribute="trailing" secondItem="39L-Nq-qfp" secondAttribute="trailing" id="vfY-Rc-FOI"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="TkN-Oh-wF8"/>
|
||||
<viewLayoutGuide key="safeArea" id="39L-Nq-qfp"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Samples" id="wCF-su-7up"/>
|
||||
<navigationItem key="navigationItem" title="Samples" id="wCF-su-7up">
|
||||
<barButtonItem key="rightBarButtonItem" title="Settings" id="rbH-U3-XyA">
|
||||
<connections>
|
||||
<action selector="showDebugMenu:" destination="jF4-A0-Eq6" id="j02-db-ZM5"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="7IS-PU-x0P" id="YFM-9W-eP4"/>
|
||||
</connections>
|
||||
@@ -76,22 +82,96 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="57" y="27"/>
|
||||
</scene>
|
||||
<!--Item 2-->
|
||||
<!--Settings View Controller-->
|
||||
<scene sceneID="Bd0-D2-agO">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="SettingsViewController" id="C1X-9Z-TyQ" customClass="SettingsViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="af9-Zr-Ppc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="197.33000000000001"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="n93-ZL-fmC">
|
||||
<rect key="frame" x="32" y="16" width="311" height="147.33333333333334"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version: 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WmC-Tq-NDN">
|
||||
<rect key="frame" x="118.33333333333334" y="0.0" width="74.333333333333343" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UINavigationBar" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ulg-gS-ah0">
|
||||
<rect key="frame" x="90.666666666666686" y="33" width="130" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="126" translatesAutoresizingMaskIntoConstraints="NO" id="uEf-g4-CeU">
|
||||
<rect key="frame" x="23.333333333333343" y="69.333333333333329" width="264.33333333333326" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Large Titles" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogl-S5-4tJ">
|
||||
<rect key="frame" x="0.0" y="5.3333333333333428" width="89.333333333333329" height="20.333333333333332"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="js8-Qv-lUC">
|
||||
<rect key="frame" x="215.33333333333334" y="0.0" width="51.000000000000028" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleLargeTitle:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="FJS-Ty-mCY"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="126" translatesAutoresizingMaskIntoConstraints="NO" id="ZtZ-Dz-4cC">
|
||||
<rect key="frame" x="23.333333333333343" y="116.33333333333334" width="264.66666666666663" height="31"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Translucent" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Z5i-rm-QgL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="89.666666666666671" height="31"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="s6b-j9-8Kw">
|
||||
<rect key="frame" x="215.66666666666666" y="0.0" width="50.999999999999972" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleTranslucent:" destination="C1X-9Z-TyQ" eventType="valueChanged" id="nL4-3L-9hh"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="0hr-ty-yWm" firstAttribute="bottom" secondItem="n93-ZL-fmC" secondAttribute="bottom" id="2Ey-ou-E1M"/>
|
||||
<constraint firstAttribute="trailing" secondItem="n93-ZL-fmC" secondAttribute="trailing" constant="32" id="DdZ-eB-F5s"/>
|
||||
<constraint firstItem="n93-ZL-fmC" firstAttribute="leading" secondItem="af9-Zr-Ppc" secondAttribute="leading" constant="32" id="TyK-GP-Ari"/>
|
||||
<constraint firstItem="n93-ZL-fmC" firstAttribute="top" secondItem="af9-Zr-Ppc" secondAttribute="topMargin" constant="16" id="mbC-6H-z9M"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="0hr-ty-yWm"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="197.33000000000001"/>
|
||||
<connections>
|
||||
<outlet property="largeTitlesSwicth" destination="js8-Qv-lUC" id="FOm-6k-ffi"/>
|
||||
<outlet property="translucentSwicth" destination="s6b-j9-8Kw" id="jmf-WH-bzZ"/>
|
||||
<outlet property="versionLabel" destination="WmC-Tq-NDN" id="Woh-kK-U0m"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="M9h-4V-3M0" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="708" y="-200"/>
|
||||
</scene>
|
||||
<!--Layout 2-->
|
||||
<scene sceneID="lRc-OZ-sL4">
|
||||
<objects>
|
||||
<viewController id="RpE-lI-27a" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="JER-jz-KSq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AiP-dx-mFn">
|
||||
<rect key="frame" x="163.5" y="323" width="48" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
|
||||
<rect key="frame" x="20" y="20" width="39" height="30"/>
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="RpE-lI-27a" eventType="touchUpInside" id="hj3-Xv-6Gq"/>
|
||||
@@ -101,35 +181,59 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="2Cd-km-qEk" secondAttribute="top" id="18k-sV-PgT"/>
|
||||
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerY" secondItem="JER-jz-KSq" secondAttribute="centerY" id="NUc-tM-0dN"/>
|
||||
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerX" secondItem="JER-jz-KSq" secondAttribute="centerX" id="hwP-mu-Vmz"/>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="2Cd-km-qEk" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="954-Dk-zvc" secondAttribute="top" id="18k-sV-PgT"/>
|
||||
<constraint firstItem="954-Dk-zvc" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="IvG-yp-yzI" secondAttribute="trailing" id="mpr-u5-MZu"/>
|
||||
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="954-Dk-zvc" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="2Cd-km-qEk"/>
|
||||
<viewLayoutGuide key="safeArea" id="954-Dk-zvc"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" tag="1" title="Item 2" id="qb3-RB-B28"/>
|
||||
<tabBarItem key="tabBarItem" tag="1" title="Layout 2" id="qb3-RB-B28"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="NhZ-u5-Beh" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="326" y="1575"/>
|
||||
<point key="canvasLocation" x="-308" y="1546"/>
|
||||
</scene>
|
||||
<!--Item 1-->
|
||||
<!--Layout 3-->
|
||||
<scene sceneID="r9h-Ql-gIv">
|
||||
<objects>
|
||||
<viewController id="pOk-Zm-vD9" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="85d-ub-G8k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NbG-e8-HdI">
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="pOk-Zm-vD9" eventType="touchUpInside" id="111-PD-Pop"/>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="1Rg-YG-TtU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="0ao-SI-QZW" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="NbG-e8-HdI" secondAttribute="trailing" id="K9F-6x-KWn"/>
|
||||
<constraint firstItem="NbG-e8-HdI" firstAttribute="top" secondItem="0ao-SI-QZW" secondAttribute="top" id="nsE-so-rTl"/>
|
||||
<constraint firstItem="NbG-e8-HdI" firstAttribute="leading" secondItem="0ao-SI-QZW" secondAttribute="leading" constant="20" id="sF4-Dm-aoY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="0ao-SI-QZW"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" tag="2" title="Layout 3" id="RJD-TF-Sdh"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Oe3-FT-q1C" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="332" y="1546"/>
|
||||
</scene>
|
||||
<!--Layout 1-->
|
||||
<scene sceneID="m6X-j6-yBM">
|
||||
<objects>
|
||||
<viewController id="lto-Zc-Vtp" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="ji9-Ez-N7i">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uoW-c8-9wx">
|
||||
<rect key="frame" x="164.5" y="323" width="46" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
|
||||
<rect key="frame" x="20" y="20" width="39" height="30"/>
|
||||
<rect key="frame" x="20" y="44" width="39" height="30"/>
|
||||
<state key="normal" title="Close"/>
|
||||
<connections>
|
||||
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="YL4-GP-ZEZ"/>
|
||||
@@ -139,18 +243,47 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="f88-U8-Vja" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
|
||||
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerY" secondItem="ji9-Ez-N7i" secondAttribute="centerY" id="Nyw-Wt-78z"/>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="f88-U8-Vja" secondAttribute="top" id="hUV-3a-XkY"/>
|
||||
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerX" secondItem="ji9-Ez-N7i" secondAttribute="centerX" id="wDv-OH-7PX"/>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="5Ns-4l-Ufg" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
|
||||
<constraint firstItem="5Ns-4l-Ufg" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="eFN-tN-4Ct" secondAttribute="trailing" id="OzZ-Dz-RNF"/>
|
||||
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="5Ns-4l-Ufg" secondAttribute="top" id="hUV-3a-XkY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="f88-U8-Vja"/>
|
||||
<viewLayoutGuide key="safeArea" id="5Ns-4l-Ufg"/>
|
||||
</view>
|
||||
<tabBarItem key="tabBarItem" title="Item 1" id="HEV-kf-jxH"/>
|
||||
<tabBarItem key="tabBarItem" title="Layout 1" id="HEV-kf-jxH"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bkL-bc-hZC" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-380" y="1576"/>
|
||||
<point key="canvasLocation" x="-962" y="1546"/>
|
||||
</scene>
|
||||
<!--Intrinsic View Controller-->
|
||||
<scene sceneID="wtJ-qZ-aCl">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="IntrinsicViewController" title="Intrinsic View Controller" id="aK0-kv-mTu" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="eLM-xc-d9e">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" text="Change this text" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ge4-RW-Gmz">
|
||||
<rect key="frame" x="24" y="24" width="327" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="ge4-RW-Gmz" secondAttribute="trailing" constant="24" id="V59-MD-Lcg"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="leading" secondItem="eLM-xc-d9e" secondAttribute="leading" constant="24" id="hAO-P0-7Kw"/>
|
||||
<constraint firstItem="ge4-RW-Gmz" firstAttribute="top" secondItem="eLM-xc-d9e" secondAttribute="top" constant="24" id="j0s-fd-MYj"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ge4-RW-Gmz" secondAttribute="bottom" constant="24" id="tEn-PO-nVD"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="ouu-g9-OiX"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="DfE-fL-zy5" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2753" y="734"/>
|
||||
</scene>
|
||||
<!--Tab Bar View Controller-->
|
||||
<scene sceneID="nQ5-PV-qFw">
|
||||
@@ -164,33 +297,34 @@
|
||||
<connections>
|
||||
<segue destination="lto-Zc-Vtp" kind="relationship" relationship="viewControllers" id="6hP-AH-YiH"/>
|
||||
<segue destination="RpE-lI-27a" kind="relationship" relationship="viewControllers" id="g6X-Sq-uSW"/>
|
||||
<segue destination="pOk-Zm-vD9" kind="relationship" relationship="viewControllers" id="OPp-iO-iDK"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Z9x-EI-p2b" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-183" y="806"/>
|
||||
<point key="canvasLocation" x="-706" y="749"/>
|
||||
</scene>
|
||||
<!--Modal View Controller-->
|
||||
<scene sceneID="C9P-Ns-Qrq">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vut-mK-Y4t" customClass="SafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="667" width="375" height="0.0"/>
|
||||
<rect key="frame" x="0.0" y="744" width="375" height="34"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbF-Az-7sy">
|
||||
<rect key="frame" x="20" y="20" width="39" height="30"/>
|
||||
<rect key="frame" x="20" y="44" 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="139.5" y="108" width="96" height="252"/>
|
||||
<rect key="frame" x="139.66666666666666" y="132" width="96" height="252"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i9x-x5-n1q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="30"/>
|
||||
@@ -225,35 +359,36 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="top" id="3VR-hj-zeQ"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="top" constant="88" id="41n-Fn-hi3"/>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="top" id="3VR-hj-zeQ"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="top" constant="88" id="41n-Fn-hi3"/>
|
||||
<constraint firstAttribute="bottom" secondItem="vut-mK-Y4t" secondAttribute="bottom" id="6eq-Kt-heZ"/>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="leading" secondItem="GBa-yx-8to" secondAttribute="leading" constant="20" id="T2G-1L-PRs"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="leading" secondItem="qwo-GK-p1U" secondAttribute="leading" id="gVC-jv-VJX"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="trailing" secondItem="GBa-yx-8to" secondAttribute="trailing" id="jkq-p2-lUm"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="centerX" secondItem="qwo-GK-p1U" secondAttribute="centerX" id="l8t-p3-ETf"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="top" secondItem="GBa-yx-8to" secondAttribute="bottom" id="rMy-JT-t4B"/>
|
||||
<constraint firstItem="sbF-Az-7sy" firstAttribute="leading" secondItem="kjr-TP-fcM" secondAttribute="leading" constant="20" id="T2G-1L-PRs"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="leading" secondItem="kjr-TP-fcM" secondAttribute="leading" id="gVC-jv-VJX"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="trailing" secondItem="kjr-TP-fcM" secondAttribute="trailing" id="jkq-p2-lUm"/>
|
||||
<constraint firstItem="9p4-06-y2T" firstAttribute="centerX" secondItem="kjr-TP-fcM" secondAttribute="centerX" id="l8t-p3-ETf"/>
|
||||
<constraint firstItem="vut-mK-Y4t" firstAttribute="top" secondItem="kjr-TP-fcM" secondAttribute="bottom" id="rMy-JT-t4B"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="GBa-yx-8to"/>
|
||||
<viewLayoutGuide key="safeArea" id="kjr-TP-fcM"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="safeAreaView" destination="vut-mK-Y4t" id="r9P-XF-wLd"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="fbi-LZ-M4Y" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="561" y="806"/>
|
||||
<point key="canvasLocation" x="1375" y="734"/>
|
||||
</scene>
|
||||
<!--Nested Scroll View Controller-->
|
||||
<scene sceneID="TfC-A3-4R0">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="NestedScrollViewController" id="LAe-jm-k6f" customClass="NestedScrollViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="414-Wy-0t1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sBe-tN-uMi">
|
||||
<rect key="frame" x="0.0" y="32" width="375" height="635"/>
|
||||
<rect key="frame" x="0.0" y="32" width="375" height="746"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="lFR-Sp-Sj1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="968"/>
|
||||
@@ -329,20 +464,21 @@
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="sBe-tN-uMi" firstAttribute="leading" secondItem="414-Wy-0t1" secondAttribute="leading" id="8Qd-my-knA"/>
|
||||
<constraint firstItem="sBe-tN-uMi" firstAttribute="leading" secondItem="ufS-Rf-F2F" secondAttribute="leading" id="8Qd-my-knA"/>
|
||||
<constraint firstItem="sBe-tN-uMi" firstAttribute="top" secondItem="414-Wy-0t1" secondAttribute="top" constant="32" id="9Js-LU-lNr"/>
|
||||
<constraint firstAttribute="bottom" secondItem="sBe-tN-uMi" secondAttribute="bottom" id="jzB-47-P7e"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sBe-tN-uMi" secondAttribute="trailing" id="nHG-wg-pLP"/>
|
||||
<constraint firstItem="ufS-Rf-F2F" firstAttribute="trailing" secondItem="sBe-tN-uMi" secondAttribute="trailing" id="nHG-wg-pLP"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="sL5-d5-za2"/>
|
||||
<viewLayoutGuide key="safeArea" id="ufS-Rf-F2F"/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="tOa-bf-zGz" appends="YES" id="zle-Sz-M3U"/>
|
||||
<outletCollection property="gestureRecognizers" destination="SCk-hG-weZ" appends="YES" id="OcK-FK-Lac"/>
|
||||
<outletCollection property="gestureRecognizers" destination="Fvp-Z6-eVc" appends="YES" id="Fds-J5-YCg"/>
|
||||
</connections>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="667"/>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="nestedScrollView" destination="xba-kG-VQ2" id="ddV-kf-37A"/>
|
||||
<outlet property="scrollView" destination="sBe-tN-uMi" id="h4S-Zl-cLO"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
@@ -363,7 +499,7 @@
|
||||
</connections>
|
||||
</swipeGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1311" y="806"/>
|
||||
<point key="canvasLocation" x="2097" y="734"/>
|
||||
</scene>
|
||||
<!--Detail View Controller-->
|
||||
<scene sceneID="b6k-zi-3wn">
|
||||
@@ -373,8 +509,19 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="778"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8yw-OC-Ubk">
|
||||
<rect key="frame" x="0.0" y="690" width="375" height="88"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="88" id="jwV-YU-tXG"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="700"/>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="noi-1a-5bZ" customClass="CloseButton" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="319" y="12" width="44" height="44"/>
|
||||
<rect key="frame" x="319" y="44" width="44" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="0jg-5D-A1F"/>
|
||||
<constraint firstAttribute="width" constant="44" id="1Cq-PA-wgW"/>
|
||||
@@ -383,22 +530,8 @@
|
||||
<action selector="closeWithSender:" destination="YC8-ae-15L" eventType="touchUpInside" id="Z2v-19-S5k"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8yw-OC-Ubk">
|
||||
<rect key="frame" x="0.0" y="690" width="375" height="88"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="88" id="jwV-YU-tXG"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="734" width="375" height="44"/>
|
||||
<color key="backgroundColor" red="0.0078431372550000003" green="0.72156862749999995" blue="0.45882352939999999" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="DQJ-cY-cKx"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="tP3-oJ-4EB">
|
||||
<rect key="frame" x="130.5" y="108" width="114" height="82"/>
|
||||
<rect key="frame" x="130.66666666666666" y="132" width="114" height="134"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c5r-jU-haj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="114" height="30"/>
|
||||
@@ -415,24 +548,33 @@
|
||||
<segue destination="bYI-y3-Rzb" kind="presentation" identifier="PresentModallySegue" id="3yq-HE-Tgn"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="01L-lp-oy6">
|
||||
<rect key="frame" x="0.0" y="104" width="114" height="30"/>
|
||||
<state key="normal" title="Update Layout"/>
|
||||
<connections>
|
||||
<action selector="buttonPressed:" destination="YC8-ae-15L" eventType="touchUpInside" id="zTb-sq-B6f"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<gestureRecognizers/>
|
||||
<constraints>
|
||||
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="g7l-kO-y7q" secondAttribute="top" constant="12" id="EQy-cr-F2Y"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="centerX" secondItem="g7l-kO-y7q" secondAttribute="centerX" id="EsD-Vf-dNZ"/>
|
||||
<constraint firstItem="noi-1a-5bZ" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="EQy-cr-F2Y"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="centerX" secondItem="aOK-7l-cA6" secondAttribute="centerX" id="EsD-Vf-dNZ"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" id="Fff-HL-4mo"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="bottom" secondItem="g7l-kO-y7q" secondAttribute="bottom" id="JOL-wC-w74"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="leading" secondItem="tAi-nk-rDB" secondAttribute="leading" id="RiJ-Hb-OOZ"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="trailing" secondItem="tAi-nk-rDB" secondAttribute="trailing" id="Sof-yL-mwK"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="tAi-nk-rDB" secondAttribute="top" constant="88" id="Zhb-Ss-epe"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="trailing" secondItem="tAi-nk-rDB" secondAttribute="trailing" id="kkp-Yo-FQW"/>
|
||||
<constraint firstItem="tAi-nk-rDB" firstAttribute="trailing" secondItem="noi-1a-5bZ" secondAttribute="trailing" constant="12" id="lv9-Nf-HNB"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="leading" secondItem="tAi-nk-rDB" secondAttribute="leading" id="oVC-i1-TwS"/>
|
||||
<constraint firstItem="tAi-nk-rDB" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" id="rW2-mF-5DR"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="RiJ-Hb-OOZ"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="Sof-yL-mwK"/>
|
||||
<constraint firstItem="tP3-oJ-4EB" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" constant="88" id="Zhb-Ss-epe"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="trailing" secondItem="aOK-7l-cA6" secondAttribute="trailing" id="kkp-Yo-FQW"/>
|
||||
<constraint firstItem="aOK-7l-cA6" firstAttribute="trailing" secondItem="noi-1a-5bZ" secondAttribute="trailing" constant="12" id="lv9-Nf-HNB"/>
|
||||
<constraint firstItem="Kva-Z7-0qY" firstAttribute="leading" secondItem="aOK-7l-cA6" secondAttribute="leading" id="oVC-i1-TwS"/>
|
||||
<constraint firstItem="aOK-7l-cA6" firstAttribute="bottom" secondItem="Kva-Z7-0qY" secondAttribute="bottom" id="rW2-mF-5DR"/>
|
||||
<constraint firstItem="8yw-OC-Ubk" firstAttribute="top" relation="greaterThanOrEqual" secondItem="tP3-oJ-4EB" secondAttribute="bottom" constant="88" id="vKQ-h9-uKt"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="tAi-nk-rDB"/>
|
||||
<viewLayoutGuide key="safeArea" id="aOK-7l-cA6"/>
|
||||
<connections>
|
||||
<outletCollection property="gestureRecognizers" destination="6Ca-p8-7uF" appends="YES" id="xOy-f1-NZE"/>
|
||||
<outletCollection property="gestureRecognizers" destination="SPY-Vr-XDT" appends="YES" id="vgS-Am-jhQ"/>
|
||||
@@ -462,7 +604,7 @@
|
||||
</connections>
|
||||
</pongPressGestureRecognizer>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1440.8" y="-23.388305847076463"/>
|
||||
<point key="canvasLocation" x="655" y="734"/>
|
||||
</scene>
|
||||
<!--Debug Text View Controller-->
|
||||
<scene sceneID="Bkq-O7-q4A">
|
||||
@@ -520,24 +662,26 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="ix0-2W-gQN" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="leading" secondItem="5ET-zC-lCb" secondAttribute="leading" id="7V3-KL-vXd"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rN1-HL-YHv" secondAttribute="bottom" id="efD-U5-Tet"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="top" secondItem="9YG-0j-Zzg" secondAttribute="top" constant="17" id="fiO-LL-nSC"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="ix0-2W-gQN" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
<constraint firstItem="rN1-HL-YHv" firstAttribute="trailing" secondItem="5ET-zC-lCb" secondAttribute="trailing" id="lfg-EE-euw"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="ix0-2W-gQN"/>
|
||||
<viewLayoutGuide key="safeArea" id="5ET-zC-lCb"/>
|
||||
</view>
|
||||
<size key="freeformSize" width="375" height="778"/>
|
||||
<connections>
|
||||
<outlet property="textView" destination="rN1-HL-YHv" id="gmr-Uf-jd8"/>
|
||||
<outlet property="textViewTopConstraint" destination="fiO-LL-nSC" id="Rum-TN-c2e"/>
|
||||
<outlet property="view" destination="9YG-0j-Zzg" id="jhb-eT-nEn"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="729" y="-23"/>
|
||||
<point key="canvasLocation" x="-1" y="734"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="3yq-HE-Tgn"/>
|
||||
<segue reference="r1P-2i-NDe"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
||||
@@ -5,21 +5,39 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
var layoutInsets: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return safeAreaInsets
|
||||
} else {
|
||||
return layoutMargins
|
||||
}
|
||||
}
|
||||
protocol LayoutGuideProvider {
|
||||
var topAnchor: NSLayoutYAxisAnchor { get }
|
||||
var bottomAnchor: NSLayoutYAxisAnchor { get }
|
||||
}
|
||||
extension UILayoutGuide: LayoutGuideProvider {}
|
||||
|
||||
var layoutGuide: UILayoutGuide {
|
||||
if #available(iOS 11.0, *) {
|
||||
return safeAreaLayoutGuide
|
||||
} else {
|
||||
return layoutMarginsGuide
|
||||
}
|
||||
class CustomLayoutGuide: LayoutGuideProvider {
|
||||
let topAnchor: NSLayoutYAxisAnchor
|
||||
let bottomAnchor: NSLayoutYAxisAnchor
|
||||
init(topAnchor: NSLayoutYAxisAnchor, bottomAnchor: NSLayoutYAxisAnchor) {
|
||||
self.topAnchor = topAnchor
|
||||
self.bottomAnchor = bottomAnchor
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
var layoutInsets: UIEdgeInsets {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view.safeAreaInsets
|
||||
} else {
|
||||
return UIEdgeInsets(top: topLayoutGuide.length,
|
||||
left: 0.0,
|
||||
bottom: bottomLayoutGuide.length,
|
||||
right: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
var layoutGuide: LayoutGuideProvider {
|
||||
if #available(iOS 11.0, *) {
|
||||
return view!.safeAreaLayoutGuide
|
||||
} else {
|
||||
return CustomLayoutGuide(topAnchor: topLayoutGuide.bottomAnchor,
|
||||
bottomAnchor: bottomLayoutGuide.topAnchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -312,7 +312,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -331,7 +331,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.Stocks;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -114,6 +114,7 @@ class FloatingPanelStocksLayout: FloatingPanelLayout {
|
||||
case .full: return 56.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 85.0 + 44.0 // Visible + ToolView
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "1.2.2"
|
||||
s.version = "1.6.0"
|
||||
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
|
||||
s.description = <<-DESC
|
||||
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
@@ -13,8 +13,7 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
s.platform = :ios, "10.0"
|
||||
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => "v#{s.version}" }
|
||||
s.source_files = "Framework/Sources/*.swift"
|
||||
s.swift_version = "4.2"
|
||||
s.pod_target_xcconfig = { 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
|
||||
s.swift_version = "4.0"
|
||||
|
||||
s.framework = "UIKit"
|
||||
|
||||
|
||||
@@ -7,18 +7,24 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */; };
|
||||
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
|
||||
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
|
||||
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
|
||||
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ViewTests.swift */; };
|
||||
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanelController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
545DB9D02151169500CA77B8 /* FloatingPanelControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* FloatingPanelControllerTests.swift */; };
|
||||
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = 545DB9C42151169500CA77B8 /* FloatingPanel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DD215118C800CA77B8 /* UIExtensions.swift */; };
|
||||
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */; };
|
||||
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */; };
|
||||
54A6B6B122968B530077F348 /* FloatingPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* FloatingPanelTests.swift */; };
|
||||
54A6B6B622968F710077F348 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54A6B6B522968F710077F348 /* LaunchScreen.storyboard */; };
|
||||
54A6B6B82296A8520077F348 /* FloatingPanelViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* FloatingPanelViewTests.swift */; };
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54ABD7AE216CCFF7002E6C13 /* Logger.swift */; };
|
||||
54CDC5D3215B6D5A007D205C /* FloatingPanelSurfaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */; };
|
||||
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */; };
|
||||
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */; };
|
||||
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */; };
|
||||
54E740CD218AFD67005C1A34 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E740CC218AFD67005C1A34 /* AppDelegate.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -29,24 +35,39 @@
|
||||
remoteGlobalIDString = 545DB9C02151169500CA77B8;
|
||||
remoteInfo = FloatingModalController;
|
||||
};
|
||||
54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 545DB9B82151169500CA77B8 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 54E740C9218AFD67005C1A34;
|
||||
remoteInfo = TestingHost;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTransitioning.swift; sourceTree = "<group>"; };
|
||||
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
|
||||
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
|
||||
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9C42151169500CA77B8 /* FloatingPanelController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanelController.h; sourceTree = "<group>"; };
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanel.h; sourceTree = "<group>"; };
|
||||
545DB9C52151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FloatingPanelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
545DB9CF2151169500CA77B8 /* ViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewTests.swift; sourceTree = "<group>"; };
|
||||
545DB9CF2151169500CA77B8 /* FloatingPanelControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelControllerTests.swift; sourceTree = "<group>"; };
|
||||
545DB9D12151169500CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
545DB9DD215118C800CA77B8 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
|
||||
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelController.swift; sourceTree = "<group>"; };
|
||||
545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabberHandleView.swift; sourceTree = "<group>"; };
|
||||
54A6B6B022968B530077F348 /* FloatingPanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTests.swift; sourceTree = "<group>"; };
|
||||
54A6B6B522968F710077F348 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
54A6B6B72296A8520077F348 /* FloatingPanelViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelViewTests.swift; sourceTree = "<group>"; };
|
||||
54ABD7AE216CCFF7002E6C13 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelSurfaceView.swift; sourceTree = "<group>"; };
|
||||
54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBackdropView.swift; sourceTree = "<group>"; };
|
||||
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelLayout.swift; sourceTree = "<group>"; };
|
||||
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanel.swift; sourceTree = "<group>"; };
|
||||
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FloatingPanelTesting.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
54E740CC218AFD67005C1A34 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
54E740D8218AFD6A005C1A34 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -65,6 +86,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54E740C7218AFD67005C1A34 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -73,6 +101,7 @@
|
||||
children = (
|
||||
545DB9C32151169500CA77B8 /* Sources */,
|
||||
545DB9CE2151169500CA77B8 /* Tests */,
|
||||
54E740CB218AFD67005C1A34 /* TestingApp */,
|
||||
545DB9C22151169500CA77B8 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -82,6 +111,7 @@
|
||||
children = (
|
||||
545DB9C12151169500CA77B8 /* FloatingPanel.framework */,
|
||||
545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */,
|
||||
54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -90,11 +120,13 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DB9C52151169500CA77B8 /* Info.plist */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanelController.h */,
|
||||
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
|
||||
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */,
|
||||
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */,
|
||||
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */,
|
||||
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */,
|
||||
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */,
|
||||
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */,
|
||||
54CDC5D2215B6D5A007D205C /* FloatingPanelSurfaceView.swift */,
|
||||
54CDC5D4215B6D8D007D205C /* FloatingPanelBackdropView.swift */,
|
||||
545DBA2A2152383100CA77B8 /* GrabberHandleView.swift */,
|
||||
@@ -107,12 +139,24 @@
|
||||
545DB9CE2151169500CA77B8 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
545DB9CF2151169500CA77B8 /* ViewTests.swift */,
|
||||
54A6B6B022968B530077F348 /* FloatingPanelTests.swift */,
|
||||
54A6B6B72296A8520077F348 /* FloatingPanelViewTests.swift */,
|
||||
545DB9CF2151169500CA77B8 /* FloatingPanelControllerTests.swift */,
|
||||
545DB9D12151169500CA77B8 /* Info.plist */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54E740CB218AFD67005C1A34 /* TestingApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54E740CC218AFD67005C1A34 /* AppDelegate.swift */,
|
||||
54E740D8218AFD6A005C1A34 /* Info.plist */,
|
||||
54A6B6B522968F710077F348 /* LaunchScreen.storyboard */,
|
||||
);
|
||||
path = TestingApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -120,7 +164,7 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545DB9D22151169500CA77B8 /* FloatingPanelController.h in Headers */,
|
||||
545DB9D22151169500CA77B8 /* FloatingPanel.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -157,19 +201,37 @@
|
||||
);
|
||||
dependencies = (
|
||||
545DB9CD2151169500CA77B8 /* PBXTargetDependency */,
|
||||
54E740DD218AFE9F005C1A34 /* PBXTargetDependency */,
|
||||
);
|
||||
name = FloatingPanelTests;
|
||||
productName = FloatingModalControllerTests;
|
||||
productReference = 545DB9CA2151169500CA77B8 /* FloatingPanelTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
54E740C9218AFD67005C1A34 /* TestingApp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 54E740D9218AFD6A005C1A34 /* Build configuration list for PBXNativeTarget "TestingApp" */;
|
||||
buildPhases = (
|
||||
54E740C6218AFD67005C1A34 /* Sources */,
|
||||
54E740C7218AFD67005C1A34 /* Frameworks */,
|
||||
54E740C8218AFD67005C1A34 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = TestingApp;
|
||||
productName = TestingHost;
|
||||
productReference = 54E740CA218AFD67005C1A34 /* FloatingPanelTesting.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
545DB9B82151169500CA77B8 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1000;
|
||||
LastSwiftUpdateCheck = 1010;
|
||||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = scenee;
|
||||
TargetAttributes = {
|
||||
@@ -179,6 +241,10 @@
|
||||
};
|
||||
545DB9C92151169500CA77B8 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
TestTargetID = 54E740C9218AFD67005C1A34;
|
||||
};
|
||||
54E740C9218AFD67005C1A34 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -188,6 +254,7 @@
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 545DB9B72151169500CA77B8;
|
||||
productRefGroup = 545DB9C22151169500CA77B8 /* Products */;
|
||||
@@ -196,6 +263,7 @@
|
||||
targets = (
|
||||
545DB9C02151169500CA77B8 /* FloatingPanel */,
|
||||
545DB9C92151169500CA77B8 /* FloatingPanelTests */,
|
||||
54E740C9218AFD67005C1A34 /* TestingApp */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -215,6 +283,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54E740C8218AFD67005C1A34 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54A6B6B622968F710077F348 /* LaunchScreen.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -225,11 +301,13 @@
|
||||
54CDC5D3215B6D5A007D205C /* FloatingPanelSurfaceView.swift in Sources */,
|
||||
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */,
|
||||
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */,
|
||||
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */,
|
||||
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */,
|
||||
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
|
||||
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */,
|
||||
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */,
|
||||
545DBA2B2152383100CA77B8 /* GrabberHandleView.swift in Sources */,
|
||||
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */,
|
||||
545DB9DE215118C800CA77B8 /* UIExtensions.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -238,7 +316,17 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */,
|
||||
54A6B6B122968B530077F348 /* FloatingPanelTests.swift in Sources */,
|
||||
545DB9D02151169500CA77B8 /* FloatingPanelControllerTests.swift in Sources */,
|
||||
54A6B6B82296A8520077F348 /* FloatingPanelViewTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
54E740C6218AFD67005C1A34 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54E740CD218AFD67005C1A34 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -250,6 +338,11 @@
|
||||
target = 545DB9C02151169500CA77B8 /* FloatingPanel */;
|
||||
targetProxy = 545DB9CC2151169500CA77B8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
54E740DD218AFE9F005C1A34 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 54E740C9218AFD67005C1A34 /* TestingApp */;
|
||||
targetProxy = 54E740DC218AFE9F005C1A34 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -378,6 +471,7 @@
|
||||
545DB9D62151169500CA77B8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -397,8 +491,9 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -406,6 +501,7 @@
|
||||
545DB9D72151169500CA77B8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -424,7 +520,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
@@ -434,7 +531,9 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -442,8 +541,9 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -452,7 +552,9 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -460,11 +562,177 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
54E740DA218AFD6A005C1A34 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
|
||||
PRODUCT_NAME = FloatingPanelTesting;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
54E740DB218AFD6A005C1A34 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
|
||||
PRODUCT_NAME = FloatingPanelTesting;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
54E79ADF224F6C9800717BC6 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
54E79AE0224F6C9800717BC6 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Sources/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG __FP_LOG";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
54E79AE1224F6C9800717BC6 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingModalControllerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FloatingPanelTesting.app/FloatingPanelTesting";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
54E8AC6A2286CFB6000C5A12 /* Test */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = TestingApp/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanelTesting;
|
||||
PRODUCT_NAME = FloatingPanelTesting;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -472,6 +740,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DB9D32151169500CA77B8 /* Debug */,
|
||||
54E79ADF224F6C9800717BC6 /* Test */,
|
||||
545DB9D42151169500CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
@@ -481,6 +750,7 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DB9D62151169500CA77B8 /* Debug */,
|
||||
54E79AE0224F6C9800717BC6 /* Test */,
|
||||
545DB9D72151169500CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
@@ -490,11 +760,22 @@
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
545DB9D92151169500CA77B8 /* Debug */,
|
||||
54E79AE1224F6C9800717BC6 /* Test */,
|
||||
545DB9DA2151169500CA77B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
54E740D9218AFD6A005C1A34 /* Build configuration list for PBXNativeTarget "TestingApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
54E740DA218AFD6A005C1A34 /* Debug */,
|
||||
54E740DB218AFD6A005C1A34 /* Release */,
|
||||
54E8AC6A2286CFB6000C5A12 /* Test */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 545DB9B82151169500CA77B8 /* Project object */;
|
||||
|
||||
@@ -23,12 +23,31 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Test"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545DB9C92151169500CA77B8"
|
||||
BuildableName = "FloatingPanelTests.xctest"
|
||||
BlueprintName = "FloatingPanelTests"
|
||||
ReferencedContainer = "container:FloatingPanel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "545DB9C02151169500CA77B8"
|
||||
BuildableName = "FloatingPanel.framework"
|
||||
BlueprintName = "FloatingPanel"
|
||||
ReferencedContainer = "container:FloatingPanel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,20 @@
|
||||
import UIKit
|
||||
|
||||
public protocol FloatingPanelBehavior {
|
||||
/// Returns the progress to redirect to the previous position
|
||||
/// Asks the behavior if the floating panel should project a momentum of a user interaction to move the proposed position.
|
||||
///
|
||||
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next posiiton. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
|
||||
/// 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 `FloatingPanelPosition.tip` or `FloatingPanelPosition.full`.
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool
|
||||
|
||||
/// Returns a deceleration rate to calculate a target position projected a dragging momentum.
|
||||
///
|
||||
/// The default implementation of this method returns the normal deceleration rate of UIScrollView.
|
||||
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat
|
||||
|
||||
/// Returns the progress to redirect to the previous position.
|
||||
///
|
||||
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next position. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
|
||||
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to project a floating panel to a position on finger up if the user dragged.
|
||||
@@ -46,13 +57,42 @@ public protocol FloatingPanelBehavior {
|
||||
///
|
||||
/// Default is a spring animator with 1.0 damping ratio. This method is called when FloatingPanelController.isRemovalInteractionEnabled is true.
|
||||
func removalInteractionAnimator(_ fpc: FloatingPanelController, with velocity: CGVector) -> UIViewPropertyAnimator
|
||||
|
||||
|
||||
/// Asks the behavior whether the rubber band effect is enabled in moving over a given edge of the surface view.
|
||||
///
|
||||
/// This method allows the behavior to activate the rubber band effect to a given edge of the surface view. By default, the effect is disabled.
|
||||
func allowsRubberBanding(for edge: UIRectEdge) -> Bool
|
||||
}
|
||||
|
||||
public extension FloatingPanelBehavior {
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
|
||||
switch (fpc.position, proposedTargetPosition) {
|
||||
case (.full, .tip):
|
||||
return false
|
||||
case (.tip, .full):
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat {
|
||||
#if swift(>=4.2)
|
||||
return UIScrollView.DecelerationRate.normal.rawValue
|
||||
#else
|
||||
return UIScrollViewDecelerationRateNormal
|
||||
#endif
|
||||
}
|
||||
|
||||
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat {
|
||||
return 0.5
|
||||
}
|
||||
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
return defaultBehavior.interactionAnimator(fpc, to: targetPosition, with: velocity)
|
||||
}
|
||||
|
||||
func addAnimator(_ fpc: FloatingPanelController, to: FloatingPanelPosition) -> UIViewPropertyAnimator {
|
||||
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
|
||||
}
|
||||
@@ -80,12 +120,22 @@ public extension FloatingPanelBehavior {
|
||||
initialVelocity: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
}
|
||||
|
||||
func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
private let defaultBehavior = FloatingPanelDefaultBehavior()
|
||||
|
||||
public class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
|
||||
public init() { }
|
||||
|
||||
public func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
let timing = timeingCurve(with: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: timing)
|
||||
animator.isInterruptible = false
|
||||
return animator
|
||||
}
|
||||
|
||||
private func timeingCurve(with velocity: CGVector) -> UITimingCurveProvider {
|
||||
|
||||
@@ -14,7 +14,10 @@ public protocol FloatingPanelControllerDelegate: class {
|
||||
|
||||
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) // changed the settled position in the model layer
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) // any offset changes
|
||||
/// Asks the delegate if dragging should begin by the pan gesture recognizer.
|
||||
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool
|
||||
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) // any surface frame changes in dragging
|
||||
|
||||
// called on start of dragging (may require some time and or distance to move)
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController)
|
||||
@@ -27,6 +30,11 @@ public protocol FloatingPanelControllerDelegate: class {
|
||||
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint)
|
||||
// called when its views are removed from a parent view controller
|
||||
func floatingPanelDidEndRemove(_ vc: FloatingPanelController)
|
||||
|
||||
/// Asks the delegate if the other gesture recognizer should be allowed to recognize the gesture in parallel.
|
||||
///
|
||||
/// By default, any tap and long gesture recognizers are allowed to recognize gestures simultaneously.
|
||||
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
|
||||
}
|
||||
|
||||
public extension FloatingPanelControllerDelegate {
|
||||
@@ -37,6 +45,9 @@ public extension FloatingPanelControllerDelegate {
|
||||
return nil
|
||||
}
|
||||
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {}
|
||||
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool {
|
||||
return true
|
||||
}
|
||||
func floatingPanelDidMove(_ vc: FloatingPanelController) {}
|
||||
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {}
|
||||
func floatingPanelDidEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetPosition: FloatingPanelPosition) {}
|
||||
@@ -45,19 +56,24 @@ public extension FloatingPanelControllerDelegate {
|
||||
|
||||
func floatingPanelDidEndDraggingToRemove(_ vc: FloatingPanelController, withVelocity velocity: CGPoint) {}
|
||||
func floatingPanelDidEndRemove(_ vc: FloatingPanelController) {}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public enum FloatingPanelPosition: Int, CaseIterable {
|
||||
|
||||
public enum FloatingPanelPosition: Int {
|
||||
case full
|
||||
case half
|
||||
case tip
|
||||
case hidden
|
||||
}
|
||||
|
||||
///
|
||||
/// A container view controller to display a floating panel to present contents in parallel as a user wants.
|
||||
///
|
||||
public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
|
||||
open class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
/// Constants indicating how safe area insets are added to the adjusted content inset.
|
||||
public enum ContentInsetAdjustmentBehavior: Int {
|
||||
case always
|
||||
@@ -65,11 +81,15 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
|
||||
/// The delegate of the floating panel controller object.
|
||||
public weak var delegate: FloatingPanelControllerDelegate?
|
||||
public weak var delegate: FloatingPanelControllerDelegate?{
|
||||
didSet{
|
||||
didUpdateDelegate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
|
||||
public var surfaceView: FloatingPanelSurfaceView! {
|
||||
return view as? FloatingPanelSurfaceView
|
||||
return floatingPanel.surfaceView
|
||||
}
|
||||
|
||||
/// Returns the backdrop view managed by the controller object.
|
||||
@@ -84,7 +104,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
|
||||
// The underlying gesture recognizer for pan gestures
|
||||
public var panGestureRecognizer: UIPanGestureRecognizer {
|
||||
return floatingPanel.panGesture
|
||||
return floatingPanel.panGestureRecognizer
|
||||
}
|
||||
|
||||
/// The current position of the floating panel controller's contents.
|
||||
@@ -102,7 +122,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
return floatingPanel.behavior
|
||||
}
|
||||
|
||||
/// The content insets of the tracking scroll view derived from the safe area of the parent view
|
||||
/// The content insets of the tracking scroll view derived from this safe area
|
||||
public var adjustedContentInsets: UIEdgeInsets {
|
||||
return floatingPanel.layoutAdapter.adjustedContentInsets
|
||||
}
|
||||
@@ -126,63 +146,92 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
private var _contentViewController: UIViewController?
|
||||
|
||||
private var floatingPanel: FloatingPanel!
|
||||
private var layoutInsetsObservations: [NSKeyValueObservation] = []
|
||||
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
|
||||
private var safeAreaInsetsObservation: NSKeyValueObservation?
|
||||
private let modalTransition = FloatingPanelModalTransition()
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
floatingPanel = FloatingPanel(self,
|
||||
layout: fetchLayout(for: self.traitCollection),
|
||||
behavior: fetchBehavior(for: self.traitCollection))
|
||||
setUp()
|
||||
}
|
||||
|
||||
/// Initialize a newly created floating panel controller.
|
||||
public init() {
|
||||
public init(delegate: FloatingPanelControllerDelegate? = nil) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.delegate = delegate
|
||||
setUp()
|
||||
}
|
||||
|
||||
private func setUp() {
|
||||
_ = FloatingPanelController.dismissSwizzling
|
||||
|
||||
modalPresentationStyle = .custom
|
||||
transitioningDelegate = modalTransition
|
||||
|
||||
floatingPanel = FloatingPanel(self,
|
||||
layout: fetchLayout(for: self.traitCollection),
|
||||
behavior: fetchBehavior(for: self.traitCollection))
|
||||
}
|
||||
|
||||
private func didUpdateDelegate(){
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.behavior = fetchBehavior(for: self.traitCollection)
|
||||
}
|
||||
|
||||
// MARK:- Overrides
|
||||
|
||||
/// Creates the view that the controller manages.
|
||||
override public func loadView() {
|
||||
open override func loadView() {
|
||||
assert(self.storyboard == nil, "Storyboard isn't supported")
|
||||
|
||||
let view = FloatingPanelSurfaceView()
|
||||
view.backgroundColor = .white
|
||||
let view = FloatingPanelPassThroughView()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
backdropView.frame = view.bounds
|
||||
view.addSubview(backdropView)
|
||||
|
||||
surfaceView.frame = view.bounds
|
||||
view.addSubview(surfaceView)
|
||||
|
||||
self.view = view as UIView
|
||||
}
|
||||
|
||||
public override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
open override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {}
|
||||
else {
|
||||
// Because {top,bottom}LayoutGuide is managed as a view
|
||||
if preSafeAreaInsets != layoutInsets {
|
||||
self.update(safeAreaInsets: layoutInsets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
if view.translatesAutoresizingMaskIntoConstraints {
|
||||
view.frame.size = size
|
||||
view.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.willTransition(to: newCollection, with: coordinator)
|
||||
|
||||
// Change layout for a new trait collection
|
||||
updateLayout(for: newCollection)
|
||||
reloadLayout(for: newCollection)
|
||||
setUpLayout()
|
||||
|
||||
floatingPanel.behavior = fetchBehavior(for: newCollection)
|
||||
}
|
||||
|
||||
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
guard previousTraitCollection != traitCollection else { return }
|
||||
|
||||
if let parent = parent {
|
||||
self.update(safeAreaInsets: parent.layoutInsets)
|
||||
}
|
||||
open override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
safeAreaInsetsObservation = nil
|
||||
}
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
// Need to update safeAreaInsets here to ensure that the `adjustedContentInsets` has a correct value.
|
||||
// Because the parent VC does not call viewSafeAreaInsetsDidChange() expectedly and
|
||||
// `view.safeAreaInsets` has a correct value of the bottom inset here.
|
||||
if let parent = parent {
|
||||
self.update(safeAreaInsets: parent.layoutInsets)
|
||||
}
|
||||
}
|
||||
// MARK:- Privates
|
||||
|
||||
private func fetchLayout(for traitCollection: UITraitCollection) -> FloatingPanelLayout {
|
||||
switch traitCollection.verticalSizeClass {
|
||||
@@ -198,12 +247,17 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
|
||||
private func update(safeAreaInsets: UIEdgeInsets) {
|
||||
// preserve the current content offset
|
||||
let contentOffset = scrollView?.contentOffset
|
||||
guard
|
||||
preSafeAreaInsets != safeAreaInsets,
|
||||
self.floatingPanel.isDecelerating == false
|
||||
else { return }
|
||||
|
||||
floatingPanel.safeAreaInsets = safeAreaInsets
|
||||
log.debug("Update safeAreaInsets", safeAreaInsets)
|
||||
|
||||
scrollView?.contentOffset = contentOffset ?? .zero
|
||||
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
|
||||
preSafeAreaInsets = safeAreaInsets
|
||||
|
||||
setUpLayout()
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
@@ -214,17 +268,67 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
}
|
||||
|
||||
private func updateLayout(for traitCollection: UITraitCollection) {
|
||||
private func reloadLayout(for traitCollection: UITraitCollection) {
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.layoutAdapter.prepareLayout(in: self)
|
||||
|
||||
guard let parent = parent else { return }
|
||||
if let parent = self.parent {
|
||||
if let layout = layout 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.")
|
||||
}
|
||||
if let behavior = behavior 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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
floatingPanel.layoutAdapter.prepareLayout(toParent: parent)
|
||||
private func setUpLayout() {
|
||||
// preserve the current content offset
|
||||
let contentOffset = scrollView?.contentOffset
|
||||
|
||||
floatingPanel.layoutAdapter.updateHeight()
|
||||
floatingPanel.layoutAdapter.activateLayout(of: floatingPanel.state)
|
||||
|
||||
scrollView?.contentOffset = contentOffset ?? .zero
|
||||
}
|
||||
|
||||
// MARK: - Container view controller interface
|
||||
|
||||
/// Shows the surface view at the initial position defined by the current layout
|
||||
public func show(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
// Must apply the current layout here
|
||||
reloadLayout(for: traitCollection)
|
||||
setUpLayout()
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
// Must track the safeAreaInsets of `self.view` to update the layout.
|
||||
// There are 2 reasons.
|
||||
// 1. This or the parent VC doesn't call viewSafeAreaInsetsDidChange() on the bottom
|
||||
// inset's update expectedly.
|
||||
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (vc, change) in
|
||||
guard change.oldValue != change.newValue else { return }
|
||||
self?.update(safeAreaInsets: vc.layoutInsets)
|
||||
}
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
// Instead, update(safeAreaInsets:) is called at `viewDidLayoutSubviews()`
|
||||
}
|
||||
|
||||
move(to: floatingPanel.layoutAdapter.layout.initialPosition,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
/// Hides the surface view to the hidden position
|
||||
public func hide(animated: Bool = false, completion: (() -> Void)? = nil) {
|
||||
safeAreaInsetsObservation = nil
|
||||
move(to: .hidden,
|
||||
animated: animated,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
/// Adds the view managed by the controller as a child of the specified view controller.
|
||||
/// - Parameters:
|
||||
/// - parent: A parent view controller object that displays FloatingPanelController's view. A container view controller object isn't applicable.
|
||||
@@ -241,43 +345,34 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
precondition((parent is UITableViewController) == false, "UITableViewController should not be the parent because the view is a table view so that a floating panel doens't work well")
|
||||
precondition((parent is UICollectionViewController) == false, "UICollectionViewController should not be the parent because the view is a collection view so that a floating panel doens't work well")
|
||||
|
||||
view.frame = parent.view.bounds
|
||||
if let belowView = belowView {
|
||||
parent.view.insertSubview(self.view, belowSubview: belowView)
|
||||
} else {
|
||||
parent.view.addSubview(self.view)
|
||||
}
|
||||
|
||||
layoutInsetsObservations.removeAll()
|
||||
|
||||
// Must track safeAreaInsets/{top,bottom}LayoutGuide of the `parent.view`
|
||||
// to update floatingPanel.safeAreaInsets`. There are 2 reasons.
|
||||
// 1. 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.
|
||||
// That's why it needs the observation to keep `adjustedContentInsets` correct.
|
||||
if #available(iOS 11.0, *) {
|
||||
let observaion = parent.observe(\.view.safeAreaInsets) { [weak self] (vc, chaneg) in
|
||||
guard let self = self else { return }
|
||||
self.update(safeAreaInsets: vc.layoutInsets)
|
||||
}
|
||||
layoutInsetsObservations.append(observaion)
|
||||
} else {
|
||||
// KVOs for topLayoutGuide & bottomLayoutGuide are not effective.
|
||||
// Instead, safeAreaInsets will be updated in viewDidAppear()
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
parent.addChild(self)
|
||||
#else
|
||||
parent.addChildViewController(self)
|
||||
#endif
|
||||
|
||||
// Must set a layout again here because `self.traitCollection` is applied correctly once it's added to a parent VC
|
||||
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
|
||||
floatingPanel.behavior = fetchBehavior(for: traitCollection)
|
||||
view.frame = parent.view.bounds // Needed for a correct safe area configuration
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
self.view.topAnchor.constraint(equalTo: parent.view.topAnchor, constant: 0.0),
|
||||
self.view.leftAnchor.constraint(equalTo: parent.view.leftAnchor, constant: 0.0),
|
||||
self.view.rightAnchor.constraint(equalTo: parent.view.rightAnchor, constant: 0.0),
|
||||
self.view.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0),
|
||||
])
|
||||
|
||||
floatingPanel.setUpViews(in: parent)
|
||||
|
||||
floatingPanel.present(animated: animated) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.didMove(toParent: parent)
|
||||
show(animated: animated) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
#if swift(>=4.2)
|
||||
self.didMove(toParent: self)
|
||||
#else
|
||||
self.didMove(toParentViewController: self)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,15 +386,22 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
return
|
||||
}
|
||||
|
||||
layoutInsetsObservations.removeAll()
|
||||
|
||||
floatingPanel.dismiss(animated: animated) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
hide(animated: animated) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
#if swift(>=4.2)
|
||||
self.willMove(toParent: nil)
|
||||
#else
|
||||
self.willMove(toParentViewController: nil)
|
||||
#endif
|
||||
|
||||
self.view.removeFromSuperview()
|
||||
self.backdropView.removeFromSuperview()
|
||||
|
||||
#if swift(>=4.2)
|
||||
self.removeFromParent()
|
||||
#else
|
||||
self.removeFromParentViewController()
|
||||
#endif
|
||||
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
@@ -310,36 +412,57 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
/// - animated: Pass true to animate the presentation; otherwise, pass false.
|
||||
/// - completion: The block to execute after the view controller has finished moving. This block has no return value and takes no parameters. You may specify nil for this parameter.
|
||||
public func move(to: FloatingPanelPosition, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
precondition(floatingPanel.layoutAdapter.vc != nil, "Use show(animated:completion)")
|
||||
floatingPanel.move(to: to, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
/// Sets the view controller responsible for the content portion of the floating panel..
|
||||
/// Sets the view controller responsible for the content portion of the floating panel.
|
||||
public func set(contentViewController: UIViewController?) {
|
||||
if let vc = _contentViewController {
|
||||
#if swift(>=4.2)
|
||||
vc.willMove(toParent: nil)
|
||||
#else
|
||||
vc.willMove(toParentViewController: nil)
|
||||
#endif
|
||||
|
||||
vc.view.removeFromSuperview()
|
||||
|
||||
#if swift(>=4.2)
|
||||
vc.removeFromParent()
|
||||
#else
|
||||
vc.removeFromParentViewController()
|
||||
#endif
|
||||
}
|
||||
|
||||
if let vc = contentViewController {
|
||||
let surfaceView = self.view as! FloatingPanelSurfaceView
|
||||
surfaceView.add(childView: vc.view)
|
||||
#if swift(>=4.2)
|
||||
addChild(vc)
|
||||
#else
|
||||
addChildViewController(vc)
|
||||
#endif
|
||||
|
||||
let surfaceView = floatingPanel.surfaceView
|
||||
surfaceView.add(contentView: vc.view)
|
||||
|
||||
#if swift(>=4.2)
|
||||
vc.didMove(toParent: self)
|
||||
#else
|
||||
vc.didMove(toParentViewController: self)
|
||||
#endif
|
||||
}
|
||||
|
||||
_contentViewController = contentViewController
|
||||
}
|
||||
|
||||
@available(*, unavailable, renamed: "set(contentViewController:)")
|
||||
public override func show(_ vc: UIViewController, sender: Any?) {
|
||||
open override func show(_ vc: UIViewController, sender: Any?) {
|
||||
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) {
|
||||
target.show(vc, sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable, renamed: "set(contentViewController:)")
|
||||
public override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
|
||||
open override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
|
||||
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.showDetailViewController(_:sender:)), sender: sender) {
|
||||
target.showDetailViewController(vc, sender: sender)
|
||||
}
|
||||
@@ -349,23 +472,30 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
|
||||
/// Tracks the specified scroll view to correspond with the scroll.
|
||||
///
|
||||
/// - Attention:
|
||||
/// The specified scroll view must be already assigned to the delegate property because the controller intermediates between the various delegate methods.
|
||||
///
|
||||
public func track(scrollView: UIScrollView) {
|
||||
floatingPanel.scrollView = scrollView
|
||||
if scrollView.delegate !== floatingPanel {
|
||||
floatingPanel.userScrollViewDelegate = scrollView.delegate
|
||||
scrollView.delegate = floatingPanel
|
||||
/// - Parameters:
|
||||
/// - scrollView: Specify a scroll view to continuously and seamlessly work in concert with interactions of the surface view or nil to cancel it.
|
||||
public func track(scrollView: UIScrollView?) {
|
||||
guard let scrollView = scrollView else {
|
||||
floatingPanel.scrollView = nil
|
||||
return
|
||||
}
|
||||
|
||||
floatingPanel.scrollView = scrollView
|
||||
|
||||
switch contentInsetAdjustmentBehavior {
|
||||
case .always:
|
||||
if #available(iOS 11.0, *) {
|
||||
scrollView.contentInsetAdjustmentBehavior = .never
|
||||
} else {
|
||||
#if swift(>=4.2)
|
||||
children.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
#else
|
||||
childViewControllers.forEach { (vc) in
|
||||
vc.automaticallyAdjustsScrollViewInsets = false
|
||||
}
|
||||
#endif
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -378,14 +508,15 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
/// by the controller immediately.
|
||||
///
|
||||
/// This method updates the `FloatingPanelLayout` object from the delegate and
|
||||
/// then it calls `layoutIfNeeded()` of the parent's root view to force the view
|
||||
/// then it calls `layoutIfNeeded()` of the root view to force the view
|
||||
/// to update the floating panel's layout immediately. It can be called in an
|
||||
/// animation block.
|
||||
public func updateLayout() {
|
||||
updateLayout(for: view.traitCollection)
|
||||
reloadLayout(for: traitCollection)
|
||||
setUpLayout()
|
||||
}
|
||||
|
||||
/// Returns the y-coordinate of the point at the origin of the surface view
|
||||
/// Returns the y-coordinate of the point at the origin of the surface view.
|
||||
public func originYOfSurface(for pos: FloatingPanelPosition) -> CGFloat {
|
||||
switch pos {
|
||||
case .full:
|
||||
@@ -394,6 +525,54 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
return floatingPanel.layoutAdapter.middleY
|
||||
case .tip:
|
||||
return floatingPanel.layoutAdapter.bottomY
|
||||
case .hidden:
|
||||
return floatingPanel.layoutAdapter.hiddenY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatingPanelController {
|
||||
private static let dismissSwizzling: Any? = {
|
||||
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 {
|
||||
// switch implementation..
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
|
||||
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) {
|
||||
// Call dismiss(animated:completion:) to a content view controller
|
||||
if let fpc = parent as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Call dismiss(animated:completion:) to FloatingPanelController directly
|
||||
if let fpc = self as? FloatingPanelController {
|
||||
if fpc.presentingViewController != nil {
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
} else {
|
||||
fpc.removePanelFromParent(animated: flag, completion: completion)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// For other view controllers
|
||||
self.fp_original_dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,46 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
/// FloatingPanelFullScreenLayout
|
||||
///
|
||||
/// Use the layout protocol if you configure full, half and tip insets from the superview, not the safe area.
|
||||
/// It can't be used with FloatingPanelIntrinsicLayout.
|
||||
public protocol FloatingPanelFullScreenLayout: FloatingPanelLayout { }
|
||||
|
||||
/// FloatingPanelIntrinsicLayout
|
||||
///
|
||||
/// Use the layout protocol if you want to layout a panel using the intrinsic height.
|
||||
/// It can't be used with FloatingPanelFullScreenLayout.
|
||||
///
|
||||
/// - Attention:
|
||||
/// `insetFor(position:)` must return `nil` for the full position. Because
|
||||
/// the inset is determined automatically by the intrinsic height.
|
||||
/// You can customize insets only for the half, tip and hidden positions.
|
||||
public protocol FloatingPanelIntrinsicLayout: FloatingPanelLayout { }
|
||||
|
||||
public extension FloatingPanelIntrinsicLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .full
|
||||
}
|
||||
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return [.full]
|
||||
}
|
||||
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FloatingPanelLayout: class {
|
||||
/// Returns the initial position of a floating panel.
|
||||
var initialPosition: FloatingPanelPosition { get }
|
||||
|
||||
/// Returns a set of FloatingPanelPosition objects to tell the applicable positions of the floating panel controller. Default is all of them.
|
||||
/// Returns a set of FloatingPanelPosition objects to tell the applicable
|
||||
/// positions of the floating panel controller.
|
||||
///
|
||||
/// By default, it returns all position except for `hidden` position. Because
|
||||
/// it's always supported by `FloatingPanelController` so you don't need to return it.
|
||||
var supportedPositions: Set<FloatingPanelPosition> { get }
|
||||
|
||||
/// Return the interaction buffer to the top from the top position. Default is 6.0.
|
||||
@@ -18,10 +53,13 @@ public protocol FloatingPanelLayout: class {
|
||||
/// Return the interaction buffer to the bottom from the bottom position. Default is 6.0.
|
||||
var bottomInteractionBuffer: CGFloat { get }
|
||||
|
||||
/// Returns a CGFloat value to determine a floating panel height for each position(full, half and tip).
|
||||
/// A value for full position indicates a top inset from a safe area.
|
||||
/// On the other hand, values for half and tip positions indicate bottom insets from a safe area.
|
||||
/// If a position doesn't contain the supported positions, return nil.
|
||||
/// Returns a CGFloat value to determine a Y coordinate of a floating panel for each position(full, half, tip and hidden).
|
||||
///
|
||||
/// Its returning value indicates a different inset for each position.
|
||||
/// For full position, a top inset from a safe area in `FloatingPanelController.view`.
|
||||
/// For half or tip position, a bottom inset from the safe area.
|
||||
/// For hidden position, a bottom inset from `FloatingPanelController.view`.
|
||||
/// If a position isn't supported or the default value is used, return nil.
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat?
|
||||
|
||||
/// Returns X-axis and width layout constraints of the surface view of a floating panel.
|
||||
@@ -41,9 +79,9 @@ public extension FloatingPanelLayout {
|
||||
var bottomInteractionBuffer: CGFloat { return 6.0 }
|
||||
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return Set(FloatingPanelPosition.allCases)
|
||||
return Set([.full, .half, .tip])
|
||||
}
|
||||
|
||||
|
||||
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.sideLayoutGuide.leftAnchor, constant: 0.0),
|
||||
@@ -57,6 +95,8 @@ public extension FloatingPanelLayout {
|
||||
}
|
||||
|
||||
public class FloatingPanelDefaultLayout: FloatingPanelLayout {
|
||||
public init() { }
|
||||
|
||||
public var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
@@ -66,11 +106,14 @@ public class FloatingPanelDefaultLayout: FloatingPanelLayout {
|
||||
case .full: return 18.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 69.0
|
||||
case .hidden: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout {
|
||||
public init() { }
|
||||
|
||||
public var initialPosition: FloatingPanelPosition {
|
||||
return .tip
|
||||
}
|
||||
@@ -89,7 +132,7 @@ public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout {
|
||||
|
||||
|
||||
class FloatingPanelLayoutAdapter {
|
||||
private weak var parent: UIViewController!
|
||||
weak var vc: UIViewController!
|
||||
private weak var surfaceView: FloatingPanelSurfaceView!
|
||||
private weak var backdropView: FloatingPanelBackdropView!
|
||||
|
||||
@@ -99,24 +142,27 @@ class FloatingPanelLayoutAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
var safeAreaInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
updateHeight()
|
||||
checkLayoutConsistance()
|
||||
}
|
||||
private var safeAreaInsets: UIEdgeInsets {
|
||||
return vc?.layoutInsets ?? .zero
|
||||
}
|
||||
|
||||
private var parentHeight: CGFloat = 0.0
|
||||
private var heightBuffer: CGFloat = 88.0 // For bounce
|
||||
private var initialConst: CGFloat = 0.0
|
||||
|
||||
private var fixedConstraints: [NSLayoutConstraint] = []
|
||||
private var fullConstraints: [NSLayoutConstraint] = []
|
||||
private var halfConstraints: [NSLayoutConstraint] = []
|
||||
private var tipConstraints: [NSLayoutConstraint] = []
|
||||
private var offConstraints: [NSLayoutConstraint] = []
|
||||
private var heightConstraints: [NSLayoutConstraint] = []
|
||||
private var interactiveTopConstraint: NSLayoutConstraint?
|
||||
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
|
||||
private var fullInset: CGFloat {
|
||||
return layout.insetFor(position: .full) ?? 0.0
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
return intrinsicHeight
|
||||
} else {
|
||||
return layout.insetFor(position: .full) ?? 0.0
|
||||
}
|
||||
}
|
||||
private var halfInset: CGFloat {
|
||||
return layout.insetFor(position: .half) ?? 0.0
|
||||
@@ -124,29 +170,75 @@ class FloatingPanelLayoutAdapter {
|
||||
private var tipInset: CGFloat {
|
||||
return layout.insetFor(position: .tip) ?? 0.0
|
||||
}
|
||||
private var hiddenInset: CGFloat {
|
||||
return layout.insetFor(position: .hidden) ?? 0.0
|
||||
}
|
||||
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
var supportedPositions = layout.supportedPositions
|
||||
supportedPositions.remove(.hidden)
|
||||
return supportedPositions
|
||||
}
|
||||
|
||||
var topMostState: FloatingPanelPosition {
|
||||
if supportedPositions.contains(.full) {
|
||||
return .full
|
||||
}
|
||||
if supportedPositions.contains(.half) {
|
||||
return .half
|
||||
}
|
||||
return .tip
|
||||
}
|
||||
|
||||
var topY: CGFloat {
|
||||
if layout.supportedPositions.contains(.full) {
|
||||
return (safeAreaInsets.top + fullInset)
|
||||
if supportedPositions.contains(.full) {
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
return surfaceView.superview!.bounds.height - surfaceView.bounds.height
|
||||
case is FloatingPanelFullScreenLayout:
|
||||
return fullInset
|
||||
default:
|
||||
return (safeAreaInsets.top + fullInset)
|
||||
}
|
||||
} else {
|
||||
return middleY
|
||||
}
|
||||
}
|
||||
|
||||
var middleY: CGFloat {
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return surfaceView.superview!.bounds.height - halfInset
|
||||
} else{
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
|
||||
}
|
||||
}
|
||||
|
||||
var bottomY: CGFloat {
|
||||
if layout.supportedPositions.contains(.tip) {
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset)
|
||||
if supportedPositions.contains(.tip) {
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return surfaceView.superview!.bounds.height - tipInset
|
||||
} else{
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset)
|
||||
}
|
||||
} else {
|
||||
return middleY
|
||||
}
|
||||
}
|
||||
|
||||
var safeAreaBottomY: CGFloat {
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom)
|
||||
var hiddenY: CGFloat {
|
||||
return surfaceView.superview!.bounds.height
|
||||
}
|
||||
|
||||
var topMaxY: CGFloat {
|
||||
return layout is FloatingPanelFullScreenLayout ? 0.0 : safeAreaInsets.top
|
||||
}
|
||||
|
||||
var bottomMaxY: CGFloat {
|
||||
if layout is FloatingPanelFullScreenLayout{
|
||||
return surfaceView.superview!.bounds.height - hiddenInset
|
||||
} else {
|
||||
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
|
||||
}
|
||||
}
|
||||
|
||||
var adjustedContentInsets: UIEdgeInsets {
|
||||
@@ -164,136 +256,276 @@ class FloatingPanelLayoutAdapter {
|
||||
return middleY
|
||||
case .tip:
|
||||
return bottomY
|
||||
case .hidden:
|
||||
return hiddenY
|
||||
}
|
||||
}
|
||||
|
||||
var intrinsicHeight: CGFloat = 0.0
|
||||
|
||||
init(surfaceView: FloatingPanelSurfaceView, backdropView: FloatingPanelBackdropView, layout: FloatingPanelLayout) {
|
||||
self.layout = layout
|
||||
self.surfaceView = surfaceView
|
||||
self.backdropView = backdropView
|
||||
}
|
||||
|
||||
func prepareLayout(toParent parent: UIViewController) {
|
||||
self.parent = parent
|
||||
func updateIntrinsicHeight() {
|
||||
#if swift(>=4.2)
|
||||
let fittingSize = UIView.layoutFittingCompressedSize
|
||||
#else
|
||||
let fittingSize = UILayoutFittingCompressedSize
|
||||
#endif
|
||||
var intrinsicHeight = surfaceView.contentView?.systemLayoutSizeFitting(fittingSize).height ?? 0.0
|
||||
var safeAreaBottom: CGFloat = 0.0
|
||||
if #available(iOS 11.0, *) {
|
||||
safeAreaBottom = surfaceView.contentView?.safeAreaInsets.bottom ?? 0.0
|
||||
if safeAreaBottom > 0 {
|
||||
intrinsicHeight -= safeAreaInsets.bottom
|
||||
}
|
||||
}
|
||||
self.intrinsicHeight = max(intrinsicHeight, 0.0)
|
||||
|
||||
log.debug("Update intrinsic height =", intrinsicHeight,
|
||||
", surface(height) =", surfaceView.frame.height,
|
||||
", content(height) =", surfaceView.contentView?.frame.height ?? 0.0,
|
||||
", content safe area(bottom) =", safeAreaBottom)
|
||||
}
|
||||
|
||||
func prepareLayout(in vc: UIViewController) {
|
||||
self.vc = vc
|
||||
|
||||
NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
surfaceView.translatesAutoresizingMaskIntoConstraints = false
|
||||
backdropView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
// Fixed constraints of surface and backdrop views
|
||||
let surfaceConstraints = layout.prepareLayout(surfaceView: surfaceView, in: parent.view!)
|
||||
let surfaceConstraints = layout.prepareLayout(surfaceView: surfaceView, in: vc.view!)
|
||||
let backdropConstraints = [
|
||||
backdropView.topAnchor.constraint(equalTo: parent.view.topAnchor,
|
||||
constant: 0.0),
|
||||
backdropView.leftAnchor.constraint(equalTo: parent.view.leftAnchor,
|
||||
constant: 0.0),
|
||||
backdropView.rightAnchor.constraint(equalTo: parent.view.rightAnchor,
|
||||
constant: 0.0),
|
||||
backdropView.bottomAnchor.constraint(equalTo: parent.view.bottomAnchor,
|
||||
constant: 0.0),
|
||||
backdropView.topAnchor.constraint(equalTo: vc.view.topAnchor, constant: 0.0),
|
||||
backdropView.leftAnchor.constraint(equalTo: vc.view.leftAnchor,constant: 0.0),
|
||||
backdropView.rightAnchor.constraint(equalTo: vc.view.rightAnchor, constant: 0.0),
|
||||
backdropView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor, constant: 0.0),
|
||||
]
|
||||
|
||||
fixedConstraints = surfaceConstraints + backdropConstraints
|
||||
|
||||
// Flexible surface constarints for full, half, tip and off
|
||||
fullConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: parent.layoutGuide.topAnchor,
|
||||
constant: fullInset),
|
||||
]
|
||||
// Flexible surface constraints for full, half, tip and off
|
||||
let topAnchor: NSLayoutYAxisAnchor = {
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return vc.view.topAnchor
|
||||
} else {
|
||||
return vc.layoutGuide.topAnchor
|
||||
}
|
||||
}()
|
||||
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
// Set up on updateHeight()
|
||||
break
|
||||
default:
|
||||
fullConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: topAnchor,
|
||||
constant: fullInset),
|
||||
]
|
||||
}
|
||||
|
||||
let bottomAnchor: NSLayoutYAxisAnchor = {
|
||||
if layout is FloatingPanelFullScreenLayout {
|
||||
return vc.view.bottomAnchor
|
||||
} else {
|
||||
return vc.layoutGuide.bottomAnchor
|
||||
}
|
||||
}()
|
||||
|
||||
halfConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: parent.layoutGuide.bottomAnchor,
|
||||
surfaceView.topAnchor.constraint(equalTo: bottomAnchor,
|
||||
constant: -halfInset),
|
||||
]
|
||||
tipConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: parent.layoutGuide.bottomAnchor,
|
||||
surfaceView.topAnchor.constraint(equalTo: bottomAnchor,
|
||||
constant: -tipInset),
|
||||
]
|
||||
|
||||
offConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: parent.view.bottomAnchor, constant: 0.0),
|
||||
surfaceView.topAnchor.constraint(equalTo:vc.view.bottomAnchor,
|
||||
constant: -hiddenInset),
|
||||
]
|
||||
}
|
||||
|
||||
func startInteraction(at state: FloatingPanelPosition) {
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
let interactiveTopConstraint: NSLayoutConstraint
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout,
|
||||
is FloatingPanelFullScreenLayout:
|
||||
initialConst = surfaceView.frame.minY
|
||||
interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.view.topAnchor,
|
||||
constant: initialConst)
|
||||
default:
|
||||
initialConst = surfaceView.frame.minY - safeAreaInsets.top
|
||||
interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
|
||||
constant: initialConst)
|
||||
}
|
||||
NSLayoutConstraint.activate([interactiveTopConstraint])
|
||||
self.interactiveTopConstraint = interactiveTopConstraint
|
||||
}
|
||||
|
||||
func endInteraction(at state: FloatingPanelPosition) {
|
||||
// Don't deactivate `interactiveTopConstraint` here because it leads to
|
||||
// unsatisfiable constraints
|
||||
}
|
||||
|
||||
// The method is separated from prepareLayout(to:) for the rotation support
|
||||
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
|
||||
func updateHeight() {
|
||||
defer {
|
||||
UIView.performWithoutAnimation {
|
||||
surfaceView.superview!.layoutIfNeeded()
|
||||
}
|
||||
guard let vc = vc else { return }
|
||||
|
||||
if let const = self.heightConstraint {
|
||||
NSLayoutConstraint.deactivate([const])
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(heightConstraints)
|
||||
// Must use the parent height, not the screen height because safe area insets
|
||||
// of the parent are relative values. For example, a view controller in
|
||||
// Navigation controller's safe area insets and frame can be changed whether
|
||||
// the navigation bar is translucent or not.
|
||||
let height = self.parent.view.bounds.height - (safeAreaInsets.top + fullInset)
|
||||
heightConstraints = [
|
||||
surfaceView.heightAnchor.constraint(equalToConstant: height)
|
||||
]
|
||||
NSLayoutConstraint.activate(heightConstraints)
|
||||
surfaceView.set(bottomOverflow: heightBuffer)
|
||||
let heightConstraint: NSLayoutConstraint
|
||||
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout:
|
||||
updateIntrinsicHeight()
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom)
|
||||
default:
|
||||
let const = -(positionY(for: topMostState))
|
||||
heightConstraint = surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
|
||||
constant: const)
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([heightConstraint])
|
||||
self.heightConstraint = heightConstraint
|
||||
|
||||
surfaceView.bottomOverflow = vc.view.bounds.height + layout.topInteractionBuffer
|
||||
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
NSLayoutConstraint.deactivate(fullConstraints)
|
||||
fullConstraints = [
|
||||
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
|
||||
constant: -fullInset),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
func activateLayout(of state: FloatingPanelPosition?) {
|
||||
func updateInteractiveTopConstraint(diff: CGFloat, allowsTopBuffer: Bool, with behavior: FloatingPanelBehavior) {
|
||||
defer {
|
||||
surfaceView.superview!.layoutIfNeeded() // MUST call here to update `surfaceView.frame`
|
||||
}
|
||||
|
||||
let topMostConst: CGFloat = {
|
||||
var ret: CGFloat = 0.0
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
|
||||
ret = topY
|
||||
default:
|
||||
ret = topY - safeAreaInsets.top
|
||||
}
|
||||
return max(ret, 0.0) // The top boundary is equal to the related topAnchor.
|
||||
}()
|
||||
let bottomMostConst: CGFloat = {
|
||||
var ret: CGFloat = 0.0
|
||||
switch layout {
|
||||
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
|
||||
ret = bottomY
|
||||
default:
|
||||
ret = bottomY - safeAreaInsets.top
|
||||
}
|
||||
return min(ret, bottomMaxY)
|
||||
}()
|
||||
let minConst = allowsTopBuffer ? topMostConst - layout.topInteractionBuffer : topMostConst
|
||||
let maxConst = bottomMostConst + layout.bottomInteractionBuffer
|
||||
|
||||
var const = initialConst + diff
|
||||
|
||||
// Rubberbanding top buffer
|
||||
if behavior.allowsRubberBanding(for: .top), const < topMostConst {
|
||||
let buffer = topMostConst - const
|
||||
const = topMostConst - rubberbandEffect(for: buffer, base: vc.view.bounds.height)
|
||||
}
|
||||
|
||||
// Rubberbanding bottom buffer
|
||||
if behavior.allowsRubberBanding(for: .bottom), const > bottomMostConst {
|
||||
let buffer = const - bottomMostConst
|
||||
const = bottomMostConst + rubberbandEffect(for: buffer, base: vc.view.bounds.height)
|
||||
}
|
||||
|
||||
interactiveTopConstraint?.constant = max(minConst, min(maxConst, const))
|
||||
}
|
||||
|
||||
// According to @chpwn's tweet: https://twitter.com/chpwn/status/285540192096497664
|
||||
// x = distance from the edge
|
||||
// c = constant value, UIScrollView uses 0.55
|
||||
// d = dimension, either width or height
|
||||
private func rubberbandEffect(for buffer: CGFloat, base: CGFloat) -> CGFloat {
|
||||
return (1.0 - (1.0 / ((buffer * 0.55 / base) + 1.0))) * base
|
||||
}
|
||||
|
||||
func activateLayout(of state: FloatingPanelPosition) {
|
||||
defer {
|
||||
surfaceView.superview!.layoutIfNeeded()
|
||||
}
|
||||
|
||||
var state = state
|
||||
|
||||
setBackdropAlpha(of: state)
|
||||
|
||||
// Must deactivate `interactiveTopConstraint` here
|
||||
if let interactiveTopConstraint = interactiveTopConstraint {
|
||||
NSLayoutConstraint.deactivate([interactiveTopConstraint])
|
||||
self.interactiveTopConstraint = nil
|
||||
}
|
||||
NSLayoutConstraint.activate(fixedConstraints)
|
||||
|
||||
guard var state = state else {
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints)
|
||||
NSLayoutConstraint.activate(offConstraints)
|
||||
return
|
||||
}
|
||||
|
||||
if layout.supportedPositions.contains(state) == false {
|
||||
if supportedPositions.union([.hidden]).contains(state) == false {
|
||||
state = layout.initialPosition
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
switch state {
|
||||
case .full:
|
||||
NSLayoutConstraint.deactivate(halfConstraints + tipConstraints + offConstraints)
|
||||
NSLayoutConstraint.activate(fullConstraints)
|
||||
case .half:
|
||||
NSLayoutConstraint.deactivate(fullConstraints + tipConstraints + offConstraints)
|
||||
NSLayoutConstraint.activate(halfConstraints)
|
||||
case .tip:
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + offConstraints)
|
||||
NSLayoutConstraint.activate(tipConstraints)
|
||||
case .hidden:
|
||||
NSLayoutConstraint.activate(offConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
func setBackdropAlpha(of target: FloatingPanelPosition?) {
|
||||
if let target = target {
|
||||
self.backdropView.alpha = layout.backdropAlphaFor(position: target)
|
||||
} else {
|
||||
private func setBackdropAlpha(of target: FloatingPanelPosition) {
|
||||
if target == .hidden {
|
||||
self.backdropView.alpha = 0.0
|
||||
} else {
|
||||
self.backdropView.alpha = layout.backdropAlphaFor(position: target)
|
||||
}
|
||||
}
|
||||
|
||||
func checkLayoutConsistance() {
|
||||
private func checkLayoutConsistance() {
|
||||
// Verify layout configurations
|
||||
let supportedPositions = layout.supportedPositions
|
||||
|
||||
assert(supportedPositions.count > 0)
|
||||
assert(supportedPositions.contains(layout.initialPosition),
|
||||
"Does not include an initial potision(\(layout.initialPosition)) in supportedPositions(\(supportedPositions))")
|
||||
assert(supportedPositions.union([.hidden]).contains(layout.initialPosition),
|
||||
"Does not include an initial position (\(layout.initialPosition)) in supportedPositions (\(supportedPositions))")
|
||||
|
||||
supportedPositions.forEach { pos in
|
||||
assert(layout.insetFor(position: pos) != nil,
|
||||
"Undefined an inset for a pos(\(pos))")
|
||||
if layout is FloatingPanelIntrinsicLayout {
|
||||
assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout")
|
||||
}
|
||||
|
||||
if halfInset > 0 {
|
||||
assert(halfInset > tipInset, "Invalid half and tip insets")
|
||||
}
|
||||
if fullInset > 0 {
|
||||
assert(middleY > topY, "Invalid insets")
|
||||
assert(bottomY > topY, "Invalid insets")
|
||||
}
|
||||
// The verification isn't working on orientation change(portrait -> landscape)
|
||||
// of a floating panel in tab bar. Because the `safeAreaInsets.bottom` is
|
||||
// updated in delay so that it can be 83.0(not 53.0) even after the surface
|
||||
// and the super view's frame is fit to landscape already.
|
||||
/*if fullInset > 0 {
|
||||
assert(middleY > topY, "Invalid insets { topY: \(topY), middleY: \(middleY) }")
|
||||
assert(bottomY > topY, "Invalid insets { topY: \(topY), bottomY: \(bottomY) }")
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,24 +5,48 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class FloatingPanelSurfaceContentView: UIView {}
|
||||
|
||||
/// A view that presents a surface interface in a floating panel.
|
||||
public class FloatingPanelSurfaceView: UIView {
|
||||
|
||||
/// A GrabberHandleView object displayed at the top of the surface view
|
||||
public var grabberHandle: GrabberHandleView!
|
||||
/// A GrabberHandleView object displayed at the top of the surface view.
|
||||
///
|
||||
/// To use a custom grabber handle, hide this and then add the custom one
|
||||
/// to the surface view at appropriate coordinates.
|
||||
public let grabberHandle: GrabberHandleView = GrabberHandleView()
|
||||
|
||||
/// Offset of the grabber handle from the top
|
||||
public var grabberTopPadding: CGFloat = 6.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
|
||||
/// The height of the grabber bar area
|
||||
public static var topGrabberBarHeight: CGFloat {
|
||||
return Default.grabberTopPadding * 2 + GrabberHandleView.Default.height // 17.0
|
||||
public var topGrabberBarHeight: CGFloat {
|
||||
return grabberTopPadding * 2 + grabberHandleHeight
|
||||
}
|
||||
|
||||
/// A UIView object that can have the surface view added to it.
|
||||
public var contentView: UIView!
|
||||
/// Grabber view width and height
|
||||
public var grabberHandleWidth: CGFloat = 36.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
public var grabberHandleHeight: CGFloat = 5.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
|
||||
/// A root view of a content view controller
|
||||
public weak var contentView: UIView!
|
||||
|
||||
/// The content insets specifying the insets around the content view.
|
||||
///
|
||||
/// - important: Currently the `bottom` inset is ignored.
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
// Needs update constraints
|
||||
self.setNeedsUpdateConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
private var color: UIColor? = .white { didSet { setNeedsLayout() } }
|
||||
private var bottomOverflow: CGFloat = 0.0 // Must not call setNeedsLayout()
|
||||
var bottomOverflow: CGFloat = 0.0 // Must not call setNeedsLayout()
|
||||
|
||||
public override var backgroundColor: UIColor? {
|
||||
get { return color }
|
||||
@@ -33,7 +57,10 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
///
|
||||
/// `self.contentView` is masked with the top rounded corners automatically on iOS 11 and later.
|
||||
/// On iOS 10, they are not automatically masked because of a UIVisualEffectView issue. See https://forums.developer.apple.com/thread/50854
|
||||
public var cornerRadius: CGFloat = 0.0 { didSet { setNeedsLayout() } }
|
||||
public var cornerRadius: CGFloat {
|
||||
set { containerView.layer.cornerRadius = newValue; setNeedsLayout() }
|
||||
get { return containerView.layer.cornerRadius }
|
||||
}
|
||||
|
||||
/// A Boolean indicating whether the surface shadow is displayed.
|
||||
public var shadowHidden: Bool = false { didSet { setNeedsLayout() } }
|
||||
@@ -56,120 +83,154 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
/// The color of the surface border.
|
||||
public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
|
||||
|
||||
private var backgroundLayer: CAShapeLayer! { didSet { setNeedsLayout() } }
|
||||
/// Offset of the container view from the top
|
||||
public var containerTopInset: CGFloat = 0.0 { didSet {
|
||||
setNeedsUpdateConstraints()
|
||||
} }
|
||||
|
||||
private struct Default {
|
||||
public static let grabberTopPadding: CGFloat = 6.0
|
||||
}
|
||||
/// The view presents an actual surface shape.
|
||||
///
|
||||
/// It renders the background color, border line and top rounded corners,
|
||||
/// specified by other properties. The reason why they're not be applied to
|
||||
/// a content view directly is because it avoids any side-effects to the
|
||||
/// content view.
|
||||
public let containerView: UIView = UIView()
|
||||
|
||||
@available(*, unavailable, renamed: "containerView")
|
||||
public var backgroundView: UIView!
|
||||
|
||||
private lazy var containerViewTopInsetConstraint: NSLayoutConstraint = containerView.topAnchor.constraint(equalTo: topAnchor, constant: containerTopInset)
|
||||
private lazy var containerViewHeightConstraint: NSLayoutConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
|
||||
|
||||
/// The content view top constraint
|
||||
private var contentViewTopConstraint: NSLayoutConstraint?
|
||||
/// The content view left constraint
|
||||
private var contentViewLeftConstraint: NSLayoutConstraint?
|
||||
/// The content right constraint
|
||||
private var contentViewRightConstraint: NSLayoutConstraint?
|
||||
/// The content height constraint
|
||||
private var contentViewHeightConstraint: NSLayoutConstraint?
|
||||
|
||||
private lazy var grabberHandleWidthConstraint: NSLayoutConstraint = grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandleWidth)
|
||||
private lazy var grabberHandleHeightConstraint: NSLayoutConstraint = grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandleHeight)
|
||||
private lazy var grabberHandleTopConstraint: NSLayoutConstraint = grabberHandle.topAnchor.constraint(equalTo: topAnchor, constant: grabberTopPadding)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
render()
|
||||
addSubViews()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
render()
|
||||
addSubViews()
|
||||
}
|
||||
|
||||
private func render() {
|
||||
private func addSubViews() {
|
||||
super.backgroundColor = .clear
|
||||
self.clipsToBounds = false
|
||||
|
||||
let backgroundLayer = CAShapeLayer()
|
||||
layer.insertSublayer(backgroundLayer, at: 0)
|
||||
self.backgroundLayer = backgroundLayer
|
||||
|
||||
let contentView = FloatingPanelSurfaceContentView()
|
||||
addSubview(contentView)
|
||||
self.contentView = contentView as UIView
|
||||
contentView.backgroundColor = color
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(containerView)
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
|
||||
contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
contentView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
|
||||
containerViewTopInsetConstraint,
|
||||
containerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
containerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
containerViewHeightConstraint,
|
||||
])
|
||||
|
||||
let grabberHandle = GrabberHandleView()
|
||||
addSubview(grabberHandle)
|
||||
self.grabberHandle = grabberHandle
|
||||
|
||||
grabberHandle.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
grabberHandle.topAnchor.constraint(equalTo: topAnchor, constant: Default.grabberTopPadding),
|
||||
grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandle.frame.width),
|
||||
grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandle.frame.height),
|
||||
grabberHandleWidthConstraint,
|
||||
grabberHandleHeightConstraint,
|
||||
grabberHandleTopConstraint,
|
||||
grabberHandle.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
public override func updateConstraints() {
|
||||
containerViewTopInsetConstraint.constant = containerTopInset
|
||||
containerViewHeightConstraint.constant = bottomOverflow
|
||||
|
||||
contentViewTopConstraint?.constant = contentInsets.top
|
||||
contentViewLeftConstraint?.constant = contentInsets.left
|
||||
contentViewRightConstraint?.constant = contentInsets.right
|
||||
contentViewHeightConstraint?.constant = -containerTopInset
|
||||
|
||||
grabberHandleTopConstraint.constant = grabberTopPadding
|
||||
grabberHandleWidthConstraint.constant = grabberHandleWidth
|
||||
grabberHandleHeightConstraint.constant = grabberHandleHeight
|
||||
|
||||
super.updateConstraints()
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
log.debug("surface view frame = \(frame)")
|
||||
|
||||
updateLayers()
|
||||
updateContentViewMask()
|
||||
containerView.backgroundColor = color
|
||||
|
||||
contentView.layer.borderColor = borderColor?.cgColor
|
||||
contentView.layer.borderWidth = borderWidth
|
||||
contentView.backgroundColor = color
|
||||
updateShadow()
|
||||
updateCornerRadius()
|
||||
updateBorder()
|
||||
}
|
||||
|
||||
private func updateLayers() {
|
||||
log.debug("SurfaceView bounds", bounds)
|
||||
|
||||
var rect = bounds
|
||||
rect.size.height += bottomOverflow // Expand the height for overflow buffer
|
||||
let path = UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.topLeft, .topRight],
|
||||
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
|
||||
backgroundLayer.path = path.cgPath
|
||||
backgroundLayer.fillColor = color?.cgColor
|
||||
|
||||
private func updateShadow() {
|
||||
if shadowHidden == false {
|
||||
layer.shadowColor = shadowColor.cgColor
|
||||
layer.shadowOffset = shadowOffset
|
||||
layer.shadowOpacity = shadowOpacity
|
||||
layer.shadowRadius = shadowRadius
|
||||
if #available(iOS 11, *) {
|
||||
// For clear background. See also, https://github.com/SCENEE/FloatingPanel/pull/51.
|
||||
layer.shadowColor = shadowColor.cgColor
|
||||
layer.shadowOffset = shadowOffset
|
||||
layer.shadowOpacity = shadowOpacity
|
||||
layer.shadowRadius = shadowRadius
|
||||
} else {
|
||||
// Can't update `layer.shadow*` directly because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user should display shadow appropriately.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateContentViewMask() {
|
||||
private func updateCornerRadius() {
|
||||
guard containerView.layer.cornerRadius != 0.0 else {
|
||||
containerView.layer.masksToBounds = false
|
||||
return
|
||||
}
|
||||
containerView.layer.masksToBounds = true
|
||||
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.storyborad of Example/Maps.
|
||||
// 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.
|
||||
let maskLayer = CAShapeLayer()
|
||||
var rect = bounds
|
||||
rect.size.height += bottomOverflow
|
||||
let path = UIBezierPath(roundedRect: rect,
|
||||
byRoundingCorners: [.topLeft, .topRight],
|
||||
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
|
||||
maskLayer.path = path.cgPath
|
||||
contentView.layer.mask = maskLayer
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
} else {
|
||||
// Don't use `contentView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user can mask the content view manually in an application.
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
func set(bottomOverflow: CGFloat) {
|
||||
self.bottomOverflow = bottomOverflow
|
||||
updateLayers()
|
||||
updateContentViewMask()
|
||||
private func updateBorder() {
|
||||
containerView.layer.borderColor = borderColor?.cgColor
|
||||
containerView.layer.borderWidth = borderWidth
|
||||
}
|
||||
|
||||
func add(contentView: UIView) {
|
||||
containerView.addSubview(contentView)
|
||||
self.contentView = contentView
|
||||
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
func add(childView: UIView) {
|
||||
contentView.addSubview(childView)
|
||||
childView.frame = contentView.bounds
|
||||
childView.translatesAutoresizingMaskIntoConstraints = false
|
||||
let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor, constant: contentInsets.top)
|
||||
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: contentInsets.left)
|
||||
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: contentInsets.right)
|
||||
let heightConstraint = contentView.heightAnchor.constraint(equalTo: heightAnchor, constant: -containerTopInset)
|
||||
NSLayoutConstraint.activate([
|
||||
childView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
|
||||
childView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 0.0),
|
||||
childView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0.0),
|
||||
childView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
|
||||
topConstraint,
|
||||
leftConstraint,
|
||||
rightConstraint,
|
||||
heightConstraint,
|
||||
])
|
||||
self.contentViewTopConstraint = topConstraint
|
||||
self.contentViewLeftConstraint = leftConstraint
|
||||
self.contentViewRightConstraint = rightConstraint
|
||||
self.contentViewHeightConstraint = heightConstraint
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/11/21.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class FloatingPanelModalTransition: NSObject, UIViewControllerTransitioningDelegate {
|
||||
func animationController(forPresented presented: UIViewController,
|
||||
presenting: UIViewController,
|
||||
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return FloatingPanelModalPresentTransition()
|
||||
}
|
||||
|
||||
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||
return FloatingPanelModalDismissTransition()
|
||||
}
|
||||
|
||||
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
|
||||
return FloatingPanelPresentationController(presentedViewController: presented, presenting: presenting)
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelPresentationController: UIPresentationController {
|
||||
override func presentationTransitionWillBegin() {
|
||||
// Must call here even if duplicating on in containerViewWillLayoutSubviews()
|
||||
// Because it let the floating panel present correctly with the presentation animation
|
||||
addFloatingPanel()
|
||||
}
|
||||
|
||||
override func presentationTransitionDidEnd(_ completed: Bool) {
|
||||
// For non-animated presentation
|
||||
if let fpc = presentedViewController as? FloatingPanelController, fpc.position == .hidden {
|
||||
fpc.show(animated: false, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||
if let fpc = presentedViewController as? FloatingPanelController {
|
||||
// For non-animated dismissal
|
||||
if fpc.position != .hidden {
|
||||
fpc.hide(animated: false, completion: nil)
|
||||
}
|
||||
fpc.view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
override func containerViewWillLayoutSubviews() {
|
||||
guard
|
||||
let fpc = presentedViewController as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
/*
|
||||
* Layout the views managed by `FloatingPanelController` here for the
|
||||
* sake of the presentation and dismissal modally from the controller.
|
||||
*/
|
||||
addFloatingPanel()
|
||||
|
||||
// Forward touch events to the presenting view controller
|
||||
(fpc.view as? FloatingPanelPassThroughView)?.eventForwardingView = presentingViewController.view
|
||||
|
||||
// Set tap-to-dismiss in the backdrop view
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
|
||||
fpc.backdropView.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
|
||||
presentedViewController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func addFloatingPanel() {
|
||||
guard
|
||||
let containerView = self.containerView,
|
||||
let fpc = presentedViewController as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
containerView.addSubview(fpc.view)
|
||||
fpc.view.frame = containerView.bounds
|
||||
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .to) as? FloatingPanelController
|
||||
else { fatalError()}
|
||||
|
||||
let animator = fpc.behavior.addAnimator(fpc, to: fpc.layout.initialPosition)
|
||||
return TimeInterval(animator.duration)
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard
|
||||
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
fpc.show(animated: true) {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingPanelModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||
guard
|
||||
let fpc = transitionContext?.viewController(forKey: .from) as? FloatingPanelController
|
||||
else { fatalError()}
|
||||
|
||||
let animator = fpc.behavior.removeAnimator(fpc, from: fpc.position)
|
||||
return TimeInterval(animator.duration)
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
guard
|
||||
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
fpc.hide(animated: true) {
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/11/21.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class FloatingPanelPassThroughView: UIView {
|
||||
public weak var eventForwardingView: UIView?
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let hitView = super.hitTest(point, with: event)
|
||||
switch hitView {
|
||||
case self:
|
||||
return eventForwardingView?.hitTest(self.convert(point, to: eventForwardingView), with: event)
|
||||
default:
|
||||
return hitView
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,32 +6,30 @@
|
||||
import UIKit
|
||||
|
||||
public class GrabberHandleView: UIView {
|
||||
public struct Default {
|
||||
public static let width: CGFloat = 36.0
|
||||
public static let height: CGFloat = 5.0
|
||||
public static let barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
public var barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0) { didSet { backgroundColor = barColor } }
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
render()
|
||||
}
|
||||
|
||||
init() {
|
||||
let size = CGSize(width: Default.width,
|
||||
height: Default.height)
|
||||
super.init(frame: CGRect(origin: .zero, size: size))
|
||||
self.backgroundColor = Default.barColor
|
||||
render()
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = barColor
|
||||
}
|
||||
|
||||
private func render() {
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.cornerRadius = frame.size.height * 0.5
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
render()
|
||||
}
|
||||
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let view = super.hitTest(point, with: event)
|
||||
return view == self ? nil : view
|
||||
}
|
||||
|
||||
private func render() {
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.cornerRadius = frame.size.height * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1.0</string>
|
||||
<string>1.6.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
@@ -19,29 +19,17 @@ struct Logger {
|
||||
case info = 1
|
||||
case warning = 2
|
||||
case error = 3
|
||||
case fault = 4
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .debug: return "DEBUG"
|
||||
case .info: return "INFO"
|
||||
case .warning: return "WARNING"
|
||||
case .error: return "ERROR"
|
||||
case .fault: return "FAULT"
|
||||
}
|
||||
}
|
||||
var shortName: String {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug:
|
||||
return "D/"
|
||||
case .info:
|
||||
return "I/"
|
||||
case .warning:
|
||||
return "W/"
|
||||
return "Warning:"
|
||||
case .error:
|
||||
return "E/"
|
||||
case .fault:
|
||||
return "F/"
|
||||
return "Error:"
|
||||
}
|
||||
}
|
||||
@available(iOS 10.0, *)
|
||||
@@ -49,31 +37,40 @@ struct Logger {
|
||||
switch self {
|
||||
case .debug: return .debug
|
||||
case .info: return .info
|
||||
case .warning: return .info
|
||||
case .warning: return .default
|
||||
case .error: return .error
|
||||
case .fault: return .fault
|
||||
}
|
||||
}
|
||||
|
||||
public static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
|
||||
static func < (lhs: Logger.Level, rhs: Logger.Level) -> Bool {
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
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], function: String, line: UInt) {
|
||||
#if __FP_LOG
|
||||
_ = s.wait(timeout: .now() + 0.033)
|
||||
defer { s.signal() }
|
||||
|
||||
let extraMessage: String = arguments.map({ String(describing: $0) }).joined(separator: " ")
|
||||
let log = "\(level.shortName) \(message) \(extraMessage) (\(function):\(line))"
|
||||
let log: String = {
|
||||
switch level {
|
||||
case .debug:
|
||||
return "\(level.displayName) \(message) \(extraMessage) (\(function):\(line))"
|
||||
default:
|
||||
return "\(level.displayName) \(message) \(extraMessage)"
|
||||
}
|
||||
}()
|
||||
|
||||
hook?(log, level)
|
||||
|
||||
os_log("%@", log: osLog, type: level.osLogType, log)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func getPrettyFunction(_ function: String, _ file: String) -> String {
|
||||
@@ -85,7 +82,9 @@ struct Logger {
|
||||
}
|
||||
|
||||
func debug(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
#if __FP_LOG
|
||||
self.log(.debug, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
#endif
|
||||
}
|
||||
|
||||
func info(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
@@ -99,8 +98,4 @@ struct Logger {
|
||||
func error(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.error, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
|
||||
func fault(_ log: Any, _ arguments: Any..., function: String = #function, file: String = #file, line: UInt = #line) {
|
||||
self.log(.fault, log, arguments, function: getPrettyFunction(function, file), line: line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,18 +62,46 @@ extension UIView {
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
func disableAutoLayout() {
|
||||
let frame = self.frame
|
||||
translatesAutoresizingMaskIntoConstraints = true
|
||||
self.frame = frame
|
||||
}
|
||||
func enableAutoLayout() {
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}
|
||||
|
||||
#if __FP_LOG
|
||||
#if swift(>=4.2)
|
||||
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .began: return "Began"
|
||||
case .changed: return "Changed"
|
||||
case .failed: return "Failed"
|
||||
case .cancelled: return "Cancelled"
|
||||
case .ended: return "Endeded"
|
||||
case .possible: return "Possible"
|
||||
case .began: return "began"
|
||||
case .changed: return "changed"
|
||||
case .failed: return "failed"
|
||||
case .cancelled: return "cancelled"
|
||||
case .ended: return "endeded"
|
||||
case .possible: return "possible"
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
extension UIGestureRecognizerState: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .began: return "began"
|
||||
case .changed: return "changed"
|
||||
case .failed: return "failed"
|
||||
case .cancelled: return "cancelled"
|
||||
case .ended: return "endeded"
|
||||
case .possible: return "possible"
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extension UIScrollView {
|
||||
var contentOffsetZero: CGPoint {
|
||||
@@ -89,3 +117,10 @@ extension UISpringTimingParameters {
|
||||
self.init(mass: mass, stiffness: stiffness, damping: damp, initialVelocity: initialVelocity)
|
||||
}
|
||||
}
|
||||
|
||||
extension CGPoint {
|
||||
static var nan: CGPoint {
|
||||
return CGPoint(x: CGFloat.nan,
|
||||
y: CGFloat.nan)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/11/01.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
let rootVC = UIViewController(nibName: nil, bundle: nil)
|
||||
rootVC.view.backgroundColor = .gray
|
||||
|
||||
let window = UIWindow()
|
||||
window.rootViewController = rootVC
|
||||
window.makeKeyAndVisible()
|
||||
self.window = window
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Copyright © 2019 scenee. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="obG-Y5-kRd">
|
||||
<rect key="frame" x="0.0" y="626.5" width="375" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="TestingApp" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
|
||||
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="obG-Y5-kRd" secondAttribute="centerX" id="5cz-MP-9tL"/>
|
||||
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
|
||||
<constraint firstItem="obG-Y5-kRd" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="SfN-ll-jLj"/>
|
||||
<constraint firstAttribute="bottom" secondItem="obG-Y5-kRd" secondAttribute="bottom" constant="20" id="Y44-ml-fuU"/>
|
||||
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
|
||||
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="20" symbolic="YES" id="x7j-FC-K8j"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
class FloatingPanelControllerTests: XCTestCase {
|
||||
|
||||
override func setUp() {}
|
||||
|
||||
override func tearDown() {}
|
||||
|
||||
func test_warningRetainCycle() {
|
||||
let myVC = MyZombieViewController(nibName: nil, bundle: nil)
|
||||
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()
|
||||
}
|
||||
}
|
||||
myVC.loadViewIfNeeded()
|
||||
wait(for: [exp], timeout: 10)
|
||||
}
|
||||
|
||||
func test_addPanel() {
|
||||
guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else { fatalError() }
|
||||
|
||||
let fpc = FloatingPanelController()
|
||||
fpc.addPanel(toParent: rootVC)
|
||||
|
||||
waitRunLoop(secs: 1.0)
|
||||
XCTAssert(fpc.surfaceView.frame.minY == (fpc.view.bounds.height - fpc.layoutInsets.bottom) - fpc.layout.insetFor(position: .half)!)
|
||||
|
||||
fpc.move(to: .tip, animated: true)
|
||||
waitRunLoop(secs: 1.0)
|
||||
XCTAssert(fpc.surfaceView.frame.minY == (fpc.view.bounds.height - fpc.layoutInsets.bottom) - fpc.layout.insetFor(position: .tip)!)
|
||||
}
|
||||
}
|
||||
|
||||
func waitRunLoop(secs: TimeInterval = 0) {
|
||||
RunLoop.main.run(until: Date(timeIntervalSinceNow: secs))
|
||||
}
|
||||
|
||||
class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
|
||||
var fpc: FloatingPanelController?
|
||||
override func viewDidLoad() {
|
||||
fpc = FloatingPanelController(delegate: self)
|
||||
fpc?.addPanel(toParent: self)
|
||||
}
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
return self
|
||||
}
|
||||
|
||||
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
|
||||
return self
|
||||
}
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .full: return UIScreen.main.bounds.height == 667.0 ? 18.0 : 16.0
|
||||
case .half: return 262.0
|
||||
case .tip: return 69.0
|
||||
case .hidden: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2019/05/23.
|
||||
// Copyright © 2019 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class FloatingPanelTests: XCTestCase {
|
||||
|
||||
override func setUp() {}
|
||||
|
||||
override func tearDown() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2019/05/23.
|
||||
// Copyright © 2019 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanel
|
||||
|
||||
class FloatingPanelViewTests: XCTestCase {
|
||||
|
||||
override func setUp() {}
|
||||
|
||||
override func tearDown() {}
|
||||
|
||||
func test_surfaceView() {
|
||||
let surface = FloatingPanelSurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
|
||||
XCTAssert(surface.contentView == nil)
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssert(surface.grabberHandle.frame.minY == 6.0)
|
||||
XCTAssert(surface.grabberHandle.frame.width == surface.grabberHandleWidth)
|
||||
XCTAssert(surface.grabberHandle.frame.height == surface.grabberHandleHeight)
|
||||
surface.backgroundColor = .red
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssert(surface.backgroundColor == surface.containerView.backgroundColor)
|
||||
}
|
||||
|
||||
func test_surfaceView_cornderRaduis() {
|
||||
let surface = FloatingPanelSurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
|
||||
XCTAssert(surface.cornerRadius == 0.0)
|
||||
XCTAssert(surface.containerView.layer.masksToBounds == false)
|
||||
|
||||
surface.cornerRadius = 10.0
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssert(surface.cornerRadius == 10.0)
|
||||
XCTAssert(surface.containerView.layer.cornerRadius == 10.0)
|
||||
XCTAssert(surface.containerView.layer.masksToBounds == true)
|
||||
|
||||
surface.containerView.layer.cornerRadius = 12.0
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssert(surface.cornerRadius == 12.0)
|
||||
XCTAssert(surface.containerView.layer.masksToBounds == true)
|
||||
|
||||
surface.cornerRadius = 0.0
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssert(surface.cornerRadius == 0.0)
|
||||
XCTAssert(surface.containerView.layer.cornerRadius == 0.0)
|
||||
XCTAssert(surface.containerView.layer.masksToBounds == false)
|
||||
|
||||
surface.containerView.layer.cornerRadius = 12.0
|
||||
surface.setNeedsLayout()
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssert(surface.cornerRadius == 12.0)
|
||||
XCTAssert(surface.containerView.layer.masksToBounds == true)
|
||||
}
|
||||
|
||||
func test_surfaceView_border() {
|
||||
let surface = FloatingPanelSurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
|
||||
XCTAssert(surface.borderColor == nil)
|
||||
XCTAssert(surface.borderWidth == 0.0)
|
||||
|
||||
surface.borderColor = .red
|
||||
surface.borderWidth = 3.0
|
||||
surface.layoutIfNeeded()
|
||||
XCTAssert(surface.containerView.layer.borderColor == UIColor.red.cgColor)
|
||||
XCTAssert(surface.containerView.layer.borderWidth == 3.0)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Created by Shin Yamamoto on 2018/09/18.
|
||||
// Copyright © 2018 Shin Yamamoto. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import FloatingPanelController
|
||||
|
||||
class ViewTests: XCTestCase {
|
||||
|
||||
override func setUp() {}
|
||||
|
||||
override func tearDown() {}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://cocoapods.org/pods/FloatingPanel)
|
||||
[](https://swift.org/)
|
||||
[](https://swift.org/)
|
||||
[](https://swift.org/)
|
||||
|
||||
# FloatingPanel
|
||||
|
||||
@@ -23,10 +25,15 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
- [CocoaPods](#cocoapods)
|
||||
- [Carthage](#carthage)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Add a floating panel as a child view controller](#add-a-floating-panel-as-a-child-view-controller)
|
||||
- [Present a floating panel as a modality](#present-a-floating-panel-as-a-modality)
|
||||
- [View hierarchy](#view-hierarchy)
|
||||
- [Usage](#usage)
|
||||
- [Show/Hide a floating panel in a view with your view hierarchy](#showhide-a-floating-panel-in-a-view-with-your-view-hierarchy)
|
||||
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
|
||||
- [Change the initial position and height](#change-the-initial-position-and-height)
|
||||
- [Support your landscape layout](#support-your-landscape-layout)
|
||||
- [Use Intrinsic height layout](#use-intrinsic-height-layout)
|
||||
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
|
||||
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
|
||||
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
|
||||
@@ -52,6 +59,7 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
- [x] Layout customization for all trait environments(i.e. Landscape orientation support)
|
||||
- [x] Behavior customization
|
||||
- [x] Free from common issues of Auto Layout and gesture handling
|
||||
- [x] Modal presentation
|
||||
|
||||
Examples are here.
|
||||
|
||||
@@ -60,7 +68,9 @@ Examples are here.
|
||||
|
||||
## Requirements
|
||||
|
||||
FloatingPanel is written in Swift 4.2. Compatible with iOS 10.0+
|
||||
FloatingPanel is written in Swift 4.0+. It can be built by Xcode 9.4.1 or later. Compatible with iOS 10.0+.
|
||||
|
||||
✏️ The default Swift version is 4.0 because it avoids build errors with Carthage on each Xcode version from the source compatibility between Swift 4.0, 4.2 and 5.0.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -73,6 +83,8 @@ it, simply add the following line to your Podfile:
|
||||
pod 'FloatingPanel'
|
||||
```
|
||||
|
||||
✏️ To suppress "Swift Conversion" warnings in Xcode, please set a Swift version to `SWIFT_VERSION` for the project in your Podfile. It will be resolved in CocoaPods v1.7.0.
|
||||
|
||||
### Carthage
|
||||
|
||||
For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`:
|
||||
@@ -81,9 +93,10 @@ For [Carthage](https://github.com/Carthage/Carthage), add the following to your
|
||||
github "scenee/FloatingPanel"
|
||||
```
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Add a floating panel as a child view controller
|
||||
|
||||
```swift
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
@@ -112,15 +125,82 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
// Remove the views managed by the `FloatingPanelController` object from self.view.
|
||||
fpc.removePanelFromParent()
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Present a floating panel as a modality
|
||||
|
||||
```swift
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = ...
|
||||
fpc.set(contentViewController: contentVC)
|
||||
|
||||
fpc.isRemovalInteractionEnabled = true // Optional: Let it removable by a swipe-down
|
||||
|
||||
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.
|
||||
|
||||
✏️ FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Framework/Sources/FloatingPanelTransitioning.swift).
|
||||
|
||||
## View hierarchy
|
||||
|
||||
`FloatingPanelController` manages the views as the following view hierarchy.
|
||||
|
||||
```
|
||||
FloatingPanelController.view (FloatingPanelPassThroughView)
|
||||
├─ .backdropView (FloatingPanelBackdropView)
|
||||
└─ .surfaceView (FloatingPanelSurfaceView)
|
||||
├─ .containerView (UIView)
|
||||
│ └─ .contentView (FloatingPanelController.contentViewController.view)
|
||||
└─ .grabberHandle (GrabberHandleView)
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Show/Hide a floating panel in a view with your view hierarchy
|
||||
|
||||
```swift
|
||||
// Add the controller and the managed views to a view controller.
|
||||
// From the second time, just call `show(animated:completion)`.
|
||||
view.addSubview(fpc.view)
|
||||
|
||||
fpc.view.frame = view.bounds // MUST
|
||||
// In addition, Auto Layout constraints are highly recommended.
|
||||
// Because it makes the layout more robust on trait collection change.
|
||||
//
|
||||
// fpc.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
// NSLayoutConstraint.activate([...])
|
||||
//
|
||||
|
||||
parent.addChild(fpc)
|
||||
|
||||
// Show a floating panel to the initial position defined in your `FloatingPanelLayout` object.
|
||||
fpc.show(animated: true) {
|
||||
|
||||
// Only for the first time
|
||||
self.didMove(toParent: self)
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
// Hide it
|
||||
fpc.hide(animated: true) {
|
||||
|
||||
// Remove it if needed
|
||||
self.willMove(toParent: nil)
|
||||
self.view.removeFromSuperview()
|
||||
self.removeFromParent()
|
||||
}
|
||||
```
|
||||
|
||||
NOTE: `FloatingPanelController` wraps `show`/`hide` with `addPanel`/`removePanelFromParent` for easy-to-use. But `show`/`hide` are more convenience for your app.
|
||||
|
||||
### Customize the layout with `FloatingPanelLayout` protocol
|
||||
|
||||
#### Change the initial position and height
|
||||
@@ -131,7 +211,6 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
return MyFloatingPanelLayout()
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
class MyFloatingPanelLayout: FloatingPanelLayout {
|
||||
@@ -144,6 +223,7 @@ class MyFloatingPanelLayout: FloatingPanelLayout {
|
||||
case .full: return 16.0 // A top inset from safe area
|
||||
case .half: return 216.0 // A bottom inset from the safe area
|
||||
case .tip: return 44.0 // A bottom inset from the safe area
|
||||
default: return nil // Or `case .hidden: return nil`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +237,6 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
return (newCollection.verticalSizeClass == .compact) ? FloatingPanelLandscapeLayout() : nil // Returning nil indicates to use the default layout
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
class FloatingPanelLandscapeLayout: FloatingPanelLayout {
|
||||
@@ -178,13 +257,40 @@ class FloatingPanelLandscapeLayout: FloatingPanelLayout {
|
||||
|
||||
public func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
|
||||
return [
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuid.leftAnchor, constant: 8.0),
|
||||
surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0),
|
||||
surfaceView.widthAnchor.constraint(equalToConstant: 291),
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Use Intrinsic height layout
|
||||
|
||||
1. Lay out your content View with the intrinsic height size. For example, see "Detail View Controller scene"/"Intrinsic View Controller scene" of [Main.storyboard](https://github.com/SCENEE/FloatingPanel/blob/master/Examples/Samples/Sources/Base.lproj/Main.storyboard). The 'Stack View.bottom' constraint determines the intrinsic height.
|
||||
2. Create a layout that adopts and conforms to `FloatingPanelIntrinsicLayout` and use it.
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
...
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
return RemovablePanelLayout()
|
||||
}
|
||||
}
|
||||
|
||||
class RemovablePanelLayout: FloatingPanelIntrinsicLayout {
|
||||
var supportedPositions: Set<FloatingPanelPosition> {
|
||||
return [.full, .half]
|
||||
}
|
||||
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
|
||||
switch position {
|
||||
case .half: return 130.0
|
||||
default: return nil // Must return nil for .full
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Customize the behavior with `FloatingPanelBehavior` protocol
|
||||
|
||||
#### Modify your floating panel's interaction
|
||||
@@ -195,90 +301,67 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
|
||||
return FloatingPanelStocksBehavior()
|
||||
}
|
||||
...
|
||||
}
|
||||
...
|
||||
|
||||
class FloatingPanelStocksBehavior: FloatingPanelBehavior {
|
||||
var velocityThreshold: CGFloat {
|
||||
return 15.0
|
||||
}
|
||||
|
||||
...
|
||||
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
|
||||
let damping = self.damping(with: velocity)
|
||||
let springTiming = UISpringTimingParameters(dampingRatio: damping, initialVelocity: velocity)
|
||||
return UIViewPropertyAnimator(duration: 0.5, timingParameters: springTiming)
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Use a custom grabber handle
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController {
|
||||
...
|
||||
override func viewDidLoad() {
|
||||
...
|
||||
let myGrabberHandleView = MyGrabberHandleView()
|
||||
fpc.surfaceView.grabberHandle.isHidden = true
|
||||
fpc.surfaceView.addSubview(myGrabberHandleView)
|
||||
}
|
||||
...
|
||||
}
|
||||
let myGrabberHandleView = MyGrabberHandleView()
|
||||
fpc.surfaceView.grabberHandle.isHidden = true
|
||||
fpc.surfaceView.addSubview(myGrabberHandleView)
|
||||
```
|
||||
|
||||
### Add tap gestures to the surface or backdrop views
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
override func viewDidLoad() {
|
||||
...
|
||||
override func viewDidLoad() {
|
||||
...
|
||||
surfaceTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleSurface(tapGesture:)))
|
||||
fpc.surfaceView.addGestureRecognizer(surfaceTapGesture)
|
||||
surfaceTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleSurface(tapGesture:)))
|
||||
fpc.surfaceView.addGestureRecognizer(surfaceTapGesture)
|
||||
|
||||
backdropTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
|
||||
fpc.backdropView.addGestureRecognizer(backdropTapGesture)
|
||||
backdropTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
|
||||
fpc.backdropView.addGestureRecognizer(backdropTapGesture)
|
||||
|
||||
surfaceTapGesture.isEnabled = (fpc.position == .tip)
|
||||
...
|
||||
}
|
||||
...
|
||||
// Enable `surfaceTapGesture` only at `tip` position
|
||||
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
|
||||
surfaceTapGesture.isEnabled = (vc.position == .tip)
|
||||
}
|
||||
surfaceTapGesture.isEnabled = (fpc.position == .tip)
|
||||
}
|
||||
|
||||
// Enable `surfaceTapGesture` only at `tip` position
|
||||
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
|
||||
surfaceTapGesture.isEnabled = (vc.position == .tip)
|
||||
}
|
||||
```
|
||||
|
||||
### Create an additional floating panel for a detail
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
var searchPanelVC: FloatingPanelController!
|
||||
var detailPanelVC: FloatingPanelController!
|
||||
override func viewDidLoad() {
|
||||
// Setup Search panel
|
||||
self.searchPanelVC = FloatingPanelController()
|
||||
|
||||
override func viewDidLoad() {
|
||||
// Setup Search panel
|
||||
self.searchPanelVC = FloatingPanelController()
|
||||
let searchVC = SearchViewController()
|
||||
self.searchPanelVC.set(contentViewController: searchVC)
|
||||
self.searchPanelVC.track(scrollView: contentVC.tableView)
|
||||
|
||||
let searchVC = SearchViewController()
|
||||
self.searchPanelVC.set(contentViewController: searchVC)
|
||||
self.searchPanelVC.track(scrollView: contentVC.tableView)
|
||||
self.searchPanelVC.addPanel(toParent: self)
|
||||
|
||||
self.searchPanelVC.addPanel(toParent: self)
|
||||
// Setup Detail panel
|
||||
self.detailPanelVC = FloatingPanelController()
|
||||
|
||||
// Setup Detail panel
|
||||
self.detailPanelVC = FloatingPanelController()
|
||||
let contentVC = ContentViewController()
|
||||
self.detailPanelVC.set(contentViewController: contentVC)
|
||||
self.detailPanelVC.track(scrollView: contentVC.scrollView)
|
||||
|
||||
let contentVC = ContentViewController()
|
||||
self.detailPanelVC.set(contentViewController: contentVC)
|
||||
self.detailPanelVC.track(scrollView: contentVC.scrollView)
|
||||
|
||||
self.detailPanelVC.addPanel(toParent: self)
|
||||
}
|
||||
...
|
||||
self.detailPanelVC.addPanel(toParent: self)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -287,15 +370,15 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
In the following example, I move a floating panel to full or half position while opening or closing a search bar like Apple Maps.
|
||||
|
||||
```swift
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
...
|
||||
fpc.move(to: .half, animated: true)
|
||||
}
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
...
|
||||
fpc.move(to: .half, animated: true)
|
||||
}
|
||||
|
||||
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
|
||||
...
|
||||
fpc.move(to: .full, animated: true)
|
||||
}
|
||||
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
|
||||
...
|
||||
fpc.move(to: .full, animated: true)
|
||||
}
|
||||
```
|
||||
|
||||
### Work your contents together with a floating panel behavior
|
||||
@@ -315,7 +398,6 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
searchVC.hideHeader()
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
@@ -323,7 +405,7 @@ class ViewController: UIViewController, FloatingPanelControllerDelegate {
|
||||
|
||||
### 'Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller
|
||||
|
||||
'Show' or 'Show Detail' segues from a content view controller will be managed by a view controller(hereinafter called 'master VC') adding a floating panel. Because a floating panel is just a subview of the master VC.
|
||||
'Show' or 'Show Detail' segues from a content view controller will be managed by a view controller(hereinafter called 'master VC') adding a floating panel. Because a floating panel is just a subview of the master VC(except for modality).
|
||||
|
||||
`FloatingPanelController` has no way to manage a stack of view controllers like `UINavigationController`. If so, it would be so complicated and the interface will become `UINavigationController`. This component should not have the responsibility to manage the stack.
|
||||
|
||||
@@ -346,17 +428,16 @@ class ViewController: UIViewController {
|
||||
|
||||
secondFpc.addPanel(toParent: self)
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
A `FloatingPanelController` object proxies an action for `show(_:sender)` to the master VC. That's why the master VC can handle a destination view controller of a 'Show' or 'Show Detail' segue and you can hook `show(_:sender)` to show a secondally floating panel set the destination view controller to the content.
|
||||
A `FloatingPanelController` object proxies an action for `show(_:sender)` to the master VC. That's why the master VC can handle a destination view controller of a 'Show' or 'Show Detail' segue and you can hook `show(_:sender)` to show a secondary floating panel set the destination view controller to the content.
|
||||
|
||||
It's a great way to decouple between a floating panel and the content VC.
|
||||
|
||||
### 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.
|
||||
* 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() {
|
||||
@@ -367,11 +448,11 @@ override func viewDidLayoutSubviews() {
|
||||
}
|
||||
}
|
||||
```
|
||||
* 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's Auto Layout settings of UIVisualEffectView in Main.storyborad.
|
||||
* 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.
|
||||
|
||||
## Author
|
||||
|
||||
Shin Yamamoto <shin@scenee.com>
|
||||
Shin Yamamoto <shin@scenee.com> | [@scenee](https://twitter.com/scenee)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user