11 Commits

Author SHA1 Message Date
Tosin Afolabi 5d2b0977bd Swift 5.0 Support 2019-11-11 10:26:03 -08:00
Simonas Daniliauskas a792f46e3d Add customizable drag indicator background color (#62)
* Add ability to customize drag indicator background color

* Add default dragIndicatorBackgroundColor test

* update PR according to comments
2019-10-28 12:57:36 -07:00
Guillian Balisi d6b4ba3d05 Make PanModalPresentationController subclassable (#60) 2019-10-22 11:10:43 -07:00
Scott Campbell 5c1d8c49a7 Add allowTapToDismiss to PanModalPresentable (#58) 2019-10-09 13:06:51 -07:00
Stephen Sowole af264ebb0d [PanModal] Fix handleScrollViewTopBounce calls (#56)
* [PanModal] Fix calls to handleScrollViewTopBounce if scrollView is not decelerating

* [PanModal] Replace `setContentOffset` with more generic `performUpdates`

* [PanModal] Add appropriate comments
2019-10-09 13:06:12 -07:00
Santos 9d73db3919 PanModal Portrait to Landscape Issue (#53)
* Fix issue where transitioning from portrait to landscape would cause a PanModal's view to disappear if its height was short

* improve transitioning to landscape fix
2019-10-04 11:45:16 -07:00
Nikita Nikitsky 6b1edd1dfb Added the ability to select a color for the background of the pan modal container (#54) 2019-10-01 15:53:39 -07:00
Stephen Sowole 9134d20032 [PanModal] Fix unbalanced calls to appearance (#52)
* [PanModal] Use init instead of viewDidLoad in NavigationController

* [PanModal] Remove unbalanced calls to viewWillAppear/viewWillDisappear
2019-09-30 14:02:59 -07:00
Shohei Yokoyama 0c6f9ff040 Notify delegate method after the pan modal is dismissed (#44)
* Notify delegate method after the pan modal is dismissed

* Rename panModalDismissCompleted panModalDidDismiss
2019-09-22 15:25:41 -07:00
liang 12ac36646f Update README.md (#43)
Add summary and blog post link
2019-08-23 10:40:20 -07:00
Tosin Afolabi f9fedcb597 Further fix for horizontal sliding + version bump 2019-06-12 20:50:49 -07:00
13 changed files with 120 additions and 77 deletions
+2 -2
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'PanModal'
s.version = '1.2.3'
s.version = '1.2.5'
s.summary = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
# This description is used to generate tags and improve search results.
@@ -24,6 +24,6 @@ Pod::Spec.new do |s|
s.source = { :git => 'https://github.com/slackhq/PanModal.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/slackhq'
s.ios.deployment_target = '10.0'
s.swift_version = '4.2'
s.swift_version = '5.0'
s.source_files = 'PanModal/**/*.{swift,h,m}'
end
@@ -72,7 +72,6 @@ public class PanModalPresentationAnimator: NSObject {
// Calls viewWillAppear and viewWillDisappear
fromVC.beginAppearanceTransition(false, animated: true)
toVC.beginAppearanceTransition(true, animated: true)
// Presents the view in shortForm position, initially
let yPos: CGFloat = presentable?.shortFormYPos ?? 0.0
@@ -94,7 +93,6 @@ public class PanModalPresentationAnimator: NSObject {
}, config: presentable) { [weak self] didComplete in
// Calls viewDidAppear and viewDidDisappear
fromVC.endAppearanceTransition()
toVC.endAppearanceTransition()
transitionContext.completeTransition(didComplete)
self?.feedbackGenerator = nil
}
@@ -111,7 +109,6 @@ public class PanModalPresentationAnimator: NSObject {
else { return }
// Calls viewWillAppear and viewWillDisappear
fromVC.beginAppearanceTransition(false, animated: true)
toVC.beginAppearanceTransition(true, animated: true)
let presentable = panModalLayoutType(from: transitionContext)
@@ -122,7 +119,6 @@ public class PanModalPresentationAnimator: NSObject {
}, config: presentable) { didComplete in
fromVC.view.removeFromSuperview()
// Calls viewDidAppear and viewDidDisappear
fromVC.endAppearanceTransition()
toVC.endAppearanceTransition()
transitionContext.completeTransition(didComplete)
}
@@ -22,7 +22,7 @@ import UIKit
By conforming to the PanModalPresentable protocol & overriding values
the presented view can define its layout configuration & presentation.
*/
public class PanModalPresentationController: UIPresentationController {
open class PanModalPresentationController: UIPresentationController {
/**
Enum representing the possible presentation states
@@ -105,13 +105,15 @@ public class PanModalPresentationController: UIPresentationController {
*/
private lazy var backgroundView: DimmedView = {
let view: DimmedView
if let alpha = presentable?.backgroundAlpha {
view = DimmedView(dimAlpha: alpha)
if let color = presentable?.panModalBackgroundColor {
view = DimmedView(dimColor: color)
} else {
view = DimmedView()
}
view.didTap = { [weak self] _ in
self?.dismissPresentedViewController()
if self?.presentable?.allowsTapToDismiss == true {
self?.dismissPresentedViewController()
}
}
return view
}()
@@ -131,7 +133,7 @@ public class PanModalPresentationController: UIPresentationController {
*/
private lazy var dragIndicatorView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
view.backgroundColor = presentable?.dragIndicatorBackgroundColor
view.layer.cornerRadius = Constants.dragIndicatorSize.height / 2.0
return view
}()
@@ -226,7 +228,6 @@ public class PanModalPresentationController: UIPresentationController {
else { return }
self.adjustPresentedViewFrame()
if presentable.shouldRoundTopCorners {
self.addRoundedCorners(to: self.presentedView)
}
@@ -259,35 +260,27 @@ public extension PanModalPresentationController {
}
/**
Set the content offset of the scroll view
Operations on the scroll view, such as content height changes,
or when inserting/deleting rows can cause the pan modal to jump,
caused by the pan modal responding to content offset changes.
Due to content offset observation, its not possible to programmatically
set the content offset directly on the scroll view while in the short form.
This method pauses the content offset KVO, performs the content offset change
and then resumes content offset observation.
To avoid this, you can call this method to perform scroll view updates,
with scroll observation temporarily disabled.
*/
func setContentOffset(offset: CGPoint) {
func performUpdates(_ updates: () -> Void) {
guard let scrollView = presentable?.panScrollable
else { return }
/**
Invalidate scroll view observer
to prevent its overriding the content offset change
*/
// Pause scroll observer
scrollObserver?.invalidate()
scrollObserver = nil
/**
Set scroll view offset & track scrolling
*/
scrollView.setContentOffset(offset, animated:false)
trackScrolling(scrollView)
// Perform updates
updates()
/**
Add the scroll view observer
*/
// Resume scroll observer
trackScrolling(scrollView)
observe(scrollView: scrollView)
}
@@ -367,7 +360,15 @@ private extension PanModalPresentationController {
else { return }
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
let panFrame = panContainerView.frame
panContainerView.frame.size = frame.size
if ![shortFormYPosition, longFormYPosition].contains(panFrame.origin.y) {
// if the container is already in the correct position, no need to adjust positioning
// (rotations & size changes cause positioning to be out of sync)
adjust(toYPosition: panFrame.origin.y - panFrame.height + frame.height)
}
panContainerView.frame.origin.x = frame.origin.x
presentedViewController.view.frame = CGRect(origin: .zero, size: adjustedSize)
}
@@ -676,7 +677,9 @@ private extension PanModalPresentationController {
*/
func dismissPresentedViewController() {
presentable?.panModalWillDismiss()
presentedViewController.dismiss(animated: true, completion: nil)
presentedViewController.dismiss(animated: true) { [weak self] in
self?.presentable?.panModalDidDismiss()
}
}
}
@@ -782,7 +785,7 @@ private extension PanModalPresentationController {
*/
func handleScrollViewTopBounce(scrollView: UIScrollView, change: NSKeyValueObservedChange<CGPoint>) {
guard let oldYValue = change.oldValue?.y
guard let oldYValue = change.oldValue?.y, scrollView.isDecelerating
else { return }
let yOffset = scrollView.contentOffset.y
@@ -822,11 +825,11 @@ extension PanModalPresentationController: UIGestureRecognizerDelegate {
}
/**
Allow simultaneous gesture recognizers only when the other gesture recognizer
is a pan gesture recognizer
Allow simultaneous gesture recognizers only when the other gesture recognizer's view
is the pan scrollable view
*/
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer == panGestureRecognizer
return otherGestureRecognizer.view == presentable?.panScrollable
}
}
@@ -46,8 +46,12 @@ public extension PanModalPresentable where Self: UIViewController {
return [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState]
}
var backgroundAlpha: CGFloat {
return 0.7
var panModalBackgroundColor: UIColor {
return UIColor.black.withAlphaComponent(0.7)
}
var dragIndicatorBackgroundColor: UIColor {
return UIColor.lightGray
}
var scrollIndicatorInsets: UIEdgeInsets {
@@ -72,6 +76,10 @@ public extension PanModalPresentable where Self: UIViewController {
return true
}
var allowsTapToDismiss: Bool {
return true
}
var isUserInteractionEnabled: Bool {
return true
}
@@ -112,4 +120,7 @@ public extension PanModalPresentable where Self: UIViewController {
}
func panModalDidDismiss() {
}
}
@@ -29,16 +29,6 @@ public extension PanModalPresentable where Self: UIViewController {
presentedVC?.transition(to: state)
}
/**
Programmatically set the content offset of the pan scrollable.
This is required to use while in the short form presentation state,
as due to content offset observation, setting the content offset directly would fail
*/
func panModalSetContentOffset(offset: CGPoint) {
presentedVC?.setContentOffset(offset: offset)
}
/**
A function wrapper over the `setNeedsLayoutUpdate()`
function in the PanModalPresentationController.
@@ -49,6 +39,16 @@ public extension PanModalPresentable where Self: UIViewController {
presentedVC?.setNeedsLayoutUpdate()
}
/**
Operations on the scroll view, such as content height changes, or when inserting/deleting rows can cause the pan modal to jump,
caused by the pan modal responding to content offset changes.
To avoid this, you can call this method to perform scroll view updates, with scroll observation temporarily disabled.
*/
func panModalPerformUpdates(_ updates: () -> Void) {
presentedVC?.performUpdates(updates)
}
/**
A function wrapper over the animate function in PanModalAnimator.
+24 -4
View File
@@ -86,13 +86,20 @@ public protocol PanModalPresentable: AnyObject {
var transitionAnimationOptions: UIView.AnimationOptions { get }
/**
The background view alpha.
The background view color.
- Note: This is only utilized at the very start of the transition.
Default Value is 0.7.
*/
var backgroundAlpha: CGFloat { get }
Default Value is black with alpha component 0.7.
*/
var panModalBackgroundColor: UIColor { get }
/**
The drag indicator view color.
Default value is light gray.
*/
var dragIndicatorBackgroundColor: UIColor { get }
/**
We configure the panScrollable's scrollIndicatorInsets interally so override this value
@@ -127,6 +134,13 @@ public protocol PanModalPresentable: AnyObject {
*/
var allowsDragToDismiss: Bool { get }
/**
A flag to determine if dismissal should be initiated when tapping on the dimmed background view.
Default value is true.
*/
var allowsTapToDismiss: Bool { get }
/**
A flag to toggle user interactions on the container view.
@@ -212,4 +226,10 @@ public protocol PanModalPresentable: AnyObject {
*/
func panModalWillDismiss()
/**
Notifies the delegate after the pan modal is dismissed.
Default value is an empty implementation.
*/
func panModalDidDismiss()
}
+4 -8
View File
@@ -31,12 +31,11 @@ public class DimmedView: UIView {
didSet {
switch dimState {
case .max:
alpha = dimAlpha
alpha = 1.0
case .off:
alpha = 0.0
case .percent(let percentage):
let val = max(0.0, min(1.0, percentage))
alpha = dimAlpha * val
alpha = max(0.0, min(1.0, percentage))
}
}
}
@@ -53,15 +52,12 @@ public class DimmedView: UIView {
return UITapGestureRecognizer(target: self, action: #selector(didTapView))
}()
private let dimAlpha: CGFloat
// MARK: - Initializers
init(dimAlpha: CGFloat = 0.7) {
self.dimAlpha = dimAlpha
init(dimColor: UIColor = UIColor.black.withAlphaComponent(0.7)) {
super.init(frame: .zero)
alpha = 0.0
backgroundColor = .black
backgroundColor = dimColor
addGestureRecognizer(tapGesture)
}
+8 -6
View File
@@ -629,7 +629,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.slack.PanModal;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -658,7 +658,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.slack.PanModal;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -679,7 +679,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModalDemo.app/PanModalDemo";
};
@@ -699,7 +699,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModalDemo.app/PanModalDemo";
};
@@ -763,6 +763,7 @@
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -817,6 +818,7 @@
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -837,7 +839,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -858,7 +860,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
+10 -1
View File
@@ -1,10 +1,13 @@
### PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.
<p align="center">
<img src="https://github.com/slackhq/PanModal/raw/master/Screenshots/panModal.gif" width="30%" height="30%" alt="Screenshot Preview" />
</p>
<p align="center">
<img src="https://img.shields.io/badge/Platform-iOS_10+-green.svg" alt="Platform: iOS 10.0+" />
<a href="https://developer.apple.com/swift" target="_blank"><img src="https://img.shields.io/badge/Language-Swift_4-blueviolet.svg" alt="Language: Swift 4" /></a>
<a href="https://developer.apple.com/swift" target="_blank"><img src="https://img.shields.io/badge/Language-Swift_5-blueviolet.svg" alt="Language: Swift 5" /></a>
<a href="https://cocoapods.org/pods/PanModal" target="_blank"><img src="https://img.shields.io/badge/CocoaPods-v1.0-red.svg" alt="CocoaPods compatible" /></a>
<a href="https://github.com/Carthage/Carthage" target="_blank"><img src="https://img.shields.io/badge/Carthage-compatible-blue.svg" alt="Carthage compatible" /></a>
<img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License: MIT" />
@@ -21,6 +24,12 @@
• <a href="#license">License</a>
</p>
<p align="center">
Read our <a href="https://slack.engineering/panmodal-better-support-for-thumb-accessibility-on-slack-mobile-52b2a7596031" target="_blank">blog</a> on how Slack is getting more :thumbsup: with PanModal
Swift 4.2 support can be found on the `Swift4.2` branch.
</p>
## Features
* Supports any type of `UIViewController`
@@ -59,8 +59,8 @@ class TransientAlertViewController: AlertViewController {
return true
}
override var backgroundAlpha: CGFloat {
return 0.0
override var panModalBackgroundColor: UIColor {
return .clear
}
override var isUserInteractionEnabled: Bool {
@@ -46,8 +46,8 @@ class AlertViewController: UIViewController, PanModalPresentable {
return shortFormHeight
}
var backgroundAlpha: CGFloat {
return 0.1
var panModalBackgroundColor: UIColor {
return UIColor.black.withAlphaComponent(0.1)
}
var shouldRoundTopCorners: Bool {
@@ -12,13 +12,17 @@ class NavigationController: UINavigationController, PanModalPresentable {
private let navGroups = NavUserGroups()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
init() {
super.init(nibName: nil, bundle: nil)
viewControllers = [navGroups]
}
override func viewDidLoad() {
super.viewDidLoad()
pushViewController(navGroups, animated: false)
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func popViewController(animated: Bool) -> UIViewController? {
+3 -1
View File
@@ -48,11 +48,13 @@ class PanModalTests: XCTestCase {
XCTAssertEqual(vc.shortFormHeight, PanModalHeight.maxHeight)
XCTAssertEqual(vc.longFormHeight, PanModalHeight.maxHeight)
XCTAssertEqual(vc.springDamping, 0.8)
XCTAssertEqual(vc.backgroundAlpha, 0.7)
XCTAssertEqual(vc.panModalBackgroundColor, UIColor.black.withAlphaComponent(0.7))
XCTAssertEqual(vc.dragIndicatorBackgroundColor, UIColor.lightGray)
XCTAssertEqual(vc.scrollIndicatorInsets, .zero)
XCTAssertEqual(vc.anchorModalToLongForm, true)
XCTAssertEqual(vc.allowsExtendedPanScrolling, false)
XCTAssertEqual(vc.allowsDragToDismiss, true)
XCTAssertEqual(vc.allowsTapToDismiss, true)
XCTAssertEqual(vc.isUserInteractionEnabled, true)
XCTAssertEqual(vc.isHapticFeedbackEnabled, true)
XCTAssertEqual(vc.shouldRoundTopCorners, false)