Compare commits

...

85 Commits
1.0 ... master

Author SHA1 Message Date
Ivan Smolin ced7c1703f Merge pull request 'make DimmedView init public' (#5) from feature/dimmed_view_public_init into master
Reviewed-on: #5
2023-07-26 13:59:02 +03:00
Ivan Smolin 36be7f0f25 make DimmedView init public 2023-07-26 13:58:13 +03:00
Ivan Smolin b8f07ab26b Merge pull request 'update source url of podspec' (#4) from update_podspec_source_url into master
Reviewed-on: #4
2023-07-25 16:54:41 +03:00
Ivan Smolin 1c47459734 update source url of podspec 2023-07-25 16:53:39 +03:00
Ivan Smolin be82eddb52 Merge pull request 'fix merge issues' (#3) from fix_merge into master
Reviewed-on: TouchInstinct/TIPanModal#3
2023-07-25 16:19:01 +03:00
Ivan Smolin 73c00b4563 fix merge issues 2023-07-25 16:17:51 +03:00
Ivan Smolin 8ac7096ec9 Merge pull request 'feature/minimize_pan_modal_changes' (#2) from feature/minimize_pan_modal_changes into master
Reviewed-on: TouchInstinct/TIPanModal#2
2023-07-25 15:40:33 +03:00
Ivan Smolin 0c3ed5a2ef update source urls 2023-07-25 15:38:31 +03:00
Ivan Smolin eaf349654d Merge branch 'master' into feature/minimize_pan_modal_changes 2023-07-25 15:36:33 +03:00
Ivan Smolin 83d4b7024c Add mediumForm and open DimmedView customization 2023-07-25 14:30:56 +03:00
Nikita Semenov 871fc46020 Merge pull request 'feat: detents api' (#1) from feature/detents_api into master
Reviewed-on: TouchInstinct/TIPanModal#1
2023-07-09 10:58:44 +03:00
Nikita Semenov d28aab13a6 feat: detents api 2023-07-09 10:57:15 +03:00
Jordan Pichler b2f5bd7d16
Add completion block to present function (#94) 2020-04-21 11:16:30 -07:00
Tosin Afolabi b012aecb6b bump version number. 2020-03-30 12:33:24 -07:00
Stephen Sowole 22b4ddd47e
[PanModal] Track all dismiss events through panModalPresentable callbacks (#91) 2020-03-25 00:34:58 -07:00
Stephen Sowole 17a8231f20 Update README.md 2020-03-24 17:10:08 -07:00
Stephen Sowole 1819113e97 [ReadMe] Add link to Swift Package Manager 2020-02-27 09:10:21 -08:00
Stephen Sowole 208640e03e
[PanModal] Round frame positions for comparison (#83) 2020-02-24 15:20:26 -08:00
Stephen Sowole f02439dc0c
[PanModal] Fix issue with incorrect returned bottom offset value (#84) 2020-02-24 15:20:14 -08:00
Kyohei Ito 047415090a
Fix Background dimming animation broken (#77) 2020-02-24 14:02:45 -08:00
Abdullah Selek 1e7c0534fc
Use keyPath to read UIApplication.shared. (#81) 2020-02-12 13:02:33 -06:00
Tosin Afolabi 7f07cdff27 Initial SPM Support 2019-11-11 10:52:18 -08:00
Nikita Nikitsky 45f8dfcf19 Add support for Swift Package Manager (#55) 2019-11-11 10:49:59 -08:00
Tosin Afolabi 5d2b0977bd Swift 5.0 Support 2019-11-11 10:26:03 -08:00
Simonas Daniliauskas a792f46e3d Add customizable drag indicator background color (#62)
* Add ability to customize drag indicator background color

* Add default dragIndicatorBackgroundColor test

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

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

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

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

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

* Rename panModalDismissCompleted panModalDidDismiss
2019-09-22 15:25:41 -07:00
liang 12ac36646f Update README.md (#43)
Add summary and blog post link
2019-08-23 10:40:20 -07:00
Tosin Afolabi f9fedcb597 Further fix for horizontal sliding + version bump 2019-06-12 20:50:49 -07:00
Tosin Afolabi 4206308afd
Version Bump to 1.2.3 2019-06-08 13:56:00 -07:00
Tosin Afolabi 0234d82979
Fix Horizonal Sliding Issues by only recogizing simultaneous gestures if the other gesture its the pan gesture (#32) 2019-06-08 13:52:59 -07:00
Giulio b0fb9e7eed Improve Swift syntax (#25)
* Replace init-ializable struct with enums

* Add AnyObject requirement to protocols

* Hide init?(coder aDecoder: NSCoder) func

* Improve Swift syntax

* Revert "Replace init-ializable struct with enums"

This reverts commit 1be2e8c23cfc75959d7fd1c90028d1a7a257b7d1.
2019-05-17 11:42:21 -05:00
tun57 a9edeb1d71 Update "Full Screen" mode in Sample App 2019-05-11 18:38:12 -07:00
tun57 b8f7e613ec Update PanModalTests.swift 2019-05-11 18:34:10 -07:00
tun57 8a662e829b Update PanModalPresentationAnimator.swift 2019-05-11 18:34:04 -07:00
tun57 0b2152f878 Set default transition values on PanModalPresentable 2019-05-11 18:32:07 -07:00
tun57 13251e1db7 Update PanModalAnimator.swift 2019-05-11 18:31:26 -07:00
tun57 da4c94e47e Remove redundant public accessors 2019-05-11 18:30:53 -07:00
tun57 0d999396a5 Add transitionDuration & transitionAnimationOptions to PanModalPresentatable 2019-05-11 18:29:19 -07:00
tun57 8e39823698 Default PanModalPresenter to internal 2019-05-11 18:26:20 -07:00
Stephen Sowole 457ebaed94 Add "Full Screen" mode to Sample app 2019-05-09 15:49:05 -07:00
Stephen Sowole 32e4a6fa24 No need to dismiss during size class change 2019-05-09 15:20:27 -07:00
Stephen Sowole 2736468072 Update UserGroupViewController.swift 2019-05-07 11:38:05 -07:00
Stephen Sowole c76ae09bc9 Update SampleViewController.swift 2019-05-02 16:47:02 -07:00
Stephen Sowole 12d6380d07 Add FullScreenViewController 2019-05-02 16:46:55 -07:00
Stephen Sowole 77c9f6f806 Group BasicViewController 2019-05-02 16:46:42 -07:00
Rune Madsen 964d444ff6 Adding functionality for UIViewController appearance callback forwarding (#21) 2019-04-23 22:27:37 -05:00
Stephen Sowole 9456969502 Limit when we adjust container view frame 2019-04-04 10:17:07 -07:00
Stephen Sowole 0750bd980e
Update PanModalPresentationController.swift (#15) 2019-04-02 14:49:45 -07:00
Tosin Afolabi 056351af4b
Update PanModal.podspec 2019-04-02 07:17:25 -07:00
Eude Lesperance dab02d34c0 Correct some docs typos (#11) 2019-03-24 20:58:33 -07:00
Stephen Sowole 96591ef52c Update PanModalPresentationController.swift 2019-03-22 14:10:06 -04:00
Tosin Afolabi 1defbf2a47
Update PanModal.podspec 2019-03-20 14:09:32 -07:00
Tosin Afolabi b58552279e
Update PanModal.podspec 2019-03-20 14:09:01 -07:00
Stephen Sowole ef7f00dab7
Replace `isPanScrollEnabled` with `shouldRespond(to ..` (#10)
* Replace `isPanScrollEnabled` with `shouldRespond(to ..`

* Update PanModalPresentable+Defaults.swift
2019-03-20 13:58:17 -07:00
Stephen Sowole d70dce231f Documentation nits. 2019-03-19 18:56:55 -07:00
Abdurahim Jauzee 5baac6732e [Trivial] Exclude Info.plist from Compile Sources (#8) 2019-03-18 09:47:12 -07:00
Tosin Afolabi 1d8b218056 minor nits. 2019-03-14 13:56:25 -07:00
Tosin Afolabi ae78bd6f64 Minor nits. 2019-03-14 13:56:15 -07:00
Tosin Afolabi 2b3029333e Fix Tests Scheme. 2019-03-14 13:48:38 -07:00
Tosin Afolabi f71fa70302
Merge pull request #5 from Marcocanc/intrinsic-height
Add Intrinsic Height to PanModalHeight
2019-03-14 13:44:47 -07:00
Tosin Afolabi 6438b952cc
Merge pull request #6 from Marcocanc/corner-radius
Add configurable cornerRadius
2019-03-14 13:43:55 -07:00
Marco Cancellieri a7d7033ef0 nit 2019-03-14 21:40:13 +01:00
Marco Cancellieri 3f3124ae37 pr comment 2019-03-14 21:38:02 +01:00
Marco Cancellieri 0da0a44c4a pr comments 2019-03-14 21:35:20 +01:00
Tosin Afolabi 88dc3324f6
Merge pull request #4 from Marcocanc/status-bar-appearance
Add support for changing status bar appearance
2019-03-14 13:30:26 -07:00
Marco Cancellieri d0b094292f Add configurable cornerRadius 2019-03-14 19:23:45 +01:00
Tosin Afolabi 15f39a1929
Update CONTRIBUTING.md 2019-03-14 11:04:31 -07:00
Tosin Afolabi f5379a1051
Merge pull request #2 from Marcocanc/carthage-deployment-target
Set Deployment Target to 10.0 for Carthage
2019-03-14 10:36:52 -07:00
Marco Cancellieri 21bcb6f268 Add Intrinsic Height 2019-03-14 18:17:29 +01:00
Marco Cancellieri f7cb63caaa Add support for changing status bar appearance 2019-03-14 16:17:49 +01:00
Marco Cancellieri c83516694f Set Deployment Target to 10.0 2019-03-14 13:41:55 +01:00
Tosin Afolabi d9f37de98c Update Scheme. 2019-03-13 16:00:53 -07:00
Tosin Afolabi b1c2d82029 Progress on Carthage Support. 2019-03-13 15:54:44 -07:00
Tosin Afolabi 68e21c3988 Add Project Scheme. 2019-03-13 15:25:13 -07:00
Tosin Afolabi a8408ebb80 Update podspec. 2019-03-13 15:02:48 -07:00
Tosin Afolabi 714d1bf03f Update version number. 2019-03-13 15:02:17 -07:00
Tosin Afolabi 444b342748 Update swift version in Podspec. 2019-03-13 14:55:40 -07:00
Tosin Afolabi 7b3c3d210c Update Podspec version 2019-03-13 14:46:42 -07:00
Tosin Afolabi f58cda2863 update podspec. 2019-03-13 14:42:09 -07:00
35 changed files with 1153 additions and 249 deletions

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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.

22
Package.swift Normal file
View File

@ -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")]
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

22
PanModal/Info.plist Normal file
View File

@ -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>

19
PanModal/PanModal.h Normal file
View File

@ -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>

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 */,

View File

@ -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>

View File

@ -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>

View File

@ -1,10 +1,13 @@
### PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.
<p align="center"> <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

View File

@ -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()
} }
} }
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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
} }

View File

@ -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
}
}

View File

@ -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? {

View File

@ -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 {

View File

@ -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)

View File

@ -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() {