PanModal icon indicating copy to clipboard operation
PanModal copied to clipboard

Pan modal with fixed view bottom

Open CavalcanteLeo opened this issue 2 years ago • 3 comments

Is it possible to open panModal with fixed view bottom like instagram?

https://user-images.githubusercontent.com/1735944/169672201-4abdf405-a2d7-4048-badd-4dd36ba8e09c.MOV

CavalcanteLeo avatar May 21 '22 23:05 CavalcanteLeo

I have the same problem with this issue. Can anyone help us?

anh-le9-tiki avatar Jun 10 '22 13:06 anh-le9-tiki

In PanModelPresentable you can find topOffset that will help you

concentrat1on avatar Jun 29 '22 18:06 concentrat1on

Hi there! I spent a few hours trying to solve this problem. Here is my solution. Possibly it can save you some hours of work :)

import UIKit
import SnapKit
import PanModal

class ExampleViewController: UIViewController, PanModalPresentable {
    
    // MARK: - Outlets
    
    private lazy var tableView: UITableView = {
        let view = UITableView(frame: .zero, style: .grouped)
        view.dataSource = self
        view.alwaysBounceVertical = false
        view.contentInsetAdjustmentBehavior = .never
        view.automaticallyAdjustsScrollIndicatorInsets = false
        view.insetsContentViewsToSafeArea = false
        return view
    }()
    
    private lazy var footerView: UIView = {
        let view = UIView()
        view.backgroundColor = .systemYellow
        return view
    }()
    
    // MARK: - Variables
    
    private var isViewConfigured: Bool = false
    private var panModalGestureRecognizer: UIPanGestureRecognizer?

    private var contentHeight: CGFloat {
        return self.calculateContentHeight()
    }

    private var minContentHeight: CGFloat {
        return min(self.contentHeight, 300)
    }

    private var maxTopOffset: CGFloat {
        return UIScreen.main.bounds.height - self.minContentHeight
    }

    private var minTopOffset: CGFloat {
        return UIScreen.main.bounds.height - self.contentHeight
    }
    
    // MARK: - Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.configureSubviews()
        self.configureConstraints()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        let bottomInset = self.footerView.frame.height
        self.tableView.contentInset.bottom = bottomInset
        self.tableView.verticalScrollIndicatorInsets.bottom = bottomInset
        if !self.isViewConfigured {
            self.isViewConfigured = true
            self.pinFooter(for: .shortForm)
            self.panModalSetNeedsLayoutUpdate()
            self.panModalTransition(to: .shortForm)
        }
    }
    
    // MARK: - Configuration

    private func configureSubviews() {
        self.view.addSubview(self.tableView)
        self.view.addSubview(self.footerView)
    }

    private func configureConstraints() {
        self.tableView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        self.footerView.snp.makeConstraints { make in
            make.left.right.bottom.equalToSuperview()
            make.height.equalTo(100)
        }
    }
    
    // MARK: - Actions

    private func calculateContentHeight() -> CGFloat {
        return 1000 // Your calculated content height
    }
    
    private func pinFooter(for state: PanModalPresentationController.PresentationState) {
        let targetHeight: CGFloat = {
            switch state {
            case .longForm: return self.contentHeight
            case .shortForm: return self.minContentHeight
            }
        }()
        let offset = self.view.frame.height - targetHeight
        self.footerView.transform = CGAffineTransform(translationX: 0, y: min(-offset, 0))
    }
    
    // MARK: - PanModalPresentable

    func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) {
        self.panModalGestureRecognizer = panModalGestureRecognizer
        var yPanTranslation = panModalGestureRecognizer.translation(in: self.footerView).y
        if let presentedView = self.presentationController?.presentedView {
            let desiredOffset = presentedView.frame.minY + yPanTranslation
            if desiredOffset >= self.maxTopOffset {
                yPanTranslation = 0 // Disable fixing while hiding
            }
            if desiredOffset <= self.minTopOffset {
                yPanTranslation /= 2 // Compensation of bounce effect
            }
        }
        if yPanTranslation != 0 {
            self.footerView.transform = self.footerView.transform.translatedBy(x: 0, y: -yPanTranslation)
        }
    }

    func willTransition(to state: PanModalPresentationController.PresentationState) {
        guard self.panModalGestureRecognizer?.state != .changed else {
            self.pinFooter(for: state)
            return
        }
        self.panModalAnimate({ [weak self] in
            self?.pinFooter(for: state)
        })
    }

    var panScrollable: UIScrollView? {
        return self.tableView
    }

    var longFormHeight: PanModalHeight {
        return .contentHeightIgnoringSafeArea(self.contentHeight)
    }

    var shortFormHeight: PanModalHeight {
        return .contentHeightIgnoringSafeArea(self.minContentHeight)
    }
    
}

ilia3546 avatar Sep 05 '22 13:09 ilia3546