如何显示在 tableview 之外并且可滚动的父视图?

Posted

技术标签:

【中文标题】如何显示在 tableview 之外并且可滚动的父视图?【英文标题】:How to show a parent view which is outside tableview and is scrollable? 【发布时间】:2018-11-14 05:10:39 【问题描述】:

我有一个场景,我需要显示一个带有阴影和圆角半径的父视图,其中包含一长串可重复使用的项目。我使用 tableView 来显示项目。但我坚持让我的 tableview 扩展与其 contentSize 一样多。它有效但不准确。有什么解决办法吗?

编辑:

期望的结果:

我使用以下参考来进行自定尺寸表视图。 Self Sizing UITableView

我做了一些修改如下:

final class SelfSizedTableView: UITableView 

    var maxHeight = CGFloat.greatestFiniteMagnitude

    override func reloadData() 
        super.reloadData()
        self.invalidateIntrinsicContentSize()
        self.layoutIfNeeded()
    

    override var intrinsicContentSize: CGSize 
        let height = min(contentSize.height, maxHeight)
        let size = CGSize(width: contentSize.width, height: height)
        return size
    


我使用了一个父 tableView,其中一个单元格包含我的 containerView 并嵌入了这个自定大小的 tableView。

class MyContainerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate 

    // MARK: - IBOutlets
    @IBOutlet weak var parentTableView: UITableView!

    // MARK: - Life Cycle
    override func viewDidLoad() 
        super.viewDidLoad()
        setupViews()
    

    private func estimateDataHeight() -> CGFloat 
        let detailCellHeight: CGFloat = 32
        let headingCellHeight: CGFloat = 43
        let headings: CGFloat = headingCellHeight*2
        let detailsHeight: CGFloat = detailCellHeight*4
        let baseHeight = headings + detailsHeight
        let membersHeight =
            CGFloat(sectionsArray.count) * detailCellHeight
        return baseHeight + membersHeight
    


// MARK: - UITableViewDataSource
extension MyContainerViewController 

    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int 
        return 1
    

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let id = String(describing: MyContainerTVCell.self)
        guard let cell = tableView
            .dequeueReusableCell(withIdentifier: id, for: indexPath)
            as? MyContainerTVCell else 
                return UITableViewCell()
        

        cell.policyDetails = dataSource
        // my cheat/trick doesn't work on large data.
        DispatchQueue.main.asyncAfter(deadline: .now()+0.4) 
            tableView.beginUpdates()
            cell.tableView.layoutIfNeeded()
            cell.tableView.reloadData() // the overridden one
            tableView.endUpdates()
        
        return cell
    


extension MyContainerViewController 

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        return UITableViewAutomaticDimension
    

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat 
        return estimateDataHeight()
    

我的单元格类有自己大小的 tableView 和 containerView:

class MyContainerTVCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate 

    // MARK: - IBOutlets
    @IBOutlet weak var containerView: UIView!
    @IBOutlet weak var shadowView: UIView!
    @IBOutlet weak var tableView: SelfSizedTableView!

    // MARK: - Properties
    let titles = ["Email ID:", "Mobile Number:", "Address:", "ID: "] // first section data array
    let moreData: [String] = [] // remaining reusable sections array

    // no of subsequent sections for moreData array type
    var numberOfSections: Int 
        return 4
    

    // MARK: -
    var dataSource: MyDataSource!

    // MARK: - Life Cycle
    override func awakeFromNib() 
        super.awakeFromNib()
        setupView()
    

    override func layoutSubviews() 
        super.layoutSubviews()
    

    // MARK: - Setup
    func setupView() 
        containerView.rounded(with: 10)
        shadowView.layer.applyShadow()

        tableView.dataSource = self
        tableView.delegate = self
    


// MARK: - UITableViewDataSource
extension MyContainerTVCell 
    func numberOfSections(in tableView: UITableView) -> Int 
        return numberOfSections + 1
    

    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int 
        if section == 0  return titles.count + 1 
        else if section == 1  return moreData.count + 1 
        else  return moreData.count 
    

    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let headerID = String(describing: MyHeaderTVCell.self)
        let itemID = String(describing: MyItemTVCell.self)

        switch indexPath.section 
        case 0:
            if indexPath.row == 0 
                guard let cell = tableView
                    .dequeueReusableCell(withIdentifier: headerID, for: indexPath)
                    as? MyHeaderTVCell else 
                        return UITableViewCell()
                
                cell.titleLabel.text = dataSource.title
                return cell
             else 
                guard let cell = tableView
                    .dequeueReusableCell(withIdentifier: itemID, for: indexPath)
                    as? MyItemTVCell else 
                        return UITableViewCell()
                
                let item = titles[indexPath.row-1]
                cell.titleLabel.text = item
                cell.separatorView.isHidden = true
                let data: String
                switch indexPath.row 
                case 1:
                    data = dataSource.emailID
                case 2:
                    data = dataSource.mobileNo
                case 3:
                    data = dataSource.address
                case 4:
                    data = dataSource.name
                case 5:
                    data = dataSource.age
                case 6:
                    data = dataSource.id
                case 7:
                    data = dataSource.office
                case 8:
                    data = dataSource.academic
                default: data = String()
                
                cell.detailLabel.text = data
                return cell
            

        case 1:
            if indexPath.row == 0 
                guard let cell = tableView
                    .dequeueReusableCell(withIdentifier: headerID, for: indexPath)
                    as? MyHeaderTVCell else 
                        return UITableViewCell()
                
                cell.titleLabel.text = "More Data"
                return cell
             else 
                guard let cell = tableView
                    .dequeueReusableCell(withIdentifier: itemID, for: indexPath)
                    as? MyItemTVCell else 
                        return UITableViewCell()
                
                let sectionIndex = indexPath.section-1
                guard sectionIndex <= numberOfSections-1,
                    let section = sectionsArray?[indexPath.section-1] else 
                        return UITableViewCell()
                
                cell.titleLabel.text = moreData[indexPath.row-1]
                cell.separatorView.isHidden = true
                switch indexPath.row 
                case 1:
                    cell.detailLabel.text = section.a
                case 2:
                    cell.detailLabel.text = section.b
                case 3:
                    cell.detailLabel.text = "\(section.c ?? 0)"
                case 4:
                    cell.detailLabel.text = section.d
                case 5:
                    cell.detailLabel.text = section.e
                case 6:
                    cell.detailLabel.text = section.f
                    if indexPath.section < numberOfSections 
                        cell.separatorView.isHidden = false
                    
                default: break
                
                return cell
            
        default:
            guard let cell = tableView
                .dequeueReusableCell(withIdentifier: itemID, for: indexPath)
                as? MyItemTVCell else 
                    return UITableViewCell()
            
            let sectionIndex = indexPath.section-1
            guard sectionIndex <= numberOfSections-1,
                let section = sectionsArray?[indexPath.section-1] else 
                    return UITableViewCell()
            
            cell.titleLabel.text = moreData[indexPath.row]
            cell.separatorView.isHidden = true
            switch indexPath.row 
            case 0:
                cell.detailLabel.text = section.a
            case 1:
                cell.detailLabel.text = section.b
            case 2:
                cell.detailLabel.text = "\(section.c ?? 0)"
            case 3:
                cell.detailLabel.text = section.d
            case 4:
                cell.detailLabel.text = section.e
            case 5:
                cell.detailLabel.text = section.f
                if indexPath.section < numberOfSections 
                    cell.separatorView.isHidden = false
                
            default: break
            
            return cell
        
    


// MARK: - UITableViewDelegate
extension MyContainerTVCell 
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        return UITableViewAutomaticDimension
    

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat 
        if indexPath.section == 0 && indexPath.row == 0  return 43 
        if indexPath.section == 1 && indexPath.row == 0  return 43 
        return 32
    

【问题讨论】:

你有截图要分享吗? 请显示代码。你试过什么?我看不出为什么 Objective-c 和 swift 是这里的标签,因为你的问题中没有代码。 @Glenn 我在编辑中添加了详细信息。如果对理解代码或预期结果有任何疑问,请询问。 @MikeTaverne 我添加了代码并编辑了标签。如果有任何疑问,请查看并询问。 这是正确的方法还是其他解决方案? 【参考方案1】:

tableView 已经可以滚动时,为什么要将tableView 扩展到其内容大小以使其可滚动?

但是,如果您在屏幕上还有一些其他内容,除了表格,并且您希望它们一起滚动,那么您需要将所有内容嵌入到UIScrollView

然后,在 xib/storyboard 中为您的 tableView 设置一个高度限制,并具有任何值。 然后你可能会做这样的事情:

// in your view controller
private var heightObservation: NSKeyValueObservation?

// called once, for example, in viewDidLoad()
private func setupTableView() 
    ...

    observation = tableView.constraintFrameHeightToContentSizeHeight()


extension UITableView 

    func constraintFrameHeightToContentSizeHeight() -> NSKeyValueObservation 
        return observe(\.contentSize, changeHandler:  (tableView, _) in
            tableView.heightConstraint?.constant = tableView.contentSize.height
        )
    


// find height constraint
extension UIView 

    var heightConstraint: NSLayoutConstraint? 
        return constraints.first(where:  $0.firstAttribute == .height )
    

不要忘记在 xib/storyboard 中取消选中该表格视图的“启用滚动”。

【讨论】:

以上是关于如何显示在 tableview 之外并且可滚动的父视图?的主要内容,如果未能解决你的问题,请参考以下文章

禁用表格视图滚动

可水平滚动的 TableView 的右列未完全显示

SWRevealController TableView 滚动状态栏

滚动时如何在tableview中添加20 -20行的循环?

如何滚动到 UITableView 或 UICollectionView 中的最后一个单元格之外

如何将 panGestureRecognizer 添加到 TableView,并且在识别平底锅时仍然让 tableview 滚动?