问题
I'm having an issue with UICollectionView scrolling inside UITableViewCell.
Unfortunately, the CollectionView doesn't scroll at all. When I'm trying to disable other UITableViewCells it works without any problem and vice versa.
CollectionViewCell:
import UIKit
class CategoriesCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layoutUI()
}
required init(coder adecoder: NSCoder) {
fatalError("init(codeer:) has not been implemented")
}
lazy var categoriesImage: UIImageView = {
let categoriesImage = UIImageView()
categoriesImage.contentMode = .scaleToFill
categoriesImage.layer.cornerRadius = 8.0
categoriesImage.image = UIImage(named: "pizza")
categoriesImage.layer.cornerRadius = 8.0
categoriesImage.layer.masksToBounds = true
categoriesImage.translatesAutoresizingMaskIntoConstraints = false
return categoriesImage
}()
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .black
containerView.alpha = 0.7
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var categoryName: UILabel = {
let categoryName = UILabel()
categoryName.textColor = .white
categoryName.font = UIFont(name: "AvenirNext-DemiBold", size: 16)
categoryName.text = "Soup"
categoryName.textAlignment = .left
categoryName.translatesAutoresizingMaskIntoConstraints = false
return categoryName
}()
lazy var recipesNumber: UILabel = {
let recipesNumber = UILabel()
recipesNumber.textColor = .white
recipesNumber.font = UIFont(name: "AvenirNext-Regular", size: 16)
recipesNumber.text = "33"
recipesNumber.textAlignment = .left
recipesNumber.translatesAutoresizingMaskIntoConstraints = false
return recipesNumber
}()
func setupcategoriesImageConstraints() {
NSLayoutConstraint.activate([
categoriesImage.topAnchor.constraint(equalTo: topAnchor),
categoriesImage.bottomAnchor.constraint(equalTo: bottomAnchor),
categoriesImage.leadingAnchor.constraint(equalTo: leadingAnchor),
categoriesImage.trailingAnchor.constraint(equalTo: trailingAnchor),
])
}
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: categoriesImage.topAnchor),
containerView.bottomAnchor.constraint(equalTo: categoriesImage.bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: categoriesImage.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: categoriesImage.trailingAnchor)
])
}
func setupCategoryNameConstraints() {
NSLayoutConstraint.activate([
categoryName.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 16),
categoryName.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
categoryName.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 16)
])
}
func setuprecipesNumberConstraints() {
NSLayoutConstraint.activate([
recipesNumber.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16),
recipesNumber.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
recipesNumber.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 16)
])
}
func addSubviews() {
addSubview(categoriesImage)
categoriesImage.addSubview(containerView)
containerView.addSubview(categoryName)
containerView.addSubview(recipesNumber)
}
func layoutUI() {
addSubviews()
setupcategoriesImageConstraints()
setupContainerViewConstraints()
setupCategoryNameConstraints()
setuprecipesNumberConstraints()
}
}
CollectionViewInTableViewCell:
import UIKit
class CategoriesTableViewCellCollectionViewCell: UITableViewCell, UICollectionViewDelegateFlowLayout {
let categories = ["italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food"]
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var categoriesNameLabel: UILabel = {
let categoriesNameLabel = UILabel()
categoriesNameLabel.text = "Categories"
categoriesNameLabel.textColor = .customDarkGray()
categoriesNameLabel.textAlignment = .left
categoriesNameLabel.font = UIFont(name: "AvenirNext-Regular", size: 14)
categoriesNameLabel.translatesAutoresizingMaskIntoConstraints = false
return categoriesNameLabel
}()
lazy var seeAllCategoriesButton: UIButton = {
let seeAllCategoriesButton = UIButton()
seeAllCategoriesButton.setTitle("See all", for: .normal)
seeAllCategoriesButton.setTitleColor(.CustomGreen(), for: .normal)
seeAllCategoriesButton.titleLabel?.font = UIFont(name: "AvenirNext-Regular", size: 14)
seeAllCategoriesButton.translatesAutoresizingMaskIntoConstraints = false
seeAllCategoriesButton.addTarget(self, action: #selector(test), for: .touchUpInside)
return seeAllCategoriesButton
}()
@objc func test() {
print("Test worked")
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CategoriesCollectionViewCell.self, forCellWithReuseIdentifier: "CategoriesCollectionViewCell")
return collectionView
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
containerView.heightAnchor.constraint(equalTo: categoriesNameLabel.heightAnchor)
])
}
func setupCategoriesNameLabelConstraints() {
NSLayoutConstraint.activate([
categoriesNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
categoriesNameLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
func setupSeeAllCategoriesButtonConstraints() {
NSLayoutConstraint.activate([
seeAllCategoriesButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
seeAllCategoriesButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
])
}
func setupCollectionViewConstraints() {
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: categoriesNameLabel.topAnchor, constant: 16),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 16),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
])
}
func addSubviews() {
addSubview(categoriesNameLabel)
addSubview(containerView)
containerView.addSubview(seeAllCategoriesButton)
containerView.addSubview(collectionView)
}
func layoutUI() {
addSubviews()
setupCollectionViewConstraints()
setupContainerViewConstraints()
setupCategoriesNameLabelConstraints()
setupSeeAllCategoriesButtonConstraints()
}
}
extension CategoriesTableViewCellCollectionViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categories.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CategoriesCollectionViewCell", for: indexPath) as! CategoriesCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width / 2, height: self.frame.width / 4)
}
}
TableViewCell:
import UIKit
class HomeTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.layer.shadowColor = UIColor.black.cgColor
containerView.layer.shadowOpacity = 1
containerView.layer.shadowOffset = .init(width: 2, height: 2)
containerView.layer.shadowRadius = 7.0
containerView.layer.cornerRadius = 8.0
// containerView.clipsToBounds = true
return containerView
}()
lazy var foodImage: UIImageView = {
let foodImage = UIImageView()
foodImage.translatesAutoresizingMaskIntoConstraints = false
foodImage.contentMode = .scaleAspectFill
foodImage.clipsToBounds = true
foodImage.layer.cornerRadius = 8.0
return foodImage
}()
lazy var foodTitle: UILabel = {
let foodTitle = UILabel()
foodTitle.textColor = .CustomGreen()
foodTitle.numberOfLines = 0
foodTitle.translatesAutoresizingMaskIntoConstraints = false
return foodTitle
}()
func setupContainerView() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
])
}
func setupFoodImage() {
NSLayoutConstraint.activate([
foodImage.topAnchor.constraint(equalTo: containerView.topAnchor),
foodImage.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
foodImage.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
foodImage.heightAnchor.constraint(equalToConstant: 250)
])
}
func setupFoodTitle() {
NSLayoutConstraint.activate([
foodTitle.topAnchor.constraint(equalTo: foodImage.bottomAnchor, constant: 16),
foodTitle.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16),
foodTitle.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16),
foodTitle.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16)
])
}
func addSubview() {
addSubview(containerView)
containerView.addSubview(foodImage)
containerView.addSubview(foodTitle)
}
func layoutUI() {
addSubview()
setupContainerView()
setupFoodImage()
setupFoodTitle()
}
}
HomeView:
class HomeView: UIView {
var recipes: Recipes?
var recipesDetails = [Recipe]()
let indicator = ActivityIndicator()
override init( frame: CGRect) {
super.init(frame: frame)
layoutUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var foodTableView: UITableView = {
let foodTableView = UITableView()
foodTableView.translatesAutoresizingMaskIntoConstraints = false
foodTableView.backgroundColor = .white
foodTableView.delegate = self
foodTableView.dataSource = self
foodTableView.register(CategoriesTableViewCellCollectionViewCell.self, forCellReuseIdentifier: "CategoriesTableViewCellCollectionViewCell")
foodTableView.register(HomeTableViewCell.self, forCellReuseIdentifier: "HomeTableViewCell")
foodTableView.rowHeight = UITableView.automaticDimension
// foodTableView.estimatedRowHeight = 100
foodTableView.showsVerticalScrollIndicator = false
foodTableView.separatorStyle = .none
return foodTableView
}()
func setupFoodTableView() {
NSLayoutConstraint.activate([
foodTableView.topAnchor.constraint(equalTo: topAnchor),
foodTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
foodTableView.leadingAnchor.constraint(equalTo: leadingAnchor),
foodTableView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func addSubview() {
addSubview(foodTableView)
}
func layoutUI() {
indicator.setupIndicatorView(self, containerColor: .customDarkGray(), indicatorColor: .white)
addSubview()
setupFoodTableView()
fetchData()
}
func fetchData() {
AF.request("https://api.url").responseJSON { (response) in
if let error = response.error {
print(error)
}
do {
self.recipes = try JSONDecoder().decode(Recipes.self, from: response.data!)
self.recipesDetails = self.recipes?.recipes ?? []
DispatchQueue.main.async {
self.foodTableView.reloadData()
}
} catch {
print(error)
}
self.indicator.hideIndicatorView()
}
}
}
extension HomeView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recipesDetails.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoriesTableViewCellCollectionViewCell", for: indexPath) as! CategoriesTableViewCellCollectionViewCell
cell.layoutIfNeeded()
cell.collectionView.reloadData()
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
let url = URL(string: recipesDetails[indexPath.row].image ?? "Error")
cell.foodImage.kf.setImage(with: url)
cell.foodTitle.text = recipesDetails[indexPath.row].title
cell.layoutIfNeeded()
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return 160
} else {
return 250
}
}
}
Can anyone tell me what is the problem or what I did wrong?
Thanks.
回答1:
You cannot scroll your collection view because it is outside the bounds of its superview. In your code, you have this line:
containerView.heightAnchor.constraint(equalTo: categoriesNameLabel.heightAnchor)
That makes your containerView
height only around 20-pts, but you also have your collection view as a subview of containerView
.
Here is how your code looks for me (pretty much as-is):
And, I cannot scroll the collection view.
You can confirm it is outside the containerView
bounds by giving containerView
a background color (I used cyan):
You can also confirm it by setting containerView.clipsToBounds = true
:
Now, we don't even see the collection view.
You have a couple other constraint issues, but they don't really relate to being unable to scroll the collection view.
Your layout is also a little confusing, in that you have your Categories
label outside your containerView
... it seems it would make much more sense to have that label + the "See All" button + the collection view all inside the containerView
.
I made a few edits to your CategoriesTableViewCellCollectionViewCell
which resolves that issue -- and I think is close to your ultimate goal. You may need to do a little tweaking, but hopefully it will give you a good direction.
class CategoriesTableViewCellCollectionViewCell: UITableViewCell, UICollectionViewDelegateFlowLayout {
let categories = ["italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food", "italian food", "chinese food", "korean food"]
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutUI()
selectionStyle = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .clear
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
lazy var categoriesNameLabel: UILabel = {
let categoriesNameLabel = UILabel()
categoriesNameLabel.text = "Categories"
categoriesNameLabel.textColor = .gray // .customDarkGray()
categoriesNameLabel.textAlignment = .left
categoriesNameLabel.font = UIFont(name: "AvenirNext-Regular", size: 14)
categoriesNameLabel.translatesAutoresizingMaskIntoConstraints = false
return categoriesNameLabel
}()
lazy var seeAllCategoriesButton: UIButton = {
let seeAllCategoriesButton = UIButton()
seeAllCategoriesButton.setTitle("See all", for: .normal)
// seeAllCategoriesButton.setTitleColor(.CustomGreen(), for: .normal)
seeAllCategoriesButton.setTitleColor(UIColor(red: 0.0, green: 0.5, blue: 0.0, alpha: 1.0), for: .normal)
seeAllCategoriesButton.titleLabel?.font = UIFont(name: "AvenirNext-Regular", size: 14)
seeAllCategoriesButton.translatesAutoresizingMaskIntoConstraints = false
seeAllCategoriesButton.addTarget(self, action: #selector(test), for: .touchUpInside)
return seeAllCategoriesButton
}()
@objc func test() {
print("Test worked")
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CategoriesCollectionViewCell.self, forCellWithReuseIdentifier: "CategoriesCollectionViewCell")
return collectionView
}()
func setupContainerViewConstraints() {
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
// should not be constrained to categoriesNameLabel height
//containerView.heightAnchor.constraint(equalTo: categoriesNameLabel.heightAnchor)
// constrain 16-pts from bottom of cell
containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16)
])
}
func setupCategoriesNameLabelConstraints() {
NSLayoutConstraint.activate([
categoriesNameLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
// centerY to seeAllCategoriesButton
categoriesNameLabel.centerYAnchor.constraint(equalTo: seeAllCategoriesButton.centerYAnchor)
])
}
func setupSeeAllCategoriesButtonConstraints() {
NSLayoutConstraint.activate([
seeAllCategoriesButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
// constrain top to containerView top
seeAllCategoriesButton.topAnchor.constraint(equalTo: containerView.topAnchor)
])
}
func setupCollectionViewConstraints() {
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: seeAllCategoriesButton.bottomAnchor, constant: 0),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
])
}
func addSubviews() {
// but categoriesNameLabel inside containerView
//addSubview(categoriesNameLabel)
addSubview(containerView)
containerView.addSubview(categoriesNameLabel)
containerView.addSubview(seeAllCategoriesButton)
containerView.addSubview(collectionView)
}
func layoutUI() {
addSubviews()
setupCollectionViewConstraints()
setupContainerViewConstraints()
setupCategoriesNameLabelConstraints()
setupSeeAllCategoriesButtonConstraints()
}
}
The result looks like this - and you can see that I have scrolled the collection view a bit:
来源:https://stackoverflow.com/questions/60505427/uicollectionview-doesnt-scroll-inside-uitableviewcell