【问题标题】:How do I make a fully expanded TableView inside of UICollectionViewCell?如何在 UICollectionViewCell 内制作一个完全展开的 TableView?
【发布时间】:2021-04-25 15:52:00
【问题描述】:

我正在尝试以编程方式在 UICollectionViewCell 内创建一个表格视图。我见过其他问题,但他们似乎正在使用 AutoLayout。我能够获得数据来显示,但是我的单元格不能正确显示。无论我做什么,细胞似乎都以一种或另一种方式重叠。我需要在 UICollectionViewCell 内完全展开 tableview。

import Foundation
import UIKit
import MaterialComponents

class FollowingItemCollectionViewCell: MDCCardCollectionCell, UITableViewDataSource, UITableViewDelegate {
    
    static let Identifer = "FollowingItemCollectionViewCell"
    private var title: UILabel!
    private var viewMoreButton: MDCButton!
    private var tableView: SelfSizingTableView!
    private var items: [Any] = []
    private var item: FollowingItem!
    private var clickListener: (FollowingItemClick) -> Void = { _ in }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        createView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        createView()
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        backgroundColor = Colors.Card.background
    }
    
    private func createView() {
        backgroundColor = Colors.Card.background
        createTitle()
        createTableView()
        createViewMoreButton()
    }
    
    private func createTitle() {
        title = UILabel()
        title.font = UIFont(name: "HelveticaNeue-Bold", size: 24)
        title.translatesAutoresizingMaskIntoConstraints = false
        title.textColor = Colors.Card.text
        
        contentView.addSubview(title)
        
        title.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 2).isActive = true
        title.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8).isActive = true
        title.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -2).isActive = true
    }

    private func createTableView() {
        tableView = SelfSizingTableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(ArticleUiTableViewCell.self, forCellReuseIdentifier: ArticleUiTableViewCell.Identifier)
        tableView.register(SearchTableViewCell.self, forCellReuseIdentifier: SearchTableViewCell.Identifier)
        tableView.backgroundColor = Colors.Card.background

        let titleGuide = UILayoutGuide()
        contentView.addSubview(tableView)
        contentView.addLayoutGuide(titleGuide)

        titleGuide.bottomAnchor.constraint(equalTo: title.bottomAnchor).isActive = true

        tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: titleGuide.bottomAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }
    
    private func createViewMoreButton() {
        viewMoreButton = MDCButton()
        viewMoreButton.translatesAutoresizingMaskIntoConstraints = false
        viewMoreButton.setBackgroundColor(Colors.clear)
        viewMoreButton.setTitleColor(Colors.royal, for: .normal)
        viewMoreButton.addTarget(self, action: #selector(onViewMoreClick), for: .touchUpInside)

        let tableViewGuide = UILayoutGuide()
        contentView.addSubview(viewMoreButton)
        contentView.addLayoutGuide(tableViewGuide)

        tableViewGuide.bottomAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true

        viewMoreButton.topAnchor.constraint(equalTo: tableViewGuide.bottomAnchor).isActive = true
        viewMoreButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        viewMoreButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
    }
    
    @objc func onViewMoreClick() {
        if item is FollowingSearchItem {
            clickListener(FollowingItemClick.SearchViewMoreClick)
        } else if item is FollowingArticleItem {
            clickListener(FollowingItemClick.ArticleViewMoreClick)
        }
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = items[indexPath.row]
        if let item = item as? Article {
            let cell = ArticleUiTableViewCell(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 80))
            cell.setArticle(article: item, menuClickListener: { article in
                self.clickListener(FollowingItemClick.ArticleMenuClick(article))
            }, contentClickListener: { article in
                self.clickListener(FollowingItemClick.ArticleClick(article))
            })
            cell.backgroundColor = Colors.Card.background
            return cell
        } else if let item = item as? SearchPresentable {
            let cell = SearchTableViewCell(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 80))
            cell.setData(searchPresentable: item, menuClickListener: { searchPresentable in
                self.clickListener(FollowingItemClick.SearchMenuClick(searchPresentable))
            }, contentClickListener: { searchPresentable in
                self.clickListener(FollowingItemClick.SearchClick(searchPresentable))
            })
            cell.backgroundColor = Colors.Card.background
            return cell
        } else {
            fatalError("No proper item type found for \(item)")
        }
    }
    
    func setData(item: FollowingItem, width: CGFloat, clickListener: @escaping (FollowingItemClick) -> Void) {
        widthAnchor.constraint(equalToConstant: width).isActive = true
        contentView.widthAnchor.constraint(equalToConstant: width).isActive = true
        self.clickListener = clickListener
        self.item = item
        if let item = item as? FollowingArticleItem {
            items = item.items
        } else if let item = item as? FollowingSearchItem {
            items = item.items
        }
        tableView.reloadData()
        title.text = item.title
        viewMoreButton.setTitle(item.viewMoreText, for: .normal)
    }
}

自定义大小的 TableView

class SelfSizingTableView: UITableView {

    override func reloadData() {
        super.reloadData()
        invalidateIntrinsicContentSize()
        layoutIfNeeded()
    }

    override var contentSize: CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }
    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        let height = contentSize.height
        return CGSize(width: contentSize.width, height: height)
    }
}

第一次打开标签时的屏幕截图。请注意,列表中还有更多文章(共 3 篇文章)。单元格似乎只首先出现在屏幕视图中。

开始在选项卡内滚动时的屏幕截图。注意要重叠的单元格。

导航到另一个选项卡并返回此选项卡时的屏幕截图。注意一切都变得疯狂。

【问题讨论】:

  • 你能显示它被重叠的图像吗?
  • @rptwsthi 完成。

标签: ios swift uitableview uicollectionview


【解决方案1】:

所以我注意到了。这是您在此处实现的一些复杂架构。我建议您仅使用表格视图来设计此页面(如果仅垂直滚动我们可以实现)。

但是让我们先解决这个问题,因为它是一个复杂的架构,tableView 在单元格内,并且在这种情况下,单元格不知道如何调整大小(因为涉及滚动)。因此,您必须从func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { 内部手动计算每个集合视图组件的大小。您可以通过编写一些函数来计算单元格中的行数并将其乘以大小来实现。

但又一次。我会建议使用具有多个部分和单元格类型的 tableView 来制作此 UI。

【讨论】:

  • 我需要将每个列表都包装在一张卡片中,这就是为什么我需要在 MDCCardCollectionCell 中使用嵌套的表格视图。我怎么知道我的 MDCButton 和 UILabel 的高度应该是多少?它们是 MDCCArdCollectionCell 的一部分
  • 我们可以通过自定义部分的第一个和最后一个单元格的 UI 来实现该 UI。您知道按钮的大小,因为它们是动作和设计静态的。并且标签大小可以使用字符串的boundingRect 来确定。因为您已经知道它的起源和对尾随点的调整。
  • 如果它们是静态的,我应该调用什么来获取按钮的大小?如何使用字符串的 boundingRect 来获取它的高度?
  • 例如:如果您查看单元格ArticleUiTableViewCell ,所有静态项目都将具有固定的高度和宽度。对于标签高度,保留所有填充和它的宽度并计算字符串高度。然后添加垂直填充,就是这样。我建议您在单元格内创建一个函数,在其中传递单元格数据并返回单元格高度。
  • 谢谢!我将此答案标记为已接受,尽管我仍然很好奇如何将其实现为单个 TableView,因为卡片标题和列表需要在同一个卡片视图中
【解决方案2】:

尝试实现 UITableViewDelegate 方法返回行高。

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  let item = items[indexPath.row]
  if let item = item as? Something {
    return 80 //Or whatever value it should be
  } else {
    return 0 //Or whatever value it should be
  }
}

【讨论】:

    猜你喜欢
    • 2020-09-14
    • 2019-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-13
    • 2013-09-22
    • 2021-10-12
    相关资源
    最近更新 更多