Reputation: 843
I am trying to programatically create a tableview inside an UICollectionViewCell. I have seen other questions but they seem to be using AutoLayout. I was able to get data to show however my cell's do not display properly. The cells seem to overlap in one way or another no matter what I do. I need the tableview to be fully expanded inside the UICollectionViewCell.
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)
}
}
Self Sizing 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)
}
}
Screenshot when first opening the tab. Notice there are still more articles in the list ( A total of 3 articles). The cells seem to only first the screen view.
Screenshot when starting to scroll within the tab. Notice the cells being to overlap.
Screenshot when navigating to another tab, and back to this one. Notice everything just goes crazy.
Upvotes: 0
Views: 50
Reputation: 10172
So I noticed. This is some complicated architecture you have implemented here. I would suggest you to design this page using Table view only (if scroll in only vertical we can achieve that).
But lets solve this problem first as it's a complicated architecture of a tableView
being inside the cell and cell doesn't know how to adjust size (as a scroll is involved) in this case. Hence you will have to manually calculate the size of each collection view component from inside func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
. That you can achieve by writing some function which will calculate number of rows that will be in cell and multiply that with size.
But Again. I will suggests to make this UI using just tableView
with multiple section and cell type.
Upvotes: 1
Reputation: 186
Try implementing the UITableViewDelegate method to return the height of the row.
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
}
}
Upvotes: 0