如何在 UICollectionViewCell 中制作完全展开的 TableView?
How do I make a fully expanded TableView inside of UICollectionViewCell?
我正在尝试以编程方式在 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 篇)。这些单元格似乎只是首先显示屏幕视图。
开始在选项卡内滚动时的屏幕截图。注意单元格重叠。
导航到另一个选项卡并返回到这个选项卡时的屏幕截图。注意一切都变得疯狂。
尝试将 UITableViewDelegate 方法实现到 return 行的高度。
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
}
}
所以我注意到了。这是您在此处实现的一些复杂架构。我建议您仅使用 Table 视图设计此页面(如果仅垂直滚动,我们可以实现)。
但是让我们先解决这个问题,因为它是一个复杂的架构,tableView
位于单元格内部,并且在这种情况下单元格不知道如何调整大小(因为涉及滚动)。因此,您将不得不从 func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
内部手动计算每个集合视图组件的大小。您可以通过编写一些函数来实现,该函数将计算单元格中的行数并将其乘以大小。
但又一次。我建议只使用 tableView
和多个部分和单元格类型来制作这个 UI。
我正在尝试以编程方式在 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 篇)。这些单元格似乎只是首先显示屏幕视图。
开始在选项卡内滚动时的屏幕截图。注意单元格重叠。
导航到另一个选项卡并返回到这个选项卡时的屏幕截图。注意一切都变得疯狂。
尝试将 UITableViewDelegate 方法实现到 return 行的高度。
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
}
}
所以我注意到了。这是您在此处实现的一些复杂架构。我建议您仅使用 Table 视图设计此页面(如果仅垂直滚动,我们可以实现)。
但是让我们先解决这个问题,因为它是一个复杂的架构,tableView
位于单元格内部,并且在这种情况下单元格不知道如何调整大小(因为涉及滚动)。因此,您将不得不从 func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
内部手动计算每个集合视图组件的大小。您可以通过编写一些函数来实现,该函数将计算单元格中的行数并将其乘以大小。
但又一次。我建议只使用 tableView
和多个部分和单元格类型来制作这个 UI。