20 Commits

Author SHA1 Message Date
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 1be2e8c23c.
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
15 changed files with 228 additions and 36 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'PanModal'
s.version = '1.2.2'
s.version = '1.2.3'
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.
+8 -4
View File
@@ -16,18 +16,22 @@ struct PanModalAnimator {
Constant Animation Properties
*/
struct Constants {
static let transitionDuration: TimeInterval = 0.5
static let defaultTransitionDuration: TimeInterval = 0.5
}
static func animate(_ animations: @escaping PanModalPresentable.AnimationBlockType,
config: PanModalPresentable?,
_ 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,
usingSpringWithDamping: config?.springDamping ?? 1.0,
usingSpringWithDamping: springDamping,
initialSpringVelocity: 0,
options: [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState],
options: animationOptions,
animations: animations,
completion: completion)
}
@@ -63,11 +63,17 @@ public class PanModalPresentationAnimator: NSObject {
*/
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 }
let presentable = toVC as? PanModalPresentable.LayoutType
let presentable = panModalLayoutType(from: transitionContext)
// Calls viewWillAppear and viewWillDisappear
fromVC.beginAppearanceTransition(false, animated: true)
toVC.beginAppearanceTransition(true, animated: true)
// Presents the view in shortForm position, initially
let yPos: CGFloat = presentable?.shortFormYPos ?? 0.0
@@ -86,6 +92,9 @@ public class PanModalPresentationAnimator: NSObject {
PanModalAnimator.animate({
panView.frame.origin.y = yPos
}, config: presentable) { [weak self] didComplete in
// Calls viewDidAppear and viewDidDisappear
fromVC.endAppearanceTransition()
toVC.endAppearanceTransition()
transitionContext.completeTransition(didComplete)
self?.feedbackGenerator = nil
}
@@ -96,20 +105,41 @@ public class PanModalPresentationAnimator: NSObject {
*/
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 }
let presentable = fromVC as? PanModalPresentable.LayoutType
// Calls viewWillAppear and viewWillDisappear
fromVC.beginAppearanceTransition(false, animated: true)
toVC.beginAppearanceTransition(true, animated: true)
let presentable = panModalLayoutType(from: transitionContext)
let panView: UIView = transitionContext.containerView.panContainerView ?? fromVC.view
PanModalAnimator.animate({
panView.frame.origin.y = transitionContext.containerView.frame.height
}, config: presentable) { didComplete in
fromVC.view.removeFromSuperview()
// Calls viewDidAppear and viewDidDisappear
fromVC.endAppearanceTransition()
toVC.endAppearanceTransition()
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
@@ -120,7 +150,13 @@ extension PanModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
Returns the transition duration
*/
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
}
/**
@@ -213,6 +213,26 @@ public class PanModalPresentationController: UIPresentationController {
backgroundView.removeFromSuperview()
}
/**
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
@@ -223,7 +243,7 @@ public extension PanModalPresentationController {
Transition the PanModalPresentationController
to the given presentation state
*/
public func transition(to state: PresentationState) {
func transition(to state: PresentationState) {
guard presentable?.shouldTransition(to: state) == true
else { return }
@@ -247,7 +267,7 @@ public extension PanModalPresentationController {
This method pauses the content offset KVO, performs the content offset change
and then resumes content offset observation.
*/
public func setContentOffset(offset: CGPoint) {
func setContentOffset(offset: CGPoint) {
guard let scrollView = presentable?.panScrollable
else { return }
@@ -278,7 +298,7 @@ public extension PanModalPresentationController {
- Note: This should be called whenever any
pan modal presentable value changes after the initial presentation
*/
public func setNeedsLayoutUpdate() {
func setNeedsLayoutUpdate() {
configureViewLayout()
adjustPresentedViewFrame()
observe(scrollView: presentable?.panScrollable)
@@ -342,9 +362,13 @@ private extension PanModalPresentationController {
Reduce height of presentedView so that it sits at the bottom of the screen
*/
func adjustPresentedViewFrame() {
let frame = containerView?.frame ?? .zero
let size = CGSize(width: frame.size.width, height: frame.height - anchoredYPosition)
presentedViewController.view.frame = CGRect(origin: .zero, size: size)
guard let frame = containerView?.frame
else { return }
let adjustedSize = CGSize(width: frame.size.width, height: frame.size.height - anchoredYPosition)
panContainerView.frame.size = frame.size
presentedViewController.view.frame = CGRect(origin: .zero, size: adjustedSize)
}
/**
@@ -802,7 +826,7 @@ extension PanModalPresentationController: UIGestureRecognizerDelegate {
is a pan gesture recognizer
*/
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
return otherGestureRecognizer == panGestureRecognizer
}
}
@@ -73,7 +73,6 @@ extension PanModalPresentationDelegate: UIAdaptivePresentationControllerDelegate
Dismisses the presented view controller
*/
public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
controller.presentedViewController.dismiss(animated: false, completion: nil)
return .none
}
@@ -38,6 +38,14 @@ public extension PanModalPresentable where Self: UIViewController {
return 0.8
}
var transitionDuration: Double {
return PanModalAnimator.Constants.defaultTransitionDuration
}
var transitionAnimationOptions: UIView.AnimationOptions {
return [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState]
}
var backgroundAlpha: CGFloat {
return 0.7
}
+17 -1
View File
@@ -18,7 +18,7 @@ import UIKit
}
```
*/
public protocol PanModalPresentable {
public protocol PanModalPresentable: AnyObject {
/**
The scroll view embedded in the view controller.
@@ -69,6 +69,22 @@ public protocol PanModalPresentable {
*/
var springDamping: CGFloat { get }
/**
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 alpha.
+1 -1
View File
@@ -17,7 +17,7 @@ import UIKit
sourceRect: .zero)
```
*/
public protocol PanModalPresenter {
protocol PanModalPresenter: AnyObject {
/**
A flag that returns true if the current presented view controller
+5 -2
View File
@@ -20,8 +20,9 @@ class PanContainerView: UIView {
addSubview(presentedView)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError()
fatalError("init(coder:) has not been implemented")
}
}
@@ -33,7 +34,9 @@ extension UIView {
from the view hierachy
*/
var panContainerView: PanContainerView? {
return subviews.compactMap({ $0 as? PanContainerView }).first
return subviews.first(where: { view -> Bool in
view is PanContainerView
}) as? PanContainerView
}
}
+21 -1
View File
@@ -41,6 +41,7 @@
943904ED2226366700859537 /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EC2226366700859537 /* AlertViewController.swift */; };
943904EF2226383700859537 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943904EE2226383700859537 /* NavigationController.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 */; };
94795C9D21F03368008045A0 /* PanContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94795C9C21F03368008045A0 /* PanContainerView.swift */; };
DC13905E216D90D5007A3E64 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DC13905D216D90D5007A3E64 /* Assets.xcassets */; };
@@ -112,6 +113,7 @@
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>"; };
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>"; };
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>"; };
@@ -238,6 +240,22 @@
path = Presenter;
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 */ = {
isa = PBXGroup;
children = (
@@ -310,7 +328,8 @@
DC139079216D9AAA007A3E64 /* View Controllers */ = {
isa = PBXGroup;
children = (
943904EA2226354100859537 /* BasicViewController.swift */,
944EBA2B227BB7D900C4C97B /* Basic */,
944EBA2C227BB7E100C4C97B /* Full Screen */,
DC3B2EBB222A5882000C8A4A /* Alert */,
DC3B2EBC222A5893000C8A4A /* Alert (Transient) */,
743CB2AB222661EA00665A55 /* User Groups (Stacked) */,
@@ -567,6 +586,7 @@
DC139075216D9458007A3E64 /* DimmedView.swift in Sources */,
743CABD322265F2E00634A5A /* ProfileViewController.swift in Sources */,
DC139070216D9458007A3E64 /* PanModalPresentationAnimator.swift in Sources */,
944EBA2E227BB7F400C4C97B /* FullScreenNavController.swift in Sources */,
DCA741AE216D90410021F2F2 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
+19 -12
View File
@@ -67,6 +67,7 @@ private extension SampleViewController {
enum RowType: Int, CaseIterable {
case basic
case fullScreen
case alert
case transientAlert
case userGroups
@@ -77,6 +78,7 @@ private extension SampleViewController {
var presentable: RowPresentable {
switch self {
case .basic: return Basic()
case .fullScreen: return FullScreen()
case .alert: return Alert()
case .transientAlert: return TransientAlert()
case .userGroups: return UserGroup()
@@ -86,33 +88,38 @@ private extension SampleViewController {
}
struct Basic: RowPresentable {
var string: String { return "Basic" }
var rowVC: PanModalPresentable.LayoutType { return BasicViewController() }
let string: String = "Basic"
let rowVC: PanModalPresentable.LayoutType = BasicViewController()
}
struct FullScreen: RowPresentable {
let string: String = "Full Screen"
let rowVC: PanModalPresentable.LayoutType = FullScreenNavController()
}
struct Alert: RowPresentable {
var string: String { return "Alert" }
var rowVC: PanModalPresentable.LayoutType { return AlertViewController() }
let string: String = "Alert"
let rowVC: PanModalPresentable.LayoutType = AlertViewController()
}
struct TransientAlert: RowPresentable {
var string: String { return "Alert (Transient)"}
var rowVC: PanModalPresentable.LayoutType { return TransientAlertViewController() }
let string: String = "Alert (Transient)"
let rowVC: PanModalPresentable.LayoutType = TransientAlertViewController()
}
struct UserGroup: RowPresentable {
var string: String { return "User Groups" }
var rowVC: PanModalPresentable.LayoutType { return UserGroupViewController() }
let string: String = "User Groups"
let rowVC: PanModalPresentable.LayoutType = UserGroupViewController()
}
struct Navigation: RowPresentable {
var string: String { return "User Groups (NavigationController)" }
var rowVC: PanModalPresentable.LayoutType { return NavigationController() }
let string: String = "User Groups (NavigationController)"
let rowVC: PanModalPresentable.LayoutType = NavigationController()
}
struct Stacked: RowPresentable {
var string: String { return "User Groups (Stacked)" }
var rowVC: PanModalPresentable.LayoutType { return UserGroupStackedViewController() }
let string: String = "User Groups (Stacked)"
let rowVC: PanModalPresentable.LayoutType = UserGroupStackedViewController()
}
}
}
@@ -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
}
}
@@ -8,7 +8,7 @@
import UIKit
class UserGroupViewController: UITableViewController, PanModalPresentable, UIGestureRecognizerDelegate {
class UserGroupViewController: UITableViewController, PanModalPresentable {
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)),
+2
View File
@@ -59,6 +59,8 @@ class PanModalTests: XCTestCase {
XCTAssertEqual(vc.showDragIndicator, false)
XCTAssertEqual(vc.shouldRoundTopCorners, false)
XCTAssertEqual(vc.cornerRadius, 8.0)
XCTAssertEqual(vc.transitionDuration, PanModalAnimator.Constants.defaultTransitionDuration)
XCTAssertEqual(vc.transitionAnimationOptions, [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState])
}
func testPresentableYValues() {