Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16fea625be | |||
| 6c69694cfe | |||
| 561d783479 | |||
| 1bd2e60200 | |||
| ec0e1b2dad | |||
| 9958fc5017 | |||
| 11dfc0d2f3 | |||
| 34246d1f37 |
@@ -14,9 +14,11 @@
|
||||
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 */; };
|
||||
546341A125C6415100CA0596 /* UseCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341A025C6415100CA0596 /* UseCases.swift */; };
|
||||
546341AC25C6426500CA0596 /* CustomState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546341AB25C6426500CA0596 /* CustomState.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 */; };
|
||||
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B51115216AFE5F0033A6F3 /* Extensions.swift */; };
|
||||
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CDC5D7215BBE23007D205C /* UIComponents.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -65,8 +67,10 @@
|
||||
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>"; };
|
||||
546341A025C6415100CA0596 /* UseCases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCases.swift; sourceTree = "<group>"; };
|
||||
546341AB25C6426500CA0596 /* CustomState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomState.swift; 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>"; };
|
||||
54B51115216AFE5F0033A6F3 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||
54CDC5D7215BBE23007D205C /* UIComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIComponents.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -125,8 +129,9 @@
|
||||
545DB9F121511E6300CA77B8 /* Main.storyboard */,
|
||||
545DB9ED21511E6300CA77B8 /* AppDelegate.swift */,
|
||||
545DB9EF21511E6300CA77B8 /* ViewController.swift */,
|
||||
54B51115216AFE5F0033A6F3 /* UIExtensions.swift */,
|
||||
54B51115216AFE5F0033A6F3 /* Extensions.swift */,
|
||||
54CDC5D7215BBE23007D205C /* UIComponents.swift */,
|
||||
546341AA25C6421000CA0596 /* UseCases */,
|
||||
545DB9F921511E6400CA77B8 /* Info.plist */,
|
||||
);
|
||||
path = Sources;
|
||||
@@ -150,6 +155,15 @@
|
||||
path = UITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
546341AA25C6421000CA0596 /* UseCases */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
546341A025C6415100CA0596 /* UseCases.swift */,
|
||||
546341AB25C6426500CA0596 /* CustomState.swift */,
|
||||
);
|
||||
path = UseCases;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -304,7 +318,9 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
54CDC5D8215BBE23007D205C /* UIComponents.swift in Sources */,
|
||||
54B51116216AFE5F0033A6F3 /* UIExtensions.swift in Sources */,
|
||||
54B51116216AFE5F0033A6F3 /* Extensions.swift in Sources */,
|
||||
546341AC25C6426500CA0596 /* CustomState.swift in Sources */,
|
||||
546341A125C6415100CA0596 /* UseCases.swift in Sources */,
|
||||
545DB9F021511E6300CA77B8 /* ViewController.swift in Sources */,
|
||||
545DB9EE21511E6300CA77B8 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
import FloatingPanel
|
||||
|
||||
extension FloatingPanelState {
|
||||
static let lastQuart: FloatingPanelState = FloatingPanelState(rawValue: "lastQuart", order: 750)
|
||||
static let firstQuart: FloatingPanelState = FloatingPanelState(rawValue: "firstQuart", order: 250)
|
||||
}
|
||||
|
||||
class FloatingPanelLayoutWithCustomState: FloatingPanelBottomLayout {
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
.lastQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.75, edge: .bottom, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
.firstQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.25, edge: .bottom, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2018-Present Shin Yamamoto. All rights reserved. MIT license.
|
||||
|
||||
import Foundation
|
||||
|
||||
enum UseCases: Int, CaseIterable {
|
||||
case trackingTableView
|
||||
case trackingTextView
|
||||
case showDetail
|
||||
case showModal
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
case showNestedScrollView
|
||||
case showRemovablePanel
|
||||
case showIntrinsicView
|
||||
case showContentInset
|
||||
case showContainerMargins
|
||||
case showNavigationController
|
||||
case showTopPositionedPanel
|
||||
case showAdaptivePanel
|
||||
case showAdaptivePanelWithCustomGuide
|
||||
case showCustomStatePanel
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .trackingTableView: return "Scroll tracking(TableView)"
|
||||
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 .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"
|
||||
case .showTopPositionedPanel: return "Show Top Positioned Panel"
|
||||
case .showAdaptivePanel: return "Show Adaptive Panel"
|
||||
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
|
||||
case .showCustomStatePanel: return "Show Panel with Custom state"
|
||||
}
|
||||
}
|
||||
|
||||
var storyboardID: String? {
|
||||
switch self {
|
||||
case .trackingTableView: return nil
|
||||
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 .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"
|
||||
case .showTopPositionedPanel: return nil
|
||||
case .showAdaptivePanel,
|
||||
.showAdaptivePanelWithCustomGuide:
|
||||
return "ImageViewController"
|
||||
case .showCustomStatePanel:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,78 +6,7 @@ import FloatingPanel
|
||||
class SampleListViewController: UIViewController {
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
|
||||
enum Menu: Int, CaseIterable {
|
||||
case trackingTableView
|
||||
case trackingTextView
|
||||
case showDetail
|
||||
case showModal
|
||||
case showPanelModal
|
||||
case showMultiPanelModal
|
||||
case showPanelInSheetModal
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showPageContentView
|
||||
case showNestedScrollView
|
||||
case showRemovablePanel
|
||||
case showIntrinsicView
|
||||
case showContentInset
|
||||
case showContainerMargins
|
||||
case showNavigationController
|
||||
case showTopPositionedPanel
|
||||
case showAdaptivePanel
|
||||
case showAdaptivePanelWithCustomGuide
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .trackingTableView: return "Scroll tracking(TableView)"
|
||||
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 .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"
|
||||
case .showTopPositionedPanel: return "Show Top Positioned Panel"
|
||||
case .showAdaptivePanel: return "Show Adaptive Panel"
|
||||
case .showAdaptivePanelWithCustomGuide: return "Show Adaptive Panel (Custom Layout Guide)"
|
||||
}
|
||||
}
|
||||
|
||||
var storyboardID: String? {
|
||||
switch self {
|
||||
case .trackingTableView: return nil
|
||||
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 .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"
|
||||
case .showTopPositionedPanel: return nil
|
||||
case .showAdaptivePanel,
|
||||
.showAdaptivePanelWithCustomGuide:
|
||||
return "ImageViewController"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentMenu: Menu = .trackingTableView
|
||||
var currentMenu: UseCases = .trackingTableView
|
||||
|
||||
var mainPanelVC: FloatingPanelController!
|
||||
var detailPanelVC: FloatingPanelController!
|
||||
@@ -254,19 +183,19 @@ extension SampleListViewController: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if #available(iOS 11.0, *) {
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return Menu.allCases.count + 30
|
||||
return UseCases.allCases.count + 30
|
||||
} else {
|
||||
return Menu.allCases.count
|
||||
return UseCases.allCases.count
|
||||
}
|
||||
} else {
|
||||
return Menu.allCases.count
|
||||
return UseCases.allCases.count
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
|
||||
if Menu.allCases.count > indexPath.row {
|
||||
let menu = Menu.allCases[indexPath.row]
|
||||
if UseCases.allCases.count > indexPath.row {
|
||||
let menu = UseCases.allCases[indexPath.row]
|
||||
cell.textLabel?.text = menu.name
|
||||
} else {
|
||||
cell.textLabel?.text = "\(indexPath.row) row"
|
||||
@@ -277,8 +206,8 @@ extension SampleListViewController: UITableViewDataSource {
|
||||
|
||||
extension SampleListViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard Menu.allCases.count > indexPath.row else { return }
|
||||
let menu = Menu.allCases[indexPath.row]
|
||||
guard UseCases.allCases.count > indexPath.row else { return }
|
||||
let menu = UseCases.allCases[indexPath.row]
|
||||
let contentVC: UIViewController = {
|
||||
guard let storyboardID = menu.storyboardID else { return DebugTableViewController() }
|
||||
guard let vc = self.storyboard?.instantiateViewController(withIdentifier: storyboardID) else { fatalError() }
|
||||
@@ -450,6 +379,8 @@ extension SampleListViewController: FloatingPanelControllerDelegate {
|
||||
fallthrough
|
||||
case .showContentInset:
|
||||
return FloatingPanelBottomLayout()
|
||||
case .showCustomStatePanel:
|
||||
return FloatingPanelLayoutWithCustomState()
|
||||
default:
|
||||
return (newCollection.verticalSizeClass == .compact) ? FloatingPanelBottomLayout() : self
|
||||
}
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
#import "ViewController.h"
|
||||
@import FloatingPanel;
|
||||
|
||||
// Defining a custom FloatingPanelState
|
||||
@interface FloatingPanelState(Extended)
|
||||
+ (FloatingPanelState *)LastQuart;
|
||||
@end
|
||||
|
||||
@implementation FloatingPanelState(Extended)
|
||||
static FloatingPanelState *_lastQuart;
|
||||
+ (FloatingPanelState *)LastQuart {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_lastQuart = [[FloatingPanelState alloc] initWithRawValue:@"lastquart" order:750];
|
||||
});
|
||||
return _lastQuart;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ViewController()<FloatingPanelControllerDelegate>
|
||||
@end
|
||||
|
||||
@@ -59,6 +75,9 @@
|
||||
}
|
||||
- (NSDictionary<FloatingPanelState *, id<FloatingPanelLayoutAnchoring>> *)anchors {
|
||||
return @{
|
||||
FloatingPanelState.LastQuart: [[FloatingPanelLayoutAnchor alloc] initWithFractionalInset:0.25
|
||||
edge:FloatingPanelReferenceEdgeTop
|
||||
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
|
||||
FloatingPanelState.Half: [[FloatingPanelLayoutAnchor alloc] initWithFractionalInset:0.5
|
||||
edge:FloatingPanelReferenceEdgeTop
|
||||
referenceGuide:FloatingPanelLayoutReferenceGuideSafeArea],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "2.2.0"
|
||||
s.version = "2.3.0"
|
||||
s.summary = "FloatingPanel is a clean and easy-to-use UI component of a floating panel interface."
|
||||
s.description = <<-DESC
|
||||
FloatingPanel is a clean and easy-to-use UI component for a new interface introduced in Apple Maps, Shortcuts and Stocks app.
|
||||
|
||||
@@ -36,6 +36,7 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
- [Use the intrinsic size of a content in your panel layout](#use-the-intrinsic-size-of-a-content-in-your-panel-layout)
|
||||
- [Specify an anchor for each state by an inset of the `FloatingPanelController.view` frame](#specify-an-anchor-for-each-state-by-an-inset-of-the-floatingpanelcontrollerview-frame)
|
||||
- [Change the backdrop alpha](#change-the-backdrop-alpha)
|
||||
- [Using custome panel states](#using-custome-panel-states)
|
||||
- [Customize the behavior with `FloatingPanelBehavior` protocol](#customize-the-behavior-with-floatingpanelbehavior-protocol)
|
||||
- [Modify your floating panel's interaction](#modify-your-floating-panels-interaction)
|
||||
- [Activate the rubber-band effect on panel edges](#activate-the-rubber-band-effect-on-panel-edges)
|
||||
@@ -72,7 +73,7 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
- [x] Multi panel support
|
||||
- [x] Modal presentation
|
||||
- [x] 4 positioning support(top, left, bottom, right)
|
||||
- [x] 1~3 magnetic anchors(full, half, tip)
|
||||
- [x] 1 or more magnetic anchors(full, half, tip and more)
|
||||
- [x] Layout support for all trait environments(i.e. Landscape orientation)
|
||||
- [x] Common UI elements: surface, backdrop and grabber handle
|
||||
- [x] Free from common issues of Auto Layout and gesture handling
|
||||
@@ -388,6 +389,29 @@ class MyPanelLayout: FloatingPanelLayout {
|
||||
}
|
||||
```
|
||||
|
||||
#### Using custome panel states
|
||||
|
||||
You're able to define custom panel states and use them as the following example.
|
||||
|
||||
```swift
|
||||
extension FloatingPanelState {
|
||||
static let lastQuart: FloatingPanelState = FloatingPanelState(rawValue: "lastQuart", order: 750)
|
||||
static let firstQuart: FloatingPanelState = FloatingPanelState(rawValue: "firstQuart", order: 250)
|
||||
}
|
||||
|
||||
class FloatingPanelLayoutWithCustomState: FloatingPanelBottomLayout {
|
||||
override var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] {
|
||||
return [
|
||||
.full: FloatingPanelLayoutAnchor(absoluteInset: 0.0, edge: .top, referenceGuide: .safeArea),
|
||||
.lastQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.75, edge: .bottom, referenceGuide: .safeArea),
|
||||
.half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea),
|
||||
.firstQuart: FloatingPanelLayoutAnchor(fractionalInset: 0.25, edge: .bottom, referenceGuide: .safeArea),
|
||||
.tip: FloatingPanelLayoutAnchor(absoluteInset: 20.0, edge: .bottom, referenceGuide: .safeArea),
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Customize the behavior with `FloatingPanelBehavior` protocol
|
||||
|
||||
#### Modify your floating panel's interaction
|
||||
|
||||
@@ -5,7 +5,7 @@ import UIKit
|
||||
/// A set of methods implemented by the delegate of a panel controller allows the adopting delegate to respond to
|
||||
/// messages from the FloatingPanelController class and thus respond to, and in some affect, operations such as
|
||||
/// dragging, attracting a panel, layout of a panel and the content, and transition animations.
|
||||
@objc public protocol FloatingPanelControllerDelegate: class {
|
||||
@objc public protocol FloatingPanelControllerDelegate {
|
||||
/// Returns a FloatingPanelLayout object. If you use the default one, you can use a `FloatingPanelBottomLayout` object.
|
||||
@objc(floatingPanel:layoutForTraitCollection:) optional
|
||||
func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout
|
||||
|
||||
+23
-9
@@ -32,7 +32,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
|
||||
var isRemovalInteractionEnabled: Bool = false
|
||||
|
||||
fileprivate var animator: UIViewPropertyAnimator?
|
||||
fileprivate var isSuspended: Bool = false // Prevent a memory leak in the modal transition
|
||||
fileprivate var transitionAnimator: UIViewPropertyAnimator?
|
||||
fileprivate var moveAnimator: NumericSpringAnimator?
|
||||
|
||||
private var initialSurfaceLocation: CGPoint = .zero
|
||||
@@ -114,7 +115,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
interruptAnimationIfNeeded()
|
||||
|
||||
if animated {
|
||||
func updateScrollView() {
|
||||
let updateScrollView: () -> Void = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if self.state == self.layoutAdapter.edgeMostState, abs(self.layoutAdapter.offsetFromEdgeMost) <= 1.0 {
|
||||
self.unlockScrollView()
|
||||
} else {
|
||||
@@ -158,12 +160,15 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
animator.addCompletion { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.animator = nil
|
||||
self.transitionAnimator = nil
|
||||
updateScrollView()
|
||||
self.ownerVC?.notifyDidMove()
|
||||
completion?()
|
||||
}
|
||||
self.animator = animator
|
||||
self.transitionAnimator = animator
|
||||
if isSuspended {
|
||||
return
|
||||
}
|
||||
animator.startAnimation()
|
||||
} else {
|
||||
self.state = to
|
||||
@@ -376,7 +381,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
if interactionInProgress {
|
||||
lockScrollView()
|
||||
} else {
|
||||
if state == layoutAdapter.edgeMostState, self.animator == nil {
|
||||
if state == layoutAdapter.edgeMostState, self.transitionAnimator == nil {
|
||||
switch layoutAdapter.position {
|
||||
case .top, .left:
|
||||
if offsetDiff < 0 && velocity > 0 {
|
||||
@@ -496,7 +501,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
animator.stopAnimation(true)
|
||||
endAttraction(false)
|
||||
}
|
||||
if let animator = self.animator {
|
||||
if let animator = self.transitionAnimator {
|
||||
guard 0 >= layoutAdapter.offsetFromEdgeMost else { return }
|
||||
log.debug("a panel animation(interruptible: \(animator.isInterruptible)) interrupted!!!")
|
||||
if animator.isInterruptible {
|
||||
@@ -710,14 +715,14 @@ class Core: NSObject, UIGestureRecognizerDelegate {
|
||||
|
||||
// Workaround: Disable a tracking scroll to prevent bouncing a scroll content in a panel animating
|
||||
let isScrollEnabled = scrollView?.isScrollEnabled
|
||||
if let scrollView = scrollView, targetPosition != .full {
|
||||
if let scrollView = scrollView, targetPosition != layoutAdapter.edgeMostState {
|
||||
scrollView.isScrollEnabled = false
|
||||
}
|
||||
|
||||
startAttraction(to: targetPosition, with: velocity)
|
||||
|
||||
// Workaround: Reset `self.scrollView.isScrollEnabled`
|
||||
if let scrollView = scrollView, targetPosition != .full,
|
||||
if let scrollView = scrollView, targetPosition != layoutAdapter.edgeMostState,
|
||||
let isScrollEnabled = isScrollEnabled {
|
||||
scrollView.isScrollEnabled = isScrollEnabled
|
||||
}
|
||||
@@ -1038,7 +1043,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
|
||||
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
initialLocation = touches.first?.location(in: view) ?? .zero
|
||||
if floatingPanel?.animator != nil || floatingPanel?.moveAnimator != nil {
|
||||
if floatingPanel?.transitionAnimator != nil || floatingPanel?.moveAnimator != nil {
|
||||
self.state = .began
|
||||
}
|
||||
}
|
||||
@@ -1199,3 +1204,12 @@ private class NumericSpringAnimator: NSObject {
|
||||
v = (v + h * o2 * (xt - x)) / det
|
||||
}
|
||||
}
|
||||
|
||||
extension FloatingPanelController {
|
||||
func suspendTransitionAnimator(_ suspended: Bool) {
|
||||
self.floatingPanel.isSuspended = suspended
|
||||
}
|
||||
var transitionAnimator: UIViewPropertyAnimator? {
|
||||
return self.floatingPanel.transitionAnimator
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.2.0</string>
|
||||
<string>2.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
+17
-31
@@ -82,9 +82,8 @@ class LayoutAdapter {
|
||||
private var initialConst: CGFloat = 0.0
|
||||
|
||||
private var fixedConstraints: [NSLayoutConstraint] = []
|
||||
private var fullConstraints: [NSLayoutConstraint] = []
|
||||
private var halfConstraints: [NSLayoutConstraint] = []
|
||||
private var tipConstraints: [NSLayoutConstraint] = []
|
||||
|
||||
private var stateConstraints: [FloatingPanelState: [NSLayoutConstraint]] = [:]
|
||||
private var offConstraints: [NSLayoutConstraint] = []
|
||||
private var fitToBoundsConstraint: NSLayoutConstraint?
|
||||
|
||||
@@ -452,25 +451,13 @@ class LayoutAdapter {
|
||||
}
|
||||
|
||||
private func updateStateConstraints() {
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
|
||||
if let fullAnchor = layout.anchors[.full] {
|
||||
fullConstraints = fullAnchor.layoutConstraints(vc, for: position)
|
||||
fullConstraints.forEach {
|
||||
$0.identifier = "FloatingPanel-full-constraint"
|
||||
}
|
||||
}
|
||||
if let halfAnchor = layout.anchors[.half] {
|
||||
halfConstraints = halfAnchor.layoutConstraints(vc, for: position)
|
||||
halfConstraints.forEach {
|
||||
$0.identifier = "FloatingPanel-half-constraint"
|
||||
}
|
||||
}
|
||||
if let tipAnchors = layout.anchors[.tip] {
|
||||
tipConstraints = tipAnchors.layoutConstraints(vc, for: position)
|
||||
tipConstraints.forEach {
|
||||
$0.identifier = "FloatingPanel-tip-constraint"
|
||||
}
|
||||
let allStateConstraints = stateConstraints.flatMap { $1 }
|
||||
NSLayoutConstraint.deactivate(allStateConstraints + offConstraints)
|
||||
stateConstraints.removeAll()
|
||||
for state in layout.anchors.keys {
|
||||
stateConstraints[state] = layout.anchors[state]?
|
||||
.layoutConstraints(vc, for: position)
|
||||
.map{ $0.identifier = "FloatingPanel-\(state)-constraint"; return $0 }
|
||||
}
|
||||
let hiddenAnchor = layout.anchors[.hidden] ?? self.hiddenAnchor
|
||||
offConstraints = hiddenAnchor.layoutConstraints(vc, for: position)
|
||||
@@ -487,7 +474,7 @@ class LayoutAdapter {
|
||||
|
||||
tearDownAttraction()
|
||||
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
NSLayoutConstraint.deactivate(stateConstraints.flatMap { $1 } + offConstraints)
|
||||
|
||||
initialConst = edgePosition(surfaceView.frame) + offset.y
|
||||
|
||||
@@ -527,7 +514,7 @@ class LayoutAdapter {
|
||||
|
||||
let anchor = layout.anchors[state] ?? self.hiddenAnchor
|
||||
|
||||
NSLayoutConstraint.deactivate(fullConstraints + halfConstraints + tipConstraints + offConstraints)
|
||||
NSLayoutConstraint.deactivate(stateConstraints.flatMap { $1 } + offConstraints)
|
||||
NSLayoutConstraint.deactivate(constraint: interactionConstraint)
|
||||
interactionConstraint = nil
|
||||
|
||||
@@ -768,17 +755,16 @@ class LayoutAdapter {
|
||||
// on-screen and off-screen view which includes
|
||||
// UIStackView(i.e. Settings view in Samples.app)
|
||||
updateStateConstraints()
|
||||
|
||||
switch state {
|
||||
case .full:
|
||||
NSLayoutConstraint.activate(fullConstraints)
|
||||
case .half:
|
||||
NSLayoutConstraint.activate(halfConstraints)
|
||||
case .tip:
|
||||
NSLayoutConstraint.activate(tipConstraints)
|
||||
case .hidden:
|
||||
NSLayoutConstraint.activate(offConstraints)
|
||||
default:
|
||||
break
|
||||
if let constraints = stateConstraints[state] {
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
} else {
|
||||
log.error("Couldn't find any constraints for \(state)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@ import Foundation
|
||||
|
||||
/// An object that represents the display state of a panel in a screen.
|
||||
@objc
|
||||
public class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
|
||||
open class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
|
||||
public typealias RawValue = String
|
||||
|
||||
required public init?(rawValue: RawValue) {
|
||||
@@ -13,6 +13,7 @@ public class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc
|
||||
public init(rawValue: RawValue, order: Int) {
|
||||
self.rawValue = rawValue
|
||||
self.order = order
|
||||
@@ -33,7 +34,7 @@ public class FloatingPanelState: NSObject, NSCopying, RawRepresentable {
|
||||
}
|
||||
|
||||
public override var debugDescription: String {
|
||||
return description
|
||||
return "<FloatingPanel.FloatingPanelState: \(Unmanaged.passUnretained(self).toOpaque())>"
|
||||
}
|
||||
|
||||
/// A panel state indicates the entire panel is shown.
|
||||
|
||||
@@ -90,14 +90,25 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
return TimeInterval(animator.duration)
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
|
||||
guard
|
||||
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
fpc.show(animated: true) {
|
||||
if let animator = fpc.transitionAnimator {
|
||||
return animator
|
||||
}
|
||||
|
||||
fpc.suspendTransitionAnimator(true)
|
||||
fpc.show(animated: true) { [weak fpc] in
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
return fpc.transitionAnimator!
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +122,25 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
|
||||
return TimeInterval(animator.duration)
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
|
||||
guard
|
||||
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
|
||||
else { fatalError() }
|
||||
|
||||
fpc.hide(animated: true) {
|
||||
if let animator = fpc.transitionAnimator {
|
||||
return animator
|
||||
}
|
||||
|
||||
fpc.suspendTransitionAnimator(true)
|
||||
fpc.hide(animated: true) { [weak fpc] in
|
||||
fpc?.suspendTransitionAnimator(false)
|
||||
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
|
||||
}
|
||||
return fpc.transitionAnimator!
|
||||
}
|
||||
|
||||
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||
self.interruptibleAnimator(using: transitionContext).startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user