Compare commits
85 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ced7c1703f | |
|
|
36be7f0f25 | |
|
|
b8f07ab26b | |
|
|
1c47459734 | |
|
|
be82eddb52 | |
|
|
73c00b4563 | |
|
|
8ac7096ec9 | |
|
|
0c3ed5a2ef | |
|
|
eaf349654d | |
|
|
83d4b7024c | |
|
|
871fc46020 | |
|
|
d28aab13a6 | |
|
|
b2f5bd7d16 | |
|
|
b012aecb6b | |
|
|
22b4ddd47e | |
|
|
17a8231f20 | |
|
|
1819113e97 | |
|
|
208640e03e | |
|
|
f02439dc0c | |
|
|
047415090a | |
|
|
1e7c0534fc | |
|
|
7f07cdff27 | |
|
|
45f8dfcf19 | |
|
|
5d2b0977bd | |
|
|
a792f46e3d | |
|
|
d6b4ba3d05 | |
|
|
5c1d8c49a7 | |
|
|
af264ebb0d | |
|
|
9d73db3919 | |
|
|
6b1edd1dfb | |
|
|
9134d20032 | |
|
|
0c6f9ff040 | |
|
|
12ac36646f | |
|
|
f9fedcb597 | |
|
|
4206308afd | |
|
|
0234d82979 | |
|
|
b0fb9e7eed | |
|
|
a9edeb1d71 | |
|
|
b8f7e613ec | |
|
|
8a662e829b | |
|
|
0b2152f878 | |
|
|
13251e1db7 | |
|
|
da4c94e47e | |
|
|
0d999396a5 | |
|
|
8e39823698 | |
|
|
457ebaed94 | |
|
|
32e4a6fa24 | |
|
|
2736468072 | |
|
|
c76ae09bc9 | |
|
|
12d6380d07 | |
|
|
77c9f6f806 | |
|
|
964d444ff6 | |
|
|
9456969502 | |
|
|
0750bd980e | |
|
|
056351af4b | |
|
|
dab02d34c0 | |
|
|
96591ef52c | |
|
|
1defbf2a47 | |
|
|
b58552279e | |
|
|
ef7f00dab7 | |
|
|
d70dce231f | |
|
|
5baac6732e | |
|
|
1d8b218056 | |
|
|
ae78bd6f64 | |
|
|
2b3029333e | |
|
|
f71fa70302 | |
|
|
6438b952cc | |
|
|
a7d7033ef0 | |
|
|
3f3124ae37 | |
|
|
0da0a44c4a | |
|
|
88dc3324f6 | |
|
|
d0b094292f | |
|
|
15f39a1929 | |
|
|
f5379a1051 | |
|
|
21bcb6f268 | |
|
|
f7cb63caaa | |
|
|
c83516694f | |
|
|
d9f37de98c | |
|
|
b1c2d82029 | |
|
|
68e21c3988 | |
|
|
a8408ebb80 | |
|
|
714d1bf03f | |
|
|
444b342748 | |
|
|
7b3c3d210c | |
|
|
f58cda2863 |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -35,7 +35,7 @@ Issues labelled `good first contribution`.
|
||||||
|
|
||||||
For your contribution to be accepted:
|
For your contribution to be accepted:
|
||||||
|
|
||||||
- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/PanModal).
|
- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackHQ/PanModal).
|
||||||
- [x] The test suite must be complete and pass.
|
- [x] The test suite must be complete and pass.
|
||||||
- [x] The changes must be approved by code review.
|
- [x] The changes must be approved by code review.
|
||||||
- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.
|
- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// swift-tools-version:5.1
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "PanModal",
|
||||||
|
platforms: [.iOS(.v10)],
|
||||||
|
products: [
|
||||||
|
.library(
|
||||||
|
name: "PanModal",
|
||||||
|
targets: ["PanModal"]),
|
||||||
|
],
|
||||||
|
dependencies: [],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "PanModal",
|
||||||
|
dependencies: [],
|
||||||
|
path: "PanModal")
|
||||||
|
],
|
||||||
|
swiftLanguageVersions: [.version("5.0")]
|
||||||
|
)
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'PanModal'
|
s.name = 'PanModal'
|
||||||
s.version = '1.0'
|
s.version = '1.3.1'
|
||||||
s.summary = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
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.
|
# This description is used to generate tags and improve search results.
|
||||||
|
|
@ -17,17 +17,13 @@ Pod::Spec.new do |s|
|
||||||
# * Write the description between the DESC delimiters below.
|
# * Write the description between the DESC delimiters below.
|
||||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||||
|
|
||||||
s.description = <<-DESC
|
s.description = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
||||||
PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.
|
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/PanModal'
|
||||||
DESC
|
|
||||||
|
|
||||||
s.homepage = 'https://github.com/slackhq/PanModal'
|
|
||||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||||
s.author = { 'slack' => 'opensource@slack.com' }
|
s.author = { 'slack' => 'opensource@slack.com' }
|
||||||
s.source = { :git => 'https://github.com/slackhq/PanModal.git', :tag => s.version.to_s }
|
s.source = { :git => 'https://git.svc.touchin.ru/TouchInstinct/PanModal.git', :tag => s.version.to_s }
|
||||||
s.social_media_url = 'https://twitter.com/slackhq
|
s.social_media_url = 'https://twitter.com/slackhq'
|
||||||
|
|
||||||
s.ios.deployment_target = '10.0'
|
s.ios.deployment_target = '10.0'
|
||||||
|
s.swift_version = '5.0'
|
||||||
s.source_files = 'PanModal/**/*'
|
s.source_files = 'PanModal/**/*.{swift,h,m}'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -16,19 +17,24 @@ struct PanModalAnimator {
|
||||||
Constant Animation Properties
|
Constant Animation Properties
|
||||||
*/
|
*/
|
||||||
struct Constants {
|
struct Constants {
|
||||||
static let transitionDuration: TimeInterval = 0.5
|
static let defaultTransitionDuration: TimeInterval = 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
static func animate(_ animations: @escaping PanModalPresentable.AnimationBlockType,
|
static func animate(_ animations: @escaping PanModalPresentable.AnimationBlockType,
|
||||||
config: PanModalPresentable?,
|
config: PanModalPresentable?,
|
||||||
_ completion: PanModalPresentable.AnimationCompletionType? = nil) {
|
_ completion: PanModalPresentable.AnimationCompletionType? = nil) {
|
||||||
|
|
||||||
UIView.animate(withDuration: Constants.transitionDuration,
|
let transitionDuration = config?.transitionDuration ?? Constants.defaultTransitionDuration
|
||||||
|
let springDamping = config?.springDamping ?? 1.0
|
||||||
|
let animationOptions = config?.transitionAnimationOptions ?? []
|
||||||
|
|
||||||
|
UIView.animate(withDuration: transitionDuration,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
usingSpringWithDamping: config?.springDamping ?? 1.0,
|
usingSpringWithDamping: springDamping,
|
||||||
initialSpringVelocity: 0,
|
initialSpringVelocity: 0,
|
||||||
options: [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState],
|
options: animationOptions,
|
||||||
animations: animations,
|
animations: animations,
|
||||||
completion: completion)
|
completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -63,10 +64,15 @@ public class PanModalPresentationAnimator: NSObject {
|
||||||
*/
|
*/
|
||||||
private func animatePresentation(transitionContext: UIViewControllerContextTransitioning) {
|
private func animatePresentation(transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
|
||||||
guard let toVC = transitionContext.viewController(forKey: .to)
|
guard
|
||||||
|
let toVC = transitionContext.viewController(forKey: .to),
|
||||||
|
let fromVC = transitionContext.viewController(forKey: .from)
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
let presentable = toVC as? PanModalPresentable.LayoutType
|
let presentable = panModalLayoutType(from: transitionContext)
|
||||||
|
|
||||||
|
// Calls viewWillAppear and viewWillDisappear
|
||||||
|
fromVC.beginAppearanceTransition(false, animated: true)
|
||||||
|
|
||||||
// Presents the view in shortForm position, initially
|
// Presents the view in shortForm position, initially
|
||||||
let yPos: CGFloat = presentable?.shortFormYPos ?? 0.0
|
let yPos: CGFloat = presentable?.shortFormYPos ?? 0.0
|
||||||
|
|
@ -86,6 +92,8 @@ public class PanModalPresentationAnimator: NSObject {
|
||||||
PanModalAnimator.animate({
|
PanModalAnimator.animate({
|
||||||
panView.frame.origin.y = yPos
|
panView.frame.origin.y = yPos
|
||||||
}, config: presentable) { [weak self] didComplete in
|
}, config: presentable) { [weak self] didComplete in
|
||||||
|
// Calls viewDidAppear and viewDidDisappear
|
||||||
|
fromVC.endAppearanceTransition()
|
||||||
transitionContext.completeTransition(didComplete)
|
transitionContext.completeTransition(didComplete)
|
||||||
self?.feedbackGenerator = nil
|
self?.feedbackGenerator = nil
|
||||||
}
|
}
|
||||||
|
|
@ -96,20 +104,39 @@ public class PanModalPresentationAnimator: NSObject {
|
||||||
*/
|
*/
|
||||||
private func animateDismissal(transitionContext: UIViewControllerContextTransitioning) {
|
private func animateDismissal(transitionContext: UIViewControllerContextTransitioning) {
|
||||||
|
|
||||||
guard let fromVC = transitionContext.viewController(forKey: .from)
|
guard
|
||||||
|
let toVC = transitionContext.viewController(forKey: .to),
|
||||||
|
let fromVC = transitionContext.viewController(forKey: .from)
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
let presentable = fromVC as? PanModalPresentable.LayoutType
|
// Calls viewWillAppear and viewWillDisappear
|
||||||
|
toVC.beginAppearanceTransition(true, animated: true)
|
||||||
|
|
||||||
|
let presentable = panModalLayoutType(from: transitionContext)
|
||||||
let panView: UIView = transitionContext.containerView.panContainerView ?? fromVC.view
|
let panView: UIView = transitionContext.containerView.panContainerView ?? fromVC.view
|
||||||
|
|
||||||
PanModalAnimator.animate({
|
PanModalAnimator.animate({
|
||||||
panView.frame.origin.y = transitionContext.containerView.frame.height
|
panView.frame.origin.y = transitionContext.containerView.frame.height
|
||||||
}, config: presentable) { didComplete in
|
}, config: presentable) { didComplete in
|
||||||
fromVC.view.removeFromSuperview()
|
fromVC.view.removeFromSuperview()
|
||||||
|
// Calls viewDidAppear and viewDidDisappear
|
||||||
|
toVC.endAppearanceTransition()
|
||||||
transitionContext.completeTransition(didComplete)
|
transitionContext.completeTransition(didComplete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extracts the PanModal from the transition context, if it exists
|
||||||
|
*/
|
||||||
|
private func panModalLayoutType(from context: UIViewControllerContextTransitioning) -> PanModalPresentable.LayoutType? {
|
||||||
|
switch transitionStyle {
|
||||||
|
case .presentation:
|
||||||
|
return context.viewController(forKey: .to) as? PanModalPresentable.LayoutType
|
||||||
|
case .dismissal:
|
||||||
|
return context.viewController(forKey: .from) as? PanModalPresentable.LayoutType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UIViewControllerAnimatedTransitioning Delegate
|
// MARK: - UIViewControllerAnimatedTransitioning Delegate
|
||||||
|
|
@ -120,11 +147,17 @@ extension PanModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
|
||||||
Returns the transition duration
|
Returns the transition duration
|
||||||
*/
|
*/
|
||||||
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
||||||
return PanModalAnimator.Constants.transitionDuration
|
|
||||||
|
guard
|
||||||
|
let context = transitionContext,
|
||||||
|
let presentable = panModalLayoutType(from: context)
|
||||||
|
else { return PanModalAnimator.Constants.defaultTransitionDuration }
|
||||||
|
|
||||||
|
return presentable.transitionDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Perfroms the appropriate animation based on the transition style
|
Performs the appropriate animation based on the transition style
|
||||||
*/
|
*/
|
||||||
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
||||||
switch transitionStyle {
|
switch transitionStyle {
|
||||||
|
|
@ -136,3 +169,4 @@ extension PanModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,13 +23,14 @@ import UIKit
|
||||||
By conforming to the PanModalPresentable protocol & overriding values
|
By conforming to the PanModalPresentable protocol & overriding values
|
||||||
the presented view can define its layout configuration & presentation.
|
the presented view can define its layout configuration & presentation.
|
||||||
*/
|
*/
|
||||||
public class PanModalPresentationController: UIPresentationController {
|
open class PanModalPresentationController: UIPresentationController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Enum representing the possible presentation states
|
Enum representing the possible presentation states
|
||||||
*/
|
*/
|
||||||
public enum PresentationState {
|
public enum PresentationState {
|
||||||
case shortForm
|
case shortForm
|
||||||
|
case mediumForm
|
||||||
case longForm
|
case longForm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +38,6 @@ public class PanModalPresentationController: UIPresentationController {
|
||||||
Constants
|
Constants
|
||||||
*/
|
*/
|
||||||
struct Constants {
|
struct Constants {
|
||||||
static let cornerRadius = CGFloat(8.0)
|
|
||||||
static let indicatorYOffset = CGFloat(8.0)
|
static let indicatorYOffset = CGFloat(8.0)
|
||||||
static let snapMovementSensitivity = CGFloat(0.7)
|
static let snapMovementSensitivity = CGFloat(0.7)
|
||||||
static let dragIndicatorSize = CGSize(width: 36.0, height: 5.0)
|
static let dragIndicatorSize = CGSize(width: 36.0, height: 5.0)
|
||||||
|
|
@ -79,11 +80,17 @@ public class PanModalPresentationController: UIPresentationController {
|
||||||
*/
|
*/
|
||||||
private var shortFormYPosition: CGFloat = 0
|
private var shortFormYPosition: CGFloat = 0
|
||||||
|
|
||||||
|
private var mediumFormYPosition: CGFloat = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The y value for the long form presentation state
|
The y value for the long form presentation state
|
||||||
*/
|
*/
|
||||||
private var longFormYPosition: CGFloat = 0
|
private var longFormYPosition: CGFloat = 0
|
||||||
|
|
||||||
|
private var allowsDragToDismiss: Bool {
|
||||||
|
presentable?.onDragToDismiss != nil
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Determine anchored Y postion based on the `anchorModalToLongForm` flag
|
Determine anchored Y postion based on the `anchorModalToLongForm` flag
|
||||||
*/
|
*/
|
||||||
|
|
@ -105,14 +112,14 @@ public class PanModalPresentationController: UIPresentationController {
|
||||||
Background view used as an overlay over the presenting view
|
Background view used as an overlay over the presenting view
|
||||||
*/
|
*/
|
||||||
private lazy var backgroundView: DimmedView = {
|
private lazy var backgroundView: DimmedView = {
|
||||||
let view: DimmedView
|
let view: DimmedView = presentable?.dimmedView ?? DimmedView()
|
||||||
if let alpha = presentable?.backgroundAlpha {
|
if let color = presentable?.panModalBackgroundColor {
|
||||||
view = DimmedView(dimAlpha: alpha)
|
view.backgroundColor = color
|
||||||
} else {
|
|
||||||
view = DimmedView()
|
|
||||||
}
|
}
|
||||||
view.didTap = { [weak self] _ in
|
view.didTap = { [weak self] _ in
|
||||||
self?.dismissPresentedViewController()
|
if self?.presentable?.allowsTapToDismiss == true {
|
||||||
|
self?.presentable?.onTapToDismiss?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
@ -132,7 +139,7 @@ public class PanModalPresentationController: UIPresentationController {
|
||||||
*/
|
*/
|
||||||
private lazy var dragIndicatorView: UIView = {
|
private lazy var dragIndicatorView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .lightGray
|
view.backgroundColor = presentable?.dragIndicatorBackgroundColor
|
||||||
view.layer.cornerRadius = Constants.dragIndicatorSize.height / 2.0
|
view.layer.cornerRadius = Constants.dragIndicatorSize.height / 2.0
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
@ -185,24 +192,10 @@ public class PanModalPresentationController: UIPresentationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
coordinator.animate(alongsideTransition: { [weak self] _ in
|
coordinator.animate(alongsideTransition: { [weak self] _ in
|
||||||
self?.backgroundView.dimState = .max
|
if let yPos = self?.shortFormYPosition {
|
||||||
})
|
self?.adjust(toYPosition: yPos)
|
||||||
}
|
}
|
||||||
|
self?.presentedViewController.setNeedsStatusBarAppearanceUpdate()
|
||||||
override public func dismissalTransitionWillBegin() {
|
|
||||||
|
|
||||||
guard let coordinator = presentedViewController.transitionCoordinator else {
|
|
||||||
backgroundView.dimState = .off
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Drag indicator is drawn outside of view bounds
|
|
||||||
so hiding it on view dismiss means avoids visual bugs
|
|
||||||
*/
|
|
||||||
coordinator.animate(alongsideTransition: { [weak self] _ in
|
|
||||||
self?.dragIndicatorView.alpha = 0.0
|
|
||||||
self?.backgroundView.dimState = .off
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,6 +205,50 @@ public class PanModalPresentationController: UIPresentationController {
|
||||||
backgroundView.removeFromSuperview()
|
backgroundView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func dismissalTransitionWillBegin() {
|
||||||
|
presentable?.panModalWillDismiss()
|
||||||
|
|
||||||
|
guard let coordinator = presentedViewController.transitionCoordinator else {
|
||||||
|
backgroundView.dimState = .off
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Drag indicator is drawn outside of view bounds
|
||||||
|
so hiding it on view dismiss means avoiding visual bugs
|
||||||
|
*/
|
||||||
|
coordinator.animate(alongsideTransition: { [weak self] _ in
|
||||||
|
self?.dragIndicatorView.alpha = 0.0
|
||||||
|
self?.backgroundView.dimState = .off
|
||||||
|
self?.presentingViewController.setNeedsStatusBarAppearanceUpdate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func dismissalTransitionDidEnd(_ completed: Bool) {
|
||||||
|
if !completed { return }
|
||||||
|
|
||||||
|
presentable?.panModalDidDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Update presented view size in response to size class changes
|
||||||
|
*/
|
||||||
|
override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
|
super.viewWillTransition(to: size, with: coordinator)
|
||||||
|
|
||||||
|
coordinator.animate(alongsideTransition: { [weak self] _ in
|
||||||
|
guard
|
||||||
|
let self = self,
|
||||||
|
let presentable = self.presentable
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.adjustPresentedViewFrame()
|
||||||
|
if presentable.shouldRoundTopCorners {
|
||||||
|
self.addRoundedCorners(to: self.presentedView)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public Methods
|
// MARK: - Public Methods
|
||||||
|
|
@ -219,10 +256,10 @@ public class PanModalPresentationController: UIPresentationController {
|
||||||
public extension PanModalPresentationController {
|
public extension PanModalPresentationController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Tranisition the PanModalPresentationController
|
Transition the PanModalPresentationController
|
||||||
to the given presentation state
|
to the given presentation state
|
||||||
*/
|
*/
|
||||||
public func transition(to state: PresentationState) {
|
func transition(to state: PresentationState) {
|
||||||
|
|
||||||
guard presentable?.shouldTransition(to: state) == true
|
guard presentable?.shouldTransition(to: state) == true
|
||||||
else { return }
|
else { return }
|
||||||
|
|
@ -232,52 +269,48 @@ public extension PanModalPresentationController {
|
||||||
switch state {
|
switch state {
|
||||||
case .shortForm:
|
case .shortForm:
|
||||||
snap(toYPosition: shortFormYPosition)
|
snap(toYPosition: shortFormYPosition)
|
||||||
|
|
||||||
|
case .mediumForm:
|
||||||
|
snap(toYPosition: mediumFormYPosition)
|
||||||
|
|
||||||
case .longForm:
|
case .longForm:
|
||||||
snap(toYPosition: longFormYPosition)
|
snap(toYPosition: longFormYPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
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
|
To avoid this, you can call this method to perform scroll view updates,
|
||||||
set the content offset directly on the scroll view while in the short form.
|
with scroll observation temporarily disabled.
|
||||||
|
|
||||||
This method pauses the content offset KVO, performs the content offset chnage
|
|
||||||
and then resumes content offset observation.
|
|
||||||
*/
|
*/
|
||||||
public func setContentOffset(offset: CGPoint) {
|
func performUpdates(_ updates: () -> Void) {
|
||||||
|
|
||||||
guard let scrollView = presentable?.panScrollable
|
guard let scrollView = presentable?.panScrollable
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
/**
|
// Pause scroll observer
|
||||||
Invalidate scroll view observer
|
|
||||||
to prevent its overriding the content offset change
|
|
||||||
*/
|
|
||||||
scrollObserver?.invalidate()
|
scrollObserver?.invalidate()
|
||||||
scrollObserver = nil
|
scrollObserver = nil
|
||||||
|
|
||||||
/**
|
// Perform updates
|
||||||
Set scroll view offset & track scrolling
|
updates()
|
||||||
*/
|
|
||||||
scrollView.setContentOffset(offset, animated:false)
|
|
||||||
trackScrolling(scrollView)
|
|
||||||
|
|
||||||
/**
|
// Resume scroll observer
|
||||||
Add the scroll view observer
|
trackScrolling(scrollView)
|
||||||
*/
|
|
||||||
observe(scrollView: scrollView)
|
observe(scrollView: scrollView)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Updates the PanModalPresentationController layout
|
Updates the PanModalPresentationController layout
|
||||||
based on values in the PanModalPresentabls
|
based on values in the PanModalPresentable
|
||||||
|
|
||||||
- Note: This should be called whenever any
|
- Note: This should be called whenever any
|
||||||
pan modal presentable value changes after the initial presentation
|
pan modal presentable value changes after the initial presentation
|
||||||
*/
|
*/
|
||||||
public func setNeedsLayoutUpdate() {
|
func setNeedsLayoutUpdate() {
|
||||||
configureViewLayout()
|
configureViewLayout()
|
||||||
adjustPresentedViewFrame()
|
adjustPresentedViewFrame()
|
||||||
observe(scrollView: presentable?.panScrollable)
|
observe(scrollView: presentable?.panScrollable)
|
||||||
|
|
@ -296,7 +329,7 @@ private extension PanModalPresentationController {
|
||||||
var isPresentedViewAnchored: Bool {
|
var isPresentedViewAnchored: Bool {
|
||||||
if !isPresentedViewAnimating
|
if !isPresentedViewAnimating
|
||||||
&& extendsPanScrolling
|
&& extendsPanScrolling
|
||||||
&& presentedView.frame.minY <= anchoredYPosition {
|
&& presentedView.frame.minY.rounded() <= anchoredYPosition.rounded() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,9 +374,24 @@ private extension PanModalPresentationController {
|
||||||
Reduce height of presentedView so that it sits at the bottom of the screen
|
Reduce height of presentedView so that it sits at the bottom of the screen
|
||||||
*/
|
*/
|
||||||
func adjustPresentedViewFrame() {
|
func adjustPresentedViewFrame() {
|
||||||
let frame = containerView?.frame ?? .zero
|
|
||||||
let size = CGSize(width: frame.size.width, height: frame.height - anchoredYPosition)
|
guard let frame = containerView?.frame
|
||||||
presentedViewController.view.frame = CGRect(origin: .zero, size: size)
|
else { return }
|
||||||
|
|
||||||
|
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
|
||||||
|
let panFrame = panContainerView.frame
|
||||||
|
panContainerView.frame.size = frame.size
|
||||||
|
|
||||||
|
let positions = [shortFormYPosition, mediumFormYPosition, longFormYPosition]
|
||||||
|
|
||||||
|
if !positions.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)
|
||||||
|
let yPosition = panFrame.origin.y - panFrame.height + frame.height
|
||||||
|
presentedView.frame.origin.y = max(yPosition, anchoredYPosition)
|
||||||
|
}
|
||||||
|
panContainerView.frame.origin.x = frame.origin.x
|
||||||
|
presentedViewController.view.frame = CGRect(origin: .zero, size: adjustedSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -383,7 +431,7 @@ private extension PanModalPresentationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Caluclates & stores the layout anchor points & options
|
Calculates & stores the layout anchor points & options
|
||||||
*/
|
*/
|
||||||
func configureViewLayout() {
|
func configureViewLayout() {
|
||||||
|
|
||||||
|
|
@ -391,6 +439,7 @@ private extension PanModalPresentationController {
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
shortFormYPosition = layoutPresentable.shortFormYPos
|
shortFormYPosition = layoutPresentable.shortFormYPos
|
||||||
|
mediumFormYPosition = layoutPresentable.mediumFormYPos
|
||||||
longFormYPosition = layoutPresentable.longFormYPos
|
longFormYPosition = layoutPresentable.longFormYPos
|
||||||
anchorModalToLongForm = layoutPresentable.anchorModalToLongForm
|
anchorModalToLongForm = layoutPresentable.anchorModalToLongForm
|
||||||
extendsPanScrolling = layoutPresentable.allowsExtendedPanScrolling
|
extendsPanScrolling = layoutPresentable.allowsExtendedPanScrolling
|
||||||
|
|
@ -413,14 +462,21 @@ private extension PanModalPresentationController {
|
||||||
to avoid visual bugs
|
to avoid visual bugs
|
||||||
*/
|
*/
|
||||||
scrollView.showsVerticalScrollIndicator = false
|
scrollView.showsVerticalScrollIndicator = false
|
||||||
scrollView.isScrollEnabled = presentable?.isPanScrollEnabled ?? true
|
|
||||||
scrollView.scrollIndicatorInsets = presentable?.scrollIndicatorInsets ?? .zero
|
scrollView.scrollIndicatorInsets = presentable?.scrollIndicatorInsets ?? .zero
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Set the appropriate contentInset as the configuration within this class
|
Set the appropriate contentInset as the configuration within this class
|
||||||
offsets it
|
offsets it
|
||||||
*/
|
*/
|
||||||
scrollView.contentInset.bottom = presentingViewController.bottomLayoutGuide.length
|
let bottomInset: CGFloat
|
||||||
|
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
bottomInset = presentingViewController.view.safeAreaInsets.bottom
|
||||||
|
} else {
|
||||||
|
bottomInset = presentingViewController.bottomLayoutGuide.length
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollView.contentInset.bottom = bottomInset
|
||||||
|
|
||||||
/**
|
/**
|
||||||
As we adjust the bounds during `handleScrollViewTopBounce`
|
As we adjust the bounds during `handleScrollViewTopBounce`
|
||||||
|
|
@ -443,8 +499,7 @@ private extension PanModalPresentationController {
|
||||||
@objc func didPanOnPresentedView(_ recognizer: UIPanGestureRecognizer) {
|
@objc func didPanOnPresentedView(_ recognizer: UIPanGestureRecognizer) {
|
||||||
|
|
||||||
guard
|
guard
|
||||||
presentable?.isPanScrollEnabled == true,
|
shouldRespond(to: recognizer),
|
||||||
!shouldFail(panGestureRecognizer: recognizer),
|
|
||||||
let containerView = containerView
|
let containerView = containerView
|
||||||
else {
|
else {
|
||||||
recognizer.setTranslation(.zero, in: recognizer.view)
|
recognizer.setTranslation(.zero, in: recognizer.view)
|
||||||
|
|
@ -485,12 +540,18 @@ private extension PanModalPresentationController {
|
||||||
if velocity.y < 0 {
|
if velocity.y < 0 {
|
||||||
transition(to: .longForm)
|
transition(to: .longForm)
|
||||||
|
|
||||||
} else if (nearestDistance(to: presentedView.frame.minY, inDistances: [longFormYPosition, containerView.bounds.height]) == longFormYPosition
|
} else if nearest(to: presentedView.frame.minY,
|
||||||
&& presentedView.frame.minY < shortFormYPosition) || presentable?.allowsDragToDismiss == false {
|
inValues: [mediumFormYPosition, containerView.bounds.height]) == mediumFormYPosition
|
||||||
|
&& presentedView.frame.minY < mediumFormYPosition {
|
||||||
|
transition(to: .mediumForm)
|
||||||
|
|
||||||
|
} else if (nearest(to: presentedView.frame.minY,
|
||||||
|
inValues: [longFormYPosition, containerView.bounds.height]) == longFormYPosition
|
||||||
|
&& presentedView.frame.minY < shortFormYPosition) || allowsDragToDismiss == false {
|
||||||
transition(to: .shortForm)
|
transition(to: .shortForm)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
dismissPresentedViewController()
|
presentable?.onDragToDismiss?()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -499,21 +560,48 @@ private extension PanModalPresentationController {
|
||||||
The `containerView.bounds.height` is used to determine
|
The `containerView.bounds.height` is used to determine
|
||||||
how close the presented view is to the bottom of the screen
|
how close the presented view is to the bottom of the screen
|
||||||
*/
|
*/
|
||||||
let position = nearestDistance(to: presentedView.frame.minY, inDistances: [containerView.bounds.height, shortFormYPosition, longFormYPosition])
|
let position = nearest(to: presentedView.frame.minY,
|
||||||
|
inValues: [containerView.bounds.height,
|
||||||
|
shortFormYPosition,
|
||||||
|
mediumFormYPosition,
|
||||||
|
longFormYPosition])
|
||||||
|
|
||||||
if position == longFormYPosition {
|
if position == longFormYPosition {
|
||||||
transition(to: .longForm)
|
transition(to: .longForm)
|
||||||
|
|
||||||
} else if position == shortFormYPosition || presentable?.allowsDragToDismiss == false {
|
} else if position == mediumFormYPosition {
|
||||||
|
transition(to: .mediumForm)
|
||||||
|
|
||||||
|
} else if position == shortFormYPosition || allowsDragToDismiss == false {
|
||||||
transition(to: .shortForm)
|
transition(to: .shortForm)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
dismissPresentedViewController()
|
presentable?.onDragToDismiss?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Determine if the pan modal should respond to the gesture recognizer.
|
||||||
|
|
||||||
|
If the pan modal is already being dragged & the delegate returns false, ignore until
|
||||||
|
the recognizer is back to it's original state (.began)
|
||||||
|
|
||||||
|
⚠️ This is the only time we should be cancelling the pan modal gesture recognizer
|
||||||
|
*/
|
||||||
|
func shouldRespond(to panGestureRecognizer: UIPanGestureRecognizer) -> Bool {
|
||||||
|
guard
|
||||||
|
presentable?.shouldRespond(to: panGestureRecognizer) == true ||
|
||||||
|
!(panGestureRecognizer.state == .began || panGestureRecognizer.state == .cancelled)
|
||||||
|
else {
|
||||||
|
panGestureRecognizer.isEnabled = false
|
||||||
|
panGestureRecognizer.isEnabled = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !shouldFail(panGestureRecognizer: panGestureRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Communicate intentions to presentable and adjust subviews in containerView
|
Communicate intentions to presentable and adjust subviews in containerView
|
||||||
*/
|
*/
|
||||||
|
|
@ -549,7 +637,7 @@ private extension PanModalPresentationController {
|
||||||
Allow api consumers to override the internal conditions &
|
Allow api consumers to override the internal conditions &
|
||||||
decide if the pan gesture recognizer should be prioritized.
|
decide if the pan gesture recognizer should be prioritized.
|
||||||
|
|
||||||
⚠️ This is the only time we should be cancelling a recognizer,
|
⚠️ This is the only time we should be cancelling the panScrollable recognizer,
|
||||||
for the purpose of ensuring we're no longer tracking the scrollView
|
for the purpose of ensuring we're no longer tracking the scrollView
|
||||||
*/
|
*/
|
||||||
guard !shouldPrioritize(panGestureRecognizer: panGestureRecognizer) else {
|
guard !shouldPrioritize(panGestureRecognizer: panGestureRecognizer) else {
|
||||||
|
|
@ -576,7 +664,7 @@ private extension PanModalPresentationController {
|
||||||
*/
|
*/
|
||||||
func shouldPrioritize(panGestureRecognizer: UIPanGestureRecognizer) -> Bool {
|
func shouldPrioritize(panGestureRecognizer: UIPanGestureRecognizer) -> Bool {
|
||||||
return panGestureRecognizer.state == .began &&
|
return panGestureRecognizer.state == .began &&
|
||||||
presentable?.shouldPrioritize(panModalGestureRecognizer: panGestureRecognizer) ?? false
|
presentable?.shouldPrioritize(panModalGestureRecognizer: panGestureRecognizer) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -601,39 +689,22 @@ private extension PanModalPresentationController {
|
||||||
func adjust(toYPosition yPos: CGFloat) {
|
func adjust(toYPosition yPos: CGFloat) {
|
||||||
presentedView.frame.origin.y = max(yPos, anchoredYPosition)
|
presentedView.frame.origin.y = max(yPos, anchoredYPosition)
|
||||||
|
|
||||||
guard presentedView.frame.origin.y > shortFormYPosition else {
|
let maxHeight = UIScreen.main.bounds.height - longFormYPosition
|
||||||
backgroundView.dimState = .max
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let yDisplacementFromShortForm = presentedView.frame.origin.y - shortFormYPosition
|
backgroundView.dimState = .percent(1.0 - presentedView.frame.origin.y / maxHeight)
|
||||||
|
|
||||||
/**
|
|
||||||
Once presentedView is translated below shortForm, calculate yPos relative to bottom of screen
|
|
||||||
and apply percentage to backgroundView alpha
|
|
||||||
*/
|
|
||||||
backgroundView.dimState = .percent(1.0 - (yDisplacementFromShortForm / presentedView.frame.height))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Finds the nearest distance to a given position out of a given array of distance values
|
Finds the nearest value to a given number out of a given array of float values
|
||||||
|
|
||||||
- Parameters:
|
- Parameters:
|
||||||
- position: reference postion we are trying to find the closest distance to
|
- number: reference float we are trying to find the closest value to
|
||||||
- distances: array of positions we would like to compare against
|
- values: array of floats we would like to compare against
|
||||||
*/
|
*/
|
||||||
func nearestDistance(to position: CGFloat, inDistances distances: [CGFloat]) -> CGFloat {
|
func nearest(to number: CGFloat, inValues values: [CGFloat]) -> CGFloat {
|
||||||
guard let nearestDistance = distances.min(by: { abs(position - $0) < abs(position - $1) })
|
guard let nearestVal = values.min(by: { abs(number - $0) < abs(number - $1) })
|
||||||
else { return position }
|
else { return number }
|
||||||
return nearestDistance
|
return nearestVal
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Dismiss presented view
|
|
||||||
*/
|
|
||||||
func dismissPresentedViewController() {
|
|
||||||
presentable?.panModalWillDismiss()
|
|
||||||
presentedViewController.dismiss(animated: true, completion: nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -739,7 +810,7 @@ private extension PanModalPresentationController {
|
||||||
*/
|
*/
|
||||||
func handleScrollViewTopBounce(scrollView: UIScrollView, change: NSKeyValueObservedChange<CGPoint>) {
|
func handleScrollViewTopBounce(scrollView: UIScrollView, change: NSKeyValueObservedChange<CGPoint>) {
|
||||||
|
|
||||||
guard let oldYValue = change.oldValue?.y
|
guard let oldYValue = change.oldValue?.y, scrollView.isDecelerating
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
let yOffset = scrollView.contentOffset.y
|
let yOffset = scrollView.contentOffset.y
|
||||||
|
|
@ -779,11 +850,11 @@ extension PanModalPresentationController: UIGestureRecognizerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Allow simultaneous gesture recognizers only when the other gesture recognizer
|
Allow simultaneous gesture recognizers only when the other gesture recognizer's view
|
||||||
is a pan gesture recognizer
|
is the pan scrollable view
|
||||||
*/
|
*/
|
||||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
|
return otherGestureRecognizer.view == presentable?.panScrollable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -797,37 +868,23 @@ private extension PanModalPresentationController {
|
||||||
because we render the dragIndicator outside of view bounds
|
because we render the dragIndicator outside of view bounds
|
||||||
*/
|
*/
|
||||||
func addRoundedCorners(to view: UIView) {
|
func addRoundedCorners(to view: UIView) {
|
||||||
|
let radius = presentable?.cornerRadius ?? 0
|
||||||
|
let path = UIBezierPath(roundedRect: view.bounds,
|
||||||
|
byRoundingCorners: [.topLeft, .topRight],
|
||||||
|
cornerRadii: CGSize(width: radius, height: radius))
|
||||||
|
|
||||||
let path = UIBezierPath()
|
// Draw around the drag indicator view, if displayed
|
||||||
path.move(to: CGPoint(x: 0, y: Constants.cornerRadius))
|
|
||||||
|
|
||||||
// 1. Draw left rounded corner
|
|
||||||
path.addArc(withCenter: CGPoint(x: path.currentPoint.x + Constants.cornerRadius, y: path.currentPoint.y),
|
|
||||||
radius: Constants.cornerRadius, startAngle: .pi, endAngle: 3.0 * .pi/2.0, clockwise: true)
|
|
||||||
|
|
||||||
// 2. Draw around the drag indicator view, if displayed
|
|
||||||
if presentable?.showDragIndicator == true {
|
if presentable?.showDragIndicator == true {
|
||||||
let indicatorLeftEdgeXPos = view.bounds.width/2.0 - Constants.dragIndicatorSize.width/2.0
|
let indicatorLeftEdgeXPos = view.bounds.width/2.0 - Constants.dragIndicatorSize.width/2.0
|
||||||
drawAroundDragIndicator(currentPath: path, indicatorLeftEdgeXPos: indicatorLeftEdgeXPos)
|
drawAroundDragIndicator(currentPath: path, indicatorLeftEdgeXPos: indicatorLeftEdgeXPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Draw line to right side of presented view, leaving space to draw rounded corner
|
// Set path as a mask to display optional drag indicator view & rounded corners
|
||||||
path.addLine(to: CGPoint(x: view.bounds.width - Constants.cornerRadius, y: path.currentPoint.y))
|
|
||||||
|
|
||||||
// 4. Draw right rounded corner
|
|
||||||
path.addArc(withCenter: CGPoint(x: path.currentPoint.x, y: path.currentPoint.y + Constants.cornerRadius),
|
|
||||||
radius: Constants.cornerRadius, startAngle: 3.0 * .pi/2.0, endAngle: 0, clockwise: true)
|
|
||||||
|
|
||||||
// 5. Draw around final edges of view
|
|
||||||
path.addLine(to: CGPoint(x: path.currentPoint.x, y: view.bounds.height))
|
|
||||||
path.addLine(to: CGPoint(x: 0, y: path.currentPoint.y))
|
|
||||||
|
|
||||||
// 6. Set path as a mask to display optional drag indicator view & rounded corners
|
|
||||||
let mask = CAShapeLayer()
|
let mask = CAShapeLayer()
|
||||||
mask.path = path.cgPath
|
mask.path = path.cgPath
|
||||||
view.layer.mask = mask
|
view.layer.mask = mask
|
||||||
|
|
||||||
// 7. Improve performance by rasterizing the layer
|
// Improve performance by rasterizing the layer
|
||||||
view.layer.shouldRasterize = true
|
view.layer.shouldRasterize = true
|
||||||
view.layer.rasterizationScale = UIScreen.main.scale
|
view.layer.rasterizationScale = UIScreen.main.scale
|
||||||
}
|
}
|
||||||
|
|
@ -858,3 +915,4 @@ private extension UIScrollView {
|
||||||
return isDragging && !isDecelerating || isTracking
|
return isDragging && !isDecelerating || isTracking
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,8 +74,8 @@ extension PanModalPresentationDelegate: UIAdaptivePresentationControllerDelegate
|
||||||
Dismisses the presented view controller
|
Dismisses the presented view controller
|
||||||
*/
|
*/
|
||||||
public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||||
controller.presentedViewController.dismiss(animated: false, completion: nil)
|
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// PanModal.h
|
||||||
|
// PanModal
|
||||||
|
//
|
||||||
|
// Created by Tosin A on 3/13/19.
|
||||||
|
// Copyright © 2019 Detail. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
//! Project version number for PanModal.
|
||||||
|
FOUNDATION_EXPORT double PanModalVersionNumber;
|
||||||
|
|
||||||
|
//! Project version string for PanModal.
|
||||||
|
FOUNDATION_EXPORT const unsigned char PanModalVersionString[];
|
||||||
|
|
||||||
|
// In this header, you should import all the public headers of your framework using statements like #import <PanModal/PublicHeader.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Touch Instinct
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the Software), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct ModalViewPresentationDetent: Hashable {
|
||||||
|
|
||||||
|
// MARK: - Default Values
|
||||||
|
|
||||||
|
public static var headerOnly: ModalViewPresentationDetent {
|
||||||
|
ModalViewPresentationDetent(height: CGFloat(Int.min))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func height(_ height: CGFloat) -> ModalViewPresentationDetent {
|
||||||
|
ModalViewPresentationDetent(height: height)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var maxHeight: ModalViewPresentationDetent {
|
||||||
|
ModalViewPresentationDetent(height: CGFloat(Int.max))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public Properties
|
||||||
|
|
||||||
|
public var height: CGFloat
|
||||||
|
|
||||||
|
// MARK: - Internal Methods
|
||||||
|
|
||||||
|
func panModalHeight(headerHeight: CGFloat = .zero) -> PanModalHeight {
|
||||||
|
if self == .headerOnly {
|
||||||
|
return .contentHeight(headerHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self == .maxHeight {
|
||||||
|
return .maxHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
return .contentHeight(height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Comparable
|
||||||
|
|
||||||
|
extension ModalViewPresentationDetent: Comparable {
|
||||||
|
public static func < (lhs: ModalViewPresentationDetent, rhs: ModalViewPresentationDetent) -> Bool {
|
||||||
|
lhs.height < rhs.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
//
|
//
|
||||||
// PanModalHeight.swift
|
// PanModalHeight.swift
|
||||||
// SlackUI
|
// PanModal
|
||||||
//
|
//
|
||||||
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,4 +36,9 @@ public enum PanModalHeight: Equatable {
|
||||||
*/
|
*/
|
||||||
case contentHeightIgnoringSafeArea(CGFloat)
|
case contentHeightIgnoringSafeArea(CGFloat)
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sets the height to be the intrinsic content height
|
||||||
|
*/
|
||||||
|
case intrinsicHeight
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
// PanModalPresentable+Defaults.swift
|
// PanModalPresentable+Defaults.swift
|
||||||
// PanModal
|
// PanModal
|
||||||
//
|
//
|
||||||
// Created by Stephen Sowole on 11/5/18.
|
|
||||||
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -13,14 +13,30 @@ import UIKit
|
||||||
*/
|
*/
|
||||||
public extension PanModalPresentable where Self: UIViewController {
|
public extension PanModalPresentable where Self: UIViewController {
|
||||||
|
|
||||||
|
var onTapToDismiss: (() -> Void)? {
|
||||||
|
{ [weak self] in
|
||||||
|
self?.dismiss(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var onDragToDismiss: (() -> Void)? {
|
||||||
|
{ [weak self] in
|
||||||
|
self?.dismiss(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var topOffset: CGFloat {
|
var topOffset: CGFloat {
|
||||||
return topLayoutOffset + 21.0
|
topLayoutOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
var shortFormHeight: PanModalHeight {
|
var shortFormHeight: PanModalHeight {
|
||||||
return longFormHeight
|
return longFormHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediumFormHeight: PanModalHeight {
|
||||||
|
longFormHeight
|
||||||
|
}
|
||||||
|
|
||||||
var longFormHeight: PanModalHeight {
|
var longFormHeight: PanModalHeight {
|
||||||
|
|
||||||
guard let scrollView = panScrollable
|
guard let scrollView = panScrollable
|
||||||
|
|
@ -31,16 +47,36 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
return .contentHeight(scrollView.contentSize.height)
|
return .contentHeight(scrollView.contentSize.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dimmedView: DimmedView? {
|
||||||
|
DimmedView()
|
||||||
|
}
|
||||||
|
|
||||||
|
var cornerRadius: CGFloat {
|
||||||
|
return 8.0
|
||||||
|
}
|
||||||
|
|
||||||
var springDamping: CGFloat {
|
var springDamping: CGFloat {
|
||||||
return 0.8
|
return 0.8
|
||||||
}
|
}
|
||||||
|
|
||||||
var backgroundAlpha: CGFloat {
|
var transitionDuration: Double {
|
||||||
return 0.7
|
return PanModalAnimator.Constants.defaultTransitionDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
var transitionAnimationOptions: UIView.AnimationOptions {
|
||||||
|
return [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState]
|
||||||
|
}
|
||||||
|
|
||||||
|
var panModalBackgroundColor: UIColor {
|
||||||
|
return UIColor.black.withAlphaComponent(0.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dragIndicatorBackgroundColor: UIColor {
|
||||||
|
return UIColor.lightGray
|
||||||
}
|
}
|
||||||
|
|
||||||
var scrollIndicatorInsets: UIEdgeInsets {
|
var scrollIndicatorInsets: UIEdgeInsets {
|
||||||
let top = shouldRoundTopCorners ? PanModalPresentationController.Constants.cornerRadius : 0
|
let top = shouldRoundTopCorners ? cornerRadius : 0
|
||||||
return UIEdgeInsets(top: CGFloat(top), left: 0, bottom: bottomLayoutOffset, right: 0)
|
return UIEdgeInsets(top: CGFloat(top), left: 0, bottom: bottomLayoutOffset, right: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +97,7 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var isPanScrollEnabled: Bool {
|
var allowsTapToDismiss: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +117,11 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
return shouldRoundTopCorners
|
return shouldRoundTopCorners
|
||||||
}
|
}
|
||||||
|
|
||||||
func willRespond(to panGestureRecognizer: UIPanGestureRecognizer) {
|
func shouldRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,4 +141,8 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func panModalDidDismiss() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,7 +27,11 @@ extension PanModalPresentable where Self: UIViewController {
|
||||||
Gives us the safe area inset from the top.
|
Gives us the safe area inset from the top.
|
||||||
*/
|
*/
|
||||||
var topLayoutOffset: CGFloat {
|
var topLayoutOffset: CGFloat {
|
||||||
return UIApplication.shared.keyWindow?.rootViewController?.topLayoutGuide.length ?? 0
|
|
||||||
|
guard let rootVC = rootViewController
|
||||||
|
else { return 0}
|
||||||
|
|
||||||
|
if #available(iOS 11.0, *) { return rootVC.view.safeAreaInsets.top } else { return rootVC.topLayoutGuide.length }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,11 +39,15 @@ extension PanModalPresentable where Self: UIViewController {
|
||||||
Gives us the safe area inset from the bottom.
|
Gives us the safe area inset from the bottom.
|
||||||
*/
|
*/
|
||||||
var bottomLayoutOffset: CGFloat {
|
var bottomLayoutOffset: CGFloat {
|
||||||
return UIApplication.shared.keyWindow?.rootViewController?.bottomLayoutGuide.length ?? 0
|
|
||||||
|
guard let rootVC = rootViewController
|
||||||
|
else { return 0}
|
||||||
|
|
||||||
|
if #available(iOS 11.0, *) { return rootVC.view.safeAreaInsets.bottom } else { return rootVC.bottomLayoutGuide.length }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns the short form Y postion
|
Returns the short form Y position
|
||||||
|
|
||||||
- Note: If voiceover is on, the `longFormYPos` is returned.
|
- Note: If voiceover is on, the `longFormYPos` is returned.
|
||||||
We do not support short form when voiceover is on as it would make it difficult for user to navigate.
|
We do not support short form when voiceover is on as it would make it difficult for user to navigate.
|
||||||
|
|
@ -54,8 +63,14 @@ extension PanModalPresentable where Self: UIViewController {
|
||||||
return max(shortFormYPos, longFormYPos)
|
return max(shortFormYPos, longFormYPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediumFormYPos: CGFloat {
|
||||||
|
let mediumFormYPos = topMargin(from: mediumFormHeight)
|
||||||
|
|
||||||
|
return max(mediumFormYPos, longFormYPos)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns the long form Y postion
|
Returns the long form Y position
|
||||||
|
|
||||||
- Note: We cap this value to the max possible height
|
- Note: We cap this value to the max possible height
|
||||||
to ensure content is not rendered outside of the view bounds
|
to ensure content is not rendered outside of the view bounds
|
||||||
|
|
@ -90,7 +105,22 @@ extension PanModalPresentable where Self: UIViewController {
|
||||||
return bottomYPos - (height + bottomLayoutOffset)
|
return bottomYPos - (height + bottomLayoutOffset)
|
||||||
case .contentHeightIgnoringSafeArea(let height):
|
case .contentHeightIgnoringSafeArea(let height):
|
||||||
return bottomYPos - height
|
return bottomYPos - height
|
||||||
|
case .intrinsicHeight:
|
||||||
|
view.layoutIfNeeded()
|
||||||
|
let targetSize = CGSize(width: (presentedVC?.containerView?.bounds ?? UIScreen.main.bounds).width,
|
||||||
|
height: UIView.layoutFittingCompressedSize.height)
|
||||||
|
let intrinsicHeight = view.systemLayoutSizeFitting(targetSize).height
|
||||||
|
return bottomYPos - (intrinsicHeight + bottomLayoutOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var rootViewController: UIViewController? {
|
||||||
|
|
||||||
|
guard let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
return application.keyWindow?.rootViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -29,16 +30,6 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
presentedVC?.transition(to: state)
|
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()`
|
A function wrapper over the `setNeedsLayoutUpdate()`
|
||||||
function in the PanModalPresentationController.
|
function in the PanModalPresentationController.
|
||||||
|
|
@ -49,6 +40,16 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
presentedVC?.setNeedsLayoutUpdate()
|
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.
|
A function wrapper over the animate function in PanModalAnimator.
|
||||||
|
|
||||||
|
|
@ -59,3 +60,4 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2017 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2017 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -13,12 +14,12 @@ import UIKit
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
```
|
```
|
||||||
extension UIViewController: PanModalPresentable {
|
extension YourViewController: PanModalPresentable {
|
||||||
func shouldRoundTopCorners: Bool { return false }
|
func shouldRoundTopCorners: Bool { return false }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public protocol PanModalPresentable {
|
public protocol PanModalPresentable: AnyObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The scroll view embedded in the view controller.
|
The scroll view embedded in the view controller.
|
||||||
|
|
@ -44,6 +45,8 @@ public protocol PanModalPresentable {
|
||||||
*/
|
*/
|
||||||
var shortFormHeight: PanModalHeight { get }
|
var shortFormHeight: PanModalHeight { get }
|
||||||
|
|
||||||
|
var mediumFormHeight: PanModalHeight { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The height of the pan modal container view
|
The height of the pan modal container view
|
||||||
when in the longForm presentation state.
|
when in the longForm presentation state.
|
||||||
|
|
@ -54,6 +57,14 @@ public protocol PanModalPresentable {
|
||||||
*/
|
*/
|
||||||
var longFormHeight: PanModalHeight { get }
|
var longFormHeight: PanModalHeight { get }
|
||||||
|
|
||||||
|
var dimmedView: DimmedView? { get }
|
||||||
|
/**
|
||||||
|
The corner radius used when `shouldRoundTopCorners` is enabled.
|
||||||
|
|
||||||
|
Default Value is 8.0.
|
||||||
|
*/
|
||||||
|
var cornerRadius: CGFloat { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The springDamping value used to determine the amount of 'bounce'
|
The springDamping value used to determine the amount of 'bounce'
|
||||||
seen when transitioning to short/long form.
|
seen when transitioning to short/long form.
|
||||||
|
|
@ -63,13 +74,36 @@ public protocol PanModalPresentable {
|
||||||
var springDamping: CGFloat { get }
|
var springDamping: CGFloat { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The background view alpha.
|
The transitionDuration value is used to set the speed of animation during a transition,
|
||||||
|
including initial presentation.
|
||||||
|
|
||||||
|
Default value is 0.5.
|
||||||
|
*/
|
||||||
|
var transitionDuration: Double { get }
|
||||||
|
|
||||||
|
/**
|
||||||
|
The animation options used when performing animations on the PanModal, utilized mostly
|
||||||
|
during a transition.
|
||||||
|
|
||||||
|
Default value is [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState].
|
||||||
|
*/
|
||||||
|
var transitionAnimationOptions: UIView.AnimationOptions { get }
|
||||||
|
|
||||||
|
/**
|
||||||
|
The background view color.
|
||||||
|
|
||||||
- Note: This is only utilized at the very start of the transition.
|
- Note: This is only utilized at the very start of the transition.
|
||||||
|
|
||||||
Default Value is 0.7.
|
Default Value is black with alpha component 0.7.
|
||||||
*/
|
*/
|
||||||
var backgroundAlpha: CGFloat { get }
|
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
|
We configure the panScrollable's scrollIndicatorInsets interally so override this value
|
||||||
|
|
@ -91,28 +125,29 @@ public protocol PanModalPresentable {
|
||||||
A flag to determine if scrolling should seamlessly transition from the pan modal container view to
|
A flag to determine if scrolling should seamlessly transition from the pan modal container view to
|
||||||
the embedded scroll view once the scroll limit has been reached.
|
the embedded scroll view once the scroll limit has been reached.
|
||||||
|
|
||||||
Default value is false.
|
Default value is false. Unless a scrollView is provided and the content height exceeds the longForm height.
|
||||||
Unless a scrollView is provided and the content exceeds the longForm height
|
|
||||||
*/
|
*/
|
||||||
var allowsExtendedPanScrolling: Bool { get }
|
var allowsExtendedPanScrolling: Bool { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A flag to determine if dismissal should be initiated when swiping down on the presented view.
|
A flag to determine if dismissal should be initiated when swiping down on the presented view.
|
||||||
|
|
||||||
Return false to fallback to the short form state instead of dismissing.
|
Return false to fallback to the short form state instead of dismissing.
|
||||||
|
|
||||||
Default value is true.
|
Default value is true.
|
||||||
*/
|
*/
|
||||||
var allowsDragToDismiss: Bool { get }
|
var allowsDragToDismiss: Bool { get }
|
||||||
|
|
||||||
/**
|
var onTapToDismiss: (() -> Void)? { get }
|
||||||
A flag to determine if scrolling should be enabled on the entire view.
|
|
||||||
|
|
||||||
- Note: Returning false will disable scrolling on the embedded scrollview as well as on the
|
var onDragToDismiss: (() -> Void)? { get }
|
||||||
pan modal container view.
|
|
||||||
|
/**
|
||||||
|
A flag to determine if dismissal should be initiated when tapping on the dimmed background view.
|
||||||
|
|
||||||
Default value is true.
|
Default value is true.
|
||||||
*/
|
*/
|
||||||
var isPanScrollEnabled: Bool { get }
|
var allowsTapToDismiss: Bool { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A flag to toggle user interactions on the container view.
|
A flag to toggle user interactions on the container view.
|
||||||
|
|
@ -145,6 +180,15 @@ public protocol PanModalPresentable {
|
||||||
*/
|
*/
|
||||||
var showDragIndicator: Bool { get }
|
var showDragIndicator: Bool { get }
|
||||||
|
|
||||||
|
/**
|
||||||
|
Asks the delegate if the pan modal should respond to the pan modal gesture recognizer.
|
||||||
|
|
||||||
|
Return false to disable movement on the pan modal but maintain gestures on the presented view.
|
||||||
|
|
||||||
|
Default value is true.
|
||||||
|
*/
|
||||||
|
func shouldRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Notifies the delegate when the pan modal gesture recognizer state is either
|
Notifies the delegate when the pan modal gesture recognizer state is either
|
||||||
`began` or `changed`. This method gives the delegate a chance to prepare
|
`began` or `changed`. This method gives the delegate a chance to prepare
|
||||||
|
|
@ -154,7 +198,7 @@ public protocol PanModalPresentable {
|
||||||
|
|
||||||
Default value is an empty implementation.
|
Default value is an empty implementation.
|
||||||
*/
|
*/
|
||||||
func willRespond(to panGestureRecognizer: UIPanGestureRecognizer)
|
func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Asks the delegate if the pan modal gesture recognizer should be prioritized.
|
Asks the delegate if the pan modal gesture recognizer should be prioritized.
|
||||||
|
|
@ -162,8 +206,8 @@ public protocol PanModalPresentable {
|
||||||
For example, you can use this to define a region
|
For example, you can use this to define a region
|
||||||
where you would like to restrict where the pan gesture can start.
|
where you would like to restrict where the pan gesture can start.
|
||||||
|
|
||||||
If false, then we rely on the internal conditions of when a pan gesture
|
If false, then we rely solely on the internal conditions of when a pan gesture
|
||||||
should succeed or fail, such as, if we're actively scrolling on the scrollView
|
should succeed or fail, such as, if we're actively scrolling on the scrollView.
|
||||||
|
|
||||||
Default return value is false.
|
Default return value is false.
|
||||||
*/
|
*/
|
||||||
|
|
@ -190,4 +234,11 @@ public protocol PanModalPresentable {
|
||||||
*/
|
*/
|
||||||
func panModalWillDismiss()
|
func panModalWillDismiss()
|
||||||
|
|
||||||
|
/**
|
||||||
|
Notifies the delegate after the pan modal is dismissed.
|
||||||
|
|
||||||
|
Default value is an empty implementation.
|
||||||
|
*/
|
||||||
|
func panModalDidDismiss()
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
//
|
//
|
||||||
// PanModalPresenter.swift
|
// PanModalPresenter.swift
|
||||||
// SlackUI
|
// PanModal
|
||||||
//
|
//
|
||||||
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -17,7 +18,7 @@ import UIKit
|
||||||
sourceRect: .zero)
|
sourceRect: .zero)
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public protocol PanModalPresenter {
|
protocol PanModalPresenter: AnyObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A flag that returns true if the current presented view controller
|
A flag that returns true if the current presented view controller
|
||||||
|
|
@ -28,6 +29,10 @@ public protocol PanModalPresenter {
|
||||||
/**
|
/**
|
||||||
Presents a view controller that conforms to the PanModalPresentable protocol
|
Presents a view controller that conforms to the PanModalPresentable protocol
|
||||||
*/
|
*/
|
||||||
func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType, sourceView: UIView?, sourceRect: CGRect)
|
func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType,
|
||||||
|
sourceView: UIView?,
|
||||||
|
sourceRect: CGRect,
|
||||||
|
completion: (() -> Void)?)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
//
|
//
|
||||||
// UIViewController+PanModalPresenterProtocol.swift
|
// UIViewController+PanModalPresenterProtocol.swift
|
||||||
// SlackUI
|
// PanModal
|
||||||
//
|
//
|
||||||
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2019 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,10 +35,14 @@ extension UIViewController: PanModalPresenter {
|
||||||
- viewControllerToPresent: The view controller to be presented
|
- viewControllerToPresent: The view controller to be presented
|
||||||
- sourceView: The view containing the anchor rectangle for the popover.
|
- sourceView: The view containing the anchor rectangle for the popover.
|
||||||
- sourceRect: The rectangle in the specified view in which to anchor the popover.
|
- sourceRect: The rectangle in the specified view in which to anchor the popover.
|
||||||
|
- completion: The block to execute after the presentation finishes. You may specify nil for this parameter.
|
||||||
|
|
||||||
- Note: sourceView & sourceRect are only required for presentation on an iPad.
|
- Note: sourceView & sourceRect are only required for presentation on an iPad.
|
||||||
*/
|
*/
|
||||||
public func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType, sourceView: UIView? = nil, sourceRect: CGRect = .zero) {
|
public func presentPanModal(_ viewControllerToPresent: PanModalPresentable.LayoutType,
|
||||||
|
sourceView: UIView? = nil,
|
||||||
|
sourceRect: CGRect = .zero,
|
||||||
|
completion: (() -> Void)? = nil) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Here, we deliberately do not check for size classes. More info in `PanModalPresentationDelegate`
|
Here, we deliberately do not check for size classes. More info in `PanModalPresentationDelegate`
|
||||||
|
|
@ -50,10 +55,12 @@ extension UIViewController: PanModalPresenter {
|
||||||
viewControllerToPresent.popoverPresentationController?.delegate = PanModalPresentationDelegate.default
|
viewControllerToPresent.popoverPresentationController?.delegate = PanModalPresentationDelegate.default
|
||||||
} else {
|
} else {
|
||||||
viewControllerToPresent.modalPresentationStyle = .custom
|
viewControllerToPresent.modalPresentationStyle = .custom
|
||||||
|
viewControllerToPresent.modalPresentationCapturesStatusBarAppearance = true
|
||||||
viewControllerToPresent.transitioningDelegate = PanModalPresentationDelegate.default
|
viewControllerToPresent.transitioningDelegate = PanModalPresentationDelegate.default
|
||||||
}
|
}
|
||||||
|
|
||||||
present(viewControllerToPresent, animated: true, completion: nil)
|
present(viewControllerToPresent, animated: true, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,19 @@
|
||||||
// Copyright © 2017 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2017 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A dim view for use as an overlay over content you want dimmed.
|
A dim view for use as an overlay over content you want dimmed.
|
||||||
*/
|
*/
|
||||||
public class DimmedView: UIView {
|
open class DimmedView: UIView {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Represents the possible states of the dimmed view.
|
Represents the possible states of the dimmed view.
|
||||||
max, off or a percentage of dimAlpha.
|
max, off or a percentage of dimAlpha.
|
||||||
*/
|
*/
|
||||||
enum DimState {
|
public enum DimState {
|
||||||
case max
|
case max
|
||||||
case off
|
case off
|
||||||
case percent(CGFloat)
|
case percent(CGFloat)
|
||||||
|
|
@ -29,22 +30,14 @@ public class DimmedView: UIView {
|
||||||
*/
|
*/
|
||||||
var dimState: DimState = .off {
|
var dimState: DimState = .off {
|
||||||
didSet {
|
didSet {
|
||||||
switch dimState {
|
onChange(dimState: dimState)
|
||||||
case .max:
|
|
||||||
alpha = dimAlpha
|
|
||||||
case .off:
|
|
||||||
alpha = 0.0
|
|
||||||
case .percent(let percentage):
|
|
||||||
let val = max(0.0, min(1.0, percentage))
|
|
||||||
alpha = dimAlpha * val
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The closure to be executed when a tap occurs
|
The closure to be executed when a tap occurs
|
||||||
*/
|
*/
|
||||||
var didTap: ((_ recognizer: UIGestureRecognizer) -> Void)?
|
var didTap: ((_ recognizer: UITapGestureRecognizer) -> Void)?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Tap gesture recognizer
|
Tap gesture recognizer
|
||||||
|
|
@ -53,15 +46,11 @@ public class DimmedView: UIView {
|
||||||
return UITapGestureRecognizer(target: self, action: #selector(didTapView))
|
return UITapGestureRecognizer(target: self, action: #selector(didTapView))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let dimAlpha: CGFloat
|
|
||||||
|
|
||||||
// MARK: - Initializers
|
// MARK: - Initializers
|
||||||
|
|
||||||
init(dimAlpha: CGFloat = 0.7) {
|
public init() {
|
||||||
self.dimAlpha = dimAlpha
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
alpha = 0.0
|
alpha = 0.0
|
||||||
backgroundColor = .black
|
|
||||||
addGestureRecognizer(tapGesture)
|
addGestureRecognizer(tapGesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,8 +60,22 @@ public class DimmedView: UIView {
|
||||||
|
|
||||||
// MARK: - Event Handlers
|
// MARK: - Event Handlers
|
||||||
|
|
||||||
@objc private func didTapView() {
|
@objc private func didTapView(sender: UITapGestureRecognizer) {
|
||||||
didTap?(tapGesture)
|
didTap?(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Subclass override
|
||||||
|
|
||||||
|
open func onChange(dimState: DimState) {
|
||||||
|
switch dimState {
|
||||||
|
case .max:
|
||||||
|
alpha = 1.0
|
||||||
|
case .off:
|
||||||
|
alpha = 0.0
|
||||||
|
case .percent(let percentage):
|
||||||
|
alpha = max(0.0, min(1.0, percentage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
// Copyright © 2018 Tiny Speck, Inc. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,8 +21,9 @@ class PanContainerView: UIView {
|
||||||
addSubview(presentedView)
|
addSubview(presentedView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError()
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +35,10 @@ extension UIView {
|
||||||
from the view hierachy
|
from the view hierachy
|
||||||
*/
|
*/
|
||||||
var panContainerView: PanContainerView? {
|
var panContainerView: PanContainerView? {
|
||||||
return subviews.compactMap({ $0 as? PanContainerView }).first
|
return subviews.first(where: { view -> Bool in
|
||||||
|
view is PanContainerView
|
||||||
|
}) as? PanContainerView
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,22 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
0F2A2C552239C119003BDB2F /* PanModal.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F2A2C532239C119003BDB2F /* PanModal.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
0F2A2C582239C119003BDB2F /* PanModal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F2A2C512239C119003BDB2F /* PanModal.framework */; };
|
||||||
|
0F2A2C592239C119003BDB2F /* PanModal.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0F2A2C512239C119003BDB2F /* PanModal.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
0F2A2C5E2239C137003BDB2F /* PanModalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A2220BA6E500124CE1 /* PanModalAnimator.swift */; };
|
||||||
|
0F2A2C5F2239C139003BDB2F /* PanModalPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC139066216D9458007A3E64 /* PanModalPresentationAnimator.swift */; };
|
||||||
|
0F2A2C602239C13C003BDB2F /* PanModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC13906C216D9458007A3E64 /* PanModalPresentationController.swift */; };
|
||||||
|
0F2A2C612239C140003BDB2F /* PanModalPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */; };
|
||||||
|
0F2A2C622239C148003BDB2F /* PanModalHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A4220BA76D00124CE1 /* PanModalHeight.swift */; };
|
||||||
|
0F2A2C632239C14B003BDB2F /* PanModalPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC139068216D9458007A3E64 /* PanModalPresentable.swift */; };
|
||||||
|
0F2A2C642239C14E003BDB2F /* PanModalPresentable+Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC0EE7B21917F2500208DBC /* PanModalPresentable+Defaults.swift */; };
|
||||||
|
0F2A2C652239C151003BDB2F /* PanModalPresentable+UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC139069216D9458007A3E64 /* PanModalPresentable+UIViewController.swift */; };
|
||||||
|
0F2A2C662239C153003BDB2F /* PanModalPresentable+LayoutHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A6220BA78800124CE1 /* PanModalPresentable+LayoutHelpers.swift */; };
|
||||||
|
0F2A2C672239C157003BDB2F /* PanModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC13906A216D9458007A3E64 /* PanModalPresenter.swift */; };
|
||||||
|
0F2A2C682239C15D003BDB2F /* UIViewController+PanModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A9220BA82A00124CE1 /* UIViewController+PanModalPresenter.swift */; };
|
||||||
|
0F2A2C692239C162003BDB2F /* DimmedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC13906E216D9458007A3E64 /* DimmedView.swift */; };
|
||||||
|
0F2A2C6A2239C165003BDB2F /* PanContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9C21F03368008045A0 /* PanContainerView.swift */; };
|
||||||
743CABB02225FC9F00634A5A /* UserGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABAF2225FC9F00634A5A /* UserGroupViewController.swift */; };
|
743CABB02225FC9F00634A5A /* UserGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABAF2225FC9F00634A5A /* UserGroupViewController.swift */; };
|
||||||
743CABB22225FD1100634A5A /* UserGroupHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB12225FD1100634A5A /* UserGroupHeaderView.swift */; };
|
743CABB22225FD1100634A5A /* UserGroupHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB12225FD1100634A5A /* UserGroupHeaderView.swift */; };
|
||||||
743CABB42225FE7700634A5A /* UserGroupMemberPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */; };
|
743CABB42225FE7700634A5A /* UserGroupMemberPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */; };
|
||||||
|
|
@ -25,6 +41,7 @@
|
||||||
943904ED2226366700859537 /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EC2226366700859537 /* AlertViewController.swift */; };
|
943904ED2226366700859537 /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EC2226366700859537 /* AlertViewController.swift */; };
|
||||||
943904EF2226383700859537 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EE2226383700859537 /* NavigationController.swift */; };
|
943904EF2226383700859537 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EE2226383700859537 /* NavigationController.swift */; };
|
||||||
943904F32226484F00859537 /* UserGroupStackedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904F22226484F00859537 /* UserGroupStackedViewController.swift */; };
|
943904F32226484F00859537 /* UserGroupStackedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904F22226484F00859537 /* UserGroupStackedViewController.swift */; };
|
||||||
|
944EBA2E227BB7F400C4C97B /* FullScreenNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944EBA2D227BB7F400C4C97B /* FullScreenNavController.swift */; };
|
||||||
94795C9B21F0335D008045A0 /* PanModalPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */; };
|
94795C9B21F0335D008045A0 /* PanModalPresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */; };
|
||||||
94795C9D21F03368008045A0 /* PanContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9C21F03368008045A0 /* PanContainerView.swift */; };
|
94795C9D21F03368008045A0 /* PanContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9C21F03368008045A0 /* PanContainerView.swift */; };
|
||||||
DC13905E216D90D5007A3E64 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC13905D216D90D5007A3E64 /* Assets.xcassets */; };
|
DC13905E216D90D5007A3E64 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC13905D216D90D5007A3E64 /* Assets.xcassets */; };
|
||||||
|
|
@ -42,6 +59,13 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
0F2A2C562239C119003BDB2F /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = DCA741A2216D90410021F2F2 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 0F2A2C502239C119003BDB2F;
|
||||||
|
remoteInfo = PanModal;
|
||||||
|
};
|
||||||
743CABC92226171500634A5A /* PBXContainerItemProxy */ = {
|
743CABC92226171500634A5A /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = DCA741A2216D90410021F2F2 /* Project object */;
|
containerPortal = DCA741A2216D90410021F2F2 /* Project object */;
|
||||||
|
|
@ -51,7 +75,24 @@
|
||||||
};
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
0F2A2C5D2239C119003BDB2F /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
0F2A2C592239C119003BDB2F /* PanModal.framework in Embed Frameworks */,
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0F2A2C512239C119003BDB2F /* PanModal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PanModal.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
0F2A2C532239C119003BDB2F /* PanModal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PanModal.h; sourceTree = "<group>"; };
|
||||||
|
0F2A2C542239C119003BDB2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
743CABAF2225FC9F00634A5A /* UserGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupViewController.swift; sourceTree = "<group>"; };
|
743CABAF2225FC9F00634A5A /* UserGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupViewController.swift; sourceTree = "<group>"; };
|
||||||
743CABB12225FD1100634A5A /* UserGroupHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupHeaderView.swift; sourceTree = "<group>"; };
|
743CABB12225FD1100634A5A /* UserGroupHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupHeaderView.swift; sourceTree = "<group>"; };
|
||||||
743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupMemberPresentable.swift; sourceTree = "<group>"; };
|
743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupMemberPresentable.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -72,6 +113,7 @@
|
||||||
943904EC2226366700859537 /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = "<group>"; };
|
943904EC2226366700859537 /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = "<group>"; };
|
||||||
943904EE2226383700859537 /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = "<group>"; };
|
943904EE2226383700859537 /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = "<group>"; };
|
||||||
943904F22226484F00859537 /* UserGroupStackedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupStackedViewController.swift; sourceTree = "<group>"; };
|
943904F22226484F00859537 /* UserGroupStackedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupStackedViewController.swift; sourceTree = "<group>"; };
|
||||||
|
944EBA2D227BB7F400C4C97B /* FullScreenNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenNavController.swift; sourceTree = "<group>"; };
|
||||||
94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanModalPresentationDelegate.swift; sourceTree = "<group>"; };
|
94795C9A21F0335D008045A0 /* PanModalPresentationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanModalPresentationDelegate.swift; sourceTree = "<group>"; };
|
||||||
94795C9C21F03368008045A0 /* PanContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanContainerView.swift; sourceTree = "<group>"; };
|
94795C9C21F03368008045A0 /* PanContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanContainerView.swift; sourceTree = "<group>"; };
|
||||||
DC13905D216D90D5007A3E64 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
DC13905D216D90D5007A3E64 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
|
@ -84,13 +126,20 @@
|
||||||
DC13906E216D9458007A3E64 /* DimmedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DimmedView.swift; sourceTree = "<group>"; };
|
DC13906E216D9458007A3E64 /* DimmedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DimmedView.swift; sourceTree = "<group>"; };
|
||||||
DC3B2EB9222A560A000C8A4A /* TransientAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientAlertViewController.swift; sourceTree = "<group>"; };
|
DC3B2EB9222A560A000C8A4A /* TransientAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientAlertViewController.swift; sourceTree = "<group>"; };
|
||||||
DC3B2EBD222A58C9000C8A4A /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = "<group>"; };
|
DC3B2EBD222A58C9000C8A4A /* AlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertView.swift; sourceTree = "<group>"; };
|
||||||
DCA741AA216D90410021F2F2 /* PanModal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PanModal.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
DCA741AA216D90410021F2F2 /* PanModalDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PanModalDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
DCA741AD216D90410021F2F2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
DCA741AD216D90410021F2F2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
DCA741B9216D90420021F2F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
DCA741B9216D90420021F2F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
DCC0EE7B21917F2500208DBC /* PanModalPresentable+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PanModalPresentable+Defaults.swift"; sourceTree = "<group>"; };
|
DCC0EE7B21917F2500208DBC /* PanModalPresentable+Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PanModalPresentable+Defaults.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
0F2A2C4E2239C119003BDB2F /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
743CABC12226171500634A5A /* Frameworks */ = {
|
743CABC12226171500634A5A /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -102,12 +151,22 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
0F2A2C582239C119003BDB2F /* PanModal.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
0F2A2C522239C119003BDB2F /* PanModal */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0F2A2C532239C119003BDB2F /* PanModal.h */,
|
||||||
|
0F2A2C542239C119003BDB2F /* Info.plist */,
|
||||||
|
);
|
||||||
|
path = PanModal;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
743CABAE2225FC4A00634A5A /* User Groups */ = {
|
743CABAE2225FC4A00634A5A /* User Groups */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -181,6 +240,22 @@
|
||||||
path = Presenter;
|
path = Presenter;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
944EBA2B227BB7D900C4C97B /* Basic */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
943904EA2226354100859537 /* BasicViewController.swift */,
|
||||||
|
);
|
||||||
|
path = Basic;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
944EBA2C227BB7E100C4C97B /* Full Screen */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
944EBA2D227BB7F400C4C97B /* FullScreenNavController.swift */,
|
||||||
|
);
|
||||||
|
path = "Full Screen";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DC13905F216D93AB007A3E64 /* Resources */ = {
|
DC13905F216D93AB007A3E64 /* Resources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -253,7 +328,8 @@
|
||||||
DC139079216D9AAA007A3E64 /* View Controllers */ = {
|
DC139079216D9AAA007A3E64 /* View Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
943904EA2226354100859537 /* BasicViewController.swift */,
|
944EBA2B227BB7D900C4C97B /* Basic */,
|
||||||
|
944EBA2C227BB7E100C4C97B /* Full Screen */,
|
||||||
DC3B2EBB222A5882000C8A4A /* Alert */,
|
DC3B2EBB222A5882000C8A4A /* Alert */,
|
||||||
DC3B2EBC222A5893000C8A4A /* Alert (Transient) */,
|
DC3B2EBC222A5893000C8A4A /* Alert (Transient) */,
|
||||||
743CB2AB222661EA00665A55 /* User Groups (Stacked) */,
|
743CB2AB222661EA00665A55 /* User Groups (Stacked) */,
|
||||||
|
|
@ -286,6 +362,7 @@
|
||||||
DCA741AC216D90410021F2F2 /* Sample */,
|
DCA741AC216D90410021F2F2 /* Sample */,
|
||||||
DC139062216D9431007A3E64 /* PanModal */,
|
DC139062216D9431007A3E64 /* PanModal */,
|
||||||
743CABC52226171500634A5A /* Tests */,
|
743CABC52226171500634A5A /* Tests */,
|
||||||
|
0F2A2C522239C119003BDB2F /* PanModal */,
|
||||||
DCA741AB216D90410021F2F2 /* Products */,
|
DCA741AB216D90410021F2F2 /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -293,8 +370,9 @@
|
||||||
DCA741AB216D90410021F2F2 /* Products */ = {
|
DCA741AB216D90410021F2F2 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
DCA741AA216D90410021F2F2 /* PanModal.app */,
|
DCA741AA216D90410021F2F2 /* PanModalDemo.app */,
|
||||||
743CABC42226171500634A5A /* PanModalTests.xctest */,
|
743CABC42226171500634A5A /* PanModalTests.xctest */,
|
||||||
|
0F2A2C512239C119003BDB2F /* PanModal.framework */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -312,7 +390,36 @@
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXHeadersBuildPhase section */
|
||||||
|
0F2A2C4C2239C119003BDB2F /* Headers */ = {
|
||||||
|
isa = PBXHeadersBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
0F2A2C552239C119003BDB2F /* PanModal.h in Headers */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXHeadersBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
0F2A2C502239C119003BDB2F /* PanModal */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 0F2A2C5A2239C119003BDB2F /* Build configuration list for PBXNativeTarget "PanModal" */;
|
||||||
|
buildPhases = (
|
||||||
|
0F2A2C4C2239C119003BDB2F /* Headers */,
|
||||||
|
0F2A2C4D2239C119003BDB2F /* Sources */,
|
||||||
|
0F2A2C4E2239C119003BDB2F /* Frameworks */,
|
||||||
|
0F2A2C4F2239C119003BDB2F /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = PanModal;
|
||||||
|
productName = PanModal;
|
||||||
|
productReference = 0F2A2C512239C119003BDB2F /* PanModal.framework */;
|
||||||
|
productType = "com.apple.product-type.framework";
|
||||||
|
};
|
||||||
743CABC32226171500634A5A /* PanModalTests */ = {
|
743CABC32226171500634A5A /* PanModalTests */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 743CABCB2226171500634A5A /* Build configuration list for PBXNativeTarget "PanModalTests" */;
|
buildConfigurationList = 743CABCB2226171500634A5A /* Build configuration list for PBXNativeTarget "PanModalTests" */;
|
||||||
|
|
@ -331,21 +438,23 @@
|
||||||
productReference = 743CABC42226171500634A5A /* PanModalTests.xctest */;
|
productReference = 743CABC42226171500634A5A /* PanModalTests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
DCA741A9216D90410021F2F2 /* PanModal */ = {
|
DCA741A9216D90410021F2F2 /* PanModalDemo */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModal" */;
|
buildConfigurationList = DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModalDemo" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
DCA741A6216D90410021F2F2 /* Sources */,
|
DCA741A6216D90410021F2F2 /* Sources */,
|
||||||
DCA741A7216D90410021F2F2 /* Frameworks */,
|
DCA741A7216D90410021F2F2 /* Frameworks */,
|
||||||
DCA741A8216D90410021F2F2 /* Resources */,
|
DCA741A8216D90410021F2F2 /* Resources */,
|
||||||
|
0F2A2C5D2239C119003BDB2F /* Embed Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
0F2A2C572239C119003BDB2F /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = PanModal;
|
name = PanModalDemo;
|
||||||
productName = PanModal;
|
productName = PanModal;
|
||||||
productReference = DCA741AA216D90410021F2F2 /* PanModal.app */;
|
productReference = DCA741AA216D90410021F2F2 /* PanModalDemo.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
@ -358,6 +467,9 @@
|
||||||
LastUpgradeCheck = 1000;
|
LastUpgradeCheck = 1000;
|
||||||
ORGANIZATIONNAME = Detail;
|
ORGANIZATIONNAME = Detail;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
0F2A2C502239C119003BDB2F = {
|
||||||
|
CreatedOnToolsVersion = 10.1;
|
||||||
|
};
|
||||||
743CABC32226171500634A5A = {
|
743CABC32226171500634A5A = {
|
||||||
CreatedOnToolsVersion = 10.1;
|
CreatedOnToolsVersion = 10.1;
|
||||||
TestTargetID = DCA741A9216D90410021F2F2;
|
TestTargetID = DCA741A9216D90410021F2F2;
|
||||||
|
|
@ -367,7 +479,7 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModal" */;
|
buildConfigurationList = DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModalDemo" */;
|
||||||
compatibilityVersion = "Xcode 9.3";
|
compatibilityVersion = "Xcode 9.3";
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
|
|
@ -380,13 +492,21 @@
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
DCA741A9216D90410021F2F2 /* PanModal */,
|
DCA741A9216D90410021F2F2 /* PanModalDemo */,
|
||||||
743CABC32226171500634A5A /* PanModalTests */,
|
743CABC32226171500634A5A /* PanModalTests */,
|
||||||
|
0F2A2C502239C119003BDB2F /* PanModal */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
0F2A2C4F2239C119003BDB2F /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
743CABC22226171500634A5A /* Resources */ = {
|
743CABC22226171500634A5A /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -407,6 +527,26 @@
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
0F2A2C4D2239C119003BDB2F /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
0F2A2C5E2239C137003BDB2F /* PanModalAnimator.swift in Sources */,
|
||||||
|
0F2A2C5F2239C139003BDB2F /* PanModalPresentationAnimator.swift in Sources */,
|
||||||
|
0F2A2C602239C13C003BDB2F /* PanModalPresentationController.swift in Sources */,
|
||||||
|
0F2A2C612239C140003BDB2F /* PanModalPresentationDelegate.swift in Sources */,
|
||||||
|
0F2A2C622239C148003BDB2F /* PanModalHeight.swift in Sources */,
|
||||||
|
0F2A2C632239C14B003BDB2F /* PanModalPresentable.swift in Sources */,
|
||||||
|
0F2A2C642239C14E003BDB2F /* PanModalPresentable+Defaults.swift in Sources */,
|
||||||
|
0F2A2C652239C151003BDB2F /* PanModalPresentable+UIViewController.swift in Sources */,
|
||||||
|
0F2A2C662239C153003BDB2F /* PanModalPresentable+LayoutHelpers.swift in Sources */,
|
||||||
|
0F2A2C672239C157003BDB2F /* PanModalPresenter.swift in Sources */,
|
||||||
|
0F2A2C682239C15D003BDB2F /* UIViewController+PanModalPresenter.swift in Sources */,
|
||||||
|
0F2A2C692239C162003BDB2F /* DimmedView.swift in Sources */,
|
||||||
|
0F2A2C6A2239C165003BDB2F /* PanContainerView.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
743CABC02226171500634A5A /* Sources */ = {
|
743CABC02226171500634A5A /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -446,6 +586,7 @@
|
||||||
DC139075216D9458007A3E64 /* DimmedView.swift in Sources */,
|
DC139075216D9458007A3E64 /* DimmedView.swift in Sources */,
|
||||||
743CABD322265F2E00634A5A /* ProfileViewController.swift in Sources */,
|
743CABD322265F2E00634A5A /* ProfileViewController.swift in Sources */,
|
||||||
DC139070216D9458007A3E64 /* PanModalPresentationAnimator.swift in Sources */,
|
DC139070216D9458007A3E64 /* PanModalPresentationAnimator.swift in Sources */,
|
||||||
|
944EBA2E227BB7F400C4C97B /* FullScreenNavController.swift in Sources */,
|
||||||
DCA741AE216D90410021F2F2 /* AppDelegate.swift in Sources */,
|
DCA741AE216D90410021F2F2 /* AppDelegate.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
@ -453,14 +594,77 @@
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
0F2A2C572239C119003BDB2F /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 0F2A2C502239C119003BDB2F /* PanModal */;
|
||||||
|
targetProxy = 0F2A2C562239C119003BDB2F /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
743CABCA2226171500634A5A /* PBXTargetDependency */ = {
|
743CABCA2226171500634A5A /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = DCA741A9216D90410021F2F2 /* PanModal */;
|
target = DCA741A9216D90410021F2F2 /* PanModalDemo */;
|
||||||
targetProxy = 743CABC92226171500634A5A /* PBXContainerItemProxy */;
|
targetProxy = 743CABC92226171500634A5A /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
|
0F2A2C5B2239C119003BDB2F /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_IDENTITY = "";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEFINES_MODULE = YES;
|
||||||
|
DEVELOPMENT_TEAM = 6UF7FN999R;
|
||||||
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
INFOPLIST_FILE = PanModal/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.slack.PanModal;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
VERSION_INFO_PREFIX = "";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
0F2A2C5C2239C119003BDB2F /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_IDENTITY = "";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEFINES_MODULE = YES;
|
||||||
|
DEVELOPMENT_TEAM = 6UF7FN999R;
|
||||||
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
INFOPLIST_FILE = PanModal/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.slack.PanModal;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
VERSION_INFO_PREFIX = "";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
743CABCC2226171500634A5A /* Debug */ = {
|
743CABCC2226171500634A5A /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
|
@ -475,9 +679,9 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
|
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModal.app/PanModal";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModalDemo.app/PanModalDemo";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -495,9 +699,9 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
|
PRODUCT_BUNDLE_IDENTIFIER = slack.PanModalTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModal.app/PanModal";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PanModalDemo.app/PanModalDemo";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
@ -559,6 +763,7 @@
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -613,6 +818,7 @@
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
@ -620,6 +826,7 @@
|
||||||
DCA741BD216D90420021F2F2 /* Debug */ = {
|
DCA741BD216D90420021F2F2 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
|
@ -632,7 +839,7 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
|
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
|
@ -640,6 +847,7 @@
|
||||||
DCA741BE216D90420021F2F2 /* Release */ = {
|
DCA741BE216D90420021F2F2 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
|
@ -652,7 +860,7 @@
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
|
PRODUCT_BUNDLE_IDENTIFIER = com.PanModal;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
@ -660,6 +868,15 @@
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
|
0F2A2C5A2239C119003BDB2F /* Build configuration list for PBXNativeTarget "PanModal" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
0F2A2C5B2239C119003BDB2F /* Debug */,
|
||||||
|
0F2A2C5C2239C119003BDB2F /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
743CABCB2226171500634A5A /* Build configuration list for PBXNativeTarget "PanModalTests" */ = {
|
743CABCB2226171500634A5A /* Build configuration list for PBXNativeTarget "PanModalTests" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|
@ -669,7 +886,7 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModal" */ = {
|
DCA741A5216D90410021F2F2 /* Build configuration list for PBXProject "PanModalDemo" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
DCA741BA216D90420021F2F2 /* Debug */,
|
DCA741BA216D90420021F2F2 /* Debug */,
|
||||||
|
|
@ -678,7 +895,7 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModal" */ = {
|
DCA741BC216D90420021F2F2 /* Build configuration list for PBXNativeTarget "PanModalDemo" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
DCA741BD216D90420021F2F2 /* Debug */,
|
DCA741BD216D90420021F2F2 /* Debug */,
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1010"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "0F2A2C502239C119003BDB2F"
|
||||||
|
BuildableName = "PanModal.framework"
|
||||||
|
BlueprintName = "PanModal"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "0F2A2C502239C119003BDB2F"
|
||||||
|
BuildableName = "PanModal.framework"
|
||||||
|
BlueprintName = "PanModal"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "0F2A2C502239C119003BDB2F"
|
||||||
|
BuildableName = "PanModal.framework"
|
||||||
|
BlueprintName = "PanModal"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1010"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
|
||||||
|
BuildableName = "PanModalDemo.app"
|
||||||
|
BlueprintName = "PanModalDemo"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "743CABC32226171500634A5A"
|
||||||
|
BuildableName = "PanModalTests.xctest"
|
||||||
|
BlueprintName = "PanModalTests"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
|
||||||
|
BuildableName = "PanModalDemo.app"
|
||||||
|
BlueprintName = "PanModalDemo"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
|
||||||
|
BuildableName = "PanModalDemo.app"
|
||||||
|
BlueprintName = "PanModalDemo"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "DCA741A9216D90410021F2F2"
|
||||||
|
BuildableName = "PanModalDemo.app"
|
||||||
|
BlueprintName = "PanModalDemo"
|
||||||
|
ReferencedContainer = "container:PanModalDemo.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
21
README.md
21
README.md
|
|
@ -1,10 +1,13 @@
|
||||||
|
|
||||||
|
### PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://github.com/slackhq/PanModal/raw/master/Screenshots/panModal.gif" width="30%" height="30%" alt="Screenshot Preview" />
|
<img src="https://github.com/slackhq/PanModal/raw/master/Screenshots/panModal.gif" width="30%" height="30%" alt="Screenshot Preview" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/Platform-iOS_10+-green.svg" alt="Platform: iOS 10.0+" />
|
<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://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>
|
<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" />
|
<img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License: MIT" />
|
||||||
|
|
@ -21,6 +24,12 @@
|
||||||
• <a href="#license">License</a>
|
• <a href="#license">License</a>
|
||||||
</p>
|
</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
|
## Features
|
||||||
|
|
||||||
* Supports any type of `UIViewController`
|
* Supports any type of `UIViewController`
|
||||||
|
|
@ -45,6 +54,14 @@ pod 'PanModal'
|
||||||
github "slackhq/PanModal"
|
github "slackhq/PanModal"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* <a href="https://swift.org/package-manager/" target="_blank">Swift Package Manager</a>:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/slackhq/PanModal.git", .exact("1.2.6")),
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
PanModal was designed to be used effortlessly. Simply call `presentPanModal` in the same way you would expect to present a `UIViewController`
|
PanModal was designed to be used effortlessly. Simply call `presentPanModal` in the same way you would expect to present a `UIViewController`
|
||||||
|
|
@ -130,7 +147,7 @@ We will only be fixing critical bugs, thus, for any non-critical issues or featu
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
[Stephen Sowole](https://github.com/tun57) • [Tosin Afolabi](https://github.com/tosinaf)
|
[Stephen Sowole](https://github.com/ste57) • [Tosin Afolabi](https://github.com/tosinaf)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ private extension SampleViewController {
|
||||||
|
|
||||||
enum RowType: Int, CaseIterable {
|
enum RowType: Int, CaseIterable {
|
||||||
case basic
|
case basic
|
||||||
|
case fullScreen
|
||||||
case alert
|
case alert
|
||||||
case transientAlert
|
case transientAlert
|
||||||
case userGroups
|
case userGroups
|
||||||
|
|
@ -77,6 +78,7 @@ private extension SampleViewController {
|
||||||
var presentable: RowPresentable {
|
var presentable: RowPresentable {
|
||||||
switch self {
|
switch self {
|
||||||
case .basic: return Basic()
|
case .basic: return Basic()
|
||||||
|
case .fullScreen: return FullScreen()
|
||||||
case .alert: return Alert()
|
case .alert: return Alert()
|
||||||
case .transientAlert: return TransientAlert()
|
case .transientAlert: return TransientAlert()
|
||||||
case .userGroups: return UserGroup()
|
case .userGroups: return UserGroup()
|
||||||
|
|
@ -86,33 +88,38 @@ private extension SampleViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Basic: RowPresentable {
|
struct Basic: RowPresentable {
|
||||||
var string: String { return "Basic" }
|
let string: String = "Basic"
|
||||||
var rowVC: PanModalPresentable.LayoutType { return BasicViewController() }
|
let rowVC: PanModalPresentable.LayoutType = BasicViewController()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FullScreen: RowPresentable {
|
||||||
|
let string: String = "Full Screen"
|
||||||
|
let rowVC: PanModalPresentable.LayoutType = FullScreenNavController()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Alert: RowPresentable {
|
struct Alert: RowPresentable {
|
||||||
var string: String { return "Alert" }
|
let string: String = "Alert"
|
||||||
var rowVC: PanModalPresentable.LayoutType { return AlertViewController() }
|
let rowVC: PanModalPresentable.LayoutType = AlertViewController()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TransientAlert: RowPresentable {
|
struct TransientAlert: RowPresentable {
|
||||||
var string: String { return "Alert (Transient)"}
|
let string: String = "Alert (Transient)"
|
||||||
var rowVC: PanModalPresentable.LayoutType { return TransientAlertViewController() }
|
let rowVC: PanModalPresentable.LayoutType = TransientAlertViewController()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserGroup: RowPresentable {
|
struct UserGroup: RowPresentable {
|
||||||
var string: String { return "User Groups" }
|
let string: String = "User Groups"
|
||||||
var rowVC: PanModalPresentable.LayoutType { return UserGroupViewController() }
|
let rowVC: PanModalPresentable.LayoutType = UserGroupViewController()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Navigation: RowPresentable {
|
struct Navigation: RowPresentable {
|
||||||
var string: String { return "User Groups (NavigationController)" }
|
let string: String = "User Groups (NavigationController)"
|
||||||
var rowVC: PanModalPresentable.LayoutType { return NavigationController() }
|
let rowVC: PanModalPresentable.LayoutType = NavigationController()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Stacked: RowPresentable {
|
struct Stacked: RowPresentable {
|
||||||
var string: String { return "User Groups (Stacked)" }
|
let string: String = "User Groups (Stacked)"
|
||||||
var rowVC: PanModalPresentable.LayoutType { return UserGroupStackedViewController() }
|
let rowVC: PanModalPresentable.LayoutType = UserGroupStackedViewController()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@ class TransientAlertViewController: AlertViewController {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override var backgroundAlpha: CGFloat {
|
override var panModalBackgroundColor: UIColor {
|
||||||
return 0.0
|
return .clear
|
||||||
}
|
}
|
||||||
|
|
||||||
override var isUserInteractionEnabled: Bool {
|
override var isUserInteractionEnabled: Bool {
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class AlertViewController: UIViewController, PanModalPresentable {
|
||||||
return shortFormHeight
|
return shortFormHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
var backgroundAlpha: CGFloat {
|
var panModalBackgroundColor: UIColor {
|
||||||
return 0.1
|
return UIColor.black.withAlphaComponent(0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldRoundTopCorners: Bool {
|
var shouldRoundTopCorners: Bool {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ class BasicViewController: UIViewController {
|
||||||
|
|
||||||
extension BasicViewController: PanModalPresentable {
|
extension BasicViewController: PanModalPresentable {
|
||||||
|
|
||||||
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
|
return .lightContent
|
||||||
|
}
|
||||||
|
|
||||||
var panScrollable: UIScrollView? {
|
var panScrollable: UIScrollView? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
//
|
||||||
|
// FullScreenNavController.swift
|
||||||
|
// PanModalDemo
|
||||||
|
//
|
||||||
|
// Created by Stephen Sowole on 5/2/19.
|
||||||
|
// Copyright © 2019 Detail. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class FullScreenNavController: UINavigationController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
pushViewController(FullScreenViewController(), animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FullScreenNavController: PanModalPresentable {
|
||||||
|
|
||||||
|
var panScrollable: UIScrollView? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var topOffset: CGFloat {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var springDamping: CGFloat {
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var transitionDuration: Double {
|
||||||
|
return 0.4
|
||||||
|
}
|
||||||
|
|
||||||
|
var transitionAnimationOptions: UIView.AnimationOptions {
|
||||||
|
return [.allowUserInteraction, .beginFromCurrentState]
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldRoundTopCorners: Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var showDragIndicator: Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FullScreenViewController: UIViewController {
|
||||||
|
|
||||||
|
let textLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "Drag downwards to dismiss"
|
||||||
|
label.font = UIFont(name: "Lato-Bold", size: 17)
|
||||||
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
title = "Full Screen"
|
||||||
|
view.backgroundColor = .white
|
||||||
|
setupConstraints()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupConstraints() {
|
||||||
|
view.addSubview(textLabel)
|
||||||
|
textLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
|
||||||
|
textLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -12,9 +12,17 @@ class NavigationController: UINavigationController, PanModalPresentable {
|
||||||
|
|
||||||
private let navGroups = NavUserGroups()
|
private let navGroups = NavUserGroups()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
init() {
|
||||||
super.viewDidLoad()
|
super.init(nibName: nil, bundle: nil)
|
||||||
pushViewController(navGroups, animated: false)
|
viewControllers = [navGroups]
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
|
return .lightContent
|
||||||
}
|
}
|
||||||
|
|
||||||
override func popViewController(animated: Bool) -> UIViewController? {
|
override func popViewController(animated: Bool) -> UIViewController? {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ class StackedProfileViewController: UIViewController, PanModalPresentable {
|
||||||
|
|
||||||
let presentable: UserGroupMemberPresentable
|
let presentable: UserGroupMemberPresentable
|
||||||
|
|
||||||
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
|
return .lightContent
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|
||||||
let avatarView: UIView = {
|
let avatarView: UIView = {
|
||||||
|
|
@ -85,6 +89,7 @@ class StackedProfileViewController: UIViewController, PanModalPresentable {
|
||||||
|
|
||||||
roleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
|
roleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
|
||||||
roleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 4.0).isActive = true
|
roleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 4.0).isActive = true
|
||||||
|
bottomLayoutGuide.topAnchor.constraint(greaterThanOrEqualTo: roleLabel.bottomAnchor).isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Pan Modal Presentable
|
// MARK: - Pan Modal Presentable
|
||||||
|
|
@ -94,7 +99,7 @@ class StackedProfileViewController: UIViewController, PanModalPresentable {
|
||||||
}
|
}
|
||||||
|
|
||||||
var longFormHeight: PanModalHeight {
|
var longFormHeight: PanModalHeight {
|
||||||
return .contentHeight(300)
|
return .intrinsicHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
var anchorModalToLongForm: Bool {
|
var anchorModalToLongForm: Bool {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class UserGroupViewController: UITableViewController, PanModalPresentable, UIGestureRecognizerDelegate {
|
class UserGroupViewController: UITableViewController, PanModalPresentable {
|
||||||
|
|
||||||
let members: [UserGroupMemberPresentable] = [
|
let members: [UserGroupMemberPresentable] = [
|
||||||
UserGroupMemberPresentable(name: "Naida Schill ✈️", role: "Staff Engineer - Mobile DevXP", avatarBackgroundColor: #colorLiteral(red: 0.7215686275, green: 0.9098039216, blue: 0.5607843137, alpha: 1)),
|
UserGroupMemberPresentable(name: "Naida Schill ✈️", role: "Staff Engineer - Mobile DevXP", avatarBackgroundColor: #colorLiteral(red: 0.7215686275, green: 0.9098039216, blue: 0.5607843137, alpha: 1)),
|
||||||
|
|
@ -34,6 +34,10 @@ class UserGroupViewController: UITableViewController, PanModalPresentable, UIGes
|
||||||
|
|
||||||
var isShortFormEnabled = true
|
var isShortFormEnabled = true
|
||||||
|
|
||||||
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
|
return .lightContent
|
||||||
|
}
|
||||||
|
|
||||||
let headerView = UserGroupHeaderView()
|
let headerView = UserGroupHeaderView()
|
||||||
|
|
||||||
let headerPresentable = UserGroupHeaderPresentable.init(handle: "ios-engs", description: "iOS Engineers", memberCount: 10)
|
let headerPresentable = UserGroupHeaderPresentable.init(handle: "ios-engs", description: "iOS Engineers", memberCount: 10)
|
||||||
|
|
|
||||||
|
|
@ -48,17 +48,21 @@ class PanModalTests: XCTestCase {
|
||||||
XCTAssertEqual(vc.shortFormHeight, PanModalHeight.maxHeight)
|
XCTAssertEqual(vc.shortFormHeight, PanModalHeight.maxHeight)
|
||||||
XCTAssertEqual(vc.longFormHeight, PanModalHeight.maxHeight)
|
XCTAssertEqual(vc.longFormHeight, PanModalHeight.maxHeight)
|
||||||
XCTAssertEqual(vc.springDamping, 0.8)
|
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.scrollIndicatorInsets, .zero)
|
||||||
XCTAssertEqual(vc.anchorModalToLongForm, true)
|
XCTAssertEqual(vc.anchorModalToLongForm, true)
|
||||||
XCTAssertEqual(vc.allowsExtendedPanScrolling, false)
|
XCTAssertEqual(vc.allowsExtendedPanScrolling, false)
|
||||||
XCTAssertEqual(vc.allowsDragToDismiss, true)
|
XCTAssertEqual(vc.allowsDragToDismiss, true)
|
||||||
XCTAssertEqual(vc.isPanScrollEnabled, true)
|
XCTAssertEqual(vc.allowsTapToDismiss, true)
|
||||||
XCTAssertEqual(vc.isUserInteractionEnabled, true)
|
XCTAssertEqual(vc.isUserInteractionEnabled, true)
|
||||||
XCTAssertEqual(vc.isHapticFeedbackEnabled, true)
|
XCTAssertEqual(vc.isHapticFeedbackEnabled, true)
|
||||||
XCTAssertEqual(vc.shouldRoundTopCorners, false)
|
XCTAssertEqual(vc.shouldRoundTopCorners, false)
|
||||||
XCTAssertEqual(vc.showDragIndicator, false)
|
XCTAssertEqual(vc.showDragIndicator, false)
|
||||||
XCTAssertEqual(vc.shouldRoundTopCorners, false)
|
XCTAssertEqual(vc.shouldRoundTopCorners, false)
|
||||||
|
XCTAssertEqual(vc.cornerRadius, 8.0)
|
||||||
|
XCTAssertEqual(vc.transitionDuration, PanModalAnimator.Constants.defaultTransitionDuration)
|
||||||
|
XCTAssertEqual(vc.transitionAnimationOptions, [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPresentableYValues() {
|
func testPresentableYValues() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue