Compare commits

...

34 Commits

Author SHA1 Message Date
Shin Yamamoto 7d668c8525 Release v1.3.3 2019-02-02 11:38:37 +09:00
Shin Yamamoto ebd4a32bfc Merge pull request #123 from SCENEE/fix-cut-off-by-mask
Expand the surface mask's height
2019-02-02 11:37:20 +09:00
Shin Yamamoto 6aa739231d Expand the surface mask's height
`FloatingPanelSurfaceView.updateContentViewMask()` causes a content to be cut off.
2019-02-01 10:19:51 +09:00
Shin Yamamoto 8877d32ced Merge pull request #122 from SCENEE/fix-animated-presentation-modally
Fix the presentation modally when fpc is reused
2019-01-31 19:22:46 +09:00
Shin Yamamoto 7f025ae845 Remove the unnecessary code to fix the presentation modally
It's added at `a095ace` commit, but now not needed by the previous
commit.
2019-01-31 12:48:36 +09:00
Shin Yamamoto 1b2dae2135 Fix presentation modally when fpc is reused 2019-01-30 13:09:04 +09:00
Shin Yamamoto 6e4e9df616 Merge pull request #114 from datwelk/hotfix/restore-scroll-view-delegate
Restore original scroll view delegate when updating content VC
2019-01-28 19:11:39 +09:00
Damiaan Twelker 49e868a505 restore original scroll view delegate when updating content viewcontroller 2019-01-26 12:41:17 +01:00
Shin Yamamoto f1b70e0367 Add the issue template 2019-01-25 22:08:44 +09:00
Shin Yamamoto 1d0e747578 Update README.md 2019-01-24 11:00:45 +09:00
Shin Yamamoto 2c72d07cab Merge pull request #97 from SCENEE/support-full-screen
Support full screen layout
2019-01-19 16:41:32 +09:00
Shin Yamamoto 31c057f9f8 Fix typo 2019-01-19 14:53:36 +09:00
Shin Yamamoto d3033df9da Add FloatingPanelFullScreenLayout doc comment 2019-01-19 14:51:04 +09:00
Shin Yamamoto 459d82b1c6 Update 'Show Tab Bar' for full screen layout 2019-01-19 14:06:23 +09:00
Shin Yamamoto 85d7ca640e Stop manipulating a scroll content inset for FloatingPanelFullScreenLayout
It's better that the manipulation should be operated in a user application code.
2019-01-19 14:06:23 +09:00
Shin Yamamoto c1b7f2f092 Merge pull request #106 from SCENEE/release-1.3.2
Release v1.3.2
2019-01-18 09:31:28 +09:00
Shin Yamamoto b7a7e0d4ad Release v1.3.2 2019-01-17 10:47:45 +09:00
Shin Yamamoto dc7f6d58f9 Fix the swift version in podspec 2019-01-17 10:47:45 +09:00
Shin Yamamoto a2a10bd0d3 Merge pull request #104 from SCENEE/fix-modal-presentation
Fix the modal presentation
2019-01-15 09:31:39 +09:00
Shin Yamamoto b54c8ee6ee Merge pull request #105 from CedricGatay/fix/delegateIgnored
fix(delegateIgnored): Allow to set delegate on init
2019-01-14 19:43:39 +09:00
Cedric Gatay 08d275690a fix(delegateIgnored): fix for animation use case
Do not force update layout to allow the user to animate the change later on.
2019-01-14 11:05:50 +01:00
Cedric Gatay 1c307f751e fix(delegateIgnored): listen on delegate change
Properly listens for delegate changes and trigger behavior / layout changes accordingly.

Made constructor parameter name explicit.
2019-01-14 08:49:23 +01:00
Cedric Gatay 6f06a0f7fc fix(delegateIgnored): Allow to set delegate on init
Typical use case is :

```
let floatingVC = FloatingPanelController()
floatingVC.delegate = self
```

This PR allows to set the delegate straight away by using `let floatingVC = FloatingPanelController(self)`
Its true reason is that the setup code that fetches behavior / layout through delegate is called without the delegate being set is useless on `init`ing.
Another implementation could be observing the `didSet` event on delegate to do the `setUp` for `FloatingPanel`
2019-01-12 15:15:48 +01:00
Shin Yamamoto a095ace30e Fix the modal presentation 2019-01-11 19:04:39 +09:00
Shin Yamamoto a486f61f5f Merge pull request #102 from SCENEE/fix-secound-touch-crash
Fix a crash on tap dismissal with a second touch
2019-01-10 09:38:06 +09:00
Shin Yamamoto 14e0abc240 Merge pull request #103 from SCENEE/fix-unsatisfiable-constraints-error
Fix unsatisfiable constraints error for safe area bottom on full state
2019-01-10 09:37:46 +09:00
Shin Yamamoto 32203c48bd Fix unsatisfiable constraints error for safe area bottom on full state 2019-01-10 09:05:17 +09:00
Shin Yamamoto 9d6024f603 Fix a crash on tap dismissal with a second touch
For example, a user tap the backdrop view to dismiss it while dragging
the surface view on presentation modally.
2019-01-10 08:43:46 +09:00
Shin Yamamoto a4dd4e48e7 Merge pull request #98 from SCENEE/support-swift4.1
Support Swift 4.1
2019-01-10 08:25:50 +09:00
Shin Yamamoto e6f7456a0f Merge pull request #101 from SCENEE/fix-touch-handling-on-presentation-modally
Fix the event handling on presentation modally
2019-01-10 08:25:26 +09:00
Shin Yamamoto fca79c9b0c Fix the event handling on presentation modally
If an alpha of the controller's backdrop view is zero, the presentation controller
must not block any touch event outside of surface view.
2019-01-09 09:32:17 +09:00
Shin Yamamoto 4ad7f11e93 Support Swift 4.1 2019-01-05 21:44:05 +09:00
Shin Yamamoto 0412bdc996 Support full screen layout 2019-01-05 12:26:49 +09:00
Shin Yamamoto 31faeaada3 Merge pull request #93 from SCENEE/release-1.3.1
Release v1.3.1
2018-12-30 09:52:25 +09:00
15 changed files with 325 additions and 119 deletions
+27
View File
@@ -0,0 +1,27 @@
> Please fill out this template when filing an issue.
>
> Please remove this line and everything above it before submitting.
### Short description
### Expected behavior
### Actual behavior
### Steps to reproduce
**Code example that reproduces the issue**
### Environment
**Library version**
**Installation method**
- [ ] CocoaPods
- [ ] Carthage
- [ ] Git submodules
**iOS version(s)**
**Xcode version**
+17 -7
View File
@@ -5,10 +5,10 @@ branches:
- next
cache:
directories:
- build
- vendor
- /usr/local/Homebrew
- $HOME/Library/Caches/Homebrew
before_cache:
- brew cleanup
env:
global:
- LANG=en_US.UTF-8
@@ -16,7 +16,17 @@ env:
skip_cleanup: true
jobs:
include:
- stage: carthage
- stage: Build framework(swift 4.1)
osx_image: xcode9.4
script:
- xcodebuild -scheme FloatingPanel clean build
- stage: Build framework(swift 4.2)
osx_image: xcode10
script:
- xcodebuild -scheme FloatingPanel clean build
- stage: Carthage
osx_image: xcode10
before_install:
- brew update
@@ -24,22 +34,22 @@ jobs:
script:
- carthage build --no-skip-current
- stage: podspec
- stage: Podspec
osx_image: xcode10
script:
- pod spec lint
- stage: check Maps example
- stage: Build maps example
osx_image: xcode10
script:
- xcodebuild -scheme Maps -sdk iphonesimulator clean build
- stage: check Stocks example
- stage: Build stocks example
osx_image: xcode10
script:
- xcodebuild -scheme Stocks -sdk iphonesimulator clean build
- stage: check Samples example
- stage: Build samples example
osx_image: xcode10
script:
- xcodebuild -scheme Samples -sdk iphonesimulator clean build
@@ -162,7 +162,7 @@
</objects>
<point key="canvasLocation" x="708" y="-200"/>
</scene>
<!--Item 2-->
<!--Layout 2-->
<scene sceneID="lRc-OZ-sL4">
<objects>
<viewController id="RpE-lI-27a" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
@@ -170,12 +170,6 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AiP-dx-mFn">
<rect key="frame" x="163.66666666666666" y="395.66666666666669" width="48" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="IvG-yp-yzI">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<state key="normal" title="Close"/>
@@ -188,19 +182,49 @@
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="IvG-yp-yzI" firstAttribute="top" secondItem="954-Dk-zvc" secondAttribute="top" id="18k-sV-PgT"/>
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerY" secondItem="JER-jz-KSq" secondAttribute="centerY" id="NUc-tM-0dN"/>
<constraint firstItem="AiP-dx-mFn" firstAttribute="centerX" secondItem="954-Dk-zvc" secondAttribute="centerX" id="hwP-mu-Vmz"/>
<constraint firstItem="954-Dk-zvc" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="IvG-yp-yzI" secondAttribute="trailing" id="mpr-u5-MZu"/>
<constraint firstItem="IvG-yp-yzI" firstAttribute="leading" secondItem="954-Dk-zvc" secondAttribute="leading" constant="20" id="pYt-jE-CTF"/>
</constraints>
<viewLayoutGuide key="safeArea" id="954-Dk-zvc"/>
</view>
<tabBarItem key="tabBarItem" tag="1" title="Item 2" id="qb3-RB-B28"/>
<tabBarItem key="tabBarItem" tag="1" title="Layout 2" id="qb3-RB-B28"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="NhZ-u5-Beh" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-308" y="1546"/>
</scene>
<!--Item 1-->
<!--Layout 3-->
<scene sceneID="r9h-Ql-gIv">
<objects>
<viewController id="pOk-Zm-vD9" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="85d-ub-G8k">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NbG-e8-HdI">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<state key="normal" title="Close"/>
<connections>
<action selector="closeWithSender:" destination="pOk-Zm-vD9" eventType="touchUpInside" id="111-PD-Pop"/>
<action selector="closeWithSender:" destination="bYI-y3-Rzb" eventType="touchUpInside" id="1Rg-YG-TtU"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="0ao-SI-QZW" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="NbG-e8-HdI" secondAttribute="trailing" id="K9F-6x-KWn"/>
<constraint firstItem="NbG-e8-HdI" firstAttribute="top" secondItem="0ao-SI-QZW" secondAttribute="top" id="nsE-so-rTl"/>
<constraint firstItem="NbG-e8-HdI" firstAttribute="leading" secondItem="0ao-SI-QZW" secondAttribute="leading" constant="20" id="sF4-Dm-aoY"/>
</constraints>
<viewLayoutGuide key="safeArea" id="0ao-SI-QZW"/>
</view>
<tabBarItem key="tabBarItem" tag="2" title="Layout 3" id="RJD-TF-Sdh"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Oe3-FT-q1C" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="332" y="1546"/>
</scene>
<!--Layout 1-->
<scene sceneID="m6X-j6-yBM">
<objects>
<viewController id="lto-Zc-Vtp" customClass="TabBarContentViewController" customModule="Samples" customModuleProvider="target" sceneMemberID="viewController">
@@ -208,12 +232,6 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Item 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uoW-c8-9wx">
<rect key="frame" x="164.66666666666666" y="395.66666666666669" width="46" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eFN-tN-4Ct">
<rect key="frame" x="20" y="44" width="39" height="30"/>
<state key="normal" title="Close"/>
@@ -226,13 +244,12 @@
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="eFN-tN-4Ct" firstAttribute="leading" secondItem="5Ns-4l-Ufg" secondAttribute="leading" constant="20" id="5BT-yZ-EKe"/>
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerY" secondItem="ji9-Ez-N7i" secondAttribute="centerY" id="Nyw-Wt-78z"/>
<constraint firstItem="5Ns-4l-Ufg" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="eFN-tN-4Ct" secondAttribute="trailing" id="OzZ-Dz-RNF"/>
<constraint firstItem="eFN-tN-4Ct" firstAttribute="top" secondItem="5Ns-4l-Ufg" secondAttribute="top" id="hUV-3a-XkY"/>
<constraint firstItem="uoW-c8-9wx" firstAttribute="centerX" secondItem="5Ns-4l-Ufg" secondAttribute="centerX" id="wDv-OH-7PX"/>
</constraints>
<viewLayoutGuide key="safeArea" id="5Ns-4l-Ufg"/>
</view>
<tabBarItem key="tabBarItem" title="Item 1" id="HEV-kf-jxH"/>
<tabBarItem key="tabBarItem" title="Layout 1" id="HEV-kf-jxH"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bkL-bc-hZC" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
@@ -280,6 +297,7 @@
<connections>
<segue destination="lto-Zc-Vtp" kind="relationship" relationship="viewControllers" id="6hP-AH-YiH"/>
<segue destination="RpE-lI-27a" kind="relationship" relationship="viewControllers" id="g6X-Sq-uSW"/>
<segue destination="pOk-Zm-vD9" kind="relationship" relationship="viewControllers" id="OPp-iO-iDK"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Z9x-EI-p2b" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -654,6 +672,8 @@ Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
<size key="freeformSize" width="375" height="778"/>
<connections>
<outlet property="textView" destination="rN1-HL-YHv" id="gmr-Uf-jd8"/>
<outlet property="textViewTopConstraint" destination="fiO-LL-nSC" id="Rum-TN-c2e"/>
<outlet property="view" destination="9YG-0j-Zzg" id="jhb-eT-nEn"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x1h-y1-h8q" userLabel="First Responder" sceneMemberID="firstResponder"/>
+70 -2
View File
@@ -384,6 +384,7 @@ class NestedScrollViewController: UIViewController {
class DebugTextViewController: UIViewController, UITextViewDelegate {
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var textViewTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
@@ -760,12 +761,60 @@ class TabBarContentViewController: UIViewController, FloatingPanelControllerDele
case 0:
return OneTabBarPanelLayout()
case 1:
return TwoTabBarPanel2Layout()
return TwoTabBarPanelLayout()
case 2:
return ThreeTabBarPanelLayout()
default:
return nil
}
}
func floatingPanelDidMove(_ vc: FloatingPanelController) {
guard self.tabBarItem.tag == 2 else { return }
/* Solution 1: Manipulate scoll content inset */
/*
guard let scrollView = consoleVC.textView else { return }
var insets = vc.adjustedContentInsets
if vc.surfaceView.frame.minY < vc.layoutInsets.top {
insets.top = vc.layoutInsets.top - vc.surfaceView.frame.minY
} else {
insets.top = 0.0
}
scrollView.contentInset = insets
*/
// Solution 2: Manipulate top constraint
assert(consoleVC.textViewTopConstraint != nil)
if vc.surfaceView.frame.minY + 17.0 < vc.layoutInsets.top {
consoleVC.textViewTopConstraint?.constant = vc.layoutInsets.top - vc.surfaceView.frame.minY
} else {
consoleVC.textViewTopConstraint?.constant = 17.0
}
consoleVC.view.layoutIfNeeded()
}
func floatingPanelDidChangePosition(_ vc: FloatingPanelController) {
guard self.tabBarItem.tag == 2 else { return }
/* Solution 1: Manipulate scoll content inset */
/*
guard let scrollView = consoleVC.textView else { return }
var insets = vc.adjustedContentInsets
insets.top = (vc.position == .full) ? vc.layoutInsets.top : 0.0
scrollView.contentInset = insets
if scrollView.contentOffset.y - scrollView.contentInset.top < 0.0 {
scrollView.contentOffset = CGPoint(x: 0.0,
y: 0.0 - scrollView.contentInset.top)
}
*/
// Solution 2: Manipulate top constraint
assert(consoleVC.textViewTopConstraint != nil)
consoleVC.textViewTopConstraint?.constant = (vc.position == .full) ? vc.layoutInsets.top : 17.0
consoleVC.view.layoutIfNeeded()
}
@IBAction func close(sender: UIButton) {
dismiss(animated: true, completion: nil)
}
@@ -804,7 +853,7 @@ class OneTabBarPanelLayout: FloatingPanelLayout {
}
}
class TwoTabBarPanel2Layout: FloatingPanelLayout {
class TwoTabBarPanelLayout: FloatingPanelLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
@@ -824,6 +873,25 @@ class TwoTabBarPanel2Layout: FloatingPanelLayout {
}
}
class ThreeTabBarPanelLayout: FloatingPanelFullScreenLayout {
var initialPosition: FloatingPanelPosition {
return .half
}
var supportedPositions: Set<FloatingPanelPosition> {
return [.full, .half]
}
func insetFor(position: FloatingPanelPosition) -> CGFloat? {
switch position {
case .full: return 0.0
case .half: return 261.0
default: return nil
}
}
func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat {
return 0.3
}
}
class SettingsViewController: InspectableViewController {
@IBOutlet weak var largeTitlesSwicth: UISwitch!
@IBOutlet weak var translucentSwicth: UISwitch!
+2 -2
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "FloatingPanel"
s.version = "1.3.1"
s.version = "1.3.3"
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
s.description = <<-DESC
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
@@ -13,7 +13,7 @@ The new interface displays the related contents and utilities in parallel as a u
s.platform = :ios, "10.0"
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => "v#{s.version}" }
s.source_files = "Framework/Sources/*.swift"
s.swift_version = "4.2"
s.swift_version = "4.0"
s.pod_target_xcconfig = { 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
s.framework = "UIKit"
@@ -7,8 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
54352E9621A51A2500CBCA08 /* FloatingPanelTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */; };
54352E9821A521CA00CBCA08 /* FloatingPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */; };
5450EEE421646DF500135936 /* FloatingPanelBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */; };
545DB9CB2151169500CA77B8 /* FloatingPanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 545DB9C12151169500CA77B8 /* FloatingPanel.framework */; };
545DB9D02151169500CA77B8 /* ViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545DB9CF2151169500CA77B8 /* ViewTests.swift */; };
@@ -34,8 +34,8 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
54352E9521A51A2500CBCA08 /* FloatingPanelTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelTransitioning.swift; sourceTree = "<group>"; };
54352E9721A521CA00CBCA08 /* FloatingPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelView.swift; sourceTree = "<group>"; };
5450EEE321646DF500135936 /* FloatingPanelBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanelBehavior.swift; sourceTree = "<group>"; };
545DB9C12151169500CA77B8 /* FloatingPanel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FloatingPanel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
545DB9C42151169500CA77B8 /* FloatingPanelController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FloatingPanelController.h; sourceTree = "<group>"; };
@@ -406,7 +406,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -432,7 +432,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.scenee.FloatingPanel;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
+43 -27
View File
@@ -41,6 +41,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
private var animator: UIViewPropertyAnimator?
private var initialFrame: CGRect = .zero
private var initialScrollOffset: CGPoint = .zero
private var initialScrollInset: UIEdgeInsets = .zero
private var transOffsetY: CGFloat = 0
var interactionInProgress: Bool = false
@@ -101,7 +102,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
}
animator.addAnimations { [weak self] in
guard let self = self else { return }
guard let `self` = self else { return }
self.updateLayout(to: to)
self.state = to
@@ -249,7 +250,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
return
}
// Fix the scroll offset in moving the panel from half and tip.
scrollView.contentOffset.y = initialScrollOffset.y
scrollView.contentOffset.y = initialScrollOffset.y + (initialScrollInset.top - scrollView.contentInset.top)
case .hidden:
fatalError("A floating panel hidden must not be used by a user")
}
@@ -360,6 +361,12 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
private func panningEnd(with translation: CGPoint, velocity: CGPoint) {
log.debug("panningEnd")
guard state != .hidden else {
log.debug("Already hidden")
return
}
if interactionInProgress == false {
initialFrame = surfaceView.frame
}
@@ -381,9 +388,9 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
viewcontroller.delegate?.floatingPanelDidEndDraggingToRemove(viewcontroller, withVelocity: velocity)
self.startRemovalAnimation(with: velocityVector) { [weak self] in
guard let self = self else { return }
guard let `self` = self else { return }
self.viewcontroller.dismiss(animated: false, completion: { [weak self] in
guard let self = self else { return }
guard let `self` = self else { return }
self.viewcontroller.delegate?.floatingPanelDidEndRemove(self.viewcontroller)
})
}
@@ -431,21 +438,26 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
initialFrame = surfaceView.frame
if let scrollView = scrollView {
initialScrollOffset = scrollView.contentOffset
initialScrollInset = scrollView.contentInset
}
transOffsetY = translation.y
viewcontroller.delegate?.floatingPanelWillBeginDragging(viewcontroller)
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
case const.firstAnchor:
(const.secondItem as? UIView)?.disableAutoLayout()
case const.secondAnchor:
(const.firstItem as? UIView)?.disableAutoLayout()
default:
break
}
})
if state == .full {
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
case const.firstAnchor:
(const.secondItem as? UIView)?.disableAutoLayout()
const.isActive = false
case const.secondAnchor:
(const.firstItem as? UIView)?.disableAutoLayout()
const.isActive = false
default:
break
}
})
}
interactionInProgress = true
}
@@ -459,16 +471,20 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
lockScrollView()
}
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
case const.firstAnchor:
(const.secondItem as? UIView)?.enableAutoLayout()
case const.secondAnchor:
(const.firstItem as? UIView)?.enableAutoLayout()
default:
break
}
})
if state == .full {
viewcontroller.contentViewController?.view?.constraints.forEach({ (const) in
switch viewcontroller.contentViewController?.layoutGuide.bottomAnchor {
case const.firstAnchor:
(const.secondItem as? UIView)?.enableAutoLayout()
const.isActive = true
case const.secondAnchor:
(const.firstItem as? UIView)?.enableAutoLayout()
const.isActive = true
default:
break
}
})
}
}
private func getCurrentY(from rect: CGRect, with translation: CGPoint) -> CGFloat {
@@ -497,7 +513,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
let velocityVector = (distance != 0) ? CGVector(dx: 0, dy: max(min(velocity.y/distance, 30.0), -30.0)) : .zero
let animator = behavior.interactionAnimator(self.viewcontroller, to: targetPosition, with: velocityVector)
animator.addAnimations { [weak self] in
guard let self = self else { return }
guard let `self` = self else { return }
if self.state == targetPosition {
self.surfaceView.frame.origin.y = targetY
self.layoutAdapter.setBackdropAlpha(of: targetPosition)
@@ -507,7 +523,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
self.state = targetPosition
}
animator.addCompletion { [weak self] pos in
guard let self = self else { return }
guard let `self` = self else { return }
guard
self.interactionInProgress == false,
animator == self.animator,
@@ -616,7 +632,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
// Distance travelled after decelerating to zero velocity at a constant rate.
// Refer to the slides p176 of [Designing Fluid Interfaces](https://developer.apple.com/videos/play/wwdc2018/803/)
private func project(initialVelocity: CGFloat) -> CGFloat {
let decelerationRate = UIScrollView.DecelerationRate.normal.rawValue
let decelerationRate = UIScrollViewDecelerationRateNormal
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate)
}
+40 -18
View File
@@ -51,7 +51,8 @@ public extension FloatingPanelControllerDelegate {
func floatingPanel(_ vc: FloatingPanelController, shouldRecognizeSimultaneouslyWith gestureRecognizer: UIGestureRecognizer) -> Bool { return false }
}
public enum FloatingPanelPosition: Int, CaseIterable {
public enum FloatingPanelPosition: Int {
case full
case half
case tip
@@ -69,7 +70,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
}
/// The delegate of the floating panel controller object.
public weak var delegate: FloatingPanelControllerDelegate?
public weak var delegate: FloatingPanelControllerDelegate?{
didSet{
didUpdateDelegate()
}
}
/// Returns the surface view managed by the controller object. It's the same as `self.view`.
public var surfaceView: FloatingPanelSurfaceView! {
@@ -133,14 +138,15 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
private var safeAreaInsetsObservation: NSKeyValueObservation?
private let modalTransition = FloatingPanelModalTransition()
required init?(coder aDecoder: NSCoder) {
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
/// Initialize a newly created floating panel controller.
public init() {
public init(delegate: FloatingPanelControllerDelegate? = nil) {
super.init(nibName: nil, bundle: nil)
self.delegate = delegate
setUp()
}
@@ -155,6 +161,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
behavior: fetchBehavior(for: self.traitCollection))
}
private func didUpdateDelegate(){
floatingPanel.layoutAdapter.layout = fetchLayout(for: traitCollection)
floatingPanel.behavior = fetchBehavior(for: self.traitCollection)
}
// MARK:- Overrides
/// Creates the view that the controller manages.
@@ -201,6 +212,11 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
floatingPanel.behavior = fetchBehavior(for: newCollection)
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
safeAreaInsetsObservation = nil
}
// MARK:- Privates
private func fetchLayout(for traitCollection: UITraitCollection) -> FloatingPanelLayout {
@@ -224,7 +240,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
else { return }
log.debug("Update safeAreaInsets", safeAreaInsets)
floatingPanel.layoutAdapter.safeAreaInsets = safeAreaInsets
setUpLayout()
@@ -269,7 +285,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
// 2. The safe area top inset can be variable on the large title navigation bar(iOS11+).
// That's why it needs the observation to keep `adjustedContentInsets` correct.
safeAreaInsetsObservation = self.observe(\.view.safeAreaInsets) { [weak self] (vc, chaneg) in
guard let self = self else { return }
guard let `self` = self else { return }
self.update(safeAreaInsets: vc.layoutInsets)
}
} else {
@@ -312,7 +328,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
parent.view.addSubview(self.view)
}
parent.addChild(self)
parent.addChildViewController(self)
view.frame = parent.view.bounds // Needed for a correct safe area configuration
view.translatesAutoresizingMaskIntoConstraints = false
@@ -324,8 +340,8 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
])
show(animated: animated) { [weak self] in
guard let self = self else { return }
self.didMove(toParent: parent)
guard let `self` = self else { return }
self.didMove(toParentViewController: self)
}
}
@@ -340,10 +356,10 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
}
hide(animated: animated) { [weak self] in
guard let self = self else { return }
self.willMove(toParent: nil)
guard let `self` = self else { return }
self.willMove(toParentViewController: nil)
self.view.removeFromSuperview()
self.removeFromParent()
self.removeFromParentViewController()
completion?()
}
}
@@ -361,21 +377,27 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
/// Sets the view controller responsible for the content portion of the floating panel..
public func set(contentViewController: UIViewController?) {
if let vc = _contentViewController {
vc.willMove(toParent: nil)
vc.willMove(toParentViewController: nil)
vc.view.removeFromSuperview()
vc.removeFromParent()
vc.removeFromParentViewController()
if let scrollView = floatingPanel.scrollView,
let delegate = floatingPanel.userScrollViewDelegate,
vc.view.subviews.contains(scrollView) {
scrollView.delegate = delegate
}
}
if let vc = contentViewController {
addChild(vc)
addChildViewController(vc)
let surfaceView = floatingPanel.surfaceView
surfaceView.add(contentView: vc.view)
vc.didMove(toParent: self)
vc.didMove(toParentViewController: self)
}
_contentViewController = contentViewController
}
@available(*, unavailable, renamed: "set(contentViewController:)")
public override func show(_ vc: UIViewController, sender: Any?) {
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) {
@@ -408,7 +430,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
if #available(iOS 11.0, *) {
scrollView.contentInsetAdjustmentBehavior = .never
} else {
children.forEach { (vc) in
childViewControllers.forEach { (vc) in
vc.automaticallyAdjustsScrollViewInsets = false
}
}
+44 -16
View File
@@ -5,12 +5,21 @@
import UIKit
/// FloatingPanelFullScreenLayout
///
/// Use the layout protocol if you want to configure a full inset from Superview.Top, not SafeArea.Top.
/// It can't be used with FloatingPanelIntrinsicLayout.
public protocol FloatingPanelFullScreenLayout: FloatingPanelLayout { }
/// FloatingPanelIntrinsicLayout
///
/// Use the layout protocol if you want to layout a panel using the intrinsic height.
/// It can't be used with FloatingPanelFullScreenLayout.
///
/// - Attention:
/// `insetFor(position:)` must return `nil` for full position because the inset is determined automatically.
/// You can customize insets only for half, tip and hidden positions
/// on FloatingPanelIntrinsicLayout.
/// `insetFor(position:)` must return `nil` for the full position. Because
/// the inset is determined automatically by the intrinsic height.
/// You can customize insets only for the half, tip and hidden positions.
public protocol FloatingPanelIntrinsicLayout: FloatingPanelLayout { }
public extension FloatingPanelIntrinsicLayout {
@@ -34,7 +43,7 @@ public protocol FloatingPanelLayout: class {
/// Returns a set of FloatingPanelPosition objects to tell the applicable
/// positions of the floating panel controller.
///
/// By default, it returns all position exepct for `hidden` position. Because
/// By default, it returns all position except for `hidden` position. Because
/// it's always supported by `FloatingPanelController` so you don't need to return it.
var supportedPositions: Set<FloatingPanelPosition> { get }
@@ -46,7 +55,7 @@ public protocol FloatingPanelLayout: class {
/// Returns a CGFloat value to determine a Y coordinate of a floating panel for each position(full, half, tip and hidden).
///
/// Its returning value indicates a different inset for each positiion.
/// Its returning value indicates a different inset for each position.
/// For full position, a top inset from a safe area in `FloatingPanelController.view`.
/// For half or tip position, a bottom inset from the safe area.
/// For hidden position, a bottom inset from `FloatingPanelController.view`.
@@ -72,7 +81,7 @@ public extension FloatingPanelLayout {
var supportedPositions: Set<FloatingPanelPosition> {
return Set([.full, .half, .tip])
}
func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] {
return [
surfaceView.leftAnchor.constraint(equalTo: view.sideLayoutGuide.leftAnchor, constant: 0.0),
@@ -131,7 +140,6 @@ class FloatingPanelLayoutAdapter {
var safeAreaInsets: UIEdgeInsets = .zero
private var heightBuffer: CGFloat = 88.0 // For bounce
private var fixedConstraints: [NSLayoutConstraint] = []
private var fullConstraints: [NSLayoutConstraint] = []
private var halfConstraints: [NSLayoutConstraint] = []
@@ -164,9 +172,12 @@ class FloatingPanelLayoutAdapter {
var topY: CGFloat {
if supportedPositions.contains(.full) {
if layout is FloatingPanelIntrinsicLayout {
switch layout {
case is FloatingPanelIntrinsicLayout:
return surfaceView.superview!.bounds.height - surfaceView.bounds.height
} else {
case is FloatingPanelFullScreenLayout:
return fullInset
default:
return (safeAreaInsets.top + fullInset)
}
} else {
@@ -194,7 +205,9 @@ class FloatingPanelLayoutAdapter {
return surfaceView.superview!.bounds.height - (safeAreaInsets.bottom + hiddenInset)
}
var topMaxY: CGFloat { return -safeAreaInsets.top }
var topMaxY: CGFloat {
return layout is FloatingPanelFullScreenLayout ? 0.0 : -safeAreaInsets.top
}
var bottomMaxY: CGFloat { return safeAreaBottomY }
var adjustedContentInsets: UIEdgeInsets {
@@ -226,7 +239,7 @@ class FloatingPanelLayoutAdapter {
}
func updateIntrinsicHeight() {
let fittingSize = UIView.layoutFittingCompressedSize
let fittingSize = UILayoutFittingCompressedSize
var intrinsicHeight = surfaceView.contentView?.systemLayoutSizeFitting(fittingSize).height ?? 0.0
var safeAreaBottom: CGFloat = 0.0
if #available(iOS 11.0, *) {
@@ -263,14 +276,22 @@ class FloatingPanelLayoutAdapter {
fixedConstraints = surfaceConstraints + backdropConstraints
// Flexible surface constarints for full, half, tip and off
if layout is FloatingPanelIntrinsicLayout {
switch layout {
case is FloatingPanelIntrinsicLayout:
// Set up on updateHeight()
} else {
break
case is FloatingPanelFullScreenLayout:
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.view.topAnchor,
constant: fullInset),
]
default:
fullConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.topAnchor,
constant: fullInset),
]
}
halfConstraints = [
surfaceView.topAnchor.constraint(equalTo: vc.layoutGuide.bottomAnchor,
constant: -halfInset),
@@ -292,20 +313,27 @@ class FloatingPanelLayoutAdapter {
NSLayoutConstraint.deactivate(heightConstraints)
if layout is FloatingPanelIntrinsicLayout {
switch layout {
case is FloatingPanelIntrinsicLayout:
updateIntrinsicHeight()
heightConstraints = [
surfaceView.heightAnchor.constraint(equalToConstant: intrinsicHeight + safeAreaInsets.bottom),
]
} else {
case is FloatingPanelFullScreenLayout:
heightConstraints = [
surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
constant: -fullInset),
]
default:
heightConstraints = [
surfaceView.heightAnchor.constraint(equalTo: vc.view.heightAnchor,
constant: -(safeAreaInsets.top + fullInset)),
]
}
NSLayoutConstraint.activate(heightConstraints)
surfaceView.bottomOverflow = heightBuffer + layout.topInteractionBuffer
surfaceView.bottomOverflow = vc.view.bounds.height + layout.topInteractionBuffer
if layout is FloatingPanelIntrinsicLayout {
NSLayoutConstraint.deactivate(fullConstraints)
@@ -70,7 +70,7 @@ public class FloatingPanelSurfaceView: UIView {
render()
}
required init?(coder aDecoder: NSCoder) {
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
render()
}
@@ -22,7 +22,11 @@ class FloatingPanelModalTransition: NSObject, UIViewControllerTransitioningDeleg
}
class FloatingPanelPresentationController: UIPresentationController {
override func presentationTransitionWillBegin() { }
override func presentationTransitionWillBegin() {
// Must call here even if duplicating on in containerViewWillLayoutSubviews()
// Because it let the floating panel present correclty with the presentation animation
addFloatingPanel()
}
override func presentationTransitionDidEnd(_ completed: Bool) {
// For non-animated presentation
@@ -39,26 +43,23 @@ class FloatingPanelPresentationController: UIPresentationController {
}
fpc.view.removeFromSuperview()
}
}
override func containerViewWillLayoutSubviews() {
guard
let containerView = self.containerView,
let fpc = presentedViewController as? FloatingPanelController,
let fpView = fpc.view
let fpc = presentedViewController as? FloatingPanelController
else { fatalError() }
containerView.addSubview(fpView)
fpView.frame = containerView.bounds
fpView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
fpView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
fpView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 0.0),
fpView.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: 0.0),
fpView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0),
])
/*
* Layout the views managed by `FloatingPanelController` here for the
* sake of the presentation and disimissal modally from the controller.
*/
addFloatingPanel()
// Forward touch events to the presenting view controller
(fpc.view as? FloatingPanelPassThroughView)?.eventForwardingView = presentingViewController.view
// Set tap-to-dimiss in the backdrop view
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBackdrop(tapGesture:)))
fpc.backdropView.addGestureRecognizer(tapGesture)
}
@@ -66,6 +67,17 @@ class FloatingPanelPresentationController: UIPresentationController {
@objc func handleBackdrop(tapGesture: UITapGestureRecognizer) {
presentedViewController.dismiss(animated: true, completion: nil)
}
private func addFloatingPanel() {
guard
let containerView = self.containerView,
let fpc = presentedViewController as? FloatingPanelController
else { fatalError() }
containerView.addSubview(fpc.view)
fpc.view.frame = containerView.bounds
fpc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
class FloatingPanelModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
+6 -5
View File
@@ -6,13 +6,14 @@
import UIKit
class FloatingPanelPassThroughView: UIView {
public weak var eventForwardingView: UIView?
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
switch view {
case is FloatingPanelPassThroughView:
return nil
let hitView = super.hitTest(point, with: event)
switch hitView {
case self:
return eventForwardingView?.hitTest(self.convert(point, to: eventForwardingView), with: event)
default:
return view
return hitView
}
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ public class GrabberHandleView: UIView {
public static let barColor = UIColor(displayP3Red: 0.76, green: 0.77, blue: 0.76, alpha: 1.0)
}
required init?(coder aDecoder: NSCoder) {
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
render()
}
+2 -1
View File
@@ -73,7 +73,8 @@ extension UIView {
}
}
extension UIGestureRecognizer.State: CustomDebugStringConvertible {
extension UIGestureRecognizerState: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .began: return "Began"
+2 -1
View File
@@ -2,6 +2,7 @@
[![Version](https://img.shields.io/cocoapods/v/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platform](https://img.shields.io/cocoapods/p/FloatingPanel.svg)](https://cocoapods.org/pods/FloatingPanel)
[![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/)
# FloatingPanel
@@ -66,7 +67,7 @@ Examples are here.
## Requirements
FloatingPanel is written in Swift 4.2. Compatible with iOS 10.0+
FloatingPanel is written in Swift. It can be built by Xcode 9.4.1 or later. Compatible with iOS 10.0+.
## Installation