UICollectionView doesn't scroll inside UITableViewCell - ios
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.
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:
I have the same issue, and I found that just because the structure of tableViewCell.
cell - cell's contentView
Wrong way
addSubview(aView)
cell - aView - cell's contentView
you can't interact with aView
Correct
self.contentView.addSubview(aView)
cell - cell's contentView - aView
Now you can interact with aView.
Just change the line
addSubview(containerView)
to
self.contentView.addSubview(containerView)
will fixed your problem.
Related
Swift - Blur UICollectionView Cell background
So I am trying to blur the background view of the UICollectionView cell. This is what I am trying to accomplish: I tried using this extension: extension UIView { func applyBlurEffect(_ style: UIBlurEffect.Style = .dark) { let blurEffect = UIBlurEffect(style: style) let blurEffectView = UIVisualEffectView(effect: blurEffect) blurEffectView.frame = bounds blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] addSubview(blurEffectView) } } And use it like this: cell.backgroundColor = .clear cell.backgroundView?.applyBlurEffect() But this is the result I get: Also tried changing from dark to light, but no changes.. What am I doing wrong here? EDIT: Added UICollectionViewCell class: import UIKit class MenuCollectionViewCell: UICollectionViewCell { #IBOutlet weak var categoryEmoji: UILabel! #IBOutlet weak var categoryLabel: UILabel! #IBOutlet weak var lineView: UIView! override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { contentView.backgroundColor = .clear let blurEffect = UIBlurEffect(style: .light) let blurEffectView = UIVisualEffectView(effect: blurEffect) blurEffectView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(blurEffectView) blurEffectView.layer.cornerRadius = 20 blurEffectView.clipsToBounds = true let g = contentView.layoutMarginsGuide NSLayoutConstraint.activate([ // constrain blur view to all 4 sides of contentView blurEffectView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), blurEffectView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), blurEffectView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), blurEffectView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), ]) } } And this is the result I have now. The label's are also blurred now.:
Don't try to modify the cell's background view. Much easier to add a UIVisualEffectView to the cell. Here's a very simple example. I created a background image similar to what you have in your post, and I used UIBlurEffect(style: .light) since the dark background doesn't really show the effect much: BlurCell class class BlurCell: UICollectionViewCell { public var text: String = "" { didSet { label.text = text.isEmpty ? " " : text } } private let label = UILabel() override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { contentView.backgroundColor = .clear let blurEffect = UIBlurEffect(style: .light) let blurEffectView = UIVisualEffectView(effect: blurEffect) blurEffectView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(blurEffectView) blurEffectView.layer.cornerRadius = 20 blurEffectView.clipsToBounds = true label.translatesAutoresizingMaskIntoConstraints = false label.font = .systemFont(ofSize: 60.0, weight: .bold) label.textAlignment = .center label.textColor = .white contentView.addSubview(label) let g = contentView.layoutMarginsGuide NSLayoutConstraint.activate([ // constrain blur view to all 4 sides of contentView blurEffectView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), blurEffectView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), blurEffectView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), blurEffectView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), // center the label label.centerXAnchor.constraint(equalTo: g.centerXAnchor), label.centerYAnchor.constraint(equalTo: g.centerYAnchor), ]) } } Simple example controller class class BlurVC: UIViewController { var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() let imageView = UIImageView() if let img = UIImage(named: "bkg3") { imageView.image = img } //imageView.contentMode = .scaleAspectFill imageView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(imageView) let fl = UICollectionViewFlowLayout() fl.scrollDirection = .vertical fl.itemSize = CGSize(width: 160.0, height: 180.0) fl.minimumLineSpacing = 0 fl.minimumInteritemSpacing = 0 collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl) collectionView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(collectionView) let g = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ imageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), imageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), imageView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0), collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0), collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0), collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), ]) collectionView.backgroundColor = .clear collectionView.register(BlurCell.self, forCellWithReuseIdentifier: "c") collectionView.dataSource = self collectionView.delegate = self } } extension BlurVC: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 20 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let c = collectionView.dequeueReusableCell(withReuseIdentifier: "c", for: indexPath) as! BlurCell c.text = "\(indexPath.item)" return c } } Result:
Shadow disappears then reappears on UITableView updates
I am using tableView.beginUpdates() and tableView.endUpdates() to expand/contract a cell that has shadow. When the table updates, it removes all shadows from cells and then puts them back as shown here I have tried using willDisplayCell and also tried changing the shadow to its own view, shadow on contentView and shadow on cell, none worked. How do i keep the shadow? extension UIView { func addTutShadow(shadowOpacity: Float? = nil) { self.layer.shadowColor = UIColor.black.cgColor self.layer.shadowOffset = CGSize(width: 0, height: 3) self.layer.shadowRadius = 12 * kHeightFactor self.layer.shadowOpacity = shadowOpacity ?? 0.12 } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TopicListCard cell.topic = rankedTopics[indexPath.section] cell.backgroundColor = .clear cell.backgroundView = UIView() cell.selectedBackgroundView = UIView() cell.delegate = self cell.addTutShadow() cell.setup() return cell } var selectedIndex = -1 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if selectedIndex != indexPath.section { selectedIndex = indexPath.section tableView.beginUpdates() tableView.endUpdates() } else { selectedIndex = -1 tableview.deselectRow(at: indexPath, animated: true) tableView.beginUpdates() tableView.endUpdates() } }
It's tough to say exactly, without seeing you full cell setup... I whipped up a quick example of one way to do this... Looks like this: Data Structs struct Topic { var title: String = "" var status: String = "" var icon: String = "" } struct TopicCellStruct { var topic: Topic = Topic() var expanded: Bool = false } Simple gradient view - for the background class MyGradView: UIView { override class var layerClass: AnyClass { return CAGradientLayer.self } private var gLayer: CAGradientLayer { return self.layer as! CAGradientLayer } override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } func commonInit() { gLayer.colors = [ UIColor(red: 0.71, green: 0.88, blue: 1.0, alpha: 1).cgColor, UIColor(red: 0.95, green: 0.95, blue: 1.0, alpha: 1).cgColor ] gLayer.locations = [0, 1] gLayer.startPoint = CGPoint(x: 0.5, y: 0.0) gLayer.endPoint = CGPoint(x: 0.5, y: 1.0) } } Example controller class - with some sample generated data class ShadowTestVC: UIViewController { var myData: [TopicCellStruct] = [] let tableView = UITableView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground // some sample data let p1 = "You have correctly answered " let p2 = "% of the questions you attempted in this topic." var pct: Int = 5 let titles: [String] = [ "Basic Algebra", "Isolating a Variable", "Absolute Value", "Solving Linear Equations", "Solving Radical Equations", ] let syms: [String] = [ "function", "multiply.circle", "ruler.fill", "arrow.up.arrow.down", "x.squareroot", ] for (title, sym) in zip(titles, syms) { pct += 4 let s: String = p1 + "\(pct)" + p2 let t = Topic(title: title, status: s, icon: sym) let tcs = TopicCellStruct(topic: t, expanded: false) myData.append(tcs) } for i in 6...9 { pct += 4 let s: String = p1 + "\(pct)" + p2 let t = Topic(title: "Title \(i)", status: s, icon: "\(i).circle.fill") let tcs = TopicCellStruct(topic: t, expanded: false) myData.append(tcs) } // a label above the table view let topUI = UILabel() topUI.font = .systemFont(ofSize: 24, weight: .regular) topUI.numberOfLines = 0 topUI.text = "This is some text to represent the UI elements above the table view." // a gradient view for the background let gradientBKGView = MyGradView() [gradientBKGView, topUI, tableView].forEach { v in v.translatesAutoresizingMaskIntoConstraints = false view.addSubview(v) } let g = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ gradientBKGView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), gradientBKGView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), gradientBKGView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), gradientBKGView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), topUI.topAnchor.constraint(equalTo: g.topAnchor, constant: 32.0), topUI.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0), topUI.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -48.0), tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0), tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0), tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0), tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -16.0), ]) tableView.backgroundColor = .clear tableView.register(ShadowedCell.self, forCellReuseIdentifier: ShadowedCell.ident) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .none } } extension ShadowTestVC: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return myData.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let c = tableView.dequeueReusableCell(withIdentifier: ShadowedCell.ident, for: indexPath) as! ShadowedCell c.fillData(myData[indexPath.row]) c.selectionStyle = .none return c } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let c = tableView.cellForRow(at: indexPath) as? ShadowedCell else { return } myData[indexPath.row].expanded.toggle() c.isExpanded = myData[indexPath.row].expanded UIView.animate(withDuration: 0.3, animations: { tableView.performBatchUpdates(nil, completion: nil) }) } } Example cell class - I made some guesses at your layout, and I set the shadow darker than yours (.shadowOpacity = 0.75) to make it a bit more visible. class ShadowedCell: UITableViewCell { public static let ident: String = "sc" public var isExpanded: Bool = false { didSet { expandedConstraint.isActive = isExpanded ivVerticalExpandedConstraint.isActive = isExpanded ivHorizontalExpandedConstraint.isActive = isExpanded } } private let titleLabel = UILabel() private let statusLabel = UILabel() private let theImageView = UIImageView() private let getStartedBtn = UIButton() private let containerView = UIView() private let shadowView = UIView() private var collapsedConstraint: NSLayoutConstraint! private var expandedConstraint: NSLayoutConstraint! private var ivVerticalCollapsedConstraint: NSLayoutConstraint! private var ivVerticalExpandedConstraint: NSLayoutConstraint! private var ivHorizontalCollapsedConstraint: NSLayoutConstraint! private var ivHorizontalExpandedConstraint: NSLayoutConstraint! override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { clipsToBounds = true contentView.clipsToBounds = true containerView.clipsToBounds = true [shadowView, containerView, theImageView, titleLabel, statusLabel, getStartedBtn].forEach { v in v.translatesAutoresizingMaskIntoConstraints = false } [titleLabel, statusLabel, getStartedBtn].forEach { v in v.setContentHuggingPriority(.required, for: .vertical) v.setContentCompressionResistancePriority(.required, for: .vertical) } [titleLabel, statusLabel, theImageView, getStartedBtn].forEach { v in containerView.addSubview(v) } contentView.addSubview(shadowView) contentView.addSubview(containerView) contentView.backgroundColor = .clear self.backgroundColor = .clear shadowView.backgroundColor = .white titleLabel.numberOfLines = 0 titleLabel.font = .systemFont(ofSize: 20.0, weight: .regular) statusLabel.numberOfLines = 0 statusLabel.font = .systemFont(ofSize: 18.0, weight: .regular) getStartedBtn.setTitle("Get Started", for: []) getStartedBtn.setTitleColor(.white, for: .normal) getStartedBtn.setTitleColor(.lightGray, for: .highlighted) getStartedBtn.backgroundColor = .black getStartedBtn.layer.cornerRadius = 12 let g = contentView.layoutMarginsGuide collapsedConstraint = titleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0) expandedConstraint = getStartedBtn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16.0) ivVerticalCollapsedConstraint = theImageView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5.0) ivVerticalExpandedConstraint = theImageView.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor, constant: 0.0) ivHorizontalCollapsedConstraint = theImageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0) ivHorizontalExpandedConstraint = theImageView.leadingAnchor.constraint(equalTo: statusLabel.trailingAnchor, constant: 16.0) collapsedConstraint.priority = .required - 2 expandedConstraint.priority = .required - 1 ivVerticalCollapsedConstraint.priority = .required - 2 ivVerticalExpandedConstraint.priority = .required - 1 ivHorizontalCollapsedConstraint.priority = .required - 2 ivHorizontalExpandedConstraint.priority = .required - 1 NSLayoutConstraint.activate([ shadowView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), shadowView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), shadowView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), shadowView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0), titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0), titleLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6), titleLabel.heightAnchor.constraint(equalToConstant: 60.0), statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12.0), statusLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0), statusLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6), theImageView.widthAnchor.constraint(equalToConstant: 50.0), theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor), getStartedBtn.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32.0), getStartedBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0), getStartedBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0), collapsedConstraint, ivVerticalCollapsedConstraint, ivHorizontalCollapsedConstraint, ]) shadowView.layer.shadowColor = UIColor.black.cgColor shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 3.0) shadowView.layer.shadowOpacity = 0.75 shadowView.layer.cornerRadius = 16.0 } public func fillData(_ t: TopicCellStruct) { titleLabel.text = t.topic.title statusLabel.text = t.topic.status if let img = UIImage(systemName: t.topic.icon) { theImageView.image = img } isExpanded = t.expanded } } Give that a try... it should avoid the shadow issue you were seeing. Then compare my approach to yours. As a side note: I'm sure you already noticed in your development... while the design is very nice with transparent cells and table view, the expand/collapse process looks a bit "quirky" as the cells that were "out of view" (below the bottom of the table view frame) don't really "slide up" along with the other cells. Edit -- a couple of very minor changes to improve the "glitchy" lower-cells-animation... All classes* struct Topic { var title: String = "" var status: String = "" var icon: String = "" } struct TopicCellStruct { var topic: Topic = Topic() var expanded: Bool = false } class ShadowedCell: UITableViewCell { public static let ident: String = "sc" private let titleLabel = UILabel() private let statusLabel = UILabel() private let theImageView = UIImageView() private let getStartedBtn = UIButton() private let containerView = UIView() private let shadowView = UIView() private var collapsedConstraint: NSLayoutConstraint! private var expandedConstraint: NSLayoutConstraint! private var ivVerticalCollapsedConstraint: NSLayoutConstraint! private var ivVerticalExpandedConstraint: NSLayoutConstraint! private var ivHorizontalCollapsedConstraint: NSLayoutConstraint! private var ivHorizontalExpandedConstraint: NSLayoutConstraint! override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { clipsToBounds = true contentView.clipsToBounds = true containerView.clipsToBounds = true [shadowView, containerView, theImageView, titleLabel, statusLabel, getStartedBtn].forEach { v in v.translatesAutoresizingMaskIntoConstraints = false } [titleLabel, statusLabel, getStartedBtn].forEach { v in v.setContentHuggingPriority(.required, for: .vertical) v.setContentCompressionResistancePriority(.required, for: .vertical) } [titleLabel, statusLabel, theImageView, getStartedBtn].forEach { v in containerView.addSubview(v) } contentView.addSubview(shadowView) contentView.addSubview(containerView) contentView.backgroundColor = .clear self.backgroundColor = .clear shadowView.backgroundColor = .white titleLabel.numberOfLines = 0 titleLabel.font = .systemFont(ofSize: 20.0, weight: .regular) statusLabel.numberOfLines = 0 statusLabel.font = .systemFont(ofSize: 18.0, weight: .regular) getStartedBtn.setTitle("Get Started", for: []) getStartedBtn.setTitleColor(.white, for: .normal) getStartedBtn.setTitleColor(.lightGray, for: .highlighted) getStartedBtn.backgroundColor = .black getStartedBtn.layer.cornerRadius = 12 let g = contentView.layoutMarginsGuide collapsedConstraint = titleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0) expandedConstraint = getStartedBtn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16.0) ivVerticalCollapsedConstraint = theImageView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5.0) ivVerticalExpandedConstraint = theImageView.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor, constant: 0.0) ivHorizontalCollapsedConstraint = theImageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0) ivHorizontalExpandedConstraint = theImageView.leadingAnchor.constraint(equalTo: statusLabel.trailingAnchor, constant: 16.0) collapsedConstraint.priority = .required - 2 expandedConstraint.priority = .required - 1 ivVerticalCollapsedConstraint.priority = .required - 2 ivVerticalExpandedConstraint.priority = .required - 1 ivHorizontalCollapsedConstraint.priority = .required - 2 ivHorizontalExpandedConstraint.priority = .required - 1 NSLayoutConstraint.activate([ shadowView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), shadowView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), shadowView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), shadowView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0), titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0), titleLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6), titleLabel.heightAnchor.constraint(equalToConstant: 60.0), statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12.0), statusLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0), statusLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6), theImageView.widthAnchor.constraint(equalToConstant: 50.0), theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor), getStartedBtn.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32.0), getStartedBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0), getStartedBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0), collapsedConstraint, ivVerticalCollapsedConstraint, ivHorizontalCollapsedConstraint, ]) shadowView.layer.shadowColor = UIColor.black.cgColor shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 3.0) shadowView.layer.shadowOpacity = 0.75 shadowView.layer.cornerRadius = 16.0 } public func fillData(_ t: TopicCellStruct) { titleLabel.text = t.topic.title statusLabel.text = t.topic.status if let img = UIImage(systemName: t.topic.icon) { theImageView.image = img } expandedConstraint.isActive = t.expanded ivVerticalExpandedConstraint.isActive = t.expanded ivHorizontalExpandedConstraint.isActive = t.expanded } public func expand(_ isExpanded: Bool) { expandedConstraint.isActive = isExpanded ivVerticalExpandedConstraint.isActive = isExpanded ivHorizontalExpandedConstraint.isActive = isExpanded UIView.animate(withDuration: 0.3, animations: { self.layoutIfNeeded() }) } } class ShadowTestVC: UIViewController { var myData: [TopicCellStruct] = [] let tableView = UITableView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground // some sample data let p1 = "You have correctly answered " let p2 = "% of the questions you attempted in this topic." var pct: Int = 5 let titles: [String] = [ "Basic Algebra", "Isolating a Variable", "Absolute Value", "Solving Linear Equations", "Solving Radical Equations", ] let syms: [String] = [ "function", "multiply.circle", "ruler.fill", "arrow.up.arrow.down", "x.squareroot", ] for (title, sym) in zip(titles, syms) { pct += 4 let s: String = p1 + "\(pct)" + p2 let t = Topic(title: title, status: s, icon: sym) let tcs = TopicCellStruct(topic: t, expanded: false) myData.append(tcs) } for i in 6...19 { pct += 4 let s: String = p1 + "\(pct)" + p2 let t = Topic(title: "Title \(i)", status: s, icon: "\(i).circle.fill") let tcs = TopicCellStruct(topic: t, expanded: false) myData.append(tcs) } // a label above the table view let topUI = UILabel() topUI.font = .systemFont(ofSize: 24, weight: .regular) topUI.numberOfLines = 0 topUI.text = "This is some text to represent the UI elements above the table view." // a gradient view for the background let gradientBKGView = MyGradView() [gradientBKGView, topUI, tableView].forEach { v in v.translatesAutoresizingMaskIntoConstraints = false view.addSubview(v) } let g = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ gradientBKGView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), gradientBKGView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), gradientBKGView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), gradientBKGView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0), topUI.topAnchor.constraint(equalTo: g.topAnchor, constant: 32.0), topUI.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0), topUI.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -48.0), tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0), tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0), tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0), tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -16.0), ]) tableView.backgroundColor = .clear tableView.register(ShadowedCell.self, forCellReuseIdentifier: ShadowedCell.ident) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .none } } extension ShadowTestVC: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return myData.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let c = tableView.dequeueReusableCell(withIdentifier: ShadowedCell.ident, for: indexPath) as! ShadowedCell c.fillData(myData[indexPath.row]) c.selectionStyle = .none return c } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let c = tableView.cellForRow(at: indexPath) as? ShadowedCell else { return } myData[indexPath.row].expanded.toggle() c.expand(myData[indexPath.row].expanded) tableView.beginUpdates() tableView.endUpdates() } } class MyGradView: UIView { override class var layerClass: AnyClass { return CAGradientLayer.self } private var gLayer: CAGradientLayer { return self.layer as! CAGradientLayer } override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } func commonInit() { gLayer.colors = [ UIColor(red: 0.71, green: 0.88, blue: 1.0, alpha: 1).cgColor, UIColor(red: 0.95, green: 0.95, blue: 1.0, alpha: 1).cgColor ] gLayer.locations = [0, 1] gLayer.startPoint = CGPoint(x: 0.5, y: 0.0) gLayer.endPoint = CGPoint(x: 0.5, y: 1.0) } }
How to Implement UITextView inside UITableView inside UITableView Swift
how to implement UITextView inside UITableView inside UITableView ?? (1) If you type text in 'UITextView', the height of 'UITableViewCell2' is automatically increased. (2) When the height of 'UITableViewCell2' is increased, the height of 'UITableViewCell' is automatically increased accordingly. I have implemented the case of (1) but not (2). How should I implement it?
Nesting table views may not be the ideal solution for this, but your UITableViewCell would need to estimate and measure the whole height of the embedded UITableView, and propagate changes up to the parent table.
You might want to give this a try... It using a single-section table view. Each cell contains a UIStackView that arranges the (variable) UITextViews. No #IBOutlet or #IBAction or prototype cell connections... just assign a standard UIViewController custom class to TableTextViewsViewController: // // TableTextViewsViewController.swift // Created by Don Mag on 3/10/20. // import UIKit class TextViewsCell: UITableViewCell, UITextViewDelegate { let frameView: UIView = { let v = UIView() v.backgroundColor = .clear v.layer.borderColor = UIColor(red: 0.0, green: 0.5, blue: 0.0, alpha: 1.0).cgColor v.layer.borderWidth = 1 v.translatesAutoresizingMaskIntoConstraints = false return v }() let stackView: UIStackView = { let v = UIStackView() v.axis = .vertical v.spacing = 8 v.translatesAutoresizingMaskIntoConstraints = false return v }() let stackViewPadding: CGFloat = 8.0 var textViewCosure: ((Int, String)->())? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } func commonInit() -> Void { let g = contentView.layoutMarginsGuide contentView.addSubview(frameView) frameView.addSubview(stackView) // bottom constraint needs to be less than 1000 (required) to avoid auot-layout warnings let frameViewBottomConstrait = frameView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0) frameViewBottomConstrait.priority = UILayoutPriority(rawValue: 999) NSLayoutConstraint.activate([ frameView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0), frameView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0), frameView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0), frameViewBottomConstrait, stackView.topAnchor.constraint(equalTo: frameView.topAnchor, constant: stackViewPadding), stackView.leadingAnchor.constraint(equalTo: frameView.leadingAnchor, constant: stackViewPadding), stackView.trailingAnchor.constraint(equalTo: frameView.trailingAnchor, constant: -stackViewPadding), stackView.bottomAnchor.constraint(equalTo: frameView.bottomAnchor, constant: -stackViewPadding), ]) } override func prepareForReuse() { super.prepareForReuse() stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } } func fillData(_ strings: [String]) -> Void { strings.forEach { let v = UITextView() v.font = UIFont.systemFont(ofSize: 16.0) v.isScrollEnabled = false // hugging and compression resistance set to required for cell expansion animation v.setContentHuggingPriority(.required, for: .vertical) v.setContentCompressionResistancePriority(.required, for: .vertical) v.text = $0 // frame the text view v.layer.borderColor = UIColor.blue.cgColor v.layer.borderWidth = 1 v.delegate = self stackView.addArrangedSubview(v) } } func textViewDidChange(_ textView: UITextView) { guard let idx = stackView.arrangedSubviews.firstIndex(of: textView) else { fatalError("Shouldn't happen, but couldn't find the textView index") } textViewCosure?(idx, textView.text) } } class TableTextViewsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let topLabel: UILabel = { let v = UILabel() v.text = "Top Label" v.translatesAutoresizingMaskIntoConstraints = false return v }() let tableView: UITableView = { let v = UITableView() v.layer.borderColor = UIColor.red.cgColor v.layer.borderWidth = 1 v.translatesAutoresizingMaskIntoConstraints = false return v }() var myData: [[String]] = [[String]]() var textViewsInRows: [Int] = [ 3, 4, 2, 6, 1, 4, 3, ] override func viewDidLoad() { super.viewDidLoad() // generate some dummy data var i = 1 textViewsInRows.forEach { var s: [String] = [String]() for j in 1...$0 { s.append("Table Row: \(i) TextView \(j)") } myData.append(s) i += 1 } view.addSubview(topLabel) view.addSubview(tableView) let g = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ topLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0), topLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0), topLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0), tableView.topAnchor.constraint(equalTo: topLabel.bottomAnchor, constant: 8.0), tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0), tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0), tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0), ]) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .none tableView.keyboardDismissMode = .onDrag tableView.register(TextViewsCell.self, forCellReuseIdentifier: "TextViewsCell") } // MARK: - Table view data source func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return myData.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "TextViewsCell", for: indexPath) as! TextViewsCell cell.fillData(myData[indexPath.row]) cell.textViewCosure = { [weak self] idx, str in // update our data self?.myData[indexPath.row][idx] = str // update table view cell height self?.tableView.beginUpdates() self?.tableView.endUpdates() } return cell } } Result - red border is the tableView, green border is each cell's contentView, blue border is each textView:
How to access cell members (for animation) from scrollViewDidScroll method in UICollectionViewController?
How do I access members (an UIImage or UITextView) added to the view in a UICollectionViewCell from the scrollViewDidScroll method in the UICollectionViewController? I would like to animate i.e. move the image and text at "different speed" while scrolling vertically to the next cell. I understand that this can be done within the scrollViewDidScroll method but I don't know how to access the members. the ViewController: class OnboardingViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { override func scrollViewDidScroll(_ scrollView: UIScrollView) { **code here which I just can't figure out....** } override func viewDidLoad() { super.viewDidLoad() collectionView?.backgroundColor = .white collectionView?.register(PageCell.self, forCellWithReuseIdentifier: "cellId") collectionView?.isPagingEnabled = true collectionView.showsHorizontalScrollIndicator = false // this method "creates" the UIPageControll and assigns setupPageControl() } lazy var pageControl: UIPageControl = { let pageControl = UIPageControl() pageControl.currentPage = 0 pageControl.numberOfPages = data.count <--- data provided by a model from a plist - works perfectly pageControl.currentPageIndicatorTintColor = .black pageControl.pageIndicatorTintColor = .gray pageControl.translatesAutoresizingMaskIntoConstraints = false return pageControl }() private func setupPageControl() { view.addSubview(pageControl) NSLayoutConstraint.activate([ onboardingPageControl.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), onboardingPageControl.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), onboardingPageControl.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), ]) } all the other override methods are implemented in an extension and working fine i.e. numberOfItemsInSection section: Int) -> Int { return data.count } This is the PageCell: class PageCell: UICollectionViewCell { var myPage: MyModel? { didSet { guard let unwrappedPage = myPage else { return } // the image in question: myImage.image = UIImage(named: unwrappedPage.imageName) myImage.translatesAutoresizingMaskIntoConstraints = false myImage.contentMode = .scaleAspectFit // the text in question let attributedText = NSMutableAttributedString(string: unwrappedPage.title, attributes: [:]) attributedText.append(NSAttributedString(string: "\n\(unwrappedPage.description)", attributes: [:])) myText.attributedText = attributedText myText.translatesAutoresizingMaskIntoConstraints = false myText.textColor = .black myText.textAlignment = .center myText.isEditable = false myText.isScrollEnabled = false myText.isSelectable = false } let myImage: UIImageView = { let imageView = UIImageView() return imageView }() let myText: UITextView = { let textView = UITextView() return textView }() fileprivate func setup() { addSubview(myImage) NSLayoutConstraint.activate([ myImage.safeAreaLayoutGuide.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 60), myImage.centerXAnchor.constraint(equalTo: centerXAnchor), myImage.leadingAnchor.constraint(equalTo: leadingAnchor), myImage.trailingAnchor.constraint(equalTo: trailingAnchor), myImage.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.4) ]) addSubview(myText) NSLayoutConstraint.activate([ myText.topAnchor.constraint(equalTo: myImage.bottomAnchor, constant: 16), myText.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), myText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16) ]) } override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
You can achieve this by getting current visible cells of your collectionView, and it would be great if you access them in scrollViewDidEndDecelerating intead of scrollViewDidScroll. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { for cell in collectionView.visibleCells { //cell.imageView //cell.txtView // you can access both imageView and txtView here let indexPath = collectionView.indexPath(for: cell) print(indexPath) // this will give you indexPath as well to differentiate } }
Im trying to re-create a table view from one screen to another but keep getting the error signal sigbart
I'm creating a simple app. I'm trying to make two screens look the same but I can't seem to get one of the table views to work. Instead, when the set up table view function is called, the error signal sigbart appears. I can't see why this is since on the other screen the table view works no problem and I've copied over the code. let tableview: UITableView = { let tv = UITableView() tv.backgroundColor = UIColor.white tv.translatesAutoresizingMaskIntoConstraints = false return tv }() func setupTableView() { tableview.delegate = self tableview.dataSource = self tableview.register(BunchCells.self, forCellReuseIdentifier: "cellId") tableview.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0) view.addSubview(tableview) NSLayoutConstraint.activate([ tableview.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 170), tableview.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), tableview.rightAnchor.constraint(equalTo: self.view.rightAnchor), tableview.leftAnchor.constraint(equalTo: self.view.leftAnchor) ]) } class BunchCells: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupView() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } let cellView: UIView = { let view = UIView() view.backgroundColor = UIColor.red view.layer.cornerRadius = 10 view.translatesAutoresizingMaskIntoConstraints = false return view }() let dayLabel: UILabel = { let label = UILabel() label.text = "Day 1" label.textColor = UIColor.white label.font = UIFont.boldSystemFont(ofSize: 16) label.translatesAutoresizingMaskIntoConstraints = false return label }() func setupView() { addSubview(cellView) NSLayoutConstraint.activate([ cellView.topAnchor.constraint(equalTo: self.topAnchor, constant: 20), cellView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10), cellView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 10), cellView.bottomAnchor.constraint(equalTo: self.bottomAnchor) ]) dayLabel.heightAnchor.constraint(equalToConstant: 200).isActive = true dayLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true dayLabel.centerYAnchor.constraint(equalTo: cellView.centerYAnchor).isActive = true dayLabel.leftAnchor.constraint(equalTo: cellView.leftAnchor, constant: 20).isActive = true } }
I run your program and doesn't have any error, but I think this looks a little bit weird: weak var tableView: UITableView! let tableview: UITableView = { let tv = UITableView() tv.translatesAutoresizingMaskIntoConstraints = false tv.separatorColor = UIColor.white return tv }() Maybe cause of your error was what you accidentally invoke method on tableView which is always nil.? If it is not, give some hints how to reproduce your error).