Skip to content
This repository was archived by the owner on Nov 3, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions PanModal/Animator/PanModalAnimator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,29 @@ import UIKit
*/
struct PanModalAnimator {

/**
Constant Animation Properties
*/
struct Constants {
static let defaultTransitionDuration: TimeInterval = 0.5
static func makeDefaultAnimator() -> UIViewPropertyAnimator {
// Note that `duration` is ignored when using timing parameters.
// The duration is derived from the parameters.
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: UISpringTimingParameters())
animator.isUserInteractionEnabled = true
return animator
}

static func animate(_ animations: @escaping PanModalPresentable.AnimationBlockType,
config: PanModalPresentable?,
_ completion: PanModalPresentable.AnimationCompletionType? = nil) {

let transitionDuration = config?.transitionDuration ?? Constants.defaultTransitionDuration
let springDamping = config?.springDamping ?? 1.0
let animationOptions = config?.transitionAnimationOptions ?? []

UIView.animate(withDuration: transitionDuration,
delay: 0,
usingSpringWithDamping: springDamping,
initialSpringVelocity: 0,
options: animationOptions,
animations: animations,
completion: completion)
let animator = config?.makeAnimator() ?? makeDefaultAnimator()

animator.addAnimations(animations)

if let completion = completion {
animator.addCompletion { position in
completion(position == .end)
}
}

animator.startAnimation()
}
}
#endif
4 changes: 2 additions & 2 deletions PanModal/Animator/PanModalPresentationAnimator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ extension PanModalPresentationAnimator: UIViewControllerAnimatedTransitioning {
guard
let context = transitionContext,
let presentable = panModalLayoutType(from: context)
else { return PanModalAnimator.Constants.defaultTransitionDuration }
else { return PanModalAnimator.makeDefaultAnimator().duration }

return presentable.transitionDuration
return presentable.makeAnimator().duration
}

/**
Expand Down
16 changes: 4 additions & 12 deletions PanModal/Presentable/PanModalPresentable+Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,6 @@ public extension PanModalPresentable where Self: UIViewController {
return 8.0
}

var springDamping: CGFloat {
return 0.8
}

var transitionDuration: Double {
return PanModalAnimator.Constants.defaultTransitionDuration
}

var transitionAnimationOptions: UIView.AnimationOptions {
return [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState]
}

var panModalBackgroundColor: UIColor {
return UIColor.black.withAlphaComponent(0.7)
}
Expand Down Expand Up @@ -97,6 +85,10 @@ public extension PanModalPresentable where Self: UIViewController {
return shouldRoundTopCorners
}

func makeAnimator() -> UIViewPropertyAnimator {
PanModalAnimator.makeDefaultAnimator()
}

func shouldRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool {
return true
}
Expand Down
31 changes: 7 additions & 24 deletions PanModal/Presentable/PanModalPresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,30 +62,6 @@ public protocol PanModalPresentable: AnyObject {
*/
var cornerRadius: CGFloat { get }

/**
The springDamping value used to determine the amount of 'bounce'
seen when transitioning to short/long form.

Default Value is 0.8.
*/
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 color.

Expand Down Expand Up @@ -173,6 +149,13 @@ public protocol PanModalPresentable: AnyObject {
*/
var showDragIndicator: Bool { get }

/**
Asks the delegate to create a property animator to use when transitioning to short/long form.

Default implementation creates an animator with the default UISpringTimingParameters, which matches iOS system animations.
*/
func makeAnimator() -> UIViewPropertyAnimator

/**
Asks the delegate if the pan modal should respond to the pan modal gesture recognizer.

Expand Down
12 changes: 6 additions & 6 deletions PanModalDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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 */; };
Expand All @@ -23,6 +22,8 @@
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 */; };
27B8DDA725E2F4F9009F9BAA /* PanModalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B8DDA625E2F4F9009F9BAA /* PanModalAnimator.swift */; };
27B8DDAA25E2F6DD009F9BAA /* PanModalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27B8DDA625E2F4F9009F9BAA /* PanModalAnimator.swift */; };
743CABB02225FC9F00634A5A /* UserGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABAF2225FC9F00634A5A /* UserGroupViewController.swift */; };
743CABB22225FD1100634A5A /* UserGroupHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB12225FD1100634A5A /* UserGroupHeaderView.swift */; };
743CABB42225FE7700634A5A /* UserGroupMemberPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */; };
Expand All @@ -33,7 +34,6 @@
743CABC72226171500634A5A /* PanModalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABC62226171500634A5A /* PanModalTests.swift */; };
743CABD322265F2E00634A5A /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CABD222265F2E00634A5A /* ProfileViewController.swift */; };
743CB2AA222660D100665A55 /* StackedProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743CB2A9222660D100665A55 /* StackedProfileViewController.swift */; };
74C072A3220BA6E500124CE1 /* PanModalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A2220BA6E500124CE1 /* PanModalAnimator.swift */; };
74C072A5220BA76D00124CE1 /* PanModalHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A4220BA76D00124CE1 /* PanModalHeight.swift */; };
74C072A7220BA78800124CE1 /* PanModalPresentable+LayoutHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A6220BA78800124CE1 /* PanModalPresentable+LayoutHelpers.swift */; };
74C072AA220BA82A00124CE1 /* UIViewController+PanModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74C072A9220BA82A00124CE1 /* UIViewController+PanModalPresenter.swift */; };
Expand Down Expand Up @@ -93,6 +93,7 @@
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>"; };
27B8DDA625E2F4F9009F9BAA /* PanModalAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanModalAnimator.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>"; };
743CABB32225FE7700634A5A /* UserGroupMemberPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGroupMemberPresentable.swift; sourceTree = "<group>"; };
Expand All @@ -105,7 +106,6 @@
743CABC82226171500634A5A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
743CABD222265F2E00634A5A /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
743CB2A9222660D100665A55 /* StackedProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackedProfileViewController.swift; sourceTree = "<group>"; };
74C072A2220BA6E500124CE1 /* PanModalAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanModalAnimator.swift; sourceTree = "<group>"; };
74C072A4220BA76D00124CE1 /* PanModalHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanModalHeight.swift; sourceTree = "<group>"; };
74C072A6220BA78800124CE1 /* PanModalPresentable+LayoutHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PanModalPresentable+LayoutHelpers.swift"; sourceTree = "<group>"; };
74C072A9220BA82A00124CE1 /* UIViewController+PanModalPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+PanModalPresenter.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -290,7 +290,7 @@
DC139065216D9458007A3E64 /* Animator */ = {
isa = PBXGroup;
children = (
74C072A2220BA6E500124CE1 /* PanModalAnimator.swift */,
27B8DDA625E2F4F9009F9BAA /* PanModalAnimator.swift */,
DC139066216D9458007A3E64 /* PanModalPresentationAnimator.swift */,
);
path = Animator;
Expand Down Expand Up @@ -531,12 +531,12 @@
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 */,
27B8DDA725E2F4F9009F9BAA /* PanModalAnimator.swift in Sources */,
0F2A2C642239C14E003BDB2F /* PanModalPresentable+Defaults.swift in Sources */,
0F2A2C652239C151003BDB2F /* PanModalPresentable+UIViewController.swift in Sources */,
0F2A2C662239C153003BDB2F /* PanModalPresentable+LayoutHelpers.swift in Sources */,
Expand All @@ -561,13 +561,13 @@
files = (
DC3B2EBA222A560A000C8A4A /* TransientAlertViewController.swift in Sources */,
743CABB42225FE7700634A5A /* UserGroupMemberPresentable.swift in Sources */,
74C072A3220BA6E500124CE1 /* PanModalAnimator.swift in Sources */,
743CABB8222600C600634A5A /* UserGroupMemberCell.swift in Sources */,
743CABB62225FEEE00634A5A /* UserGroupHeaderPresentable.swift in Sources */,
743CB2AA222660D100665A55 /* StackedProfileViewController.swift in Sources */,
943904EB2226354100859537 /* BasicViewController.swift in Sources */,
DC139073216D9458007A3E64 /* PanModalPresenter.swift in Sources */,
943904EF2226383700859537 /* NavigationController.swift in Sources */,
27B8DDAA25E2F6DD009F9BAA /* PanModalAnimator.swift in Sources */,
DC3B2EBE222A58C9000C8A4A /* AlertView.swift in Sources */,
74C072A5220BA76D00124CE1 /* PanModalHeight.swift in Sources */,
94795C9B21F0335D008045A0 /* PanModalPresentationDelegate.swift in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions Sample/View Controllers/Alert/AlertViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,10 @@ class AlertViewController: UIViewController, PanModalPresentable {
var isUserInteractionEnabled: Bool {
return true
}

func makeAnimator() -> UIViewPropertyAnimator {
let animator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.7, animations: nil)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think notifications are a great place for springy animations, and provided an example of a custom animator there.

animator.isUserInteractionEnabled = true
return animator
}
}
12 changes: 0 additions & 12 deletions Sample/View Controllers/Full Screen/FullScreenNavController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,6 @@ extension FullScreenNavController: PanModalPresentable {
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
}
Expand Down
3 changes: 0 additions & 3 deletions Tests/PanModalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class PanModalTests: XCTestCase {
XCTAssertEqual(vc.topOffset, 41.0)
XCTAssertEqual(vc.shortFormHeight, PanModalHeight.maxHeight)
XCTAssertEqual(vc.longFormHeight, PanModalHeight.maxHeight)
XCTAssertEqual(vc.springDamping, 0.8)
XCTAssertEqual(vc.panModalBackgroundColor, UIColor.black.withAlphaComponent(0.7))
XCTAssertEqual(vc.dragIndicatorBackgroundColor, UIColor.lightGray)
XCTAssertEqual(vc.scrollIndicatorInsets, .zero)
Expand All @@ -61,8 +60,6 @@ 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() {
Expand Down