Swift - Blur UICollectionView Cell background - ios

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:

Related

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 can implement two vertical button in swipe to delete in ios?

I am trying to implement swipe to delete feature with two options in tableview, one is to delete and another one is to Update.The things I want is these options should be vertical rather than horizontal.I have checked so many question but nothing find.
Thanks in advance for support.
.
As I mentioned in the comments, here is one approach:
add your buttons to the cell
add a "container" view to the cell
constrain the container view so it overlays / covers the buttons
add a Pan gesture recognizer to the container view so you can drag it left / right
as you drag it left, it will "reveal" the buttons underneath
You lose all of the built-in swipe functionality, but this is one approach that might give you the design you're going for.
First, an example of creating a "drag view":
class DragTestViewController: UIViewController {
let backgroundView = UIView()
let containerView = UIView()
// leading and trailing constraints for the drag view
private var leadingConstraint: NSLayoutConstraint!
private var trailingConstraint: NSLayoutConstraint!
private let origLeading = CGFloat(60.0)
private let origTrailing = CGFloat(-60.0)
private var currentLeading = CGFloat(60.0)
private var currentTrailing = CGFloat(-60.0)
override func viewDidLoad() {
super.viewDidLoad()
backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.backgroundColor = .cyan
backgroundView.clipsToBounds = true
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.backgroundColor = .red
// add a label to the container view
let exampleLabel = UILabel()
exampleLabel.translatesAutoresizingMaskIntoConstraints = false
exampleLabel.text = "Drag Me"
exampleLabel.textColor = .yellow
containerView.addSubview(exampleLabel)
backgroundView.addSubview(containerView)
view.addSubview(backgroundView)
leadingConstraint = containerView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: origLeading)
trailingConstraint = containerView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: origTrailing)
NSLayoutConstraint.activate([
// constrain backgroundView top to top + 80
backgroundView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80.0),
// constrain backgroundView leading / trailing to leading / trailing with 40-pt "padding"
backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40.0),
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -40.0),
// constrain height to 100
backgroundView.heightAnchor.constraint(equalToConstant: 100.0),
// constrain containerView top / bottom to backgroundView top / bottom with 8-pt padding
containerView.topAnchor.constraint(equalTo: backgroundView.topAnchor, constant: 8.0),
containerView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: -8.0),
// activate leading / trailing constraints
leadingConstraint,
trailingConstraint,
// constrain the example label centered in the container view
exampleLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
exampleLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
])
// pan gesture recognizer
let p = UIPanGestureRecognizer(target: self, action: #selector(self.drag(_:)))
containerView.addGestureRecognizer(p)
}
#objc func drag(_ g: UIPanGestureRecognizer) -> Void {
// when we get a Pan on the containerView - a "drag" ...
guard let sv = g.view?.superview else {
return
}
let translation = g.translation(in: sv)
switch g.state {
case .began:
// update current vars
currentLeading = leadingConstraint.constant
currentTrailing = trailingConstraint.constant
case .changed:
// only track left-right dragging
leadingConstraint.constant = currentLeading + translation.x
trailingConstraint.constant = currentTrailing + translation.x
default:
break
}
}
}
That code will produce this:
A red view with a centered label, inside a cyan view. You can drag the red "container" view left and right.
Add a view controller to a new project and assign its Custom Class to DragTestViewController from the above code. There are no #IBOutlet or #IBAction connections, so you should be able to run it as-is. See if you can drag the red view.
Using that as a starting point, we can get this:
with this code:
// simple rounded-corner shadowed view
class ShadowRoundedView: UIView {
let shadowLayer: CAShapeLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
self.layer.addSublayer(shadowLayer)
clipsToBounds = false
backgroundColor = .clear
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOffset = CGSize(width: 0.0, height: 1.0)
shadowLayer.shadowRadius = 4.0
shadowLayer.shadowOpacity = 0.6
shadowLayer.shouldRasterize = true
shadowLayer.rasterizationScale = UIScreen.main.scale
}
override func layoutSubviews() {
super.layoutSubviews()
let pth = UIBezierPath(roundedRect: bounds, cornerRadius: 16.0)
shadowLayer.path = pth.cgPath
}
}
// simple rounded button
class RoundedButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.size.height * 0.5
}
}
class DragRevealCell: UITableViewCell {
// callback closure for button taps
var callback: ((Int) -> ())?
// this will hold the "visible" labels, and will initially cover the buttons
let containerView: ShadowRoundedView = {
let v = ShadowRoundedView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// this will hold the buttons
let buttonsView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.clipsToBounds = true
return v
}()
// a "delete" button
let deleteButton: RoundedButton = {
let v = RoundedButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Delete", for: [])
v.setTitleColor(.blue, for: [])
v.setTitleColor(.lightGray, for: .highlighted)
v.backgroundColor = .white
return v
}()
// an "update" button
let updateButton: RoundedButton = {
let v = RoundedButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Update", for: [])
v.setTitleColor(.white, for: [])
v.setTitleColor(.lightGray, for: .highlighted)
v.backgroundColor = .blue
return v
}()
// single label for this example cell
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.numberOfLines = 0
return v
}()
// leading and trailing constraints for the container view
private var leadingConstraint: NSLayoutConstraint!
private var trailingConstraint: NSLayoutConstraint!
private let origLeading = CGFloat(8.0)
private let origTrailing = CGFloat(-8.0)
private var currentLeading = CGFloat(0.0)
private var currentTrailing = CGFloat(0.0)
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 {
// cell background color
backgroundColor = UIColor(white: 0.95, alpha: 1.0)
// add buttons to buttons container view
buttonsView.addSubview(deleteButton)
buttonsView.addSubview(updateButton)
// add label to container view -- this is where you would add all your labels, stack views, image views, etc.
containerView.addSubview(myLabel)
// add buttons view first
addSubview(buttonsView)
// add container view second - this will "overlay" it on top of the buttons view
addSubview(containerView)
// containerView leading / trailing constraints - these will be updated as we drag
leadingConstraint = containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: origLeading)
trailingConstraint = containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: origTrailing)
// needed to avoid layout warnings
let bottomConstraint = containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0)
bottomConstraint.priority = UILayoutPriority(rawValue: 999)
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
leadingConstraint,
trailingConstraint,
bottomConstraint,
myLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8.0),
myLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
myLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
myLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -8.0),
myLabel.heightAnchor.constraint(equalToConstant: 120.0),
buttonsView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
buttonsView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
deleteButton.topAnchor.constraint(equalTo: buttonsView.topAnchor, constant: 0.0),
deleteButton.leadingAnchor.constraint(equalTo: buttonsView.leadingAnchor, constant: 8.0),
deleteButton.trailingAnchor.constraint(equalTo: buttonsView.trailingAnchor, constant: -8.0),
updateButton.bottomAnchor.constraint(equalTo: buttonsView.bottomAnchor, constant: 0.0),
updateButton.leadingAnchor.constraint(equalTo: buttonsView.leadingAnchor, constant: 8.0),
updateButton.trailingAnchor.constraint(equalTo: buttonsView.trailingAnchor, constant: -8.0),
updateButton.topAnchor.constraint(equalTo: deleteButton.bottomAnchor, constant: 12.0),
updateButton.heightAnchor.constraint(equalTo: deleteButton.heightAnchor),
updateButton.widthAnchor.constraint(equalTo: deleteButton.widthAnchor),
deleteButton.widthAnchor.constraint(equalToConstant: 120.0),
deleteButton.heightAnchor.constraint(equalToConstant: 40.0),
])
// delete button border
deleteButton.layer.borderColor = UIColor.blue.cgColor
deleteButton.layer.borderWidth = 1.0
// targets for button taps
deleteButton.addTarget(self, action: #selector(self.deleteTapped(_:)), for: .touchUpInside)
updateButton.addTarget(self, action: #selector(self.updateTapped(_:)), for: .touchUpInside)
// pan gesture recognizer
let p = UIPanGestureRecognizer(target: self, action: #selector(self.drag(_:)))
containerView.addGestureRecognizer(p)
}
#objc func drag(_ g: UIPanGestureRecognizer) -> Void {
// when we get a Pan on the container view - a "drag" ...
guard let sv = g.view?.superview else {
return
}
let translation = g.translation(in: sv)
switch g.state {
case .began:
currentLeading = leadingConstraint.constant
currentTrailing = trailingConstraint.constant
case .changed:
// only track left-right dragging
// don't allow drag-to-the-right
if currentLeading + translation.x <= origLeading {
leadingConstraint.constant = currentLeading + translation.x
trailingConstraint.constant = currentTrailing + translation.x
}
default:
// if the drag-left did not fully reveal the buttons, animate the container view back in place
if containerView.frame.maxX > buttonsView.frame.minX {
self.leadingConstraint.constant = self.origLeading
self.trailingConstraint.constant = self.origTrailing
UIView.animate(withDuration: 0.3, animations: {
self.layoutIfNeeded()
}, completion: { _ in
//self.dragX = 0.0
})
}
}
}
#objc func deleteTapped(_ sender: Any?) -> Void {
callback?(0)
}
#objc func updateTapped(_ sender: Any?) -> Void {
callback?(1)
}
}
class DragRevealTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(DragRevealCell.self, forCellReuseIdentifier: "DragRevealCell")
tableView.separatorStyle = .none
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "DragRevealCell", for: indexPath) as! DragRevealCell
c.myLabel.text = "Row \(indexPath.row)" + "\n" + "This is where you would populate the cell's labels, image views, any other UI elements, etc."
c.selectionStyle = .none
c.callback = { value in
if value == 0 {
print("Delete action")
} else {
print("Update action")
}
}
return c
}
}
Add a UITableViewController the project and assign its Custom Class to DragRevealTableViewController from the above code. Again, there are no #IBOutlet or #IBAction connections, so you should be able to run it as-is.
NOTE: This is example code only, and should not be considered "production ready"!!! It is only partially implemented and will likely need quite a bit more work. But, it may give you a good starting point.

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:

UICollectionView doesn't scroll inside UITableViewCell

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.

CollectionView Cell Rounded Corners not Working

I have a prototype cell for a collection view and am trying to make the edges rounded, but I can't figure out how to use self.layer.cornerRadius = 3.0 and make it work. Could anyone tell me what I'm doing wrong?
import UIKit
final class TagCell: UICollectionViewCell {
let textLabel: UILabel
override init(frame: CGRect) {
self.textLabel = UILabel(frame: .zero)
self.textLabel.setFont(14)
self.textLabel.translatesAutoresizingMaskIntoConstraints = false
self.textLabel.textAlignment = .center
self.textLabel.backgroundColor = .red
self.textLabel.layer.cornerRadius = 2.0
super.init(frame: frame)
self.contentView.layer.cornerRadius = 2.0
self.contentView.clipsToBounds = true
self.contentView.addSubview(textLabel)
NSLayoutConstraint.activate([
self.textLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 16),
self.textLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -16),
self.textLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 16),
self.textLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -16),
])
}
required init?(coder decoder: NSCoder) {
fatalError()
}
}
it's better to create a subView inside the contentView and apply the corner to it
self.contentView.layer.cornerRadius = 2.0
self.contentView.clipsToBounds = true

Resources