Shadow disappears then reappears on UITableView updates - ios

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)
}
}

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:

Programmatically set constraints for uitableviewcell with uilabel and uiimageview

I am trying to build my tableview with custom UITableViewCell but facing some issues in coinstraints. Following is my custom UITablviewCell class:
class MovieCell: UITableViewCell {
static let identifier = "MovieCell"
let cellMargin = 15.0
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
view.layer.cornerRadius = 5
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
private let movieNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.movieAppBoldFont(size: 15)
label.numberOfLines = 0
return label
}()
private let movieImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
var movieCellViewModel : MoviewCellViewModel = MoviewCellViewModel(image: nil, name: "") {
didSet {
movieNameLabel.text = movieCellViewModel.name
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0)
layoutSubView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func layoutSubView() {
addSubview(containerView)
containerView.addSubview(movieNameLabel)
containerView.addSubview(movieImageView)
let marginGuide = containerView.layoutMarginsGuide
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin),
movieImageView.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 5),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
movieImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
movieImageView.widthAnchor.constraint(equalToConstant: 50),
movieImageView.heightAnchor.constraint(equalToConstant: 50),
movieNameLabel.leadingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 5),
movieNameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: -5),
movieNameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
movieNameLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5)
])
}
}
I get the following result with the above code:
I am trying to create a UIView and add content to it. I am adding it because I want to show some empty space as a separator in tableview.
Couple issues...
Your first line in layoutSubView() is:
addSubview(containerView)
where is needs to be:
contentView.addSubview(containerView)
Second, you have movieImageView constrained to contentView:
movieImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
where it should be constrained to marginGuide:
movieImageView.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5),
Third, you have movieNameLabel.leadingAnchor constrained to marginGuide.trailingAnchor:
movieNameLabel.leadingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 5),
where it should be constrained to movieImageView.trailingAnchor:
movieNameLabel.leadingAnchor.constraint(equalTo: movieImageView.trailingAnchor, constant: 5),
Making those changes will give you this (I set image view background to blue, and label background to cyan):
However, when you run the app, you'll see lots of Unable to simultaneously satisfy constraints. messages. This is common when using subviews with subviews in cells.
To get rid of the auto-layout complaints, we can give the containerView bottom anchor a less-than-required priority:
let bottomC = containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin)
bottomC.priority = .required - 1
and then activate that in your constraints block:
//containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin),
bottomC,
Here's your complete MovieCell class with those changes:
class MovieCell: UITableViewCell {
static let identifier = "MovieCell"
let cellMargin = 15.0
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
view.layer.cornerRadius = 5
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
private let movieNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 15, weight: .bold) // UIFont.movieAppBoldFont(size: 15)
label.numberOfLines = 0
return label
}()
private let movieImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
var movieCellViewModel : MoviewCellViewModel = MoviewCellViewModel(image: nil, name: "") {
didSet {
movieNameLabel.text = movieCellViewModel.name
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0)
layoutSubView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func layoutSubView() {
contentView.addSubview(containerView)
containerView.addSubview(movieNameLabel)
containerView.addSubview(movieImageView)
let marginGuide = containerView.layoutMarginsGuide
let bottomC = containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin)
bottomC.priority = .required - 1
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
containerView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
// containerView.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -cellMargin),
bottomC,
movieImageView.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor, constant: 5),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
// movieImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
movieImageView.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5),
movieImageView.widthAnchor.constraint(equalToConstant: 50),
movieImageView.heightAnchor.constraint(equalToConstant: 50),
// movieNameLabel.leadingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: 5),
movieNameLabel.leadingAnchor.constraint(equalTo: movieImageView.trailingAnchor, constant: 5),
movieNameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor, constant: -5),
movieNameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 5),
movieNameLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5)
])
// during dev, so we can easily see the frames
movieImageView.backgroundColor = .systemBlue
movieNameLabel.backgroundColor = .cyan
}
}

Left or right align shouldShowMenuForRowAt menu for a custom UITableView cell

I have a UITableView that I am using for showing chat messages. The cells of this table view are either left or right-aligned and have dynamic width. The menu displayed by shouldShowMenuForRowAt is shown at the horizontal center of the table view cell. Is there a way to shift it to the left or the right edge?
func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
if action == #selector(copy(_:)) {
if let cell = tableView.cellForRow(at: indexPath) as? DiscussionChatMessageCell {
let pasteboard = UIPasteboard.general
pasteboard.string = cell.getMessageLabel().text
}
}
}
Output:
Can the actions for cells be left and right aligned?
Message Cell:
class DiscussionChatMessageCell: UITableViewCell {
private let messageLabel: UITextView
private let senderNameLabel: UILabel
private let messageSentTimeLabel: UILabel
private let messageBubble: UIView
private var bubbleLeadingConstraint: NSLayoutConstraint!
private var bubbleTrailingConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
messageLabel = UITextView()
senderNameLabel = UILabel()
messageSentTimeLabel = UILabel()
messageBubble = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
self.contentView.addSubview(messageBubble)
messageBubble.translatesAutoresizingMaskIntoConstraints = false
messageBubble.addSubview(senderNameLabel)
senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
senderNameLabel.numberOfLines = 0
senderNameLabel.lineBreakMode = .byCharWrapping
senderNameLabel.font = getFont(name: .HelveticaNeueBold, size: .large)
senderNameLabel.textColor = .white
messageBubble.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
messageLabel.isEditable = false
messageLabel.isSelectable = false
messageLabel.dataDetectorTypes = .all
messageLabel.textContainer.lineBreakMode = .byWordWrapping
messageLabel.isScrollEnabled = false
messageLabel.backgroundColor = .clear
messageLabel.isUserInteractionEnabled = true
// messageLabel.numberOfLines = 0
// messageLabel.lineBreakMode = .byWordWrapping
messageLabel.font = getFont(name: .HelveticaNeue, size: .medium)
messageBubble.addSubview(messageSentTimeLabel)
messageSentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
messageSentTimeLabel.lineBreakMode = .byCharWrapping
messageSentTimeLabel.numberOfLines = 0
messageSentTimeLabel.font = getFont(name: .HelveticaNeueItalic, size: .small)
// set hugging and compression resistance for Name label
senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical)
senderNameLabel.setContentHuggingPriority(.required, for: .vertical)
// create bubble Leading and Trailing constraints
bubbleLeadingConstraint = messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
// priority will be changed in configureCell()
bubbleLeadingConstraint.priority = .defaultHigh
bubbleTrailingConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
bubbleLeadingConstraint,
bubbleTrailingConstraint,
messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
messageBubble.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.bottomAnchor.constraint(equalTo: messageSentTimeLabel.topAnchor, constant: -10),
messageSentTimeLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageSentTimeLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageSentTimeLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
])
// corners will have radius: 10
messageBubble.layer.cornerRadius = 10
}
func getMessageLabel() -> UITextView {
return messageLabel
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(message: DiscussionMessage, isSender: Bool, previousMessage: DiscussionMessage?) {
let senderName = isSender ? "You" : message.userName
senderNameLabel.text = senderName + " " + message.userCountryEmoji
let date = Date(timeIntervalSince1970: message.messageTimestamp)
let dayTimePeriodFormatter = DateFormatter()
dayTimePeriodFormatter.timeZone = .current
dayTimePeriodFormatter.dateFormat = "hh:mm a"
let dateString = dayTimePeriodFormatter.string(from: date)
messageLabel.text = message.message
messageSentTimeLabel.text = dateString
messageLabel.textColor = isSender ? .black : .white
senderNameLabel.textColor = isSender ? .black : .white
messageSentTimeLabel.textColor = isSender ? .black : .white
messageSentTimeLabel.textAlignment = isSender ? .right : .left
bubbleLeadingConstraint.priority = isSender ? .defaultLow : .defaultHigh
bubbleTrailingConstraint.priority = isSender ? .defaultHigh : .defaultLow
messageBubble.backgroundColor = isSender ? accentColor : .gray
let senderCorners: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
let nonSenderCorners: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
messageBubble.layer.maskedCorners = isSender ?
// topLeft, topRight, bottomRight
senderCorners
:
// topLeft, topRight, bottomLeft
nonSenderCorners
//
// if let previousMessage = previousMessage {
// if message.userEmailAddress == previousMessage.userEmailAddress && message.userCountryCode == previousMessage.userCountryCode && isSender {
// senderNameLabel.isHidden = true
// } else {
// senderNameLabel.isHidden = false
// }
// } else {
// senderNameLabel.isHidden = false
// }
}
}

UITableView Cell with dynamic height and width not getting reused properly

I have a UITableView cell with dynamic height and width. Initially, it works properly, but when reusing an old cell the constraints are not set correctly. I am deactivating all the old constrains and activating them again. I have also called setNeedsLayout() and layoutIfNeeded(). But it's not helping.
Automatic height setup: (I think this is causing an issue)
discussionTableView.rowHeight = UITableViewAutomaticDimension
discussionTableView.estimatedRowHeight = 10
My table view cell:
class DiscussionChatMessageCell: UITableViewCell {
private let messageLabel: UILabel
private let senderNameLabel: UILabel
private let messageBubble: UIView
let screenWidth: CGFloat
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
messageLabel = UILabel()
senderNameLabel = UILabel()
screenWidth = UIScreen.main.bounds.size.width
messageBubble = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
// self.contentView.backgroundColor = .clear
self.contentView.addSubview(messageBubble)
messageBubble.translatesAutoresizingMaskIntoConstraints = false
messageBubble.addSubview(senderNameLabel)
senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
senderNameLabel.numberOfLines = 0
senderNameLabel.lineBreakMode = .byCharWrapping
senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
senderNameLabel.textColor = .white
messageBubble.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.font = UIFont.systemFont(ofSize: 13)
messageLabel.textColor = .white
NSLayoutConstraint.activate([
messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
messageBubble.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
messageBubble.widthAnchor.constraint(lessThanOrEqualToConstant: screenWidth - 100),
senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(message: String, isSender: Bool) {
senderNameLabel.text = "Default Sender"
messageLabel.text = message
for constraint in messageBubble.constraints {
// messageBubble.removeConstraint(constraint)
constraint.isActive = false
}
for constraint in messageLabel.constraints {
// messageLabel.removeConstraint(constraint)
constraint.isActive = false
}
for constraint in senderNameLabel.constraints {
//senderNameLabel.removeConstraint(constraint)
constraint.isActive = false
}
NSLayoutConstraint.deactivate([
messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10),
messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
])
NSLayoutConstraint.activate([
messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
messageBubble.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
messageBubble.widthAnchor.constraint(lessThanOrEqualToConstant: screenWidth - 100),
senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
])
messageBubble.backgroundColor = isSender ? accentColor : .gray
if isSender {
NSLayoutConstraint.activate([
messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
])
// let corners: UIRectCorner = [.topLeft, .topRight, .bottomLeft]
// roundCorners(corners: corners, isSender: isSender)
} else {
NSLayoutConstraint.activate([
messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
])
// let corners: UIRectCorner = [.topLeft, .topRight, .bottomRight]
// roundCorners(corners: corners, isSender: isSender)
}
}
Reusing cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let discussionChatMessageCell = tableView.dequeueReusableCell(withIdentifier: discussionChatId, for: indexPath) as? DiscussionChatMessageCell else { return UITableViewCell()}
discussionChatMessageCell.configureCell(message: messages[indexPath.row], isSender: isSender[indexPath.row])
discussionChatMessageCell.setNeedsLayout()
discussionChatMessageCell.layoutIfNeeded()
return discussionChatMessageCell
}
Before reusing cell:
After reusing cell:
Edit
When using UITextView instead of UILabel for messageLabel, the constraints work very differently and the table view takes 2-3 seconds to load.
Changed settings for textView
// messageLabel.numberOfLines = 0
// messageLabel.lineBreakMode = .byWordWrapping
messageLabel.isEditable = false
messageLabel.dataDetectorTypes = .all
messageLabel.textContainer.lineBreakMode = .byWordWrapping
messageLabel.setContentCompressionResistancePriority(.required, for: .vertical)
messageLabel.setContentHuggingPriority(.required, for: .vertical)
Output:
Here's the code for the updated cell, where I have also added a time label. So what is needed is
UILable, UITextView, UILabel. And right now this is UILabel, UILabel, UILabel.
class DiscussionChatMessageCell: UITableViewCell {
private let messageLabel: UILabel
private let senderNameLabel: UILabel
private let messageSentTimeLabel: UILabel
private let messageBubble: UIView
private var bubbleLeadingConstraint: NSLayoutConstraint!
private var bubbleTrailingConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
messageLabel = UILabel()
senderNameLabel = UILabel()
messageSentTimeLabel = UILabel()
messageBubble = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(messageBubble)
messageBubble.translatesAutoresizingMaskIntoConstraints = false
messageBubble.addSubview(senderNameLabel)
senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
senderNameLabel.numberOfLines = 0
senderNameLabel.lineBreakMode = .byCharWrapping
senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
senderNameLabel.textColor = .white
messageBubble.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
// messageLabel.isEditable = false
// messageLabel.dataDetectorTypes = .all
// messageLabel.textContainer.lineBreakMode = .byWordWrapping
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.font = UIFont(name: "Helvetica Neue", size: 13)!
messageBubble.addSubview(messageSentTimeLabel)
messageSentTimeLabel.translatesAutoresizingMaskIntoConstraints = false
messageSentTimeLabel.lineBreakMode = .byCharWrapping
messageSentTimeLabel.numberOfLines = 0
messageSentTimeLabel.font = UIFont(name: "HelveticaNeue-Italic", size: 11)!
// set hugging and compression resistance for Name label
senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical)
senderNameLabel.setContentHuggingPriority(.required, for: .vertical)
// messageLabel.setContentCompressionResistancePriority(.required, for: .vertical)
// messageLabel.setContentHuggingPriority(.required, for: .vertical)
// create bubble Leading and Trailing constraints
bubbleLeadingConstraint = messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
// priority will be changed in configureCell()
bubbleLeadingConstraint.priority = .defaultHigh
bubbleTrailingConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
bubbleLeadingConstraint,
bubbleTrailingConstraint,
messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
messageBubble.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.bottomAnchor.constraint(equalTo: messageSentTimeLabel.topAnchor, constant: -10),
messageSentTimeLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageSentTimeLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageSentTimeLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
])
// corners will have radius: 10
messageBubble.layer.cornerRadius = 10
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(message: DiscussionMessage, isSender: Bool) {
senderNameLabel.text = message.userName + " " + message.userCountryEmoji
let date = Date(timeIntervalSince1970: message.messageTimestamp)
let dayTimePeriodFormatter = DateFormatter()
dayTimePeriodFormatter.timeZone = .current
dayTimePeriodFormatter.dateFormat = "hh:mm a"
let dateString = dayTimePeriodFormatter.string(from: date)
messageLabel.text = message.message
messageSentTimeLabel.text = dateString
messageLabel.textColor = isSender ? .black : .white
senderNameLabel.textColor = isSender ? .black : .white
messageSentTimeLabel.textColor = isSender ? .black : .white
messageSentTimeLabel.textAlignment = isSender ? .right : .left
bubbleLeadingConstraint.priority = isSender ? .defaultLow : .defaultHigh
bubbleTrailingConstraint.priority = isSender ? .defaultHigh : .defaultLow
messageBubble.backgroundColor = isSender ? accentColor : .gray
let senderCorners: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
let nonSenderCorners: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
if #available(iOS 11.0, *) {
messageBubble.layer.maskedCorners = isSender ?
// topLeft, topRight, bottomRight
senderCorners
:
// topLeft, topRight, bottomLeft
nonSenderCorners
} else {
// Fallback on earlier versions
// All corners will be rounded
}
}
}
Current output with the time label added to sender name label and message label:
You are modifying constraints way more than you need to.
A better approach would be to create both Leading and Trailing constraints for your "bubble" --- and change their Priority to determine which one is used.
So, if it's a "Received" message, we set the Leading constraint Priority to High, and the Trailing constraint Priority to Low. If it's a "Sent" message, we do the opposite.
Give this a try:
class DiscussionChatMessageCell: UITableViewCell {
let accentColor: UIColor = .systemYellow
private let messageLabel: UILabel
private let senderNameLabel: UILabel
private let messageBubble: UIView
private var bubbleLeadingConstraint: NSLayoutConstraint!
private var bubbleTrailingConstraint: NSLayoutConstraint!
// not needed
//let screenWidth: CGFloat
// wrong signature
//override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
messageLabel = UILabel()
senderNameLabel = UILabel()
messageBubble = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
// self.contentView.backgroundColor = .clear
self.contentView.addSubview(messageBubble)
messageBubble.translatesAutoresizingMaskIntoConstraints = false
messageBubble.addSubview(senderNameLabel)
senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
senderNameLabel.numberOfLines = 0
senderNameLabel.lineBreakMode = .byCharWrapping
senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
senderNameLabel.textColor = .white
messageBubble.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.font = UIFont.systemFont(ofSize: 13)
messageLabel.textColor = .white
// set hugging and compression resistance for Name label
senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical)
senderNameLabel.setContentHuggingPriority(.required, for: .vertical)
// create bubble Leading and Trailing constraints
bubbleLeadingConstraint = messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
// priority will be changed in configureCell()
bubbleLeadingConstraint.priority = .defaultHigh
bubbleTrailingConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
bubbleLeadingConstraint,
bubbleTrailingConstraint,
messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
messageBubble.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
])
// corners will have radius: 10
messageBubble.layer.cornerRadius = 10
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(message: String, isSender: Bool) {
senderNameLabel.text = "Default Sender"
messageLabel.text = message
bubbleLeadingConstraint.priority = isSender ? .defaultHigh : .defaultLow
bubbleTrailingConstraint.priority = isSender ? .defaultLow : .defaultHigh
messageBubble.backgroundColor = isSender ? accentColor : .gray
messageBubble.layer.maskedCorners = isSender ?
// topLeft, topRight, bottomRight
[.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
:
// topLeft, topRight, bottomLeft
[.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner]
}
}
Side Note: neither of these lines is needed in cellForRowAt:
//discussionChatMessageCell.setNeedsLayout()
//discussionChatMessageCell.layoutIfNeeded()
Edit - if you really want to support iOS prior to 11...
I suggest you subclass your "BubbleView" like this:
class BubbleView: UIView {
var radius: CGFloat = 0
var corners: UIRectCorner = []
var color: UIColor = .clear
lazy var shapeLayer: CAShapeLayer = self.layer as! CAShapeLayer
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
shapeLayer.path = path.cgPath
shapeLayer.fillColor = color.cgColor
}
}
and then use it like this:
class DiscussionChatMessageCell: UITableViewCell {
let accentColor: UIColor = .systemYellow
private let messageLabel: UILabel
private let senderNameLabel: UILabel
// use custom BubbleView class instead of standard UIView
private let messageBubble: BubbleView
private var bubbleLeadingConstraint: NSLayoutConstraint!
private var bubbleTrailingConstraint: NSLayoutConstraint!
// wrong signature - I beliee as of Swift 4.2
// 'UITableViewCellStyle' has been renamed to 'UITableViewCell.CellStyle'
//override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
messageLabel = UILabel()
senderNameLabel = UILabel()
messageBubble = BubbleView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(messageBubble)
messageBubble.translatesAutoresizingMaskIntoConstraints = false
messageBubble.addSubview(senderNameLabel)
senderNameLabel.translatesAutoresizingMaskIntoConstraints = false
senderNameLabel.numberOfLines = 0
senderNameLabel.lineBreakMode = .byCharWrapping
senderNameLabel.font = UIFont.boldSystemFont(ofSize: 15)
senderNameLabel.textColor = .white
messageBubble.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.font = UIFont.systemFont(ofSize: 13)
messageLabel.textColor = .white
// set hugging and compression resistance for Name label
senderNameLabel.setContentCompressionResistancePriority(.required, for: .vertical)
senderNameLabel.setContentHuggingPriority(.required, for: .vertical)
// create bubble Leading and Trailing constraints
bubbleLeadingConstraint = messageBubble.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10)
bubbleTrailingConstraint = messageBubble.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10)
// priority will be changed in configureCell()
bubbleLeadingConstraint.priority = .defaultHigh
bubbleTrailingConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
bubbleLeadingConstraint,
bubbleTrailingConstraint,
messageBubble.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 10),
messageBubble.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -10),
messageBubble.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor, constant: -100),
senderNameLabel.topAnchor.constraint(equalTo: messageBubble.topAnchor, constant: 10),
senderNameLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
senderNameLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.topAnchor.constraint(equalTo: senderNameLabel.bottomAnchor, constant: 10),
messageLabel.leadingAnchor.constraint(equalTo: messageBubble.leadingAnchor, constant: 10),
messageLabel.trailingAnchor.constraint(equalTo: messageBubble.trailingAnchor, constant: -10),
messageLabel.bottomAnchor.constraint(equalTo: messageBubble.bottomAnchor, constant: -10),
])
// corners will have radius: 10
messageBubble.radius = 10
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(message: String, isSender: Bool) {
senderNameLabel.text = "Default Sender"
messageLabel.text = message
bubbleLeadingConstraint.priority = isSender ? .defaultHigh : .defaultLow
bubbleTrailingConstraint.priority = isSender ? .defaultLow : .defaultHigh
messageBubble.color = isSender ? accentColor : .gray
let senderCorners: UIRectCorner = [.topLeft, .topRight, .bottomRight]
let nonSenderCorners: UIRectCorner = [.topLeft, .topRight, .bottomLeft]
messageBubble.corners = isSender ? senderCorners : nonSenderCorners
}
}
That will keep the "bubble" view's shape and size, even when the cell changes (such as when rotating the device).
I changed your code setting messageBuble constraint relative to the cell instead of the content view:
messageBubble.topAnchor.constraint(equalTo: self.topAnchor, constant: 10),
messageBubble.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10)
the just calling layoutIfNeeded():
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let discussionChatMessageCell = tableView.dequeueReusableCell(withIdentifier: discussionChatId, for: indexPath) as? DiscussionChatMessageCell else { return UITableViewCell()}
discussionChatMessageCell.configureCell(message: messages[indexPath.row], isSender: isSender[indexPath.row])
discussionChatMessageCell.layoutIfNeeded()
return discussionChatMessageCell
}
You add subviews a lot of times, but it's REUSABLE. Don't forget about it.
Add next code before .addSubview(....
contentView.subviews.forEach { $0.removeFromSuperview() }
Or change views values only, don't add it each time

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:

Resources