### Installation Source: https://context7.com/slackhq/panmodal/llms.txt Install PanModal using your preferred dependency manager. ```APIDOC ## Installation Install PanModal using your preferred dependency manager. ```ruby # CocoaPods - add to Podfile pod 'PanModal' # Carthage - add to Cartfile github "slackhq/PanModal" ``` ```swift // Swift Package Manager - add to Package.swift dependencies dependencies: [ .package(url: "https://github.com/slackhq/PanModal.git", .exact("1.2.6")), ] ``` ``` -------------------------------- ### Install PanModal with CocoaPods Source: https://github.com/slackhq/panmodal/blob/master/README.md Use this command to add PanModal to your project via CocoaPods. ```ruby pod 'PanModal' ``` -------------------------------- ### Install PanModal with Carthage Source: https://github.com/slackhq/panmodal/blob/master/README.md Integrate PanModal into your project using Carthage by adding this line to your Cartfile. ```ruby github "slackhq/PanModal" ``` -------------------------------- ### Install PanModal with Swift Package Manager Source: https://github.com/slackhq/panmodal/blob/master/README.md Add PanModal as a dependency in your Swift Package Manager configuration. ```swift dependencies: [ .package(url: "https://github.com/slackhq/PanModal.git", .exact("1.2.6")), ] ``` -------------------------------- ### UINavigationController with Pan Modal Source: https://context7.com/slackhq/panmodal/llms.txt Wrap a navigation stack in a PanModal for multi-screen flows. This example shows how to update the modal layout when view controllers are pushed or popped. ```swift import PanModal class ModalNavigationController: UINavigationController, PanModalPresentable { init(rootViewController: UIViewController & PanModalPresentable) { super.init(rootViewController: rootViewController) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // Update pan modal layout when pushing/popping view controllers override func pushViewController(_ viewController: UIViewController, animated: Bool) { super.pushViewController(viewController, animated: animated) panModalSetNeedsLayoutUpdate() } override func popViewController(animated: Bool) -> UIViewController? { let vc = super.popViewController(animated: animated) panModalSetNeedsLayoutUpdate() return vc } // MARK: - PanModalPresentable // Delegate to top view controller's scroll view var panScrollable: UIScrollView? { return (topViewController as? PanModalPresentable)?.panScrollable } var longFormHeight: PanModalHeight { return .maxHeight } var shortFormHeight: PanModalHeight { return longFormHeight } } // Usage class MainVC: UIViewController { func presentNavigationModal() { let listVC = UserListViewController() let navController = ModalNavigationController(rootViewController: listVC) presentPanModal(navController) } } ``` -------------------------------- ### Create Full-Screen Modal Source: https://context7.com/slackhq/panmodal/llms.txt Implement full-screen modals without rounded corners or drag indicators. This example uses a UINavigationController as the modal content. ```swift import PanModal class FullScreenModalController: UINavigationController, PanModalPresentable { override func viewDidLoad() { super.viewDidLoad() let contentVC = UIViewController() contentVC.view.backgroundColor = .white contentVC.title = "Full Screen" pushViewController(contentVC, animated: false) } var panScrollable: UIScrollView? { return nil } var topOffset: CGFloat { return 0.0 // No offset from top } var springDamping: CGFloat { return 1.0 // No bounce } var transitionDuration: Double { return 0.4 } var transitionAnimationOptions: UIView.AnimationOptions { return [.allowUserInteraction, .beginFromCurrentState] } var shouldRoundTopCorners: Bool { return false } var showDragIndicator: Bool { return false } } ``` -------------------------------- ### PanModalHeight Enum Examples Source: https://context7.com/slackhq/panmodal/llms.txt Define modal heights using the `PanModalHeight` enum. Options include fixed content height, content height ignoring safe area, maximum height, maximum height with top inset, and intrinsic height. ```swift import PanModal class HeightExamplesViewController: UIViewController, PanModalPresentable { var panScrollable: UIScrollView? { return nil } var shortFormHeight: PanModalHeight { // Option 1: Fixed content height return .contentHeight(300) // Option 2: Content height ignoring safe area // return .contentHeightIgnoringSafeArea(300) // Option 3: Maximum height // return .maxHeight // Option 4: Maximum height with top inset // return .maxHeightWithTopInset(100) // Option 5: Intrinsic content height (auto-calculated) // return .intrinsicHeight } var longFormHeight: PanModalHeight { return .maxHeightWithTopInset(40) } } ``` -------------------------------- ### Present a PanModal View Controller Source: https://github.com/slackhq/panmodal/blob/master/README.md Call `presentPanModal` to display a view controller conforming to PanModalPresentable. This is the standard way to present a modal. ```swift .presentPanModal(yourViewController) ``` -------------------------------- ### Create Alert-Style Modal Source: https://context7.com/slackhq/panmodal/llms.txt Implement compact alert-style modals with custom height and background dimming. Ensure PanModal is imported and the view controller conforms to PanModalPresentable. ```swift import PanModal class AlertModalViewController: UIViewController, PanModalPresentable { private let alertHeight: CGFloat = 80 private let alertLabel: UILabel = { let label = UILabel() label.text = "Action completed successfully!" label.textAlignment = .center label.translatesAutoresizingMaskIntoConstraints = false return label }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground view.layer.cornerRadius = 12 view.addSubview(alertLabel) NSLayoutConstraint.activate([ alertLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), alertLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), alertLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), alertLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20) ]) } // MARK: - PanModalPresentable var panScrollable: UIScrollView? { return nil } var shortFormHeight: PanModalHeight { return .contentHeight(alertHeight) } var longFormHeight: PanModalHeight { return shortFormHeight // Same as short form - no expansion } var panModalBackgroundColor: UIColor { return UIColor.black.withAlphaComponent(0.1) // Light dim } var shouldRoundTopCorners: Bool { return false // Handle corners manually } var showDragIndicator: Bool { return true } var anchorModalToLongForm: Bool { return false } } ``` -------------------------------- ### Manage Runtime Updates and Transitions Source: https://context7.com/slackhq/panmodal/llms.txt Utilizes panModalSetNeedsLayoutUpdate and panModalTransition to handle dynamic height changes and state transitions. ```swift import PanModal class DynamicModalViewController: UIViewController, PanModalPresentable { private var hasLoaded = false private var currentHeight: CGFloat = 400 var panScrollable: UIScrollView? { return nil } override func viewDidLoad() { super.viewDidLoad() // Mark as loaded and update layout hasLoaded = true panModalSetNeedsLayoutUpdate() // Programmatically transition to short form panModalTransition(to: .shortForm) } var shortFormHeight: PanModalHeight { return hasLoaded ? .contentHeight(200) : .maxHeight } var longFormHeight: PanModalHeight { return .contentHeight(currentHeight) } // Dynamically change height func expandModal() { currentHeight = 600 panModalSetNeedsLayoutUpdate() panModalTransition(to: .longForm) } // Collapse to short form func collapseModal() { panModalTransition(to: .shortForm) } // Perform scroll view updates without causing jumps func updateScrollContent(_ scrollView: UIScrollView, with newItems: [String]) { panModalPerformUpdates { // Scroll observation is temporarily disabled during this block scrollView.reloadData() // For UITableView/UICollectionView } } } ``` -------------------------------- ### Present a Pan Modal Source: https://context7.com/slackhq/panmodal/llms.txt Present any view controller as a bottom sheet modal using `presentPanModal`. The presented controller must conform to `PanModalPresentable`. Supports completion handlers and iPad popover configurations. ```swift import PanModal // Present a pan modal from any view controller class MainViewController: UIViewController { func showModal() { let modalVC = MyModalViewController() presentPanModal(modalVC) } // With completion handler func showModalWithCompletion() { let modalVC = MyModalViewController() presentPanModal(modalVC) { print("Modal presentation completed") } } // For iPad - specify source view and rect for popover func showModalOnIPad(from button: UIButton) { let modalVC = MyModalViewController() presentPanModal(modalVC, sourceView: button, sourceRect: button.bounds) } } ``` -------------------------------- ### Presenting a Pan Modal Source: https://context7.com/slackhq/panmodal/llms.txt Methods to present a view controller as a bottom sheet modal using the PanModal library. ```APIDOC ## [METHOD] presentPanModal ### Description Present any UIViewController as a bottom sheet modal. The presented controller must conform to the PanModalPresentable protocol. ### Method Swift Method ### Parameters #### Request Body - **modalVC** (UIViewController) - Required - The view controller to present, must conform to PanModalPresentable. - **completion** (Closure) - Optional - A completion handler called after the presentation finishes. - **sourceView** (UIView) - Optional - The source view for iPad popover presentation. - **sourceRect** (CGRect) - Optional - The source rect for iPad popover presentation. ``` -------------------------------- ### Gesture Recognizer Delegate Methods Source: https://context7.com/slackhq/panmodal/llms.txt Override delegate methods to customize pan gesture behavior and respond to state transitions. ```APIDOC ## Gesture Recognizer Delegate Methods Override delegate methods to customize pan gesture behavior and respond to state transitions. ```swift import PanModal class CustomGestureViewController: UIViewController, PanModalPresentable { private let headerView = UIView() var panScrollable: UIScrollView? { return nil } // Control whether the pan modal responds to the gesture func shouldRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool { // Return false to disable pan modal movement while maintaining gestures on content return true } // Called when gesture recognizer state is .began or .changed func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) { // Prepare for pan gesture - e.g., hide keyboard, pause animations view.endEditing(true) } // Prioritize pan modal gesture over scroll view in specific regions func shouldPrioritize(panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool { // Prioritize dragging from header area let location = panModalGestureRecognizer.location(in: view) return headerView.frame.contains(location) } // Control transitions between presentation states func shouldTransition(to state: PanModalPresentationController.PresentationState) -> Bool { // Prevent transition to short form under certain conditions if case .shortForm = state { return !isEditing } return true } // Called just before transitioning to a new state func willTransition(to state: PanModalPresentationController.PresentationState) { switch state { case .shortForm: print("Transitioning to short form") case .longForm: print("Transitioning to long form") } } // Called just before dismissal begins func panModalWillDismiss() { // Save state, cancel operations, etc. print("Modal will dismiss") } // Called after dismissal completes func panModalDidDismiss() { // Cleanup, notify delegates, etc. print("Modal did dismiss") } } ``` ``` -------------------------------- ### Custom Animations Source: https://context7.com/slackhq/panmodal/llms.txt Use `panModalAnimate` for consistent animation behavior within your presented view controller. ```APIDOC ## Custom Animations Use `panModalAnimate` for consistent animation behavior within your presented view controller. ```swift import PanModal class AnimatedModalViewController: UIViewController, PanModalPresentable { private let contentView = UIView() var panScrollable: UIScrollView? { return nil } func animateContentChange() { panModalAnimate({ // Animations use the same duration, spring damping, and options // as the pan modal transitions self.contentView.alpha = 0.5 self.contentView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) }) { completed in if completed { print("Animation finished") } } } func resetContent() { panModalAnimate({ self.contentView.alpha = 1.0 self.contentView.transform = .identity }) } } ``` ``` -------------------------------- ### Updating PanModal Layout at Runtime Source: https://github.com/slackhq/panmodal/blob/master/README.md Call `panModalSetNeedsLayoutUpdate()` and `panModalTransition(to:)` after modifying height properties in `viewDidLoad` or other lifecycle methods to reflect changes. ```swift func viewDidLoad() { hasLoaded = true panModalSetNeedsLayoutUpdate() panModalTransition(to: .shortForm) } var shortFormHeight: PanModalHeight { if hasLoaded { return .contentHeight(200) } return .maxHeight } ``` -------------------------------- ### Customize Pan Gesture Behavior Source: https://context7.com/slackhq/panmodal/llms.txt Override delegate methods to customize pan gesture behavior and respond to state transitions. Use `shouldRespond` to control gesture responsiveness and `willRespond` to prepare for gestures. ```swift import PanModal class CustomGestureViewController: UIViewController, PanModalPresentable { private let headerView = UIView() var panScrollable: UIScrollView? { return nil } // Control whether the pan modal responds to the gesture func shouldRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool { // Return false to disable pan modal movement while maintaining gestures on content return true } // Called when gesture recognizer state is .began or .changed func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) { // Prepare for pan gesture - e.g., hide keyboard, pause animations view.endEditing(true) } // Prioritize pan modal gesture over scroll view in specific regions func shouldPrioritize(panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool { // Prioritize dragging from header area let location = panModalGestureRecognizer.location(in: view) return headerView.frame.contains(location) } // Control transitions between presentation states func shouldTransition(to state: PanModalPresentationController.PresentationState) -> Bool { // Prevent transition to short form under certain conditions if case .shortForm = state { return !isEditing } return true } // Called just before transitioning to a new state func willTransition(to state: PanModalPresentationController.PresentationState) { switch state { case .shortForm: print("Transitioning to short form") case .longForm: print("Transitioning to long form") } } // Called just before dismissal begins func panModalWillDismiss() { // Save state, cancel operations, etc. print("Modal will dismiss") } // Called after dismissal completes func panModalDidDismiss() { // Cleanup, notify delegates, etc. print("Modal did dismiss") } } ``` -------------------------------- ### Conform to PanModalPresentable Protocol Source: https://github.com/slackhq/panmodal/blob/master/README.md Make your view controller conform to `PanModalPresentable` to enable PanModal's features. The `panScrollable` property should return nil if no scrollable view is embedded. ```swift extension YourViewController: PanModalPresentable { var panScrollable: UIScrollView? { return nil } } ``` -------------------------------- ### Implement UITableViewController with PanModal Source: https://context7.com/slackhq/panmodal/llms.txt Integrates a table view with PanModal by returning the table view in the panScrollable property for seamless gesture transitions. ```swift import PanModal class UserListViewController: UITableViewController, PanModalPresentable { private var users: [String] = ["Alice", "Bob", "Charlie", "Diana", "Eve"] private var isShortFormEnabled = true override func viewDidLoad() { super.viewDidLoad() tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") tableView.backgroundColor = .systemBackground } // MARK: - UITableViewDataSource override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return users.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = users[indexPath.row] return cell } // MARK: - PanModalPresentable var panScrollable: UIScrollView? { return tableView // Enables seamless scroll-to-pan transition } var shortFormHeight: PanModalHeight { return isShortFormEnabled ? .contentHeight(300) : longFormHeight } var longFormHeight: PanModalHeight { return .maxHeightWithTopInset(40) } var anchorModalToLongForm: Bool { return false } var scrollIndicatorInsets: UIEdgeInsets { return UIEdgeInsets(top: 8, left: 0, bottom: bottomLayoutOffset, right: 0) } // Disable short form after expanding to long form func willTransition(to state: PanModalPresentationController.PresentationState) { guard isShortFormEnabled, case .longForm = state else { return } isShortFormEnabled = false panModalSetNeedsLayoutUpdate() } } ``` -------------------------------- ### Custom Animations with panModalAnimate Source: https://context7.com/slackhq/panmodal/llms.txt Use `panModalAnimate` for consistent animation behavior within your presented view controller. Animations use the same duration, spring damping, and options as the pan modal transitions. ```swift import PanModal class AnimatedModalViewController: UIViewController, PanModalPresentable { private let contentView = UIView() var panScrollable: UIScrollView? { return nil } func animateContentChange() { panModalAnimate({ // Animations use the same duration, spring damping, and options // as the pan modal transitions self.contentView.alpha = 0.5 self.contentView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) }) { completed in if completed { print("Animation finished") } } } func resetContent() { panModalAnimate({ self.contentView.alpha = 1.0 self.contentView.transform = .identity }) } } ``` -------------------------------- ### PanModalPresentable Protocol Conformance Source: https://context7.com/slackhq/panmodal/llms.txt Conform to `PanModalPresentable` to customize modal behavior. The `panScrollable` property is required; others have default values for height, visuals, animations, and behavior. ```swift import PanModal class MyModalViewController: UIViewController, PanModalPresentable { // REQUIRED: Return the embedded scroll view or nil var panScrollable: UIScrollView? { return nil } // OPTIONAL: All properties below have default values // Height configuration var shortFormHeight: PanModalHeight { return .contentHeight(300) // Fixed height of 300pt } var longFormHeight: PanModalHeight { return .maxHeightWithTopInset(40) // Full height with 40pt from top } var topOffset: CGFloat { return topLayoutOffset + 21.0 // Default offset from top } // Visual customization var cornerRadius: CGFloat { return 8.0 // Default corner radius } var shouldRoundTopCorners: Bool { return true } var showDragIndicator: Bool { return true } var dragIndicatorBackgroundColor: UIColor { return .lightGray } var panModalBackgroundColor: UIColor { return UIColor.black.withAlphaComponent(0.7) } // Animation configuration var springDamping: CGFloat { return 0.8 } var transitionDuration: Double { return 0.5 } var transitionAnimationOptions: UIView.AnimationOptions { return [.curveEaseInOut, .allowUserInteraction, .beginFromCurrentState] } // Behavior configuration var anchorModalToLongForm: Bool { return true } var allowsExtendedPanScrolling: Bool { return false } var allowsDragToDismiss: Bool { return true } var allowsTapToDismiss: Bool { return true } var isUserInteractionEnabled: Bool { return true } var isHapticFeedbackEnabled: Bool { return true } } ``` -------------------------------- ### PanModalPresentable Protocol Source: https://context7.com/slackhq/panmodal/llms.txt Protocol used to customize the behavior and appearance of the presented bottom sheet. ```APIDOC ## [PROTOCOL] PanModalPresentable ### Description Conform to this protocol to define the behavior, height, and visual properties of the PanModal. ### Properties - **panScrollable** (UIScrollView?) - Required - The embedded scroll view to coordinate with pan gestures. - **shortFormHeight** (PanModalHeight) - Optional - The height of the modal in its short form state. - **longFormHeight** (PanModalHeight) - Optional - The height of the modal in its long form state. - **cornerRadius** (CGFloat) - Optional - The corner radius of the top edges. - **showDragIndicator** (Bool) - Optional - Whether to display the drag indicator handle. - **allowsDragToDismiss** (Bool) - Optional - Whether the modal can be dismissed by dragging down. ``` -------------------------------- ### Customizing PanModal Heights Source: https://github.com/slackhq/panmodal/blob/master/README.md Override `shortFormHeight` and `longFormHeight` to define the modal's dimensions. Use `.contentHeight` for fixed heights or `.maxHeightWithTopInset` for dynamic heights. ```swift var shortFormHeight: PanModalHeight { return .contentHeight(300) } var longFormHeight: PanModalHeight { return .maxHeightWithTopInset(40) } ``` -------------------------------- ### PanModal with Embedded UIScrollView Source: https://github.com/slackhq/panmodal/blob/master/README.md If your view controller contains a scrollable view like `UITableView`, return it from `panScrollable` for seamless gesture transitions. ```swift class TableViewController: UITableViewController, PanModalPresentable { var panScrollable: UIScrollView? { return tableView } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.