Compare commits

..

4 Commits

Author SHA1 Message Date
Shin Yamamoto 837edc6abf Update sample 2019-06-01 23:36:20 +09:00
Shin Yamamoto 20fda3888f GrabberHandleView supports UIAppearance 2019-06-01 23:36:20 +09:00
Shin Yamamoto 4728c6848b FloatingPanelBackdropView supports UIAppearance 2019-06-01 23:36:20 +09:00
Shin Yamamoto 70c945c9f5 FloatingPanelSurfaceView supports UIAppearance 2019-06-01 23:36:20 +09:00
32 changed files with 833 additions and 2532 deletions
+5 -18
View File
@@ -2,7 +2,7 @@
>
> Please remove this line and everything above it before submitting.
### Description
### Short description
### Expected behavior
@@ -12,29 +12,16 @@
**Code example that reproduces the issue**
**How do you display panel(s)?**
* Add as child view controllers
* Present modally
**How many panels do you displays?**
* 1
* 2+
### Environment
**Library version**
**Installation method**
* CocoaPods
* Carthage
* Swift Package Manager
- [ ] CocoaPods
- [ ] Carthage
- [ ] Git submodules
**iOS version(s)**
**Xcode version**
-3
View File
@@ -30,9 +30,6 @@ xcuserdata/
*.dSYM.zip
*.dSYM
## Swift Package Manager Specific
.swiftpm/
## Playgrounds
timeline.xctimeline
playground.xcworkspace
+19 -28
View File
@@ -22,40 +22,22 @@ jobs:
osx_image: xcode10
name: "Swift 4.2"
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.0 clean build
osx_image: xcode10.3
osx_image: xcode10.2
name: "Swift 5.0"
- script: xcodebuild -scheme FloatingPanel SWIFT_VERSION=5.1 SUPPORTS_MACCATALYST=NO clean build
# SUPPORTS_MACCATALYST=NO because Xcode 11 runs on macOS 10.14 in Travis CI
osx_image: xcode11
name: "Swift 5.1"
- stage: "Tests"
osx_image: xcode10.3
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.3
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.3
osx_image: xcode10.2
name: "iPhone X (iOS 12.2)"
- script: xcodebuild clean test -scheme FloatingPanel -workspace FloatingPanel.xcworkspace -destination 'platform=iOS Simulator,OS=13.0,name=iPhone 11'
osx_image: xcode11
name: "iPhone X (iOS 13.0)"
- stage: Build examples
osx_image: xcode11
script: xcodebuild -scheme Maps -sdk iphonesimulator clean build
name: "Maps"
- script: xcodebuild -scheme Stocks -sdk iphonesimulator clean build
osx_image: xcode11
name: "Stocks"
- script: xcodebuild -scheme Samples -sdk iphonesimulator clean build
osx_image: xcode11
name: "Samples"
- stage: Carthage
osx_image: xcode11
osx_image: xcode10.2
before_install:
- brew update
- brew outdated carthage || brew upgrade carthage
@@ -63,9 +45,18 @@ jobs:
- carthage build --no-skip-current
- stage: CocoaPods
osx_image: xcode11
before_install:
- gem install cocoapods
osx_image: xcode10.2
script:
- pod spec lint --allow-warnings
- pod lib lint --allow-warnings
- pod spec lint
- pod lib lint
- 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"
+6 -18
View File
@@ -7,14 +7,13 @@
objects = {
/* Begin PBXBuildFile section */
543844BD23D2BE2000D5EDE4 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 543844BC23D2BE2000D5EDE4 /* MapKit.framework */; };
549D23D2233C77D5008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */; };
549D23D3233C77D5008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B5112A216C3D840033A6F3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51129216C3D840033A6F3 /* AppDelegate.swift */; };
54B5112C216C3D840033A6F3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B5112B216C3D840033A6F3 /* ViewController.swift */; };
54B5112F216C3D840033A6F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54B5112D216C3D840033A6F3 /* Main.storyboard */; };
54B51131216C3D860033A6F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54B51130216C3D860033A6F3 /* Assets.xcassets */; };
54B51134216C3D860033A6F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54B51132216C3D860033A6F3 /* LaunchScreen.storyboard */; };
54B5113F216C407F0033A6F3 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113E216C407F0033A6F3 /* FloatingPanel.framework */; };
54B51140216C407F0033A6F3 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113E216C407F0033A6F3 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -24,7 +23,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
549D23D3233C77D5008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
54B51140216C407F0033A6F3 /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -32,8 +31,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
543844BC23D2BE2000D5EDE4 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54B51126216C3D840033A6F3 /* Maps.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Maps.app; sourceTree = BUILT_PRODUCTS_DIR; };
54B51129216C3D840033A6F3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
54B5112B216C3D840033A6F3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@@ -41,6 +38,7 @@
54B51130216C3D860033A6F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
54B51133216C3D860033A6F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
54B51135216C3D860033A6F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54B5113E216C407F0033A6F3 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -48,29 +46,19 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
543844BD23D2BE2000D5EDE4 /* MapKit.framework in Frameworks */,
549D23D2233C77D5008EF4D7 /* FloatingPanel.framework in Frameworks */,
54B5113F216C407F0033A6F3 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
543844BB23D2BE1F00D5EDE4 /* Frameworks */ = {
isa = PBXGroup;
children = (
543844BC23D2BE2000D5EDE4 /* MapKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
54B5111D216C3D840033A6F3 = {
isa = PBXGroup;
children = (
549D23D1233C77D5008EF4D7 /* FloatingPanel.framework */,
54B5113E216C407F0033A6F3 /* FloatingPanel.framework */,
54B51128216C3D840033A6F3 /* Maps */,
54B51127216C3D840033A6F3 /* Products */,
543844BB23D2BE1F00D5EDE4 /* Frameworks */,
);
sourceTree = "<group>";
};
+7 -16
View File
@@ -107,7 +107,6 @@ class ViewController: UIViewController, MKMapViewDelegate, UISearchBarDelegate,
let progress = max(0.0, min((tipY - y) / 44.0, 1.0))
self.searchVC.tableView.alpha = progress
}
debugPrint("NearbyPosition : ",vc.nearbyPosition)
}
func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) {
@@ -149,9 +148,11 @@ class SearchPanelViewController: UIViewController, UITableViewDataSource, UITabl
tableView.dataSource = self
tableView.delegate = self
searchBar.placeholder = "Search for a place or address"
searchBar.setSearchText(fontSize: 15.0)
let textField = searchBar.value(forKey: "_searchField") as! UITextField
textField.font = UIFont(name: textField.font!.fontName, size: 15.0)
hideHeader()
}
override func viewDidLayoutSubviews() {
@@ -178,7 +179,7 @@ class SearchPanelViewController: UIViewController, UITableViewDataSource, UITabl
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@@ -189,10 +190,12 @@ class SearchPanelViewController: UIViewController, UITableViewDataSource, UITabl
cell.iconImageView.image = UIImage(named: "mark")
cell.titleLabel.text = "Marked Location"
cell.subTitleLabel.text = "Golden Gate Bridge, San Francisco"
default:
case 1:
cell.iconImageView.image = UIImage(named: "like")
cell.titleLabel.text = "Favorites"
cell.subTitleLabel.text = "0 Places"
default:
break
}
}
return cell
@@ -271,15 +274,3 @@ class SearchHeaderView: UIView {
self.clipsToBounds = true
}
}
extension UISearchBar {
func setSearchText(fontSize: CGFloat) {
if #available(iOS 13, *) {
let font = searchTextField.font
searchTextField.font = font?.withSize(fontSize)
} else {
let textField = value(forKey: "_searchField") as! UITextField
textField.font = textField.font?.withSize(fontSize)
}
}
}
@@ -14,9 +14,9 @@
545DB9F821511E6400CA77B8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 545DB9F621511E6400CA77B8 /* LaunchScreen.storyboard */; };
545DBA0321511E6400CA77B8 /* SampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0221511E6400CA77B8 /* SampleTests.swift */; };
545DBA0E21511E6400CA77B8 /* SampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DBA0D21511E6400CA77B8 /* SampleUITests.swift */; };
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; };
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CA233C7779008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B51116216AFE5F0033A6F3 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* UIExtensions.swift */; };
54B5113C216C40670033A6F3 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113B216C40670033A6F3 /* FloatingPanel.framework */; };
54B5113D216C40670033A6F3 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54B5113B216C40670033A6F3 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* UIComponents.swift */; };
/* End PBXBuildFile section */
@@ -38,13 +38,13 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
549D23CD233C7779008EF4D7 /* Embed Frameworks */ = {
54B5111C216C3B300033A6F3 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
549D23CC233C7779008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
54B5113D216C40670033A6F3 /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -65,8 +65,8 @@
545DBA0921511E6400CA77B8 /* SamplesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
545DBA0D21511E6400CA77B8 /* SampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleUITests.swift; sourceTree = "<group>"; };
545DBA0F21511E6400CA77B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54B51115216AFE5F0033A6F3 /* UIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIExtensions.swift; sourceTree = "<group>"; };
54B5113B216C40670033A6F3 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
54CDC5D7215BBE23007D205C /* UIComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIComponents.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -75,7 +75,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
549D23CB233C7779008EF4D7 /* FloatingPanel.framework in Frameworks */,
54B5113C216C40670033A6F3 /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -99,11 +99,12 @@
545DB9E121511E6300CA77B8 = {
isa = PBXGroup;
children = (
549D23CA233C7779008EF4D7 /* FloatingPanel.framework */,
54B5113B216C40670033A6F3 /* FloatingPanel.framework */,
545DB9EC21511E6300CA77B8 /* Sources */,
545DBA0121511E6400CA77B8 /* Tests */,
545DBA0C21511E6400CA77B8 /* UITests */,
545DB9EB21511E6300CA77B8 /* Products */,
545DBA1B2151CC1000CA77B8 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -150,6 +151,13 @@
path = UITests;
sourceTree = "<group>";
};
545DBA1B2151CC1000CA77B8 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -161,7 +169,7 @@
545DB9E621511E6300CA77B8 /* Sources */,
545DB9E721511E6300CA77B8 /* Frameworks */,
545DB9E821511E6300CA77B8 /* Resources */,
549D23CD233C7779008EF4D7 /* Embed Frameworks */,
54B5111C216C3B300033A6F3 /* Embed Frameworks */,
);
buildRules = (
);
@@ -294,7 +302,7 @@
);
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";
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 */
@@ -4,8 +4,18 @@
//
import UIKit
import FloatingPanel
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FloatingPanelSurfaceView.appearance().shadowHidden = false
FloatingPanelSurfaceView.appearance().cornerRadius = 6.0
// FloatingPanelSurfaceView.appearance().backgroundColor = .lightGray
// FloatingPanelBackdropView.appearance().backgroundColor = .red
// GrabberHandleView.appearance().barColor = .red
return true
}
}
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RoN-h0-uBD">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<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="retina5_9" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -11,7 +13,7 @@
<!--Navigation Controller-->
<scene sceneID="Cjh-iX-VQw">
<objects>
<navigationController storyboardIdentifier="RootNavigationController" id="RoN-h0-uBD" sceneMemberID="viewController">
<navigationController id="RoN-h0-uBD" sceneMemberID="viewController">
<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"/>
@@ -33,18 +35,18 @@
<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="812"/>
<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="43.666667938232422"/>
<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.666667938232422"/>
<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.666667938232422"/>
<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"/>
@@ -60,7 +62,7 @@
<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="39L-Nq-qfp" secondAttribute="leading" id="Z6Y-Dc-cei"/>
<constraint firstItem="7IS-PU-x0P" firstAttribute="bottom" secondItem="Smh-Bd-AAc" secondAttribute="bottom" id="fNW-DP-lhV"/>
<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="39L-Nq-qfp"/>
@@ -85,35 +87,35 @@
<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.33333333333334"/>
<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="181.33333333333334"/>
<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="21"/>
<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="37" width="130" height="25"/>
<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="78" width="264.66666666666663" height="38"/>
<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="8.9999999999999982" width="89.666666666666671" height="20.333333333333329"/>
<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.66666666666666" y="3.6666666666666714" width="50.999999999999972" height="31"/>
<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>
@@ -121,16 +123,16 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="126" translatesAutoresizingMaskIntoConstraints="NO" id="ZtZ-Dz-4cC">
<rect key="frame" x="23.333333333333343" y="132" width="264.66666666666663" height="49.333333333333343"/>
<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="49.333333333333336"/>
<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="49.333333333333336"/>
<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>
@@ -307,22 +309,22 @@
<objects>
<viewController storyboardIdentifier="ModalViewController" id="bYI-y3-Rzb" customClass="ModalViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="qwo-GK-p1U">
<rect key="frame" x="0.0" y="0.0" width="375" height="724"/>
<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="724" 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="0.0" 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="134.66666666666666" y="88" width="106" height="326"/>
<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"/>
@@ -345,15 +347,8 @@
<action selector="moveToTipWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="BmL-91-9ai"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="swr-XM-GzZ">
<rect key="frame" x="0.0" y="222" width="106" height="30"/>
<state key="normal" title="Move to hidden"/>
<connections>
<action selector="moveToHiddenWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="jfJ-0f-fdk"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="szf-HE-QTk">
<rect key="frame" x="0.0" y="296" width="96" height="30"/>
<rect key="frame" x="0.0" y="222" width="96" height="30"/>
<state key="normal" title="Update layout"/>
<connections>
<action selector="updateLayout:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="Woz-a7-YMJ"/>
@@ -522,7 +517,7 @@
</constraints>
</view>
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kva-Z7-0qY" customClass="OnSafeAreaView" customModule="Samples" customModuleProvider="target">
<rect key="frame" x="0.0" y="44" width="375" height="734"/>
<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">
@@ -535,23 +530,6 @@
<action selector="closeWithSender:" destination="YC8-ae-15L" eventType="touchUpInside" id="Z2v-19-S5k"/>
</connections>
</button>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="qux-uG-4o2">
<rect key="frame" x="8" y="52" width="148.33333333333334" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="fitToBounds" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7lq-d3-PKi">
<rect key="frame" x="0.0" y="5.3333333333333357" width="91.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" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0MA-lV-KjS">
<rect key="frame" x="99.333333333333329" y="0.0" width="50.999999999999986" height="31"/>
<connections>
<action selector="modeChanged:" destination="YC8-ae-15L" eventType="valueChanged" id="IQ8-u2-Rib"/>
</connections>
</switch>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="tP3-oJ-4EB">
<rect key="frame" x="130.66666666666666" y="132" width="114" height="134"/>
<subviews>
@@ -592,11 +570,9 @@
<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="qux-uG-4o2" firstAttribute="top" secondItem="aOK-7l-cA6" secondAttribute="top" constant="8" id="naa-cf-ZIc"/>
<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"/>
<constraint firstItem="qux-uG-4o2" firstAttribute="leading" secondItem="g7l-kO-y7q" secondAttribute="leading" constant="8" id="zXb-R9-bMO"/>
</constraints>
<viewLayoutGuide key="safeArea" id="aOK-7l-cA6"/>
<connections>
@@ -608,8 +584,6 @@
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="closeButton" destination="noi-1a-5bZ" id="eWQ-ha-8y7"/>
<outlet property="intrinsicHeightConstraint" destination="vKQ-h9-uKt" id="QpA-WD-b17"/>
<outlet property="modeChangeView" destination="qux-uG-4o2" id="1Nq-fE-dXw"/>
<segue destination="bYI-y3-Rzb" kind="show" identifier="ShowSegue" id="r1P-2i-NDe"/>
</connections>
</viewController>
@@ -630,7 +604,7 @@
</connections>
</pongPressGestureRecognizer>
</objects>
<point key="canvasLocation" x="653.60000000000002" y="733.74384236453204"/>
<point key="canvasLocation" x="655" y="734"/>
</scene>
<!--Debug Text View Controller-->
<scene sceneID="Bkq-O7-q4A">
+66 -256
View File
@@ -17,18 +17,13 @@ class SampleListViewController: UIViewController {
case trackingTextView
case showDetail
case showModal
case showPanelModal
case showMultiPanelModal
case showPanelInSheetModal
case showFloatingPanelModal
case showTabBar
case showPageView
case showPageContentView
case showNestedScrollView
case showRemovablePanel
case showIntrinsicView
case showContentInset
case showContainerMargins
case showNavigationController
var name: String {
switch self {
@@ -36,18 +31,13 @@ class SampleListViewController: UIViewController {
case .trackingTextView: return "Scroll tracking(TextView)"
case .showDetail: return "Show Detail Panel"
case .showModal: return "Show Modal"
case .showPanelModal: return "Show Panel Modal"
case .showMultiPanelModal: return "Show Multi Panel Modal"
case .showPanelInSheetModal: return "Show Panel in Sheet Modal"
case .showFloatingPanelModal: return "Show Floating Panel Modal"
case .showTabBar: return "Show Tab Bar"
case .showPageView: return "Show Page View"
case .showPageContentView: return "Show Page Content View"
case .showNestedScrollView: return "Show Nested ScrollView"
case .showRemovablePanel: return "Show Removable Panel"
case .showIntrinsicView: return "Show Intrinsic View"
case .showContentInset: return "Show with ContentInset"
case .showContainerMargins: return "Show with ContainerMargins"
case .showNavigationController: return "Show Navigation Controller"
}
}
@@ -57,18 +47,13 @@ class SampleListViewController: UIViewController {
case .trackingTextView: return "ConsoleViewController"
case .showDetail: return "DetailViewController"
case .showModal: return "ModalViewController"
case .showMultiPanelModal: return nil
case .showPanelInSheetModal: return nil
case .showPanelModal: return nil
case .showFloatingPanelModal: return nil
case .showTabBar: return "TabBarViewController"
case .showPageView: return nil
case .showPageContentView: return nil
case .showNestedScrollView: return "NestedScrollViewController"
case .showRemovablePanel: return "DetailViewController"
case .showIntrinsicView: return "IntrinsicViewController"
case .showContentInset: return nil
case .showContainerMargins: return nil
case .showNavigationController: return "RootNavigationController"
}
}
}
@@ -82,14 +67,24 @@ class SampleListViewController: UIViewController {
var mainPanelObserves: [NSKeyValueObservation] = []
var settingsObserves: [NSKeyValueObservation] = []
var pages: [UIViewController] = []
lazy var pages: [UIViewController] = {
let page1 = FloatingPanelController(delegate: self)
page1.view.backgroundColor = .blue
page1.show()
let page2 = FloatingPanelController(delegate: self)
page2.view.backgroundColor = .red
page2.show()
let page3 = FloatingPanelController(delegate: self)
page3.view.backgroundColor = .green
page3.show()
return [page1, page2, page3]
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
automaticallyAdjustsScrollViewInsets = false
let searchController = UISearchController(searchResultsController: nil)
if #available(iOS 11.0, *) {
@@ -102,14 +97,11 @@ class SampleListViewController: UIViewController {
let contentVC = DebugTableViewController()
addMainPanel(with: contentVC)
var insets = UIEdgeInsets.zero
insets.bottom += 69.0
tableView.contentInset = insets
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
if let observation = navigationController?.navigationBar.observe(\.prefersLargeTitles, changeHandler: { (bar, _) in
self.tableView.reloadData()
@@ -127,15 +119,11 @@ class SampleListViewController: UIViewController {
func addMainPanel(with contentVC: UIViewController) {
mainPanelObserves.removeAll()
let oldMainPanelVC = mainPanelVC
// Initialize FloatingPanelController
mainPanelVC = FloatingPanelController()
mainPanelVC.delegate = self
mainPanelVC.contentInsetAdjustmentBehavior = .always
mainPanelVC.surfaceView.cornerRadius = 6.0
mainPanelVC.surfaceView.shadowHidden = false
// Set a content view controller
mainPanelVC.set(contentViewController: contentVC)
// Enable tap-to-hide and removal interaction
@@ -145,17 +133,11 @@ class SampleListViewController: UIViewController {
tapGesture.cancelsTouchesInView = false
tapGesture.numberOfTapsRequired = 2
mainPanelVC.surfaceView.addGestureRecognizer(tapGesture)
case .showPageContentView:
if let page = (mainPanelVC.contentViewController as? UIPageViewController)?.viewControllers?.first {
mainPanelVC.track(scrollView: (page as! DebugTableViewController).tableView)
}
case .showRemovablePanel, .showIntrinsicView:
mainPanelVC.isRemovalInteractionEnabled = true
let backdropTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
mainPanelVC.backdropView.addGestureRecognizer(backdropTapGesture)
case .showNavigationController:
mainPanelVC.contentInsetAdjustmentBehavior = .never
default:
break
}
@@ -173,23 +155,12 @@ class SampleListViewController: UIViewController {
mainPanelVC.track(scrollView: contentVC.tableView)
case let contentVC as NestedScrollViewController:
mainPanelVC.track(scrollView: contentVC.scrollView)
case let navVC as UINavigationController:
if let rootVC = (navVC.topViewController as? SampleListViewController) {
rootVC.loadViewIfNeeded()
mainPanelVC.track(scrollView: rootVC.tableView)
}
default:
break
}
// Add FloatingPanel to self.view
if let oldMainPanelVC = oldMainPanelVC {
oldMainPanelVC.removePanelFromParent(animated: true, completion: {
self.mainPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
})
} else {
mainPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
}
mainPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
}
@objc
@@ -276,8 +247,6 @@ extension SampleListViewController: UITableViewDelegate {
}()
self.currentMenu = menu
detailPanelVC?.removePanelFromParent(animated: true, completion: nil)
detailPanelVC = nil
switch menu {
case .showDetail:
@@ -285,7 +254,6 @@ extension SampleListViewController: UITableViewDelegate {
// Initialize FloatingPanelController
detailPanelVC = FloatingPanelController()
detailPanelVC.delegate = self
// Initialize FloatingPanelController and add the view
detailPanelVC.surfaceView.cornerRadius = 6.0
@@ -294,24 +262,13 @@ extension SampleListViewController: UITableViewDelegate {
// Set a content view controller
detailPanelVC.set(contentViewController: contentVC)
detailPanelVC.contentMode = .fitToBounds
(contentVC as? DetailViewController)?.intrinsicHeightConstraint.priority = .defaultLow
// Add FloatingPanel to self.view
detailPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
case .showModal, .showTabBar:
let modalVC = contentVC
modalVC.modalPresentationStyle = .fullScreen
present(modalVC, animated: true, completion: nil)
case .showPageView:
pages = [UIColor.blue, .red, .green].compactMap({ (color) -> UIViewController in
let page = FloatingPanelController(delegate: self)
page.view.backgroundColor = color
page.show()
return page
})
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
let closeButton = UIButton(type: .custom)
pageVC.view.addSubview(closeButton)
@@ -324,21 +281,11 @@ extension SampleListViewController: UITableViewDelegate {
])
pageVC.dataSource = self
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
pageVC.modalPresentationStyle = .fullScreen
present(pageVC, animated: true, completion: nil)
case .showPageContentView:
pages = [DebugTableViewController(), DebugTableViewController(), DebugTableViewController()]
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
pageVC.dataSource = self
pageVC.delegate = self
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
self.addMainPanel(with: pageVC)
case .showPanelModal:
case .showFloatingPanelModal:
let fpc = FloatingPanelController()
let contentVC = self.storyboard!.instantiateViewController(withIdentifier: "DetailViewController")
contentVC.loadViewIfNeeded()
(contentVC as? DetailViewController)?.modeChangeView.isHidden = true
fpc.set(contentViewController: contentVC)
fpc.delegate = self
@@ -348,53 +295,23 @@ extension SampleListViewController: UITableViewDelegate {
fpc.isRemovalInteractionEnabled = true
self.present(fpc, animated: true, completion: nil)
case .showMultiPanelModal:
let fpc = MultiPanelController()
self.present(fpc, animated: true, completion: nil)
case .showPanelInSheetModal:
let fpc = FloatingPanelController()
let contentVC = UIViewController()
fpc.set(contentViewController: contentVC)
fpc.delegate = self
fpc.surfaceView.cornerRadius = 38.5
fpc.surfaceView.shadowHidden = false
fpc.isRemovalInteractionEnabled = true
let mvc = UIViewController()
mvc.view.backgroundColor = UIColor(displayP3Red: 2/255, green: 184/255, blue: 117/255, alpha: 1.0)
fpc.addPanel(toParent: mvc)
self.present(mvc, animated: true, completion: nil)
case .showContentInset:
let contentViewController = UIViewController()
contentViewController.view.backgroundColor = .green
let fpc = FloatingPanelController()
fpc.set(contentViewController: contentViewController)
fpc.surfaceView.contentInsets = .init(top: 20, left: 20, bottom: 20, right: 20)
fpc.delegate = self
fpc.isRemovalInteractionEnabled = true
self.present(fpc, animated: true, completion: nil)
case .showContainerMargins:
let fpc = FloatingPanelController()
fpc.surfaceView.cornerRadius = 38.5
fpc.surfaceView.backgroundColor = .red
fpc.surfaceView.containerMargins = .init(top: 24.0, left: 8.0, bottom: layoutInsets.bottom, right: 8.0)
#if swift(>=5.1) // Actually Xcode 11 or later
if #available(iOS 13.0, *) {
fpc.surfaceView.layer.cornerCurve = .continuous
}
#endif
fpc.surfaceView.contentInsets = .init(top: 20, left: 20, bottom: 0, right: 20)
fpc.delegate = self
fpc.isRemovalInteractionEnabled = true
self.present(fpc, animated: true, completion: nil)
default:
self.addMainPanel(with: contentVC)
detailPanelVC?.removePanelFromParent(animated: true, completion: nil)
mainPanelVC?.removePanelFromParent(animated: true) {
self.addMainPanel(with: contentVC)
}
}
}
@@ -404,14 +321,6 @@ extension SampleListViewController: UITableViewDelegate {
}
extension SampleListViewController: FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackedScrollView: UIScrollView) -> CGPoint {
if currentMenu == .showNavigationController, #available(iOSApplicationExtension 11.0, *) {
// 148.0 is the SafeArea's top value for a navigation bar with a large title.
return CGPoint(x: 0.0, y: 0.0 - trackedScrollView.contentInset.top - 148.0)
}
return CGPoint(x: 0.0, y: 0.0 - trackedScrollView.contentInset.top)
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
if vc == settingsPanelVC {
return IntrinsicPanelLayout()
@@ -422,15 +331,13 @@ extension SampleListViewController: FloatingPanelControllerDelegate {
return newCollection.verticalSizeClass == .compact ? RemovablePanelLandscapeLayout() : RemovablePanelLayout()
case .showIntrinsicView:
return IntrinsicPanelLayout()
case .showPanelModal:
case .showFloatingPanelModal:
if vc != mainPanelVC && vc != detailPanelVC {
return ModalPanelLayout()
}
fallthrough
case .showContentInset:
return NoInteractionBufferPanelLayout()
default:
return (newCollection.verticalSizeClass == .compact) ? nil : self
return (newCollection.verticalSizeClass == .compact) ? nil : self
}
}
@@ -492,40 +399,9 @@ extension SampleListViewController: UIPageViewControllerDataSource {
return pages[index - 1]
}
}
extension SampleListViewController: UIPageViewControllerDelegate {
// For showPageContent
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed, let page = pageViewController.viewControllers?.first {
(pageViewController.parent as! FloatingPanelController).track(scrollView: (page as! DebugTableViewController).tableView)
}
}
}
class IntrinsicPanelLayout: FloatingPanelIntrinsicLayout { }
class NoInteractionBufferPanelLayout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .full
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 0
case .half: return 216
case .tip: return 60
case .hidden: return nil
}
}
var topInteractionBuffer: CGFloat {
return 0.0
}
var bottomInteractionBuffer: CGFloat {
return 0.0
}
}
class RemovablePanelLayout: FloatingPanelIntrinsicLayout {
var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .half]
@@ -680,8 +556,7 @@ class InspectableViewController: UIViewController {
}
class DebugTableViewController: InspectableViewController {
lazy var tableView = UITableView(frame: .zero, style: .plain)
lazy var buttonStackView = UIStackView()
weak var tableView: UITableView!
var items: [String] = []
var itemHeight: CGFloat = 66.0
@@ -695,6 +570,8 @@ class DebugTableViewController: InspectableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView(frame: .zero,
style: .plain)
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
@@ -705,17 +582,18 @@ class DebugTableViewController: InspectableViewController {
])
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.tableView = tableView
view.addSubview(buttonStackView)
buttonStackView.axis = .vertical
buttonStackView.distribution = .fillEqually
buttonStackView.alignment = .trailing
buttonStackView.spacing = 10.0
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView()
view.addSubview(stackView)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .trailing
stackView.spacing = 10.0
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
buttonStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 22.0),
buttonStackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -22.0),
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 22.0),
stackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -22.0),
])
for menu in Menu.allCases {
@@ -731,12 +609,13 @@ class DebugTableViewController: InspectableViewController {
button.addTarget(self, action: #selector(reorderItems), for: .touchUpInside)
reorderButton = button
}
buttonStackView.addArrangedSubview(button)
stackView.addArrangedSubview(button)
}
for i in 0...100 {
items.append("Items \(i)")
}
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
@objc func animateScroll() {
@@ -794,10 +673,6 @@ class DebugTableViewController: InspectableViewController {
// Remove FloatingPanel from a view
(self.parent as! FloatingPanelController).removePanelFromParent(animated: true, completion: nil)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("TableView --- ", scrollView.contentOffset, scrollView.contentInset)
}
}
extension DebugTableViewController: UITableViewDataSource {
@@ -840,8 +715,6 @@ extension DebugTableViewController: UITableViewDelegate {
}
class DetailViewController: InspectableViewController {
@IBOutlet weak var modeChangeView: UIStackView!
@IBOutlet weak var intrinsicHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var closeButton: UIButton!
@IBAction func close(sender: UIButton) {
// (self.parent as? FloatingPanelController)?.removePanelFromParent(animated: true, completion: nil)
@@ -858,10 +731,6 @@ class DetailViewController: InspectableViewController {
break
}
}
@IBAction func modeChanged(_ sender: Any) {
guard let fpc = parent as? FloatingPanelController else { return }
fpc.contentMode = (fpc.contentMode == .static) ? .fitToBounds : .static
}
@IBAction func tapped(_ sender: Any) {
print("Detail panel is tapped!")
@@ -882,7 +751,8 @@ class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
var isNewlayout: Bool = false
override func viewDidLoad() {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
@@ -902,8 +772,8 @@ class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
fpc.addPanel(toParent: self, belowView: safeAreaView)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove FloatingPanel from a view
fpc.removePanelFromParent(animated: false)
}
@@ -921,9 +791,7 @@ class ModalViewController: UIViewController, FloatingPanelControllerDelegate {
@IBAction func moveToTip(sender: UIButton) {
fpc.move(to: .tip, animated: true)
}
@IBAction func moveToHidden(sender: UIButton) {
fpc.move(to: .hidden, animated: true)
}
@IBAction func updateLayout(_ sender: Any) {
isNewlayout = !isNewlayout
UIView.animate(withDuration: 0.5) {
@@ -964,15 +832,20 @@ class TabBarContentViewController: UIViewController {
}
}
}
lazy var fpc = FloatingPanelController()
var fpc: FloatingPanelController!
var consoleVC: DebugTextViewController!
var threeLayout: ThreeTabBarPanelLayout!
var tab3Mode: Tab3Mode = .changeAutoLayout
var switcherLabel: UILabel!
override func viewDidLoad() {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Initialize FloatingPanelController
fpc = FloatingPanelController()
fpc.delegate = self
// Initialize FloatingPanelController and add the view
fpc.surfaceView.cornerRadius = 6.0
fpc.surfaceView.shadowHidden = false
@@ -1022,6 +895,12 @@ class TabBarContentViewController: UIViewController {
fpc.updateLayout()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Remove FloatingPanel from a view
fpc.removePanelFromParent(animated: false)
}
// MARK: - Action
@IBAction func close(sender: UIButton) {
@@ -1050,7 +929,7 @@ extension TabBarContentViewController: UITextViewDelegate {
// Using KVO of `scrollView.contentOffset`). Because it can lead to an
// infinite loop if a user also resets a content offset as below and,
// in the situation, a user has to modify the library.
if fpc.position != .full, fpc.surfaceView.frame.minY > fpc.originYOfSurface(for: .full) {
if fpc.position != .full, fpc.surfaceView.frame.minY < fpc.originYOfSurface(for: .full) {
scrollView.contentOffset = .zero
}
}
@@ -1219,8 +1098,8 @@ class TwoTabBarPanelLayout: FloatingPanelLayout {
}
class TwoTabBarPanelBehavior: FloatingPanelBehavior {
func allowsRubberBanding(for edges: UIRectEdge) -> Bool {
return [UIRectEdge.top, UIRectEdge.bottom].contains(edges)
func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
return (edge == .bottom || edge == .top)
}
}
@@ -1299,72 +1178,3 @@ class SettingsViewController: InspectableViewController {
navigationController?.navigationBar.isTranslucent = sender.isOn
}
}
// MARK -: Multi Panel
import WebKit
final class MultiPanelController: FloatingPanelController, FloatingPanelControllerDelegate {
private final class FirstPanelContentViewController: UIViewController {
lazy var webView: WKWebView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(webView)
webView.frame = view.bounds
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.load(URLRequest(url: URL(string: "https://www.apple.com")!))
let vc = MultiSecondPanelController()
vc.setUpContent()
vc.addPanel(toParent: self)
}
}
private final class MultiSecondPanelController: FloatingPanelController {
private final class SecondPanelContentViewController: DebugTableViewController {}
func setUpContent() {
contentInsetAdjustmentBehavior = .never
let vc = SecondPanelContentViewController()
vc.loadViewIfNeeded()
vc.title = "Second Panel"
vc.buttonStackView.isHidden = true
let navigationController = UINavigationController(rootViewController: vc)
navigationController.navigationBar.barTintColor = .white
navigationController.navigationBar.titleTextAttributes = [
.foregroundColor: UIColor.black
]
set(contentViewController: navigationController)
self.track(scrollView: vc.tableView)
surfaceView.containerMargins = .init(top: 24.0, left: 0.0, bottom: layoutInsets.bottom, right: 0.0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
isRemovalInteractionEnabled = true
let vc = FirstPanelContentViewController()
set(contentViewController: vc)
track(scrollView: vc.webView.scrollView)
}
private final class FirstViewLayout: FloatingPanelLayout {
let initialPosition: FloatingPanelPosition = .full
let supportedPositions: Set<FloatingPanelPosition> = [.full]
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 40.0
default: return nil
}
}
}
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
return FirstViewLayout()
}
}
@@ -7,13 +7,13 @@
objects = {
/* Begin PBXBuildFile section */
5433F24B21717EA300BDAA5D /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5433F24A21717EA300BDAA5D /* FloatingPanel.framework */; };
5433F24C21717EA300BDAA5D /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5433F24A21717EA300BDAA5D /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
548DF95421705BE00041922A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95321705BE00041922A /* AppDelegate.swift */; };
548DF95621705BE00041922A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 548DF95521705BE00041922A /* ViewController.swift */; };
548DF95921705BE00041922A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95721705BE00041922A /* Main.storyboard */; };
548DF95B21705BE10041922A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95A21705BE10041922A /* Assets.xcassets */; };
548DF95E21705BE10041922A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 548DF95C21705BE10041922A /* LaunchScreen.storyboard */; };
549D23CF233C77CF008EF4D7 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */; };
549D23D0233C77CF008EF4D7 /* FloatingPanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -23,7 +23,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
549D23D0233C77CF008EF4D7 /* FloatingPanel.framework in Embed Frameworks */,
5433F24C21717EA300BDAA5D /* FloatingPanel.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -31,6 +31,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5433F24A21717EA300BDAA5D /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
548DF95021705BE00041922A /* Stocks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stocks.app; sourceTree = BUILT_PRODUCTS_DIR; };
548DF95321705BE00041922A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
548DF95521705BE00041922A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@@ -38,7 +39,6 @@
548DF95A21705BE10041922A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
548DF95D21705BE10041922A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
548DF95F21705BE10041922A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -46,7 +46,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
549D23CF233C77CF008EF4D7 /* FloatingPanel.framework in Frameworks */,
5433F24B21717EA300BDAA5D /* FloatingPanel.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -56,7 +56,7 @@
548DF94721705BE00041922A = {
isa = PBXGroup;
children = (
549D23CE233C77CF008EF4D7 /* FloatingPanel.framework */,
5433F24A21717EA300BDAA5D /* FloatingPanel.framework */,
548DF95221705BE00041922A /* Stocks */,
548DF95121705BE00041922A /* Products */,
);
+2 -2
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.7.5"
s.version = "1.5.1"
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
s.description = <<-DESC
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
@@ -13,7 +13,7 @@ The new interface displays the related contents and utilities in parallel as a u
s.platform = :ios, "10.0"
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => "v#{s.version}" }
s.source_files = "Framework/Sources/*.swift"
s.swift_versions = ["4.0", "4.2", "5.0"]
s.swift_version = "4.0"
s.framework = "UIKit"
@@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
542753C622C49A6E00D17955 /* FloatingPanelLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542753C522C49A6E00D17955 /* FloatingPanelLayoutTests.swift */; };
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */; };
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
@@ -17,16 +16,14 @@
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 */; };
546055BF2333C4740069F400 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542753C722C49A8F00D17955 /* Utils.swift */; };
549E944522CF295D0050AECF /* FloatingPanelPositionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E944422CF295D0050AECF /* FloatingPanelPositionTests.swift */; };
54A6B6B122968B530077F348 /* FloatingPanelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B022968B530077F348 /* FloatingPanelTests.swift */; };
54A6B6B622968F710077F348 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54A6B6B522968F710077F348 /* LaunchScreen.storyboard */; };
54A6B6B82296A8520077F348 /* FloatingPanelSurfaceViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A6B6B72296A8520077F348 /* FloatingPanelSurfaceViewTests.swift */; };
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 /* FloatingPanelCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFBFC4215CD09C006B5735 /* FloatingPanelCore.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 */
@@ -48,8 +45,6 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
542753C522C49A6E00D17955 /* FloatingPanelLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelLayoutTests.swift; sourceTree = "<group>"; };
542753C722C49A8F00D17955 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTransitioning.swift; sourceTree = "<group>"; };
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
@@ -62,15 +57,14 @@
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>"; };
549E944422CF295D0050AECF /* FloatingPanelPositionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelPositionTests.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 /* FloatingPanelSurfaceViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelSurfaceViewTests.swift; 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 /* FloatingPanelCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelCore.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>"; };
@@ -129,7 +123,7 @@
545DB9C42151169500CA77B8 /* FloatingPanel.h */,
545DB9DF21511AC100CA77B8 /* FloatingPanelController.swift */,
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */,
54CFBFC4215CD09C006B5735 /* FloatingPanelCore.swift */,
54CFBFC4215CD09C006B5735 /* FloatingPanel.swift */,
54CFBFC2215CD045006B5735 /* FloatingPanelLayout.swift */,
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */,
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */,
@@ -146,11 +140,8 @@
isa = PBXGroup;
children = (
54A6B6B022968B530077F348 /* FloatingPanelTests.swift */,
54A6B6B72296A8520077F348 /* FloatingPanelViewTests.swift */,
545DB9CF2151169500CA77B8 /* FloatingPanelControllerTests.swift */,
542753C522C49A6E00D17955 /* FloatingPanelLayoutTests.swift */,
54A6B6B72296A8520077F348 /* FloatingPanelSurfaceViewTests.swift */,
549E944422CF295D0050AECF /* FloatingPanelPositionTests.swift */,
542753C722C49A8F00D17955 /* Utils.swift */,
545DB9D12151169500CA77B8 /* Info.plist */,
);
path = Tests;
@@ -311,7 +302,7 @@
54CFBFC3215CD045006B5735 /* FloatingPanelLayout.swift in Sources */,
54CDC5D5215B6D8D007D205C /* FloatingPanelBackdropView.swift in Sources */,
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */,
54CFBFC5215CD09C006B5735 /* FloatingPanelCore.swift in Sources */,
54CFBFC5215CD09C006B5735 /* FloatingPanel.swift in Sources */,
54ABD7AF216CCFF7002E6C13 /* Logger.swift in Sources */,
545DB9E021511AC100CA77B8 /* FloatingPanelController.swift in Sources */,
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */,
@@ -327,10 +318,7 @@
files = (
54A6B6B122968B530077F348 /* FloatingPanelTests.swift in Sources */,
545DB9D02151169500CA77B8 /* FloatingPanelControllerTests.swift in Sources */,
549E944522CF295D0050AECF /* FloatingPanelPositionTests.swift in Sources */,
542753C622C49A6E00D17955 /* FloatingPanelLayoutTests.swift in Sources */,
54A6B6B82296A8520077F348 /* FloatingPanelSurfaceViewTests.swift in Sources */,
546055BF2333C4740069F400 /* Utils.swift in Sources */,
54A6B6B82296A8520077F348 /* FloatingPanelViewTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -484,7 +472,6 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
@@ -515,7 +502,6 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
@@ -704,7 +690,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TEST DEBUG __FP_LOG";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG __FP_LOG";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
@@ -29,9 +29,7 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES"
testExecutionOrdering = "random">
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "545DB9C92151169500CA77B8"
File diff suppressed because it is too large Load Diff
@@ -7,5 +7,25 @@ import UIKit
/// A view that presents a backdrop interface behind a floating panel.
public class FloatingPanelBackdropView: UIView {
public var dismissalTapGestureRecognizer: UITapGestureRecognizer!
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
private func setUp() {
layer.backgroundColor = UIColor.black.cgColor
}
@objc dynamic public override var backgroundColor: UIColor? {
get {
guard let color = layer.backgroundColor else { return nil }
return UIColor(cgColor: color)
}
set { layer.backgroundColor = newValue?.cgColor }
}
}
@@ -23,11 +23,6 @@ public protocol FloatingPanelBehavior {
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.
///
/// - Attention:
/// By default, it returns a non-interruptible animator to prevent a propagation of the animation to a content view.
/// However returning an interruptible animator is working well depending on a content view and it can be better
/// than using a non-interruptible one.
func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator
/// Returns a UIViewPropertyAnimator object to add a floating panel to a position.
@@ -72,7 +67,14 @@ public protocol FloatingPanelBehavior {
public extension FloatingPanelBehavior {
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
return false
switch (fpc.position, proposedTargetPosition) {
case (.full, .tip):
return false
case (.tip, .full):
return false
default:
return true
}
}
func momentumProjectionRate(_ fpc: FloatingPanelController) -> CGFloat {
@@ -132,7 +134,7 @@ public class FloatingPanelDefaultBehavior: FloatingPanelBehavior {
public func interactionAnimator(_ fpc: FloatingPanelController, to targetPosition: FloatingPanelPosition, with velocity: CGVector) -> UIViewPropertyAnimator {
let timing = timeingCurve(with: velocity)
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: timing)
animator.isInterruptible = false // Prevent a propagation of the animation(spring etc) to a content view
animator.isInterruptible = false
return animator
}
+43 -150
View File
@@ -12,9 +12,7 @@ public protocol FloatingPanelControllerDelegate: class {
// if it returns nil, FloatingPanelController uses the default behavior
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior?
/// Called when the floating panel has changed to a new position. Can be called inside an animation block, so any
/// view properties set inside this function will be automatically animated alongside the panel.
func floatingPanelDidChangePosition(_ vc: FloatingPanelController)
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) // changed the settled position in the model layer
/// Asks the delegate if dragging should begin by the pan gesture recognizer.
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool
@@ -37,15 +35,6 @@ public protocol FloatingPanelControllerDelegate: class {
///
/// By default, any tap and long gesture recognizers are allowed to recognize gestures simultaneously.
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
/// Asks the delegate for a content offset of the tracked scroll view to be pinned when a floating panel moves
///
/// If you do not implement this method, the controller uses a value of the content offset plus the content insets
/// of the tracked scroll view. Your implementation of this method can return a value for a navigation bar with a large
/// title, for example.
///
/// This method will not be called if the controller doesn't track any scroll view.
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackedScrollView: UIScrollView) -> CGPoint
}
public extension FloatingPanelControllerDelegate {
@@ -71,9 +60,6 @@ public extension FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func floatingPanel(_ vc: FloatingPanelController, contentOffsetForPinning trackedScrollView: UIScrollView) -> CGPoint {
return CGPoint(x: 0.0, y: 0.0 - trackedScrollView.contentInset.top)
}
}
@@ -82,60 +68,18 @@ public enum FloatingPanelPosition: Int {
case half
case tip
case hidden
static var allCases: [FloatingPanelPosition] {
return [.full, .half, .tip, .hidden]
}
func next(in positions: [FloatingPanelPosition]) -> FloatingPanelPosition {
#if swift(>=4.2)
guard
let index = positions.firstIndex(of: self),
positions.indices.contains(index + 1)
else { return self }
#else
guard
let index = positions.index(of: self),
positions.indices.contains(index + 1)
else { return self }
#endif
return positions[index + 1]
}
func pre(in positions: [FloatingPanelPosition]) -> FloatingPanelPosition {
#if swift(>=4.2)
guard
let index = positions.firstIndex(of: self),
positions.indices.contains(index - 1)
else { return self }
#else
guard
let index = positions.index(of: self),
positions.indices.contains(index - 1)
else { return self }
#endif
return positions[index - 1]
}
}
///
/// A container view controller to display a floating panel to present contents in parallel as a user wants.
///
open class FloatingPanelController: UIViewController {
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
case never
}
/// A flag used to determine how the controller object lays out the content view when the surface position changes.
public enum ContentMode: Int {
/// The option to fix the content to keep the height of the top most position.
case `static`
/// The option to scale the content to fit the bounds of the root view by changing the surface position.
case fitToBounds
}
/// The delegate of the floating panel controller object.
public weak var delegate: FloatingPanelControllerDelegate?{
didSet{
@@ -199,23 +143,9 @@ open class FloatingPanelController: UIViewController {
set { set(contentViewController: newValue) }
get { return _contentViewController }
}
/// The NearbyPosition determines that finger's nearby position.
public var nearbyPosition: FloatingPanelPosition {
let currentY = surfaceView.frame.minY
return floatingPanel.targetPosition(from: currentY, with: .zero)
}
public var contentMode: ContentMode = .static {
didSet {
guard position != .hidden else { return }
activateLayout()
}
}
private var _contentViewController: UIViewController?
private(set) var floatingPanel: FloatingPanelCore!
private var floatingPanel: FloatingPanel!
private var preSafeAreaInsets: UIEdgeInsets = .zero // Capture the latest one
private var safeAreaInsetsObservation: NSKeyValueObservation?
private let modalTransition = FloatingPanelModalTransition()
@@ -238,7 +168,7 @@ open class FloatingPanelController: UIViewController {
modalPresentationStyle = .custom
transitioningDelegate = modalTransition
floatingPanel = FloatingPanelCore(self,
floatingPanel = FloatingPanel(self,
layout: fetchLayout(for: self.traitCollection),
behavior: fetchBehavior(for: self.traitCollection))
}
@@ -247,7 +177,7 @@ open class FloatingPanelController: UIViewController {
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
floatingPanel.behavior = fetchBehavior(for: self.traitCollection)
}
// MARK:- Overrides
/// Creates the view that the controller manages.
@@ -266,19 +196,18 @@ open class FloatingPanelController: UIViewController {
self.view = view as UIView
}
open override func viewDidLayoutSubviews() {
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11.0, *) {}
else {
// Because {top,bottom}LayoutGuide is managed as a view
if preSafeAreaInsets != layoutInsets,
floatingPanel.isDecelerating == false {
if preSafeAreaInsets != layoutInsets {
self.update(safeAreaInsets: layoutInsets)
}
}
}
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if view.translatesAutoresizingMaskIntoConstraints {
@@ -287,9 +216,14 @@ open class FloatingPanelController: UIViewController {
}
}
open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
self.prepare(for: newCollection)
// Change layout for a new trait collection
reloadLayout(for: newCollection)
setUpLayout()
floatingPanel.behavior = fetchBehavior(for: newCollection)
}
open override func viewWillDisappear(_ animated: Bool) {
@@ -297,50 +231,6 @@ open class FloatingPanelController: UIViewController {
safeAreaInsetsObservation = nil
}
// MARK:- Child view controller to consult
#if swift(>=4.2)
open override var childForStatusBarStyle: UIViewController? {
return contentViewController
}
open override var childForStatusBarHidden: UIViewController? {
return contentViewController
}
open override var childForScreenEdgesDeferringSystemGestures: UIViewController? {
return contentViewController
}
open override var childForHomeIndicatorAutoHidden: UIViewController? {
return contentViewController
}
#else
open override var childViewControllerForStatusBarStyle: UIViewController? {
return contentViewController
}
open override var childViewControllerForStatusBarHidden: UIViewController? {
return contentViewController
}
open override func childViewControllerForScreenEdgesDeferringSystemGestures() -> UIViewController? {
return contentViewController
}
open override func childViewControllerForHomeIndicatorAutoHidden() -> UIViewController? {
return contentViewController
}
#endif
// MARK:- Internals
func prepare(for newCollection: UITraitCollection) {
guard newCollection.shouldUpdateLayout(from: traitCollection) else { return }
// Change a layout & behavior for a new trait collection
reloadLayout(for: newCollection)
activateLayout()
floatingPanel.behavior = fetchBehavior(for: newCollection)
}
// MARK:- Privates
private func fetchLayout(for traitCollection: UITraitCollection) -> FloatingPanelLayout {
@@ -358,7 +248,8 @@ open class FloatingPanelController: UIViewController {
private func update(safeAreaInsets: UIEdgeInsets) {
guard
preSafeAreaInsets != safeAreaInsets
preSafeAreaInsets != safeAreaInsets,
self.floatingPanel.isDecelerating == false
else { return }
log.debug("Update safeAreaInsets", safeAreaInsets)
@@ -366,11 +257,12 @@ open class FloatingPanelController: UIViewController {
// Prevent an infinite loop on iOS 10: setUpLayout() -> viewDidLayoutSubviews() -> setUpLayout()
preSafeAreaInsets = safeAreaInsets
activateLayout()
setUpLayout()
switch contentInsetAdjustmentBehavior {
case .always:
scrollView?.contentInset = adjustedContentInsets
scrollView?.scrollIndicatorInsets = adjustedContentInsets
default:
break
}
@@ -378,6 +270,7 @@ open class FloatingPanelController: UIViewController {
private func reloadLayout(for traitCollection: UITraitCollection) {
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
floatingPanel.layoutAdapter.prepareLayout(in: self)
if let parent = self.parent {
if let layout = layout as? UIViewController, layout == parent {
@@ -389,21 +282,14 @@ open class FloatingPanelController: UIViewController {
}
}
private func activateLayout() {
floatingPanel.layoutAdapter.prepareLayout(in: self)
// preserve the current content offset if contentInsetAdjustmentBehavior is `.always`
var contentOffset: CGPoint?
if contentInsetAdjustmentBehavior == .always {
contentOffset = scrollView?.contentOffset
}
private func setUpLayout() {
// preserve the current content offset
let contentOffset = scrollView?.contentOffset
floatingPanel.layoutAdapter.updateHeight()
floatingPanel.layoutAdapter.activateLayout(of: floatingPanel.state)
if let contentOffset = contentOffset {
scrollView?.contentOffset = contentOffset
}
scrollView?.contentOffset = contentOffset ?? .zero
}
// MARK: - Container view controller interface
@@ -412,7 +298,7 @@ open class FloatingPanelController: UIViewController {
public func show(animated: Bool = false, completion: (() -> Void)? = nil) {
// Must apply the current layout here
reloadLayout(for: traitCollection)
activateLayout()
setUpLayout()
if #available(iOS 11.0, *) {
// Must track the safeAreaInsets of `self.view` to update the layout.
@@ -421,11 +307,9 @@ open class FloatingPanelController: UIViewController {
// inset's update expectedly.
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
// That's why it needs the observation to keep `adjustedContentInsets` correct.
safeAreaInsetsObservation = self.view.observe(\.safeAreaInsets, options: [.initial, .new, .old]) { [weak self] (_, change) in
// Use `self.view.safeAreaInsets` becauese `change.newValue` can be nil in particular case when
// is reported in https://github.com/SCENEE/FloatingPanel/issues/330
guard let `self` = self, change.oldValue != self.view.safeAreaInsets else { return }
self.update(safeAreaInsets: self.view.safeAreaInsets)
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.
@@ -439,6 +323,7 @@ open class FloatingPanelController: UIViewController {
/// 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)
@@ -484,9 +369,9 @@ open class FloatingPanelController: UIViewController {
show(animated: animated) { [weak self] in
guard let `self` = self else { return }
#if swift(>=4.2)
self.didMove(toParent: parent)
self.didMove(toParent: self)
#else
self.didMove(toParentViewController: parent)
self.didMove(toParentViewController: self)
#endif
}
}
@@ -628,12 +513,21 @@ open class FloatingPanelController: UIViewController {
/// animation block.
public func updateLayout() {
reloadLayout(for: traitCollection)
activateLayout()
setUpLayout()
}
/// Returns the y-coordinate of the point at the origin of the surface view.
public func originYOfSurface(for pos: FloatingPanelPosition) -> CGFloat {
return floatingPanel.layoutAdapter.positionY(for: pos)
switch pos {
case .full:
return floatingPanel.layoutAdapter.topY
case .half:
return floatingPanel.layoutAdapter.middleY
case .tip:
return floatingPanel.layoutAdapter.bottomY
case .hidden:
return floatingPanel.layoutAdapter.hiddenY
}
}
}
@@ -670,8 +564,7 @@ public extension UIViewController {
}
// Call dismiss(animated:completion:) to FloatingPanelController directly
if let fpc = self as? FloatingPanelController {
// When a panel is presented modally and it's not a child view controller of the presented view controller.
if fpc.presentingViewController != nil, fpc.parent == nil {
if fpc.presentingViewController != nil {
self.fp_original_dismiss(animated: flag, completion: completion)
} else {
fpc.removePanelFromParent(animated: flag, completion: completion)
+108 -193
View File
@@ -11,24 +11,15 @@ import UIKit
/// It can't be used with FloatingPanelIntrinsicLayout.
public protocol FloatingPanelFullScreenLayout: FloatingPanelLayout { }
public extension FloatingPanelFullScreenLayout {
var positionReference: FloatingPanelLayoutReference {
return .fromSuperview
}
}
/// FloatingPanelIntrinsicLayout
///
/// Use the layout protocol if you want to layout a panel using the intrinsic height.
/// It can't be used with `FloatingPanelFullScreenLayout`.
/// 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.
///
/// - Note:
/// By default, the `positionReference` is set to `.fromSafeArea`.
public protocol FloatingPanelIntrinsicLayout: FloatingPanelLayout { }
public extension FloatingPanelIntrinsicLayout {
@@ -43,15 +34,6 @@ public extension FloatingPanelIntrinsicLayout {
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
return nil
}
var positionReference: FloatingPanelLayoutReference {
return .fromSafeArea
}
}
public enum FloatingPanelLayoutReference: Int {
case fromSafeArea = 0
case fromSuperview = 1
}
public protocol FloatingPanelLayout: class {
@@ -61,16 +43,14 @@ public protocol FloatingPanelLayout: class {
/// Returns a set of FloatingPanelPosition objects to tell the applicable
/// positions of the floating panel controller.
///
/// By default, it returns full, half and tip positions.
/// 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.
var topInteractionBuffer: CGFloat { get }
/// Return the interaction buffer to the bottom from the bottom position. Default is 6.0.
///
/// - Important:
/// The specified buffer is ignored when `FloatingPanelController.isRemovalInteractionEnabled` is set to true.
var bottomInteractionBuffer: CGFloat { get }
/// Returns a CGFloat value to determine a Y coordinate of a floating panel for each position(full, half, tip and hidden).
@@ -92,9 +72,6 @@ public protocol FloatingPanelLayout: class {
///
/// Default is 0.3 at full position, otherwise 0.0.
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat
var positionReference: FloatingPanelLayoutReference { get }
}
public extension FloatingPanelLayout {
@@ -115,10 +92,6 @@ public extension FloatingPanelLayout {
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return position == .full ? 0.3 : 0.0
}
var positionReference: FloatingPanelLayoutReference {
return .fromSafeArea
}
}
public class FloatingPanelDefaultLayout: FloatingPanelLayout {
@@ -157,13 +130,9 @@ public class FloatingPanelDefaultLandscapeLayout: FloatingPanelLayout {
}
}
struct LayoutSegment {
let lower: FloatingPanelPosition?
let upper: FloatingPanelPosition?
}
class FloatingPanelLayoutAdapter {
weak var vc: FloatingPanelController!
weak var vc: UIViewController!
private weak var surfaceView: FloatingPanelSurfaceView!
private weak var backdropView: FloatingPanelBackdropView!
@@ -185,8 +154,6 @@ class FloatingPanelLayoutAdapter {
private var tipConstraints: [NSLayoutConstraint] = []
private var offConstraints: [NSLayoutConstraint] = []
private var interactiveTopConstraint: NSLayoutConstraint?
private var bottomConstraint: NSLayoutConstraint?
private var heightConstraint: NSLayoutConstraint?
@@ -208,35 +175,70 @@ class FloatingPanelLayoutAdapter {
}
var supportedPositions: Set<FloatingPanelPosition> {
return layout.supportedPositions
}
var validPositions: Set<FloatingPanelPosition> {
return supportedPositions.union([.hidden])
var supportedPositions = layout.supportedPositions
supportedPositions.remove(.hidden)
return supportedPositions
}
var topMostState: FloatingPanelPosition {
return supportedPositions.sorted(by: { $0.rawValue < $1.rawValue }).first ?? .hidden
}
var bottomMostState: FloatingPanelPosition {
return supportedPositions.sorted(by: { $0.rawValue < $1.rawValue }).last ?? .hidden
if supportedPositions.contains(.full) {
return .full
}
if supportedPositions.contains(.half) {
return .half
}
return .tip
}
var topY: CGFloat {
return positionY(for: topMostState)
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 {
if layout is FloatingPanelFullScreenLayout {
return surfaceView.superview!.bounds.height - halfInset
} else{
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
}
}
var bottomY: CGFloat {
return positionY(for: bottomMostState)
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 hiddenY: CGFloat {
return surfaceView.superview!.bounds.height
}
var topMaxY: CGFloat {
return topY - layout.topInteractionBuffer
return layout is FloatingPanelFullScreenLayout ? 0.0 : safeAreaInsets.top
}
var bottomMaxY: CGFloat {
return bottomY + layout.bottomInteractionBuffer
if layout is FloatingPanelFullScreenLayout{
return surfaceView.superview!.bounds.height - hiddenInset
} else {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
}
}
var adjustedContentInsets: UIEdgeInsets {
@@ -249,31 +251,13 @@ class FloatingPanelLayoutAdapter {
func positionY(for pos: FloatingPanelPosition) -> CGFloat {
switch pos {
case .full:
if layout is FloatingPanelIntrinsicLayout {
return surfaceView.superview!.bounds.height - surfaceView.bounds.height
}
switch layout.positionReference {
case .fromSafeArea:
return (safeAreaInsets.top + fullInset)
case .fromSuperview:
return fullInset
}
return topY
case .half:
switch layout.positionReference {
case .fromSafeArea:
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + halfInset)
case .fromSuperview:
return surfaceView.superview!.bounds.height - halfInset
}
return middleY
case .tip:
switch layout.positionReference {
case .fromSafeArea:
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + tipInset)
case .fromSuperview:
return surfaceView.superview!.bounds.height - tipInset
}
return bottomY
case .hidden:
return surfaceView.superview!.bounds.height - hiddenInset
return hiddenY
}
}
@@ -307,14 +291,10 @@ class FloatingPanelLayoutAdapter {
", content safe area(bottom) =", safeAreaBottom)
}
func prepareLayout(in vc: FloatingPanelController) {
func prepareLayout(in vc: UIViewController) {
self.vc = vc
NSLayoutConstraint.deactivate(fixedConstraints + fullConstraints + halfConstraints + tipConstraints + offConstraints)
NSLayoutConstraint.deactivate(constraint: self.heightConstraint)
self.heightConstraint = nil
NSLayoutConstraint.deactivate(constraint: self.bottomConstraint)
self.bottomConstraint = nil
surfaceView.translatesAutoresizingMaskIntoConstraints = false
backdropView.translatesAutoresizingMaskIntoConstraints = false
@@ -330,14 +310,9 @@ class FloatingPanelLayoutAdapter {
fixedConstraints = surfaceConstraints + backdropConstraints
if vc.contentMode == .fitToBounds {
bottomConstraint = surfaceView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor,
constant: 0.0)
}
// Flexible surface constraints for full, half, tip and off
let topAnchor: NSLayoutYAxisAnchor = {
if layout.positionReference == .fromSuperview {
if layout is FloatingPanelFullScreenLayout {
return vc.view.topAnchor
} else {
return vc.layoutGuide.topAnchor
@@ -356,7 +331,7 @@ class FloatingPanelLayoutAdapter {
}
let bottomAnchor: NSLayoutYAxisAnchor = {
if layout.positionReference == .fromSuperview {
if layout is FloatingPanelFullScreenLayout {
return vc.view.bottomAnchor
} else {
return vc.layoutGuide.bottomAnchor
@@ -378,20 +353,20 @@ class FloatingPanelLayoutAdapter {
]
}
func startInteraction(at state: FloatingPanelPosition, offset: CGPoint = .zero) {
guard self.interactiveTopConstraint == nil else { return }
func startInteraction(at state: FloatingPanelPosition) {
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
let interactiveTopConstraint: NSLayoutConstraint
switch layout.positionReference {
case .fromSafeArea:
initialConst = surfaceView.frame.minY - safeAreaInsets.top + offset.y
interactiveTopConstraint = surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
constant: initialConst)
case .fromSuperview:
initialConst = surfaceView.frame.minY + offset.y
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
@@ -400,75 +375,67 @@ class FloatingPanelLayoutAdapter {
func endInteraction(at state: FloatingPanelPosition) {
// Don't deactivate `interactiveTopConstraint` here because it leads to
// unsatisfiable constraints
if self.interactiveTopConstraint == nil {
// Actiavate `interactiveTopConstraint` for `fitToBounds` mode.
// It goes throught this path when the pan gesture state jumps
// from .begin to .end.
startInteraction(at: state)
}
}
// The method is separated from prepareLayout(to:) for the rotation support
// It must be called in FloatingPanelController.traitCollectionDidChange(_:)
func updateHeight() {
guard let vc = vc else { return }
NSLayoutConstraint.deactivate(constraint: heightConstraint)
heightConstraint = nil
if layout is FloatingPanelIntrinsicLayout {
updateIntrinsicHeight()
}
defer {
if layout is FloatingPanelIntrinsicLayout {
NSLayoutConstraint.deactivate(fullConstraints)
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
constant: -fullInset),
]
}
if let const = self.heightConstraint {
NSLayoutConstraint.deactivate([const])
}
guard vc.contentMode != .fitToBounds else { return }
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(constraint: heightConstraint)
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 updateInteractiveTopConstraint(diff: CGFloat, allowsTopBuffer: Bool, with behavior: FloatingPanelBehavior) {
defer {
layoutSurfaceIfNeeded() // MUST be called to update `surfaceView.frame`
surfaceView.superview!.layoutIfNeeded() // MUST call here to update `surfaceView.frame`
}
let topMostConst: CGFloat = {
var ret: CGFloat = 0.0
switch layout.positionReference {
case .fromSafeArea:
ret = topY - safeAreaInsets.top
case .fromSuperview:
switch layout {
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
ret = topY
default:
ret = topY - safeAreaInsets.top
}
return ret
return max(ret, 0.0) // The top boundary is equal to the related topAnchor.
}()
let bottomMostConst: CGFloat = {
var ret: CGFloat = 0.0
let _bottomY = vc.isRemovalInteractionEnabled ? positionY(for: .hidden) : bottomY
switch layout.positionReference {
case .fromSafeArea:
ret = _bottomY - safeAreaInsets.top
case .fromSuperview:
ret = _bottomY
switch layout {
case is FloatingPanelIntrinsicLayout, is FloatingPanelFullScreenLayout:
ret = bottomY
default:
ret = bottomY - safeAreaInsets.top
}
return ret
return min(ret, bottomMaxY)
}()
let minConst = allowsTopBuffer ? topMostConst - layout.topInteractionBuffer : topMostConst
let maxConst = bottomMostConst + layout.bottomInteractionBuffer
@@ -498,29 +465,23 @@ class FloatingPanelLayoutAdapter {
return (1.0 - (1.0 / ((buffer * 0.55 / base) + 1.0))) * base
}
func activateFixedLayout() {
// Must deactivate `interactiveTopConstraint` here
NSLayoutConstraint.deactivate(constraint: self.interactiveTopConstraint)
self.interactiveTopConstraint = nil
NSLayoutConstraint.activate(fixedConstraints)
if vc.contentMode == .fitToBounds {
NSLayoutConstraint.activate(constraint: self.bottomConstraint)
}
}
func activateInteractiveLayout(of state: FloatingPanelPosition) {
func activateLayout(of state: FloatingPanelPosition) {
defer {
layoutSurfaceIfNeeded()
log.debug("activateLayout -- surface.presentation = \(self.surfaceView.presentationFrame) surface.frame = \(self.surfaceView.frame)")
surfaceView.superview!.layoutIfNeeded()
}
var state = state
setBackdropAlpha(of: state)
if validPositions.contains(state) == false {
// Must deactivate `interactiveTopConstraint` here
if let interactiveTopConstraint = interactiveTopConstraint {
NSLayoutConstraint.deactivate([interactiveTopConstraint])
self.interactiveTopConstraint = nil
}
NSLayoutConstraint.activate(fixedConstraints)
if supportedPositions.union([.hidden]).contains(state) == false {
state = layout.initialPosition
}
@@ -537,18 +498,6 @@ class FloatingPanelLayoutAdapter {
}
}
func activateLayout(of state: FloatingPanelPosition) {
activateFixedLayout()
activateInteractiveLayout(of: state)
}
private func layoutSurfaceIfNeeded() {
#if !TEST
guard surfaceView.window != nil else { return }
#endif
surfaceView.superview?.layoutIfNeeded()
}
private func setBackdropAlpha(of target: FloatingPanelPosition) {
if target == .hidden {
self.backdropView.alpha = 0.0
@@ -560,8 +509,8 @@ class FloatingPanelLayoutAdapter {
private func checkLayoutConsistance() {
// Verify layout configurations
assert(supportedPositions.count > 0)
assert(validPositions.contains(layout.initialPosition),
"Does not include an initial position (\(layout.initialPosition)) in (\(validPositions))")
assert(supportedPositions.union([.hidden]).contains(layout.initialPosition),
"Does not include an initial position (\(layout.initialPosition)) in supportedPositions (\(supportedPositions))")
if layout is FloatingPanelIntrinsicLayout {
assert(layout.insetFor(position: .full) == nil, "Return `nil` for full position on FloatingPanelIntrinsicLayout")
@@ -579,38 +528,4 @@ class FloatingPanelLayoutAdapter {
assert(bottomY > topY, "Invalid insets { topY: \(topY), bottomY: \(bottomY) }")
}*/
}
func segument(at posY: CGFloat, forward: Bool) -> LayoutSegment {
/// ----------------------->Y
/// --> forward <-- backward
/// |-------|===o===|-------| |-------|-------|===o===|
/// |-------|-------x=======| |-------|=======x-------|
/// |-------|-------|===o===| |-------|===o===|-------|
/// pos: o/x, seguement: =
let sortedPositions = supportedPositions.sorted(by: { $0.rawValue < $1.rawValue })
let upperIndex: Int?
if forward {
#if swift(>=4.2)
upperIndex = sortedPositions.firstIndex(where: { posY < positionY(for: $0) })
#else
upperIndex = sortedPositions.index(where: { posY < positionY(for: $0) })
#endif
} else {
#if swift(>=4.2)
upperIndex = sortedPositions.firstIndex(where: { posY <= positionY(for: $0) })
#else
upperIndex = sortedPositions.index(where: { posY <= positionY(for: $0) })
#endif
}
switch upperIndex {
case 0:
return LayoutSegment(lower: nil, upper: sortedPositions.first)
case let upperIndex?:
return LayoutSegment(lower: sortedPositions[upperIndex - 1], upper: sortedPositions[upperIndex])
default:
return LayoutSegment(lower: sortedPositions[sortedPositions.endIndex - 1], upper: nil)
}
}
}
@@ -15,7 +15,7 @@ public class FloatingPanelSurfaceView: UIView {
public let grabberHandle: GrabberHandleView = GrabberHandleView()
/// Offset of the grabber handle from the top
public var grabberTopPadding: CGFloat = 6.0 { didSet {
@objc dynamic public var grabberTopPadding: CGFloat = 6.0 { didSet {
setNeedsUpdateConstraints()
} }
@@ -25,17 +25,19 @@ public class FloatingPanelSurfaceView: UIView {
}
/// Grabber view width and height
public var grabberHandleWidth: CGFloat = 36.0 { didSet {
@objc dynamic public var grabberHandleWidth: CGFloat = 36.0 { didSet {
setNeedsUpdateConstraints()
} }
public var grabberHandleHeight: CGFloat = 5.0 { didSet {
@objc dynamic 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
@@ -46,7 +48,7 @@ public class FloatingPanelSurfaceView: UIView {
private var color: UIColor? = .white { didSet { setNeedsLayout() } }
var bottomOverflow: CGFloat = 0.0 // Must not call setNeedsLayout()
public override var backgroundColor: UIColor? {
@objc dynamic public override var backgroundColor: UIColor? {
get { return color }
set { color = newValue }
}
@@ -55,34 +57,34 @@ 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 {
@objc dynamic 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() } }
@objc dynamic public var shadowHidden: Bool = false { didSet { setNeedsLayout() } }
/// The color of the surface shadow.
public var shadowColor: UIColor = .black { didSet { setNeedsLayout() } }
@objc dynamic public var shadowColor: UIColor = .black { didSet { setNeedsLayout() } }
/// The offset (in points) of the surface shadow.
public var shadowOffset: CGSize = CGSize(width: 0.0, height: 1.0) { didSet { setNeedsLayout() } }
@objc dynamic public var shadowOffset: CGSize = CGSize(width: 0.0, height: 1.0) { didSet { setNeedsLayout() } }
/// The opacity of the surface shadow.
public var shadowOpacity: Float = 0.2 { didSet { setNeedsLayout() } }
@objc dynamic public var shadowOpacity: Float = 0.2 { didSet { setNeedsLayout() } }
/// The blur radius (in points) used to render the surface shadow.
public var shadowRadius: CGFloat = 3 { didSet { setNeedsLayout() } }
@objc dynamic public var shadowRadius: CGFloat = 3 { didSet { setNeedsLayout() } }
/// The width of the surface border.
public var borderColor: UIColor? { didSet { setNeedsLayout() } }
@objc dynamic public var borderColor: UIColor? { didSet { setNeedsLayout() } }
/// The color of the surface border.
public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
@objc dynamic public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
/// The margins to use when laying out the container view wrapping content.
public var containerMargins: UIEdgeInsets = .zero { didSet {
/// Offset of the container view from the top
@objc dynamic public var containerTopInset: CGFloat = 0.0 { didSet {
setNeedsUpdateConstraints()
} }
@@ -97,11 +99,9 @@ public class FloatingPanelSurfaceView: UIView {
@available(*, unavailable, renamed: "containerView")
public var backgroundView: UIView!
private lazy var containerViewTopConstraint = containerView.topAnchor.constraint(equalTo: topAnchor, constant: containerMargins.top)
private lazy var containerViewHeightConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
private lazy var containerViewLeftConstraint = containerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0)
private lazy var containerViewRightConstraint = containerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0)
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
@@ -111,11 +111,9 @@ public class FloatingPanelSurfaceView: UIView {
/// The content height constraint
private var contentViewHeightConstraint: NSLayoutConstraint?
private lazy var grabberHandleWidthConstraint = grabberHandle.widthAnchor.constraint(equalToConstant: grabberHandleWidth)
private lazy var grabberHandleHeightConstraint = grabberHandle.heightAnchor.constraint(equalToConstant: grabberHandleHeight)
private lazy var grabberHandleTopConstraint = grabberHandle.topAnchor.constraint(equalTo: topAnchor, constant: grabberTopPadding)
public override class var requiresConstraintBasedLayout: Bool { return true }
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)
@@ -134,9 +132,9 @@ public class FloatingPanelSurfaceView: UIView {
addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerViewTopConstraint,
containerViewLeftConstraint,
containerViewRightConstraint,
containerViewTopInsetConstraint,
containerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
containerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
containerViewHeightConstraint,
])
@@ -151,21 +149,18 @@ public class FloatingPanelSurfaceView: UIView {
}
public override func updateConstraints() {
containerViewTopConstraint.constant = containerMargins.top
containerViewLeftConstraint.constant = containerMargins.left
containerViewRightConstraint.constant = -containerMargins.right
containerViewHeightConstraint.constant = (containerMargins.bottom == 0) ? bottomOverflow : -(containerMargins.top + containerMargins.bottom)
super.updateConstraints()
containerViewTopInsetConstraint.constant = containerTopInset
containerViewHeightConstraint.constant = bottomOverflow
contentViewTopConstraint?.constant = containerMargins.top + contentInsets.top
contentViewLeftConstraint?.constant = containerMargins.left + contentInsets.left
contentViewRightConstraint?.constant = containerMargins.right + contentInsets.right
contentViewHeightConstraint?.constant = -(containerMargins.top + containerMargins.bottom + contentInsets.top + contentInsets.bottom)
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() {
@@ -200,7 +195,6 @@ public class FloatingPanelSurfaceView: UIView {
return
}
containerView.layer.masksToBounds = true
guard containerMargins.bottom == 0 else { return }
if #available(iOS 11, *) {
// Don't use `contentView.clipToBounds` because it prevents content view from expanding the height of a subview of it
// for the bottom overflow like Auto Layout settings of UIVisualEffectView in Main.storyboard of Example/Maps.
@@ -223,12 +217,10 @@ public class FloatingPanelSurfaceView: UIView {
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
contentView.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = contentView.topAnchor.constraint(equalTo: topAnchor, constant: containerMargins.top + contentInsets.top)
let leftConstraint = contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: containerMargins.left + contentInsets.left)
let rightConstraint = rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: containerMargins.right + contentInsets.right)
let heightPadding = containerMargins.top + containerMargins.bottom + contentInsets.top + contentInsets.bottom
let heightConstraint = contentView.heightAnchor.constraint(equalTo: heightAnchor, constant: -heightPadding)
heightConstraint.priority = UILayoutPriority(999)
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([
topConstraint,
leftConstraint,
@@ -59,7 +59,9 @@ class FloatingPanelPresentationController: UIPresentationController {
// Forward touch events to the presenting view controller
(fpc.view as? FloatingPanelPassThroughView)?.eventForwardingView = presentingViewController.view
fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true
// 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) {
+3 -1
View File
@@ -7,7 +7,9 @@ import UIKit
public class GrabberHandleView: UIView {
public var barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0) { didSet { backgroundColor = barColor } }
@objc dynamic 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)
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.7.5</string>
<string>1.5.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
+5 -37
View File
@@ -21,7 +21,7 @@ class CustomLayoutGuide: LayoutGuideProvider {
}
extension UIViewController {
@objc var layoutInsets: UIEdgeInsets {
var layoutInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return view.safeAreaInsets
} else {
@@ -60,10 +60,6 @@ extension UIView {
return self
}
}
var presentationFrame: CGRect {
return layer.presentation()?.frame ?? frame
}
}
extension UIView {
@@ -75,12 +71,6 @@ extension UIView {
func enableAutoLayout() {
translatesAutoresizingMaskIntoConstraints = false
}
static func performWithLinear(startTime: Double = 0.0, relativeDuration: Double = 1.0, _ animations: @escaping (() -> Void)) {
UIView.animateKeyframes(withDuration: 0.0, delay: 0.0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: startTime, relativeDuration: relativeDuration, animations: animations)
}, completion: nil)
}
}
#if __FP_LOG
@@ -114,8 +104,8 @@ extension UIGestureRecognizerState: CustomDebugStringConvertible {
#endif
extension UIScrollView {
var isLocked: Bool {
return !showsVerticalScrollIndicator && !bounces && isDirectionalLockEnabled
var contentOffsetZero: CGPoint {
return CGPoint(x: 0.0, y: 0.0 - contentInset.top)
}
}
@@ -130,29 +120,7 @@ extension UISpringTimingParameters {
extension CGPoint {
static var nan: CGPoint {
return CGPoint(x: CGFloat.nan, y: CGFloat.nan)
}
static func - (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
}
extension UITraitCollection {
func shouldUpdateLayout(from previous: UITraitCollection) -> Bool {
return previous.horizontalSizeClass != horizontalSizeClass
|| previous.verticalSizeClass != verticalSizeClass
|| previous.preferredContentSizeCategory != preferredContentSizeCategory
|| previous.layoutDirection != layoutDirection
}
}
extension NSLayoutConstraint {
static func activate(constraint: NSLayoutConstraint?) {
guard let constraint = constraint else { return }
self.activate([constraint])
}
static func deactivate(constraint: NSLayoutConstraint?) {
guard let constraint = constraint else { return }
self.deactivate([constraint])
return CGPoint(x: CGFloat.nan,
y: CGFloat.nan)
}
}
@@ -7,7 +7,9 @@ import XCTest
@testable import FloatingPanel
class FloatingPanelControllerTests: XCTestCase {
override func setUp() {}
override func tearDown() {}
func test_warningRetainCycle() {
@@ -26,141 +28,24 @@ class FloatingPanelControllerTests: XCTestCase {
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: false)
XCTAssert(fpc.surfaceView.frame.minY == (fpc.view.bounds.height - fpc.layoutInsets.bottom) - fpc.layout.insetFor(position: .tip)!)
}
@available(iOS 12.0, *)
func test_updateLayout_willTransition() {
class MyDelegate: FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
if newCollection.userInterfaceStyle == .dark {
XCTFail()
}
return nil
}
}
let myDelegate = MyDelegate()
let fpc = FloatingPanelController(delegate: myDelegate)
let traitCollection = UITraitCollection(traitsFrom: [fpc.traitCollection,
UITraitCollection(userInterfaceStyle: .dark)])
XCTAssertEqual(traitCollection.userInterfaceStyle, .dark)
fpc.prepare(for: traitCollection)
}
func test_moveTo() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
XCTAssertEqual(delegate.position, .hidden)
fpc.showForTest()
XCTAssertEqual(delegate.position, .half)
fpc.hide()
XCTAssertEqual(delegate.position, .hidden)
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.position, .full)
XCTAssertEqual(delegate.position, .full)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full))
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.position, .half)
XCTAssertEqual(delegate.position, .half)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .half))
fpc.move(to: .tip, animated: false)
XCTAssertEqual(fpc.position, .tip)
XCTAssertEqual(delegate.position, .tip)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .tip))
fpc.move(to: .hidden, animated: false)
XCTAssertEqual(fpc.position, .hidden)
XCTAssertEqual(delegate.position, .hidden)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .hidden))
fpc.move(to: .full, animated: true)
XCTAssertEqual(fpc.position, .full)
XCTAssertEqual(delegate.position, .full)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full))
fpc.move(to: .half, animated: true)
XCTAssertEqual(fpc.position, .half)
XCTAssertEqual(delegate.position, .half)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .half))
fpc.move(to: .tip, animated: true)
XCTAssertEqual(fpc.position, .tip)
XCTAssertEqual(delegate.position, .tip)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .tip))
fpc.move(to: .hidden, animated: true)
XCTAssertEqual(fpc.position, .hidden)
XCTAssertEqual(delegate.position, .hidden)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .hidden))
}
func test_moveWithNearbyPosition() {
let delegate = FloatingPanelTestDelegate()
let fpc = FloatingPanelController(delegate: delegate)
XCTAssertEqual(delegate.position, .hidden)
fpc.showForTest()
XCTAssertEqual(fpc.nearbyPosition, .half)
fpc.hide()
XCTAssertEqual(fpc.nearbyPosition, .tip)
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.nearbyPosition, .full)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full))
}
func test_originSurfaceY() {
let fpc = FloatingPanelController(delegate: nil)
fpc.loadViewIfNeeded()
fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
fpc.show(animated: false, completion: nil)
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .full))
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .half))
fpc.move(to: .tip, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .tip))
fpc.move(to: .hidden, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.minY, fpc.originYOfSurface(for: .hidden))
}
func test_contentMode() {
let fpc = FloatingPanelController(delegate: nil)
fpc.loadViewIfNeeded()
fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
fpc.show(animated: false, completion: nil)
fpc.contentMode = .static
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full))
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full))
fpc.move(to: .tip, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full))
fpc.contentMode = .fitToBounds
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .full))
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .half))
fpc.move(to: .tip, animated: false)
XCTAssertEqual(fpc.surfaceView.frame.height, fpc.view.bounds.height - fpc.originYOfSurface(for: .tip))
waitRunLoop(secs: 1.0)
XCTAssert(fpc.surfaceView.frame.minY == (fpc.view.bounds.height - fpc.layoutInsets.bottom) - fpc.layout.insetFor(position: .tip)!)
}
}
private class MyZombieViewController: UIViewController, FloatingPanelLayout, FloatingPanelBehavior, FloatingPanelControllerDelegate {
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)
@@ -1,291 +0,0 @@
//
// Created by Shin Yamamoto on 2019/06/27.
// Copyright © 2019 scenee. All rights reserved.
//
import XCTest
@testable import FloatingPanel
class FloatingPanelLayoutTests: XCTestCase {
var fpc: FloatingPanelController!
override func setUp() {
fpc = FloatingPanelController(delegate: nil)
fpc.loadViewIfNeeded()
fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
}
override func tearDown() {}
func test_layoutAdapter_topAndBottomMostState() {
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.topMostState, .full)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.bottomMostState, .tip)
class FloatingPanelLayoutWithHidden: FloatingPanelLayout {
func insetFor(position: FloatingPanelPosition) -> CGFloat? { return nil }
let initialPosition: FloatingPanelPosition = .hidden
let supportedPositions: Set<FloatingPanelPosition> = [.hidden, .half, .full]
}
class FloatingPanelLayout2Positions: FloatingPanelLayout {
func insetFor(position: FloatingPanelPosition) -> CGFloat? { return nil }
let initialPosition: FloatingPanelPosition = .tip
let supportedPositions: Set<FloatingPanelPosition> = [.tip, .half]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayoutWithHidden()
fpc.delegate = delegate
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.topMostState, .full)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.bottomMostState, .hidden)
delegate.layout = FloatingPanelLayout2Positions()
fpc.delegate = delegate
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.topMostState, .half)
XCTAssertEqual(fpc.floatingPanel.layoutAdapter.bottomMostState, .tip)
}
func test_layoutSegment_3position() {
class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .half
let supportedPositions: Set<FloatingPanelPosition> = [.tip, .half, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout3Positions()
fpc.delegate = delegate
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let tipPos = fpc.originYOfSurface(for: .tip)
let minPos = CGFloat.leastNormalMagnitude
let maxPos = CGFloat.greatestFiniteMagnitude
assertLayoutSegment(fpc.floatingPanel, with: [
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
(#line, pos: halfPos, forwardY: true, lower: .half, upper: .tip),
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
(#line, pos: tipPos, forwardY: true, lower: .tip, upper: nil),
(#line, pos: tipPos, forwardY: false, lower: .half, upper: .tip),
(#line, pos: maxPos, forwardY: true, lower: .tip, upper: nil),
(#line, pos: maxPos, forwardY: false, lower: .tip, upper: nil),
])
}
func test_layoutSegment_2positions() {
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .half
let supportedPositions: Set<FloatingPanelPosition> = [.half, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout2Positions()
fpc.delegate = delegate
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let minPos = CGFloat.leastNormalMagnitude
let maxPos = CGFloat.greatestFiniteMagnitude
assertLayoutSegment(fpc.floatingPanel, with: [
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
(#line, pos: fullPos, forwardY: true, lower: .full, upper: .half),
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
(#line, pos: halfPos, forwardY: true, lower: .half, upper: nil),
(#line, pos: halfPos, forwardY: false, lower: .full, upper: .half),
(#line, pos: maxPos, forwardY: true, lower: .half, upper: nil),
(#line, pos: maxPos, forwardY: false, lower: .half, upper: nil),
])
}
func test_layoutSegment_1positions() {
class FloatingPanelLayout1Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .full
let supportedPositions: Set<FloatingPanelPosition> = [.full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout1Positions()
fpc.delegate = delegate
let fullPos = fpc.originYOfSurface(for: .full)
let minPos = CGFloat.leastNormalMagnitude
let maxPos = CGFloat.greatestFiniteMagnitude
assertLayoutSegment(fpc.floatingPanel, with: [
(#line, pos: minPos, forwardY: true, lower: nil, upper: .full),
(#line, pos: minPos, forwardY: false, lower: nil, upper: .full),
(#line, pos: fullPos, forwardY: true, lower: .full, upper: nil),
(#line, pos: fullPos, forwardY: false, lower: nil, upper: .full),
(#line, pos: maxPos, forwardY: true, lower: .full, upper: nil),
(#line, pos: maxPos, forwardY: false, lower: .full, upper: nil),
])
}
func test_updateInteractiveTopConstraint() {
fpc.showForTest()
fpc.move(to: .full, animated: false)
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.position)
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.position) // Should be ignore
let fullPos = fpc.originYOfSurface(for: .full)
let tipPos = fpc.originYOfSurface(for: .tip)
var pre: CGFloat
var next: CGFloat
pre = fpc.surfaceView.frame.minY
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: false, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, pre)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, fullPos - fpc.layout.topInteractionBuffer)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: 100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, fullPos + 100.0)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: tipPos - fullPos, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, tipPos)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: tipPos - fullPos + 100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, tipPos + fpc.layout.bottomInteractionBuffer)
fpc.floatingPanel.layoutAdapter.endInteraction(at: fpc.position)
}
func test_updateInteractiveTopConstraintWithHidden() {
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .hidden
let supportedPositions: Set<FloatingPanelPosition> = [.hidden, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout2Positions()
fpc.delegate = delegate
fpc.showForTest()
fpc.move(to: .full, animated: false)
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.position)
let fullPos = fpc.originYOfSurface(for: .full)
let hiddenPos = fpc.originYOfSurface(for: .hidden)
var pre: CGFloat
var next: CGFloat
pre = fpc.surfaceView.frame.minY
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: false, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, pre)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, fullPos - fpc.layout.topInteractionBuffer)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: hiddenPos - fullPos + 100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, hiddenPos + fpc.layout.bottomInteractionBuffer)
fpc.floatingPanel.layoutAdapter.endInteraction(at: fpc.position)
}
func test_updateInteractiveTopConstraintWithMinusInsets() {
class FloatingPanelLayoutMinusInsets: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .full
let supportedPositions: Set<FloatingPanelPosition> = [.tip, .full]
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full, .tip: return -200
default: return nil
}
}
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayoutMinusInsets()
fpc.delegate = delegate
fpc.showForTest()
fpc.floatingPanel.layoutAdapter.startInteraction(at: fpc.position)
let fullPos = fpc.originYOfSurface(for: .full)
let tipPos = fpc.originYOfSurface(for: .tip)
let current = fpc.surfaceView.frame.minY
var next: CGFloat
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: false, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, current)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: -100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, fullPos - fpc.layout.topInteractionBuffer)
fpc.floatingPanel.layoutAdapter.updateInteractiveTopConstraint(diff: tipPos - fullPos + 100.0, allowsTopBuffer: true, with: fpc.behavior)
next = fpc.surfaceView.frame.minY
XCTAssertEqual(next, tipPos + fpc.layout.bottomInteractionBuffer)
fpc.floatingPanel.layoutAdapter.endInteraction(at: fpc.position)
}
func test_positionReference() {
fpc = CustomSafeAreaFloatingPanelController()
fpc.loadViewIfNeeded()
fpc.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
class MyFloatingPanelFullLayout: FloatingPanelTestLayout {
var initialPosition: FloatingPanelPosition = .half
var positionReference: FloatingPanelLayoutReference {
return .fromSuperview
}
}
class MyFloatingPanelSafeAreaLayout: FloatingPanelTestLayout {
var initialPosition: FloatingPanelPosition = .half
var positionReference: FloatingPanelLayoutReference {
return .fromSafeArea
}
}
let fullLayout = MyFloatingPanelFullLayout()
let delegate = FloatingPanelTestDelegate()
delegate.layout = fullLayout
fpc.delegate = delegate
fpc.showForTest()
XCTAssertEqual(fpc.layout.positionReference, .fromSuperview)
XCTAssertEqual(fpc.originYOfSurface(for: .full), fullLayout.insetFor(position: .full))
XCTAssertEqual(fpc.originYOfSurface(for: .half), fpc.view!.frame.height - fullLayout.insetFor(position: .half)!)
XCTAssertEqual(fpc.originYOfSurface(for: .tip), fpc.view!.frame.height - fullLayout.insetFor(position: .tip)!)
let safeAreaLayout = MyFloatingPanelSafeAreaLayout()
delegate.layout = safeAreaLayout
fpc.delegate = delegate
XCTAssertEqual(fpc.layout.positionReference, .fromSafeArea)
XCTAssertEqual(fpc.originYOfSurface(for: .full),
fullLayout.insetFor(position: .full)! + fpc.layoutInsets.top)
XCTAssertEqual(fpc.originYOfSurface(for: .half),
fpc.view!.frame.height - (fullLayout.insetFor(position: .half)! + fpc.layoutInsets.bottom))
XCTAssertEqual(fpc.originYOfSurface(for: .tip),
fpc.view!.frame.height - (fullLayout.insetFor(position: .tip)! + fpc.layoutInsets.bottom))
}
}
private typealias LayoutSegmentTestParameter = (UInt, pos: CGFloat, forwardY: Bool, lower: FloatingPanelPosition?, upper: FloatingPanelPosition?)
private func assertLayoutSegment(_ floatingPanel: FloatingPanelCore, with params: [LayoutSegmentTestParameter]) {
params.forEach { (line, pos, forwardY, lowr, upper) in
let segument = floatingPanel.layoutAdapter.segument(at: pos, forward: forwardY)
XCTAssertEqual(segument.lower, lowr, line: line)
XCTAssertEqual(segument.upper, upper, line: line)
}
}
private class CustomSafeAreaFloatingPanelController: FloatingPanelController { }
extension CustomSafeAreaFloatingPanelController {
override var layoutInsets: UIEdgeInsets {
return UIEdgeInsets(top: 64.0, left: 0.0, bottom: 0.0, right: 34.0)
}
}
@@ -1,27 +0,0 @@
//
// Created by Shin Yamamoto on 2019/07/05.
// Copyright © 2019 scenee. All rights reserved.
//
import XCTest
@testable import FloatingPanel
class FloatingPanelPositionTests: XCTestCase {
override func setUp() { }
override func tearDown() { }
func test_nextAndPre() {
var positions: [FloatingPanelPosition]
positions = [.full, .half, .tip, .hidden]
XCTAssertEqual(FloatingPanelPosition.full.next(in: positions), .half)
XCTAssertEqual(FloatingPanelPosition.full.pre(in: positions), .full)
XCTAssertEqual(FloatingPanelPosition.hidden.next(in: positions), .hidden)
XCTAssertEqual(FloatingPanelPosition.hidden.pre(in: positions), .tip)
positions = [.full, .hidden]
XCTAssertEqual(FloatingPanelPosition.full.next(in: positions), .hidden)
XCTAssertEqual(FloatingPanelPosition.full.pre(in: positions), .full)
XCTAssertEqual(FloatingPanelPosition.hidden.next(in: positions), .hidden)
XCTAssertEqual(FloatingPanelPosition.hidden.pre(in: positions), .full)
}
}
@@ -1,118 +0,0 @@
//
// Created by Shin Yamamoto on 2019/05/23.
// Copyright © 2019 Shin Yamamoto. All rights reserved.
//
import XCTest
@testable import FloatingPanel
class FloatingPanelSurfaceViewTests: 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))
XCTAssertTrue(FloatingPanelSurfaceView.requiresConstraintBasedLayout)
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_grabberHandle() {
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.grabberHandleWidth = 44.0
surface.grabberHandleHeight = 12.0
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssert(surface.grabberHandle.frame.width == surface.grabberHandleWidth, "\(surface.grabberHandle.frame.width) == \(surface.grabberHandleWidth)")
XCTAssert(surface.grabberHandle.frame.height == surface.grabberHandleHeight, "\(surface.grabberHandle.frame.height) == \(surface.grabberHandleHeight)")
}
func test_surfaceView_containerMargins() {
let surface = FloatingPanelSurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
surface.layoutIfNeeded()
XCTAssertEqual(surface.containerView.frame, surface.bounds)
surface.containerMargins = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.containerView.frame, surface.bounds.inset(by: surface.containerMargins))
}
func test_surfaceView_contentInsets() {
let surface = FloatingPanelSurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
let contentView = UIView()
surface.add(contentView: contentView)
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView.frame, surface.bounds)
surface.contentInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView.frame, surface.bounds.inset(by: surface.contentInsets))
}
func test_surfaceView_containerMargins_and_contentInsets() {
let surface = FloatingPanelSurfaceView(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 480.0))
let contentView = UIView()
surface.add(contentView: contentView)
surface.layoutIfNeeded()
XCTAssertEqual(surface.contentView.frame, surface.bounds)
surface.containerMargins = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.contentInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
surface.setNeedsLayout()
surface.layoutIfNeeded()
XCTAssertEqual(surface.containerView.frame, surface.bounds.inset(by: surface.containerMargins))
XCTAssertEqual(surface.contentView.frame, surface.containerView.bounds.inset(by: surface.contentInsets))
}
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)
}
}
+2 -525
View File
@@ -4,534 +4,11 @@
//
import XCTest
@testable import FloatingPanel
class FloatingPanelTests: XCTestCase {
override func setUp() {}
override func tearDown() {}
func test_scrolllock() {
let fpc = FloatingPanelController()
let contentVC1 = UITableViewController(nibName: nil, bundle: nil)
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
XCTAssertEqual(contentVC1.tableView.bounces, true)
fpc.set(contentViewController: contentVC1)
fpc.track(scrollView: contentVC1.tableView)
fpc.showForTest()
XCTAssertEqual(fpc.position, .half)
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
XCTAssertEqual(contentVC1.tableView.bounces, false)
fpc.move(to: .full, animated: false)
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
XCTAssertEqual(contentVC1.tableView.bounces, true)
fpc.move(to: .tip, animated: false)
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
XCTAssertEqual(contentVC1.tableView.bounces, false)
let exp1 = expectation(description: "move to full with animation")
fpc.move(to: .full, animated: true) {
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, true)
XCTAssertEqual(contentVC1.tableView.bounces, true)
exp1.fulfill()
}
wait(for: [exp1], timeout: 1.0)
let exp2 = expectation(description: "move to tip with animation")
fpc.move(to: .tip, animated: false) {
XCTAssertEqual(contentVC1.tableView.showsVerticalScrollIndicator, false)
XCTAssertEqual(contentVC1.tableView.bounces, false)
exp2.fulfill()
}
wait(for: [exp2], timeout: 1.0)
// Reset the content vc
let contentVC2 = UITableViewController(nibName: nil, bundle: nil)
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, true)
XCTAssertEqual(contentVC2.tableView.bounces, true)
fpc.set(contentViewController: contentVC2)
fpc.track(scrollView: contentVC2.tableView)
fpc.show(animated: false, completion: nil)
XCTAssertEqual(fpc.position, .half)
XCTAssertEqual(contentVC2.tableView.showsVerticalScrollIndicator, false)
XCTAssertEqual(contentVC2.tableView.bounces, false)
}
func test_getBackdropAlpha_1positions() {
class FloatingPanelLayout1Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .full
let supportedPositions: Set<FloatingPanelPosition> = [.full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout1Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos - 100.0, with: CGPoint(x: 0.0, y: -100.0)), 0.3)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: CGPoint(x: 0.0, y: 0.0)), 0.3)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos + 100.0, with: CGPoint(x: 0.0, y: 100.0)), 0.3) // ok??
}
func test_getBackdropAlpha_2positions() {
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .half
let supportedPositions: Set<FloatingPanelPosition> = [.half, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout2Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let distance1 = abs(halfPos - fullPos)
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: CGPoint(x: 0.0, y: 0.0)), 0.3)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos + distance1 * 0.5, with: CGPoint(x: 0.0, y: distance1 * 0.5)), 0.3 * 0.5)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos, with: CGPoint(x: 0.0, y: distance1)), 0.0)
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos, with: CGPoint(x: 0.0, y: 0.0)), 0.0)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos + distance1 * 0.5, with: CGPoint(x: 0.0, y: -0.5 * distance1)), 0.3 * 0.5)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: CGPoint(x: 0.0, y: -1 * distance1)), 0.3)
}
func test_getBackdropAlpha_2positionsWithHidden() {
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .hidden
let supportedPositions: Set<FloatingPanelPosition> = [.hidden, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout2Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let hiddenPos = fpc.originYOfSurface(for: .hidden)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos - 100.0, with: CGPoint(x: 0.0, y: -100.0)), 0.3)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: CGPoint(x: 0.0, y: 0.0)), 0.3)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: hiddenPos, with: CGPoint(x: 0.0, y: 100.0)), 0.0)
}
func test_getBackdropAlpha_3positions() {
let fpc = FloatingPanelController()
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let tipPos = fpc.originYOfSurface(for: .tip)
let distance1 = abs(halfPos - fullPos)
let distance2 = abs(tipPos - halfPos)
fpc.move(to: .full, animated: false)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: CGPoint(x: 0.0, y: 0.0)), 0.3)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos + distance1 * 0.5, with: CGPoint(x: 0.0, y: distance1 * 0.5)), 0.3 * 0.5)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos, with: CGPoint(x: 0.0, y: distance1)), 0.0)
fpc.move(to: .half, animated: false)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos, with: CGPoint(x: 0.0, y: 0.0)), 0.0)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos + distance1 * 0.5, with: CGPoint(x: 0.0, y: -0.5 * distance1)), 0.3 * 0.5)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: fullPos, with: CGPoint(x: 0.0, y: -1 * distance1)), 0.3)
fpc.move(to: .tip, animated: false)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: tipPos, with: CGPoint(x: 0.0, y: 0.0)), 0.0)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos + distance2 * 0.5, with: CGPoint(x: 0.0, y: -0.5 * distance2)), 0.0)
XCTAssertEqual(fpc.floatingPanel.getBackdropAlpha(at: halfPos, with: CGPoint(x: 0.0, y: -1 * distance2)), 0.0)
}
func test_targetPosition_1positions() {
class FloatingPanelLayout1Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .full
let supportedPositions: Set<FloatingPanelPosition> = [.full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout1Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .full), // redirect
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
])
}
func test_targetPosition_2positions() {
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .half
let supportedPositions: Set<FloatingPanelPosition> = [.half, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout2Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .half), // redirect
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
])
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // project to half
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .half), // redirect
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
])
}
func test_targetPosition_2positionsWithHidden() {
class FloatingPanelLayout2Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .hidden
let supportedPositions: Set<FloatingPanelPosition> = [.hidden, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout2Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let hiddenPos = fpc.originYOfSurface(for: .hidden)
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, hiddenPos - 10.0, CGPoint(x: 0.0, y: -100.0), .hidden), // redirect
(#line, hiddenPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
(#line, hiddenPos, CGPoint(x: 0.0, y: -100.0), .hidden),
(#line, hiddenPos, CGPoint(x: 0.0, y: 0.0), .hidden),
(#line, hiddenPos, CGPoint(x: 0.0, y: 100.0), .hidden),
(#line, hiddenPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // redirect
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
])
fpc.move(to: .hidden, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
(#line, fullPos, CGPoint(x: 0.0, y: -1000.0), .full), // redirect
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // project to hidden
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, hiddenPos - 10.0, CGPoint(x: 0.0, y: -100.0), .hidden), // redirect
(#line, hiddenPos, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
(#line, hiddenPos, CGPoint(x: 0.0, y: -100.0), .hidden),
(#line, hiddenPos, CGPoint(x: 0.0, y: 0.0), .hidden),
(#line, hiddenPos, CGPoint(x: 0.0, y: 100.0), .hidden),
(#line, hiddenPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // redirect
(#line, hiddenPos + 10.0, CGPoint(x: 0.0, y: -1000.0), .full), // project to full
])
}
func test_targetPosition_2positionsFromFull() {
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout3Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let tipPos = fpc.originYOfSurface(for: .tip)
// From .full
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full), //project to full
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
])
}
func test_targetPosition_3positionsFromHalf() {
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout3Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let tipPos = fpc.originYOfSurface(for: .tip)
// From .half
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),// project to full
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
])
}
func test_targetPosition_3positionsFromTip() {
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout3Positions()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let tipPos = fpc.originYOfSurface(for: .tip)
// From .tip
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: -100.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 0.0), .full), // far from topMostState
(#line, fullPos - 500.0, CGPoint(x: 0.0, y: 100.0), .full), // far from topMostState
(#line, fullPos, CGPoint(x: 0.0, y: -100.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 0.0), .full),
(#line, fullPos, CGPoint(x: 0.0, y: 500.0), .half), // project to half
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .half), // block projecting to tip at half
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .half), // block projecting to tip at half
(#line, fullPos + 10.0, CGPoint(x: 0.0, y: 100.0), .full), // redirect
(#line, halfPos - 10.0, CGPoint(x: 0.0, y: -100.0), .half), // redirect
(#line, halfPos, CGPoint(x: 0.0, y: -3000.0), .full), // project to full
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip), // project to tip
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirect
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirect
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), // block projecting to full at half
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half), // block projecting to full at half
(#line, tipPos, CGPoint(x: 0.0, y: -500.0), .half), // project to half
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 100.0), .tip),
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: -100.0), .tip), // far from bottomMostState
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 0.0), .tip), // far from bottomMostState
(#line, tipPos + 500.0, CGPoint(x: 0.0, y: 100.0), .tip), // far from bottomMostState
])
}
func test_targetPosition_3positionsAllProjection() {
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout3Positions()
delegate.behavior = FloatingPanelProjectionalBehavior()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
let fullPos = fpc.originYOfSurface(for: .full)
let halfPos = fpc.originYOfSurface(for: .half)
let tipPos = fpc.originYOfSurface(for: .tip)
// From .full
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .full),
])
// From .half
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
])
// From .tip
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fullPos - 10.0, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, fullPos, CGPoint(x: 0.0, y: 3000.0), .tip),
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .tip),
(#line, halfPos, CGPoint(x: 0.0, y: -1000.0), .full),
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .full),
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .full),
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: -3000.0), .full),
])
}
func test_targetPosition_3positionsWithHidden() {
class FloatingPanelLayout3PositionsWithHidden: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .hidden
let supportedPositions: Set<FloatingPanelPosition> = [.hidden, .half, .full]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout3PositionsWithHidden()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
XCTAssertEqual(fpc.position, .hidden)
fpc.move(to: .full, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .half),
])
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -100.0), .half),
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: -1000.0), .full),
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 0.0), .half),
(#line, fpc.surfaceView.frame.minY, CGPoint(x: 0.0, y: 1000.0), .hidden),
])
}
func test_targetPosition_3positionsWithHiddenWithoutFull() {
class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .hidden
let supportedPositions: Set<FloatingPanelPosition> = [.hidden, .tip, .half]
}
let delegate = FloatingPanelTestDelegate()
delegate.layout = FloatingPanelLayout3Positions()
delegate.behavior = FloatingPanelProjectionalBehavior()
let fpc = FloatingPanelController(delegate: delegate)
fpc.showForTest()
XCTAssertEqual(fpc.position, .hidden)
let halfPos = fpc.originYOfSurface(for: .half)
let tipPos = fpc.originYOfSurface(for: .tip)
//let hiddenPos = fpc.originYOfSurface(for: .hidden)
fpc.move(to: .half, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, halfPos, CGPoint(x: 0.0, y: -100.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 0.0), .half),
(#line, halfPos, CGPoint(x: 0.0, y: 385.0), .tip), // projection
(#line, halfPos, CGPoint(x: 0.0, y: 1000.0), .hidden), // projection
(#line, halfPos + 10.0, CGPoint(x: 0.0, y: 100.0), .half), // redirection
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: -100.0), .tip), // redirection
(#line, tipPos, CGPoint(x: 0.0, y: -3000.0), .half), //projection
(#line, tipPos, CGPoint(x: 0.0, y: -10.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 10.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .hidden), //projection
(#line, tipPos + 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
(#line, tipPos - 10.0, CGPoint(x: 0.0, y: 10.0), .tip), // redirection
])
fpc.move(to: .tip, animated: false)
assertTargetPosition(fpc.floatingPanel, with: [
(#line, tipPos, CGPoint(x: 0.0, y: -100.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: -1000.0), .half),
(#line, tipPos, CGPoint(x: 0.0, y: 0.0), .tip),
(#line, tipPos, CGPoint(x: 0.0, y: 1000.0), .hidden),
])
}
}
private class FloatingPanelLayout3Positions: FloatingPanelTestLayout {
let initialPosition: FloatingPanelPosition = .tip
let supportedPositions: Set<FloatingPanelPosition> = [.tip, .half, .full]
}
private typealias TestParameter = (UInt, CGFloat,CGPoint, FloatingPanelPosition)
private func assertTargetPosition(_ floatingPanel: FloatingPanelCore, with params: [TestParameter]) {
params.forEach { (line, pos, velocity, result) in
floatingPanel.surfaceView.frame.origin.y = pos
XCTAssertEqual(floatingPanel.targetPosition(from: pos, with: velocity), result, line: line)
}
}
private class FloatingPanelProjectionalBehavior: FloatingPanelBehavior {
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
return true
}
}
@@ -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)
}
}
-46
View File
@@ -1,46 +0,0 @@
//
// Created by Shin Yamamoto on 2019/06/27.
// Copyright © 2019 scenee. All rights reserved.
//
import Foundation
@testable import FloatingPanel
func waitRunLoop(secs: TimeInterval = 0) {
RunLoop.main.run(until: Date(timeIntervalSinceNow: secs))
}
extension FloatingPanelController {
func showForTest() {
loadViewIfNeeded()
view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
show(animated: false, completion: nil)
}
}
class FloatingPanelTestDelegate: FloatingPanelControllerDelegate {
var layout: FloatingPanelLayout?
var behavior: FloatingPanelBehavior?
var position: FloatingPanelPosition = .hidden
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
return layout
}
func floatingPanel(_ vc: FloatingPanelController, behaviorFor newCollection: UITraitCollection) -> FloatingPanelBehavior? {
return behavior
}
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
position = vc.position
}
}
protocol FloatingPanelTestLayout: FloatingPanelFullScreenLayout {}
extension FloatingPanelTestLayout {
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 20.0
case .half: return 250.0
case .tip: return 60.0
default: return nil
}
}
}
-27
View File
@@ -1,27 +0,0 @@
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FloatingPanel",
platforms: [
.iOS(.v10)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "FloatingPanel",
targets: ["FloatingPanel"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(name: "FloatingPanel", path: "Framework/Sources"),
],
swiftLanguageVersions: [.version("5")]
)
+29 -172
View File
@@ -5,9 +5,9 @@
[![Swift 4.1](https://img.shields.io/badge/Swift-4.1-orange.svg?style=flat)](https://swift.org/)
[![Swift 4.2](https://img.shields.io/badge/Swift-4.2-orange.svg?style=flat)](https://swift.org/)
[![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg?style=flat)](https://swift.org/)
[![Swift 5.1](https://img.shields.io/badge/Swift-5.1-orange.svg?style=flat)](https://swift.org/)
# FloatingPanel
# FloatingPanel
FloatingPanel is a simple and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
The new interface displays the related contents and utilities in parallel as a user wants.
@@ -24,37 +24,25 @@ The new interface displays the related contents and utilities in parallel as a u
- [Installation](#installation)
- [CocoaPods](#cocoapods)
- [Carthage](#carthage)
- [Swift Package Manager with Xcode 11](#swift-package-manager-with-xcode-11)
- [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)
- [Scale the content view when the surface position changes](#scale-the-content-view-when-the-surface-position-changes)
- [Customize the layout with `FloatingPanelLayout` protocol](#customize-the-layout-with-floatingpanellayout-protocol)
- [Change the initial 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)
- [Specify position insets from the frame of `FloatingPanelController.view`, not the SafeArea](#specify-position-insets-from-the-frame-of-floatingpanelcontrollerview-not-the-safearea)
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
- [Activate the rubber-band effect on the top/bottom edges](#activate-the-rubber-band-effect-on-the-topbottom-edges)
- [Manage the projection of a pan gesture momentum](#manage-the-projection-of-a-pan-gesture-momentum)
- [Customize the surface design](#customize-the-surface-design)
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
- [Customize layout of the grabber handle](#customize-layout-of-the-grabber-handle)
- [Customize content padding from surface edges](#customize-content-padding-from-surface-edges)
- [Customize margins of the surface edges](#customize-margins-of-the-surface-edges)
- [Customize gestures](#customize-gestures)
- [Suppress the panel interaction](#suppress-the-panel-interaction)
- [Add tap gestures to the surface or backdrop views](#add-tap-gestures-to-the-surface-or-backdrop-views)
- [Use a custom grabber handle](#use-a-custom-grabber-handle)
- [Add tap gestures to the surface or backdrop views](#add-tap-gestures-to-the-surface-or-backdrop-views)
- [Create an additional floating panel for a detail](#create-an-additional-floating-panel-for-a-detail)
- [Move a position with an animation](#move-a-position-with-an-animation)
- [Work your contents together with a floating panel behavior](#work-your-contents-together-with-a-floating-panel-behavior)
- [Notes](#notes)
- ['Show' or 'Show Detail' Segues from `FloatingPanelController`'s content view controller](#show-or-show-detail-segues-from-floatingpanelcontrollers-content-view-controller)
- [UISearchController issue](#uisearchcontroller-issue)
- [FloatingPanelSurfaceView's issue on iOS 10](#floatingpanelsurfaceviews-issue-on-ios-10)
- [Author](#author)
- [License](#license)
@@ -95,7 +83,7 @@ it, simply add the following line to your Podfile:
pod 'FloatingPanel'
```
✏️FloatingPanel v1.7.0 or later requires CocoaPods v1.7.0+ for `swift_versions` support.
✏️ 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
@@ -105,10 +93,6 @@ For [Carthage](https://github.com/Carthage/Carthage), add the following to your
github "scenee/FloatingPanel"
```
### Swift Package Manager with Xcode 11
Follow [this doc](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).
## Getting Started
### Add a floating panel as a child view controller
@@ -181,72 +165,41 @@ FloatingPanelController.view (FloatingPanelPassThroughView)
### Show/Hide a floating panel in a view with your view hierarchy
If you need more control over showing and hiding the floating panel, you can forgo the `addPanel` and `removePanelFromParent` methods. These methods are a convenience wrapper for **FloatingPanel**'s `show` and `hide` methods along with some required setup.
There are two ways to work with the `FloatingPanelController`:
1. Add it to the hierarchy once and then call `show` and `hide` methods to make it appear/disappear.
2. Add it to the hierarchy when needed and remove afterwards.
The following example shows how to add the controller to your `UIViewController` and how to remove it. Make sure that you never add the same `FloatingPanelController` to the hierarchy before removing it.
**NOTE**: `self.` prefix is not required, nor recommended. It's used here to make it clearer where do the functions used come from. `self` is an instance of a custom UIViewController in your code.
```swift
// Add the floating panel view to the controller's view on top of other views.
self.view.addSubview(fpc.view)
// REQUIRED. It makes the floating panel view have the same size as the controller's view.
fpc.view.frame = self.view.bounds
// 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.
// Constraint the fpc.view to all four edges of your controller's view.
// It makes the layout more robust on trait collection change.
fpc.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
fpc.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0),
fpc.view.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0.0),
fpc.view.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0.0),
fpc.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0),
])
// Because it makes the layout more robust on trait collection change.
//
// fpc.view.translatesAutoresizingMaskIntoConstraints = false
// NSLayoutConstraint.activate([...])
//
// Add the floating panel controller to the controller hierarchy.
self.addChild(fpc)
parent.addChild(fpc)
// Show the floating panel at the initial position defined in your `FloatingPanelLayout` object.
// Show a floating panel to the initial position defined in your `FloatingPanelLayout` object.
fpc.show(animated: true) {
// Inform the floating panel controller that the transition to the controller hierarchy has completed.
fpc.didMove(toParent: self)
// Only for the first time
self.didMove(toParent: self)
}
```
After you add the `FloatingPanelController` as seen above, you can call `fpc.show(animated: true) { }` to show the panel and `fpc.hide(animated: true) { }` to hide it.
...
To remove the `FloatingPanelController` from the hierarchy, follow the example below.
```swift
// Inform the panel controller that it will be removed from the hierarchy.
fpc.willMove(toParent: nil)
// Hide the floating panel.
// Hide it
fpc.hide(animated: true) {
// Remove the floating panel view from your controller's view.
fpc.view.removeFromSuperview()
// Remove the floating panel controller from the controller hierarchy.
fpc.removeFromParent()
// Remove it if needed
self.willMove(toParent: nil)
self.view.removeFromSuperview()
self.removeFromParent()
}
```
### Scale the content view when the surface position changes
Specify the `contentMode` to `.fitToBounds` if the surface height fits the bounds of `FloatingPanelController.view` when the surface position changes
```swift
fpc.contentMode = .fitToBounds
```
Otherwise, `FloatingPanelController` fixes the content by the height of the top most position.
✏️ In `.fitToBounds` mode, the surface height changes as following a user interaction so that you have a responsibility to configure Auto Layout constrains not to break the layout of a content view by the elastic surface height.
NOTE: `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
@@ -338,27 +291,6 @@ class RemovablePanelLayout: FloatingPanelIntrinsicLayout {
}
```
#### Specify position insets from the frame of `FloatingPanelController.view`, not the SafeArea
There are 2 ways. One is returning `.fromSuperview` for `FloatingPanelLayout.positionReference` in your layout.
```swift
class MyFullScreenLayout: FloatingPanelLayout {
...
var positionReference: FloatingPanelLayoutReference {
return .fromSuperview
}
}
```
Another is using `FloatingPanelFullScreenLayout` protocol.
```swift
class MyFullScreenLayout: FloatingPanelFullScreenLayout {
...
}
```
### Customize the behavior with `FloatingPanelBehavior` protocol
#### Modify your floating panel's interaction
@@ -381,33 +313,7 @@ class FloatingPanelStocksBehavior: FloatingPanelBehavior {
}
```
#### Activate the rubber-band effect on the top/bottom edges
```swift
class FloatingPanelBehavior: FloatingPanelBehavior {
...
func allowsRubberBanding(for edge: UIRectEdge) -> Bool {
return true
}
}
```
#### Manage the projection of a pan gesture momentum
This allows full projectional panel behavior. For example, a user can swipe up a panel from tip to full nearby the tip position.
```swift
class FloatingPanelBehavior: FloatingPanelBehavior {
...
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool {
return true
}
}
```
### Customize the surface design
#### Use a custom grabber handle
### Use a custom grabber handle
```swift
let myGrabberHandleView = MyGrabberHandleView()
@@ -415,50 +321,7 @@ fpc.surfaceView.grabberHandle.isHidden = true
fpc.surfaceView.addSubview(myGrabberHandleView)
```
#### Customize layout of the grabber handle
```swift
fpc.surfaceView.grabberTopPadding = 10.0
fpc.surfaceView.grabberHandleWidth = 44.0
fpc.surfaceView.grabberHandleHeight = 12.0
```
#### Customize content padding from surface edges
```swift
fpc.surfaceView.contentInsets = .init(top: 20, left: 20, bottom: 20, right: 20)
```
#### Customize margins of the surface edges
```swift
fpc.surfaceView.containerMargins = .init(top: 20.0, left: 16.0, bottom: 16.0, right: 16.0)
```
The feature can be used for these 2 kind panels
* Facebook/Slack-like panel whose surface top edge is separated from the grabber handle.
* iOS native panel to display AirPods information, for example.
### Customize gestures
#### Suppress the panel interaction
You can disable the pan gesture recognizer directly
```swift
fpc.panGestureRecognizer.isEnable = false
```
Or use this `FloatingPanelControllerDelegate` method.
```swift
func floatingPanelShouldBeginDragging(_ vc: FloatingPanelController) -> Bool {
return aCondition ? false : true
}
```
#### Add tap gestures to the surface or backdrop views
### Add tap gestures to the surface or backdrop views
```swift
override func viewDidLoad() {
@@ -572,12 +435,6 @@ A `FloatingPanelController` object proxies an action for `show(_:sender)` to the
It's a great way to decouple between a floating panel and the content VC.
### UISearchController issue
`UISearchController` isn't able to be used with `FloatingPanelController` by the system design.
Because `UISearchController` automatically presents itself modally when a user interacts with the search bar, and then it swaps the superview of the search bar to the view managed by itself while it displays. As a result, `FloatingPanelController` can't control the search bar when it's active, as you can see from [the screen shot](https://github.com/SCENEE/FloatingPanel/issues/248#issuecomment-521263831).
### FloatingPanelSurfaceView's issue on iOS 10
* On iOS 10, `FloatingPanelSurfaceView.cornerRadius` isn't not automatically masked with the top rounded corners because of `UIVisualEffectView` issue. See https://forums.developer.apple.com/thread/50854.