intuit / CardParts

A reactive, card-based UI framework built on UIKit for iOS developers.
Other
2.52k stars 224 forks source link

Auto Resizing Issue on Modal Views Presented in formSheet Style #221

Open rborquezdiaz opened 4 years ago

rborquezdiaz commented 4 years ago

Describe the bug

There seems to be an issue with Autolayout behavior when the CardsViewController is presented modally using a modalPresentationStyle of types: formSheet or actionSheet. I haven't tried it on iPhone but it's really evident on iPads running iOS13 (Since formSheet is the new automatic behavior). Also worth noting, if the device is rotated, Autolayout will then rearrange the elements to another incorrect state that will remain until the controller is dismissed.

If the modal view controller is presented on fullScreen, there is no issue whatsoever.

To Reproduce Steps to reproduce the behavior:

  1. Create a CardsViewController
  2. Create and initialize multiple CardPartsViewControllers instances.
  3. Load CardPartsViewControllers into the CardsViewController.
  4. Present CardsViewController modally with a modal presentation style of: .formSheet or .pageSheet.

Expected behavior The elements should be displayed in the same way as if they were presented in .fullscreen.

Screenshots Fullscreen Presentation Portrait IMG_0011

Fullscreen Presentation Landscape IMG_0012

Formsheet Presentation Portrait Before Rotation IMG_0006

Formsheet Presentation Landscape After Rotation IMG_0007

Formsheet Presentation Portait After Rotation IMG_0008

Info (please complete the following information):

Additional context Will answer through responese.

croossin commented 4 years ago

@rborquezdiaz Thank you for the very detailed bug description - we will try to take a look into this! Would be interesting to understand if you see the same issue if trying on an iPhone

rborquezdiaz commented 4 years ago

Thank you for answeing so fast and being so dilligent with updating the framework, hopefully it's just a mistake on my part of the implementation. I don't know if it's relevant, but it's being presented from a non RxSwift module (In other words, a classic presentation).

It's happening on iPhone as well. I'll attach a Screenshot.

File

croossin commented 4 years ago

@rborquezdiaz Do you mind sharing the code used to present and the code of the CardsViewController as well as the CardPartsViewController? This can help us debug the issue.

rborquezdiaz commented 4 years ago

@croossin Sure thing. One thing I noticed is that if I present the controller embeded in a Navigation Controller, it fixes the issue. But I seem to be having issues presenting the CardsViewController inside a ContainerView.

MainViewController (CardsViewController)

class MainViewController: CardsViewController {
    var isModallyPresented = false

    var productInfoCardController: ProductInfoCardController!
    var existenciaInfoCardController: ExistenciaInfoCardController!
    var ofertasCardController: OfertasCardController!

    let barcodeScannerController = BarcodeScannerController()

    @IBOutlet weak var barcodeButton: LGButton?

    let disposeBag = DisposeBag()

    let viewModel = MainViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

        barcodeScannerController.configureScanner()

        productInfoCardController = ProductInfoCardController()
        existenciaInfoCardController = ExistenciaInfoCardController()
        ofertasCardController = OfertasCardController()

        self.setupBindings()
        self.setupUI()

        let cards =  [productInfoCardController, existenciaInfoCardController, ofertasCardController]

        loadCards(cards: cards as! [CardController])
    }

    private func setupUI() {
        if let barcodeButton = barcodeButton {
            self.view.bringSubviewToFront(barcodeButton)
            barcodeButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        }

        if !isModallyPresented {
            let logo = UIImage(named: "Proscai_logo")
            let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 219, height: 60))
            imageView.image = logo
            imageView.contentMode = .scaleAspectFit
            self.navigationItem.titleView = imageView
        }
    }

    private func setupBindings() {

        viewModel.ean.asObservable()
            .bind(to: productInfoCardController.viewModel.setCurrentEAN)
            .disposed(by: disposeBag)

        viewModel.icod.asObservable()
            .bind(to: existenciaInfoCardController.viewModel.setCurrentIcod)
            .disposed(by: disposeBag)

        viewModel.icod.asObservable()
            .bind(to: ofertasCardController.viewModel.setCurrentIcod)
            .disposed(by: disposeBag)

        barcodeScannerController.viewModel.didSelectEAN.asObservable()
        .bind(to: viewModel.setCurrentEAN)
        .disposed(by: disposeBag)

        productInfoCardController.viewModel.didSelectIcod.asObservable()
        .bind(to: viewModel.setCurrentIcod)
        .disposed(by: disposeBag)

    }

    @objc func buttonTapped() {

        barcodeScannerController.startScanning()
        // Show the scanner.
        if let picker = barcodeScannerController.picker {
            picker.modalPresentationStyle = .overFullScreen
            present(picker, animated: true, completion: nil)
        }

    }

     @objc func dismissSelf() {
        self.dismiss(animated: true, completion: nil)
    }
}

MainContainerViewController


class MainContainerViewController: UIViewController {
    @IBOutlet weak var closeBarButton: UIBarButtonItem!
    @IBOutlet weak var containerView: UIView!

    var containedViewController : UIViewController?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        addChild(containedViewController!)
        containerView.addSubview(containedViewController!.view)
        containedViewController?.didMove(toParent: self)
        self.view.layoutIfNeeded()
    }

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */
    @IBAction func didPressClose(_ sender: Any) {
        self.dismiss(animated: true, completion: nil)
    }

}

Presentation

let mventoryStoryboard = UIStoryboard(name: "Mventory", bundle: nil)
let mventoryVC = MainViewController()
let mainContainerVC = mventoryStoryboard.instantiateViewController(withIdentifier: "MainContainerViewController") as? MainContainerViewController
mainContainerVC?.containedViewController = mventoryVC
mainContainerVC?.modalPresentationStyle = .formSheet

self.present(mainContainerVC, animated: true) {
    mventoryVC.barcodeScannerController.barcodeScanner(didScan: barcode)
}

Unfortunately, the CardPartsViewControllers shows more business logic than I'm allowed to share. But I reproduced the issue by presenting the CardsViewController from your Demo. Therefore, it seems that the CardPartsViewController itself is irrelevant to the issue. You can actually reproduce it by making a MainContainerViewController on your demo, that's how I tested it.

Ps. The MainContainerViewController view is just a NavigationBar, with a right bar button item for closing and a ContainerView with constraints (0-top, 0-trailing, 0-bottom, 0-leading).