Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d5f03bb6e | |||
| 680b16aa25 | |||
| 2394c03dca | |||
| c8f211f2bf | |||
| 9076ba8933 | |||
| 08e79bfc5c | |||
| e4808516aa | |||
| 5888104e98 | |||
| 2b8d29759a | |||
| 8e4b56ff17 | |||
| 23c5761c14 | |||
| 835ec0c3a0 | |||
| d2dce0b6f8 | |||
| 3f8628af01 | |||
| 40c6fae07c | |||
| e1185fda93 | |||
| 458ed903c5 | |||
| 4b640f4f01 | |||
| 8743c5efd0 | |||
| 9bd9d31d40 | |||
| 7e3d720720 | |||
| 7a512191ab | |||
| af767863bb | |||
| 1b233f4f87 | |||
| 5df36a6601 |
@@ -9,12 +9,7 @@
|
||||
import UIKit
|
||||
import FloatingPanel
|
||||
|
||||
/**
|
||||
- Attention: `FloatingPanelLayout` must not be applied by the parent view
|
||||
controller of a floating panel. But here `SampleListViewController` adopts it
|
||||
purposely to check if the library prints an appropriate warning.
|
||||
*/
|
||||
class SampleListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FloatingPanelControllerDelegate, FloatingPanelLayout {
|
||||
class SampleListViewController: UIViewController {
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
|
||||
enum Menu: Int, CaseIterable {
|
||||
@@ -24,6 +19,7 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
case showModal
|
||||
case showFloatingPanelModal
|
||||
case showTabBar
|
||||
case showPageView
|
||||
case showNestedScrollView
|
||||
case showRemovablePanel
|
||||
case showIntrinsicView
|
||||
@@ -36,6 +32,7 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
case .showModal: return "Show Modal"
|
||||
case .showFloatingPanelModal: return "Show Floating Panel Modal"
|
||||
case .showTabBar: return "Show Tab Bar"
|
||||
case .showPageView: return "Show Page View"
|
||||
case .showNestedScrollView: return "Show Nested ScrollView"
|
||||
case .showRemovablePanel: return "Show Removable Panel"
|
||||
case .showIntrinsicView: return "Show Intrinsic View"
|
||||
@@ -50,6 +47,7 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
case .showModal: return "ModalViewController"
|
||||
case .showFloatingPanelModal: return nil
|
||||
case .showTabBar: return "TabBarViewController"
|
||||
case .showPageView: return nil
|
||||
case .showNestedScrollView: return "NestedScrollViewController"
|
||||
case .showRemovablePanel: return "DetailViewController"
|
||||
case .showIntrinsicView: return "IntrinsicViewController"
|
||||
@@ -66,6 +64,19 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
var mainPanelObserves: [NSKeyValueObservation] = []
|
||||
var settingsObserves: [NSKeyValueObservation] = []
|
||||
|
||||
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
|
||||
@@ -164,31 +175,6 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}
|
||||
}
|
||||
|
||||
// MARK:- TableViewDatasource
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if #available(iOS 11.0, *) {
|
||||
if navigationController?.navigationBar.prefersLargeTitles == true {
|
||||
return Menu.allCases.count + 30
|
||||
} else {
|
||||
return Menu.allCases.count
|
||||
}
|
||||
} else {
|
||||
return Menu.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]
|
||||
cell.textLabel?.text = menu.name
|
||||
} else {
|
||||
cell.textLabel?.text = "\(indexPath.row) row"
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
// MARK:- Actions
|
||||
@IBAction func showDebugMenu(_ sender: UIBarButtonItem) {
|
||||
guard settingsPanelVC == nil else { return }
|
||||
@@ -213,9 +199,34 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
// Add FloatingPanel to self.view
|
||||
settingsPanelVC.addPanel(toParent: self, belowView: nil, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK:- TableViewDelegate
|
||||
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
|
||||
} else {
|
||||
return Menu.allCases.count
|
||||
}
|
||||
} else {
|
||||
return Menu.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]
|
||||
cell.textLabel?.text = menu.name
|
||||
} else {
|
||||
cell.textLabel?.text = "\(indexPath.row) row"
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension SampleListViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard Menu.allCases.count > indexPath.row else { return }
|
||||
let menu = Menu.allCases[indexPath.row]
|
||||
@@ -246,6 +257,22 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
case .showModal, .showTabBar:
|
||||
let modalVC = contentVC
|
||||
present(modalVC, animated: true, completion: nil)
|
||||
|
||||
case .showPageView:
|
||||
let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
|
||||
let closeButton = UIButton(type: .custom)
|
||||
pageVC.view.addSubview(closeButton)
|
||||
closeButton.setTitle("Close", for: .normal)
|
||||
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
closeButton.addTarget(self, action: #selector(dismissPresentedVC), for: .touchUpInside)
|
||||
NSLayoutConstraint.activate([
|
||||
closeButton.topAnchor.constraint(equalTo: pageVC.layoutGuide.topAnchor, constant: 16.0),
|
||||
closeButton.leftAnchor.constraint(equalTo: pageVC.view.leftAnchor, constant: 16.0),
|
||||
])
|
||||
pageVC.dataSource = self
|
||||
pageVC.setViewControllers([pages[0]], direction: .forward, animated: false, completion: nil)
|
||||
present(pageVC, animated: true, completion: nil)
|
||||
|
||||
case .showFloatingPanelModal:
|
||||
let fpc = FloatingPanelController()
|
||||
let contentVC = self.storyboard!.instantiateViewController(withIdentifier: "DetailViewController")
|
||||
@@ -266,6 +293,12 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dismissPresentedVC() {
|
||||
self.presentedViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension SampleListViewController: FloatingPanelControllerDelegate {
|
||||
func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout? {
|
||||
if vc == settingsPanelVC {
|
||||
return IntrinsicPanelLayout()
|
||||
@@ -290,6 +323,9 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
switch currentMenu {
|
||||
case .showNestedScrollView:
|
||||
return (vc.contentViewController as? NestedScrollViewController)?.nestedScrollView.gestureRecognizers?.contains(gestureRecognizer) ?? false
|
||||
case .showPageView:
|
||||
// Tips: Need to allow recognizing the pan gesture of UIPageViewController simultaneously.
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -303,7 +339,14 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- Attention: `FloatingPanelLayout` must not be applied by the parent view
|
||||
controller of a floating panel. But here `SampleListViewController` adopts it
|
||||
purposely to check if the library prints an appropriate warning.
|
||||
*/
|
||||
extension SampleListViewController: FloatingPanelLayout {
|
||||
var initialPosition: FloatingPanelPosition {
|
||||
return .half
|
||||
}
|
||||
@@ -318,6 +361,23 @@ class SampleListViewController: UIViewController, UITableViewDataSource, UITable
|
||||
}
|
||||
}
|
||||
|
||||
extension SampleListViewController: UIPageViewControllerDataSource {
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let index = pages.firstIndex(of: viewController),
|
||||
index + 1 < pages.count
|
||||
else { return nil }
|
||||
return pages[index + 1]
|
||||
}
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||
guard
|
||||
let index = pages.firstIndex(of: viewController),
|
||||
index - 1 >= 0
|
||||
else { return nil }
|
||||
return pages[index - 1]
|
||||
}
|
||||
}
|
||||
|
||||
class IntrinsicPanelLayout: FloatingPanelIntrinsicLayout { }
|
||||
|
||||
class RemovablePanelLayout: FloatingPanelIntrinsicLayout {
|
||||
@@ -817,7 +877,7 @@ class TabBarContentViewController: UIViewController {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MAKR: - Private
|
||||
// MARK: - Private
|
||||
|
||||
@objc
|
||||
private func changeTab3Mode(_ sender: UISwitch) {
|
||||
@@ -835,9 +895,9 @@ extension TabBarContentViewController: UITextViewDelegate {
|
||||
guard self.tabBarItem.tag == 2 else { return }
|
||||
// Reset an invalid content offset by a user after updating the layout
|
||||
// of `consoleVC.textView`.
|
||||
// NOTE: FloatingPanel doesn't implicity reset the offset(i.e.
|
||||
// NOTE: FloatingPanel doesn't implicitly reset the offset(i.e.
|
||||
// Using KVO of `scrollView.contentOffset`). Because it can lead to an
|
||||
// infinit loop if a user also resets a content offset as below and,
|
||||
// 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) {
|
||||
scrollView.contentOffset = .zero
|
||||
@@ -876,7 +936,7 @@ extension TabBarContentViewController: FloatingPanelControllerDelegate {
|
||||
}
|
||||
case .changeOffset:
|
||||
/*
|
||||
Bad solution: Manipulate scoll content inset
|
||||
Bad solution: Manipulate scroll content inset
|
||||
|
||||
FloatingPanelController keeps a content offset in moving a panel
|
||||
so that changing content inset or offset causes a buggy behavior.
|
||||
@@ -918,7 +978,7 @@ extension TabBarContentViewController: FloatingPanelControllerDelegate {
|
||||
consoleVC.textViewTopConstraint?.constant = (vc.position == .full) ? vc.layoutInsets.top : 17.0
|
||||
|
||||
case .changeOffset:
|
||||
/* Bad Solution: Manipulate scoll content inset */
|
||||
/* Bad Solution: Manipulate scroll content inset */
|
||||
guard let scrollView = consoleVC.textView else { return }
|
||||
var insets = vc.adjustedContentInsets
|
||||
insets.top = (vc.position == .full) ? vc.layoutInsets.top : 0.0
|
||||
@@ -1047,7 +1107,7 @@ class SettingsViewController: InspectableViewController {
|
||||
override func viewDidLoad() {
|
||||
versionLabel.text = "Version: \(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "--")"
|
||||
}
|
||||
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "FloatingPanel"
|
||||
s.version = "1.5.0"
|
||||
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.
|
||||
@@ -14,7 +14,7 @@ The new interface displays the related contents and utilities in parallel as a u
|
||||
s.source = { :git => "https://github.com/SCENEE/FloatingPanel.git", :tag => "v#{s.version}" }
|
||||
s.source_files = "Framework/Sources/*.swift"
|
||||
s.swift_version = "4.0"
|
||||
s.pod_target_xcconfig = { 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
|
||||
s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
|
||||
|
||||
s.framework = "UIKit"
|
||||
|
||||
|
||||
@@ -38,7 +38,15 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
|
||||
var isRemovalInteractionEnabled: Bool = false
|
||||
|
||||
fileprivate var animator: UIViewPropertyAnimator?
|
||||
fileprivate var animator: UIViewPropertyAnimator? {
|
||||
didSet {
|
||||
// This intends to avoid `tableView(_:didSelectRowAt:)` not being
|
||||
// called on first tap after the moving animation, but it doesn't
|
||||
// seem to be enough. The same issue happens on Apple Maps so it
|
||||
// might be an issue in `UITableView`.
|
||||
scrollView?.isUserInteractionEnabled = (animator == nil)
|
||||
}
|
||||
}
|
||||
|
||||
private var initialFrame: CGRect = .zero
|
||||
private var initialTranslationY: CGFloat = 0
|
||||
@@ -299,15 +307,19 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
if let animator = self.animator {
|
||||
log.debug("panel animation interrupted!!!")
|
||||
if animator.isInterruptible {
|
||||
animator.stopAnimation(false)
|
||||
animator.finishAnimation(at: .current)
|
||||
// Prevent aborting touch events when the current animator is
|
||||
// released almost at a target position. Because any tap gestures
|
||||
// shouldn't be disturbed at the position.
|
||||
if fabs(surfaceView.frame.minY - layoutAdapter.topY) > 40.0 {
|
||||
if animator.isInterruptible {
|
||||
animator.stopAnimation(false)
|
||||
animator.finishAnimation(at: .current)
|
||||
}
|
||||
self.animator = nil
|
||||
}
|
||||
|
||||
self.animator = nil
|
||||
|
||||
// A user can stop a panel at the nearest Y of a target position
|
||||
if abs(surfaceView.frame.minY - layoutAdapter.topY) < 1 {
|
||||
if abs(surfaceView.frame.minY - layoutAdapter.topY) < 1.0 {
|
||||
surfaceView.frame.origin.y = layoutAdapter.topY
|
||||
}
|
||||
}
|
||||
@@ -636,6 +648,7 @@ class FloatingPanel: NSObject, UIGestureRecognizerDelegate, UIScrollViewDelegate
|
||||
|
||||
private func finishAnimation(at targetPosition: FloatingPanelPosition) {
|
||||
log.debug("finishAnimation to \(targetPosition)")
|
||||
|
||||
self.isDecelerating = false
|
||||
self.animator = nil
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public protocol FloatingPanelBehavior {
|
||||
/// Asks the behavior object if the floating panel should project a momentum of a user interaction to move the proposed position.
|
||||
///
|
||||
/// The default implementation of this method returns true. This method is called for a layout to support all positions(tip, half and full).
|
||||
/// Therfore, `proposedTargetPosition` can only be `FloatingPanelPosition.tip` or `FloatingPanelPosition.full`.
|
||||
/// Therefore, `proposedTargetPosition` can only be `FloatingPanelPosition.tip` or `FloatingPanelPosition.full`.
|
||||
func shouldProjectMomentum(_ fpc: FloatingPanelController, for proposedTargetPosition: FloatingPanelPosition) -> Bool
|
||||
|
||||
/// Returns a deceleration rate to calculate a target position projected a dragging momentum.
|
||||
@@ -19,7 +19,7 @@ public protocol FloatingPanelBehavior {
|
||||
|
||||
/// Returns the progress to redirect to the previous position.
|
||||
///
|
||||
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next posiiton. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
|
||||
/// The progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the floating panel is impossible to move to the next position. The default value is 0.5. Values less than 0.0 and greater than 1.0 are pinned to those limits.
|
||||
func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelPosition, to: FloatingPanelPosition) -> CGFloat
|
||||
|
||||
/// Returns a UIViewPropertyAnimator object to project a floating panel to a position on finger up if the user dragged.
|
||||
|
||||
@@ -73,7 +73,7 @@ public enum FloatingPanelPosition: Int {
|
||||
///
|
||||
/// A container view controller to display a floating panel to present contents in parallel as a user wants.
|
||||
///
|
||||
public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
open class FloatingPanelController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
/// Constants indicating how safe area insets are added to the adjusted content inset.
|
||||
public enum ContentInsetAdjustmentBehavior: Int {
|
||||
case always
|
||||
@@ -181,7 +181,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
// MARK:- Overrides
|
||||
|
||||
/// Creates the view that the controller manages.
|
||||
override public func loadView() {
|
||||
open override func loadView() {
|
||||
assert(self.storyboard == nil, "Storyboard isn't supported")
|
||||
|
||||
let view = FloatingPanelPassThroughView()
|
||||
@@ -196,7 +196,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
self.view = view as UIView
|
||||
}
|
||||
|
||||
public override func viewDidLayoutSubviews() {
|
||||
open override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if #available(iOS 11.0, *) {}
|
||||
else {
|
||||
@@ -207,7 +207,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
}
|
||||
|
||||
public 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 {
|
||||
@@ -216,7 +216,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
}
|
||||
|
||||
public 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)
|
||||
|
||||
// Change layout for a new trait collection
|
||||
@@ -226,7 +226,7 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
floatingPanel.behavior = fetchBehavior(for: newCollection)
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
open override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
safeAreaInsetsObservation = nil
|
||||
}
|
||||
@@ -455,14 +455,14 @@ public class FloatingPanelController: UIViewController, UIScrollViewDelegate, UI
|
||||
}
|
||||
|
||||
@available(*, unavailable, renamed: "set(contentViewController:)")
|
||||
public override func show(_ vc: UIViewController, sender: Any?) {
|
||||
open override func show(_ vc: UIViewController, sender: Any?) {
|
||||
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.show(_:sender:)), sender: sender) {
|
||||
target.show(vc, sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable, renamed: "set(contentViewController:)")
|
||||
public override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
|
||||
open override func showDetailViewController(_ vc: UIViewController, sender: Any?) {
|
||||
if let target = self.parent?.targetViewController(forAction: #selector(UIViewController.showDetailViewController(_:sender:)), sender: sender) {
|
||||
target.showDetailViewController(vc, sender: sender)
|
||||
}
|
||||
|
||||
@@ -59,8 +59,19 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
/// The color of the surface border.
|
||||
public var borderWidth: CGFloat = 0.0 { didSet { setNeedsLayout() } }
|
||||
|
||||
|
||||
/// The view presents an actual surface shape.
|
||||
///
|
||||
/// It renders the background color, border line and top rounded corners,
|
||||
/// specified by other properties. The reason why they're not be applied to
|
||||
/// a content view directly is because it avoids any side-effects to the
|
||||
/// content view.
|
||||
public var containerView: UIView!
|
||||
|
||||
@available(*, unavailable, renamed: "containerView")
|
||||
public var backgroundView: UIView!
|
||||
private var backgroundHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
private var containerViewHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
private struct Default {
|
||||
public static let grabberTopPadding: CGFloat = 6.0
|
||||
@@ -80,20 +91,19 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
super.backgroundColor = .clear
|
||||
self.clipsToBounds = false
|
||||
|
||||
let backgroundView = UIView()
|
||||
addSubview(backgroundView)
|
||||
self.backgroundView = backgroundView
|
||||
let containerView = UIView()
|
||||
addSubview(containerView)
|
||||
self.containerView = containerView
|
||||
|
||||
backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
backgroundHeightConstraint = backgroundView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerViewHeightConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
|
||||
NSLayoutConstraint.activate([
|
||||
backgroundView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
|
||||
backgroundView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
backgroundView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
backgroundHeightConstraint,
|
||||
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
|
||||
containerView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
containerView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
containerViewHeightConstraint,
|
||||
])
|
||||
|
||||
|
||||
let grabberHandle = GrabberHandleView()
|
||||
addSubview(grabberHandle)
|
||||
self.grabberHandle = grabberHandle
|
||||
@@ -109,7 +119,7 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
|
||||
public override func updateConstraints() {
|
||||
super.updateConstraints()
|
||||
backgroundHeightConstraint.constant = bottomOverflow
|
||||
containerViewHeightConstraint.constant = bottomOverflow
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
@@ -118,18 +128,17 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
|
||||
updateLayers()
|
||||
updateContentViewMask()
|
||||
updateBorder()
|
||||
|
||||
contentView?.layer.borderColor = borderColor?.cgColor
|
||||
contentView?.layer.borderWidth = borderWidth
|
||||
contentView?.frame = bounds
|
||||
}
|
||||
|
||||
private func updateLayers() {
|
||||
backgroundView.backgroundColor = color
|
||||
containerView.backgroundColor = color
|
||||
|
||||
if cornerRadius != 0.0, backgroundView.layer.cornerRadius != cornerRadius {
|
||||
backgroundView.layer.masksToBounds = true
|
||||
backgroundView.layer.cornerRadius = cornerRadius
|
||||
if cornerRadius != 0.0, containerView.layer.cornerRadius != cornerRadius {
|
||||
containerView.layer.masksToBounds = true
|
||||
containerView.layer.cornerRadius = cornerRadius
|
||||
}
|
||||
|
||||
if shadowHidden == false {
|
||||
@@ -142,26 +151,30 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
|
||||
private func updateContentViewMask() {
|
||||
guard
|
||||
let contentView = contentView,
|
||||
cornerRadius != 0.0,
|
||||
contentView.layer.cornerRadius != cornerRadius
|
||||
containerView.layer.cornerRadius != cornerRadius
|
||||
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.
|
||||
// Because the bottom of contentView must be fit to the bottom of a screen to work the `safeLayoutGuide` of a content VC.
|
||||
contentView.layer.masksToBounds = true
|
||||
contentView.layer.cornerRadius = cornerRadius
|
||||
contentView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
containerView.layer.masksToBounds = true
|
||||
containerView.layer.cornerRadius = cornerRadius
|
||||
containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
} else {
|
||||
// Don't use `contentView.layer.mask` because of a UIVisualEffectView issue in iOS 10, https://forums.developer.apple.com/thread/50854
|
||||
// Instead, a user can mask the content view manually in an application.
|
||||
}
|
||||
}
|
||||
|
||||
private func updateBorder() {
|
||||
containerView.layer.borderColor = borderColor?.cgColor
|
||||
containerView.layer.borderWidth = borderWidth
|
||||
}
|
||||
|
||||
func add(contentView: UIView) {
|
||||
insertSubview(contentView, belowSubview: grabberHandle)
|
||||
containerView.addSubview(contentView)
|
||||
self.contentView = contentView
|
||||
/* contentView.frame = bounds */ // MUST NOT: Because the top safe area inset of a content VC will be incorrect.
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -169,7 +182,7 @@ public class FloatingPanelSurfaceView: UIView {
|
||||
contentView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
|
||||
contentView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0),
|
||||
contentView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0),
|
||||
contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
|
||||
contentView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.0</string>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
|
||||
@@ -68,7 +68,9 @@ Examples are here.
|
||||
|
||||
## Requirements
|
||||
|
||||
FloatingPanel is written in Swift. It can be built by Xcode 9.4.1 or later. Compatible with iOS 10.0+.
|
||||
FloatingPanel is written in Swift 4.0+. It can be built by Xcode 9.4.1 or later. Compatible with iOS 10.0+.
|
||||
|
||||
✏️ The default Swift version is 4.0 because it avoids build errors with Carthage on each Xcode version from the source compatibility between Swift 4.0, 4.2 and 5.0.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -81,6 +83,8 @@ it, simply add the following line to your Podfile:
|
||||
pod 'FloatingPanel'
|
||||
```
|
||||
|
||||
✏️ To suppress "Swift Conversion" warnings in Xcode, please set a Swift version to `SWIFT_VERSION` for the project in your Podfile. It will be resolved in CocoaPods v1.7.0.
|
||||
|
||||
### Carthage
|
||||
|
||||
For [Carthage](https://github.com/Carthage/Carthage), add the following to your `Cartfile`:
|
||||
@@ -142,7 +146,7 @@ self.present(fpc, animated: true, completion: nil)
|
||||
|
||||
You can show a floating panel over UINavigationController from the container view controllers as a modality of `.overCurrentContext` style.
|
||||
|
||||
NOTE: FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Framework/Sources/FloatingPanelTransitioning.swift).
|
||||
✏️ FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see [FloatingPanelTransitioning](https://github.com/SCENEE/FloatingPanel/blob/master/Framework/Sources/FloatingPanelTransitioning.swift).
|
||||
|
||||
## View hierarchy
|
||||
|
||||
@@ -152,8 +156,9 @@ NOTE: FloatingPanelController has the custom presentation controller. If you wou
|
||||
FloatingPanelController.view (FloatingPanelPassThroughView)
|
||||
├─ .backdropView (FloatingPanelBackdropView)
|
||||
└─ .surfaceView (FloatingPanelSurfaceView)
|
||||
├─ .contentView == FloatingPanelController.contentViewController.view
|
||||
└─ .grabberHandle (GrabberHandleView)
|
||||
├─ .containerView (UIView)
|
||||
│ └─ .contentView (FloatingPanelController.contentViewController.view)
|
||||
└─ .grabberHandle (GrabberHandleView)
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Reference in New Issue
Block a user