Compare commits
13 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ced7c1703f | |
|
|
36be7f0f25 | |
|
|
b8f07ab26b | |
|
|
1c47459734 | |
|
|
be82eddb52 | |
|
|
73c00b4563 | |
|
|
8ac7096ec9 | |
|
|
0c3ed5a2ef | |
|
|
eaf349654d | |
|
|
83d4b7024c | |
|
|
871fc46020 | |
|
|
d28aab13a6 | |
|
|
b2f5bd7d16 |
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'PanModal'
|
s.name = 'PanModal'
|
||||||
s.version = '1.2.7'
|
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.
|
||||||
|
|
@ -18,10 +18,10 @@ Pod::Spec.new do |s|
|
||||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||||
|
|
||||||
s.description = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
s.description = 'PanModal is an elegant and highly customizable presentation API for constructing bottom sheet modals on iOS.'
|
||||||
s.homepage = 'https://github.com/slackhq/PanModal'
|
s.homepage = 'https://git.svc.touchin.ru/TouchInstinct/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.swift_version = '5.0'
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ open class PanModalPresentationController: UIPresentationController {
|
||||||
*/
|
*/
|
||||||
public enum PresentationState {
|
public enum PresentationState {
|
||||||
case shortForm
|
case shortForm
|
||||||
|
case mediumForm
|
||||||
case longForm
|
case longForm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,11 +80,17 @@ open 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,15 +112,13 @@ open 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 color = presentable?.panModalBackgroundColor {
|
if let color = presentable?.panModalBackgroundColor {
|
||||||
view = DimmedView(dimColor: color)
|
view.backgroundColor = color
|
||||||
} else {
|
|
||||||
view = DimmedView()
|
|
||||||
}
|
}
|
||||||
view.didTap = { [weak self] _ in
|
view.didTap = { [weak self] _ in
|
||||||
if self?.presentable?.allowsTapToDismiss == true {
|
if self?.presentable?.allowsTapToDismiss == true {
|
||||||
self?.presentedViewController.dismiss(animated: true)
|
self?.presentable?.onTapToDismiss?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return view
|
return view
|
||||||
|
|
@ -187,7 +192,9 @@ open 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()
|
self?.presentedViewController.setNeedsStatusBarAppearanceUpdate()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -262,6 +269,10 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
@ -370,8 +381,10 @@ private extension PanModalPresentationController {
|
||||||
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
|
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
|
||||||
let panFrame = panContainerView.frame
|
let panFrame = panContainerView.frame
|
||||||
panContainerView.frame.size = frame.size
|
panContainerView.frame.size = frame.size
|
||||||
|
|
||||||
|
let positions = [shortFormYPosition, mediumFormYPosition, longFormYPosition]
|
||||||
|
|
||||||
if ![shortFormYPosition, longFormYPosition].contains(panFrame.origin.y) {
|
if !positions.contains(panFrame.origin.y) {
|
||||||
// if the container is already in the correct position, no need to adjust positioning
|
// if the container is already in the correct position, no need to adjust positioning
|
||||||
// (rotations & size changes cause positioning to be out of sync)
|
// (rotations & size changes cause positioning to be out of sync)
|
||||||
let yPosition = panFrame.origin.y - panFrame.height + frame.height
|
let yPosition = panFrame.origin.y - panFrame.height + frame.height
|
||||||
|
|
@ -426,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
|
||||||
|
|
@ -454,7 +468,15 @@ private extension PanModalPresentationController {
|
||||||
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`
|
||||||
|
|
@ -518,12 +540,18 @@ private extension PanModalPresentationController {
|
||||||
if velocity.y < 0 {
|
if velocity.y < 0 {
|
||||||
transition(to: .longForm)
|
transition(to: .longForm)
|
||||||
|
|
||||||
} else if (nearest(to: presentedView.frame.minY, inValues: [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 {
|
||||||
presentedViewController.dismiss(animated: true)
|
presentable?.onDragToDismiss?()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -532,16 +560,23 @@ 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 = nearest(to: presentedView.frame.minY, inValues: [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 == mediumFormYPosition {
|
||||||
|
transition(to: .mediumForm)
|
||||||
|
|
||||||
} else if position == shortFormYPosition || presentable?.allowsDragToDismiss == false {
|
} else if position == shortFormYPosition || allowsDragToDismiss == false {
|
||||||
transition(to: .shortForm)
|
transition(to: .shortForm)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
presentedViewController.dismiss(animated: true)
|
presentable?.onDragToDismiss?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -653,19 +688,10 @@ 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 {
|
|
||||||
backgroundView.dimState = .max
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let yDisplacementFromShortForm = presentedView.frame.origin.y - shortFormYPosition
|
let maxHeight = UIScreen.main.bounds.height - longFormYPosition
|
||||||
|
|
||||||
/**
|
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,6 +47,10 @@ public extension PanModalPresentable where Self: UIViewController {
|
||||||
return .contentHeight(scrollView.contentSize.height)
|
return .contentHeight(scrollView.contentSize.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dimmedView: DimmedView? {
|
||||||
|
DimmedView()
|
||||||
|
}
|
||||||
|
|
||||||
var cornerRadius: CGFloat {
|
var cornerRadius: CGFloat {
|
||||||
return 8.0
|
return 8.0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,12 @@ 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 position
|
Returns the long form Y position
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ public protocol PanModalPresentable: AnyObject {
|
||||||
*/
|
*/
|
||||||
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.
|
||||||
|
|
@ -55,6 +57,7 @@ public protocol PanModalPresentable: AnyObject {
|
||||||
*/
|
*/
|
||||||
var longFormHeight: PanModalHeight { get }
|
var longFormHeight: PanModalHeight { get }
|
||||||
|
|
||||||
|
var dimmedView: DimmedView? { get }
|
||||||
/**
|
/**
|
||||||
The corner radius used when `shouldRoundTopCorners` is enabled.
|
The corner radius used when `shouldRoundTopCorners` is enabled.
|
||||||
|
|
||||||
|
|
@ -135,6 +138,10 @@ public protocol PanModalPresentable: AnyObject {
|
||||||
*/
|
*/
|
||||||
var allowsDragToDismiss: Bool { get }
|
var allowsDragToDismiss: Bool { get }
|
||||||
|
|
||||||
|
var onTapToDismiss: (() -> Void)? { get }
|
||||||
|
|
||||||
|
var onDragToDismiss: (() -> Void)? { get }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A flag to determine if dismissal should be initiated when tapping on the dimmed background view.
|
A flag to determine if dismissal should be initiated when tapping on the dimmed background view.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,10 @@ protocol PanModalPresenter: AnyObject {
|
||||||
/**
|
/**
|
||||||
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -35,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`
|
||||||
|
|
@ -55,7 +59,7 @@ extension UIViewController: PanModalPresenter {
|
||||||
viewControllerToPresent.transitioningDelegate = PanModalPresentationDelegate.default
|
viewControllerToPresent.transitioningDelegate = PanModalPresentationDelegate.default
|
||||||
}
|
}
|
||||||
|
|
||||||
present(viewControllerToPresent, animated: true, completion: nil)
|
present(viewControllerToPresent, animated: true, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,13 @@ 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)
|
||||||
|
|
@ -30,21 +30,14 @@ public class DimmedView: UIView {
|
||||||
*/
|
*/
|
||||||
var dimState: DimState = .off {
|
var dimState: DimState = .off {
|
||||||
didSet {
|
didSet {
|
||||||
switch dimState {
|
onChange(dimState: dimState)
|
||||||
case .max:
|
|
||||||
alpha = 1.0
|
|
||||||
case .off:
|
|
||||||
alpha = 0.0
|
|
||||||
case .percent(let percentage):
|
|
||||||
alpha = max(0.0, min(1.0, percentage))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
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
|
||||||
|
|
@ -55,10 +48,9 @@ public class DimmedView: UIView {
|
||||||
|
|
||||||
// MARK: - Initializers
|
// MARK: - Initializers
|
||||||
|
|
||||||
init(dimColor: UIColor = UIColor.black.withAlphaComponent(0.7)) {
|
public init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
alpha = 0.0
|
alpha = 0.0
|
||||||
backgroundColor = dimColor
|
|
||||||
addGestureRecognizer(tapGesture)
|
addGestureRecognizer(tapGesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,8 +60,21 @@ 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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue