material-components-ios icon indicating copy to clipboard operation
material-components-ios copied to clipboard

[BottomSheet] In Extended state last cell of tableview is always half visible

Open alcrus opened this issue 5 years ago • 1 comments

For usage questions: ask on Stack Overflow.

A clear and concise description of what the bug is.

BottomSheet with tableview long enough to be scrollable in Extended State (BottomSheet opened to full screen). Last cell in TableView always covered by Home Indicator on X/XR/XS devices. Other iPhone devices show only half of cell.

Reproduction steps

To reproduce standard BottomSheetTableViewExample can be used. Add 20 cells (or more) to make table view scrollable in extended state. Open BottomSheet and scroll to the end of table. Reproducible in portrait mode only. In landscape mode there is correct bottom safe area indent.

Expected behavior

Last cell must be above home indicator and bottom safe area and fully visible.

Platform (please complete the following information)

  • Device: any iPhone
  • OS: iOS 12

Additional context

Simulator Screen Shot - iPhone XR - 2019-04-29 at 12 16 16 Simulator Screen Shot - iPhone 8 - 2019-04-29 at 20 01 42


Internal data

alcrus avatar Apr 29 '19 19:04 alcrus

I've been scratching my head trying to work this out for the last day! Now need to figure out how to work around this.


UPDATE: I finally have something working (after many hours of hacking at it and looking at the view hierarchy debugger). Hopefully this can help someone else.

At a high level what I did involved.

  • Creating a UIScrollView and placing this as the root element in the UIViewController that was being displayed as a BottomSheet.
  • Setting the trackingScrollView property on the MDCBottomSheetController to the UIScrollView mentioned above
  • Adding the UITableView to the UIScrollView
  • (In order to place a UITableView in a UIScrollView I had to use a self sizing table view which I'll include below but you can find other examples online)
  • Added additionalSafeAreaInsets.bottom to the height of the top safe area. The MDCBottomSheetController doesn't cover the top safe area and it seems to me that this has been done by moving the whole view down which means there is a bit of the view which overlaps the bottom of the screen meaning things get drawn down off the bottom of the screen
  • Added scrollView.contentInset.bottom to the height of the bottom safe area (I think the two above points are the key to make it work)

Code

Objective C code I used to initialise & present the view controller

MDCBottomSheetController *bottomSheet = [[MDCBottomSheetController alloc] initWithContentViewController:vc];
bottomSheet.trackingScrollView = vc.scrollView;
[source presentViewController:bottomSheet animated:true completion:nil];

My swift view controller code (I've cut out a few irrelevant bits, but hopefully it still works)

import Foundation
import MaterialComponents.MaterialButtons
import MaterialComponents.MaterialButtons_Theming
import MaterialComponents.MaterialTextControls_FilledTextFields
import MaterialComponents.MaterialTextControls_FilledTextFieldsTheming
import MaterialComponents.MaterialTypography

@objc class NewSampleSectorListVC: UIViewController {
    // UI
    @objc let scrollView: UIScrollView = {
        let view = UIScrollView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    private let sectorsListTableData = SectorsListTableDelegateAndDataSource()
    lazy var sectorsListTable: UITableView = {
        let table = IntrinsicTableView(frame: .zero, style: UITableView.Style.plain)
        table.translatesAutoresizingMaskIntoConstraints = false
        table.rowHeight = UITableView.automaticDimension
        table.estimatedRowHeight = 44 // need to set this as the cell is self sizing
        table.isScrollEnabled = false // the tableView doesn't need to scroll as it's in a scroll view
        table.delegate = sectorsListTableData
        table.dataSource = sectorsListTableData
        table.register(SpacedTitleCell.self, forCellReuseIdentifier: sectorsListTableData.cellIdentifier)
        table.tableFooterView = UIView(frame: .zero) // set an empty footer for the table => don't show empty rows
        table.separatorColor = .clear // hide the dividers
        table.bounces = false // it's unnecessary for the table to bounce when the whole bottomsheet also can be dragged
        return table
    }()

    // MARK: - UIViewController overrides

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

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if #available(iOS 11.0, *) {
            if let window = UIApplication.shared.keyWindow {
                additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: window.safeAreaInsets.top, right: 0)
                scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: window.safeAreaInsets.bottom, right: 0)
            }
        }
    }

    func setupUIComponents() {
        view.addSubview(scrollView)
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        if #available(iOS 11.0, *) {
            scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        }

        scrollView.addSubview(sectorsListTable)
        sectorsListTable.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        sectorsListTable.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        sectorsListTable.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
        sectorsListTable.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        sectorsListTable.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
    }
}

Helper class to make a self sizing table view

/** A table view with an intrinsic size so that it can be placed in a scroll view
 See: https://stackoverflow.com/a/34902501/238166 for more info */
class IntrinsicTableView: UITableView {
    override var contentSize: CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

intiocean avatar Jan 28 '21 17:01 intiocean