Snapkit and multiple custom cells in TableView - swift

I am using UItableView as a base for a user profile page. I have several custom cells. The first custom cell is images wrapped in scrollview. I have this error when I open this view:
"<SnapKit.LayoutConstraint:0x1c42a2940#CardFullscreenViewController.swift#143 UIScrollView:SCROLL_VIEW.top == Lez.ProfileImagesCell:0x12a02e800.top>",
"<SnapKit.LayoutConstraint:0x1c02a9180#CardFullscreenViewController.swift#144 UIScrollView:SCROLL_VIEW.height == 512.0>",
"<SnapKit.LayoutConstraint:0x1c02a91e0#CardFullscreenViewController.swift#145 UIScrollView:SCROLL_VIEW.bottom == Lez.ProfileImagesCell:0x12a02e800.bottom - 16.0>",
"<NSLayoutConstraint:0x1c0094690 'UIView-Encapsulated-Layout-Height' Lez.ProfileImagesCell:0x12a02e800'ProfileImagesCell'.height == 44 (active)>"
And here is a custom code for this cell:
class ProfileImagesCell: UITableViewCell {
var scrollView = UIScrollView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupScrollView()
layoutIfNeeded()
}
private func setupScrollView() {
addSubview(scrollView)
let x = frame.width * 1.6
scrollView.snp.makeConstraints { (make) in
make.top.left.right.equalToSuperview()
make.height.equalTo(x)
make.bottom.equalToSuperview().inset(16)
}
scrollView.snp.setLabel("SCROLL_VIEW")
scrollView.auk.settings.contentMode = .scaleAspectFill
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I assume there is something wrong with height, but I can't solve this. Any input is appretiated.

Reason behind this conflict UIView-Encapsulated-Layout-Height is initial height of the cell , you can try to set the priority of the bottom constraint of the scrollView to 999 , I mean this one
make.bottom.equalToSuperview().inset(16)

Related

Custom UITableViewCell Constraints

I'm creating a chat using a UITable with custom cells for the chat bubbles. Like WhatsApp, I want some bubbles to be on the left, and some to be on the right.
I'm using dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?, which calls the cell's init(style:reuseIdentifier:) method so setup my cell with:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
cellBackground = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
setupConstraints()
}
func setupConstraints() {
NSLayoutConstraint.activate([
cellBackground.topAnchor.constraint(equalTo: contentView.topAnchor),
cellBackground.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
cellBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
cellBackground.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.5)
])
}
but this isn't satisfactory. The cell is always on the left, due to the leadingAnchor constraint - when the cell is on the right it needs to be a trailingAnchor constraint.
If I use a setup function for the cell, should setupConstraints be called there, or should the init setup the constraints it knows about at that time? Alternatively, should these constraints all be set up in layoutSubviews()?
Add 2 instance variables as a references to the leading and trailing constraints inside the cell custom class
var leadCon,traCon:NSLayoutConstraint!
Then inside init also but out of the activate
leadCon = cellBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
leadCon.isActive = true
And
traCon = cellBackground.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
traCon.isActive = true
According to what you need add a func
func toLeft(_ value:Bool) {
if value {
leadCon.isActive = true
traCon.isActive = false
}
else {
leadCon.isActive = false
traCon.isActive = true
}
}
And call it after dequeue line in cellForRowAt

UIDatePicker not respecting autolayout constraints iOS 14

The new UIDatePicker style's doesn't appear to be respecting autolayout. It should be right aligned with the label expanding to take up the majority of the cell. Here is the code for the cell.
class DatePickerTableViewCell: UITableViewCell {
public let label = UILabel()
public let datePicker = UIDatePicker()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Cell settings
self.label.translatesAutoresizingMaskIntoConstraints = false
self.datePicker.translatesAutoresizingMaskIntoConstraints = false
// Add views
self.contentView.addSubview(self.label)
self.contentView.addSubview(self.datePicker)
// Constrain views
self.label.setContentHuggingPriority(.defaultLow - 1, for: .horizontal)
self.datePicker.setContentHuggingPriority(.defaultLow + 1, for: .horizontal)
NSLayoutConstraint.activate([
self.label.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
self.label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
self.label.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
self.label.trailingAnchor.constraint(equalTo: self.datePicker.leadingAnchor, constant: -8),
self.datePicker.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
self.datePicker.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
self.datePicker.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are some pictures showing what it is doing:
This is similar to this stack overflow question but doesn't appear to be fixed by their solution UIDatePicker with .compact style doesn't respect content hugging priority

How to access the correct cell width inside a UITabelViewCell Class?

I am trying to obtain the correct width for a custom cell inside a UITableViewCell Class. I tried the below:
import UIKit
import ChameleonFramework
class IsectionsCustomTableViewCell: UITableViewCell {
let cellContainer: UIView = {
let container = UIView()
container.backgroundColor = UIColor.flatPink()
container.translatesAutoresizingMaskIntoConstraints = false
return container
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(cellContainer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func applyAppropriateSizeAndConstraintsForCellItems() {
NSLayoutConstraint.activate([
cellContainer.topAnchor.constraint(equalTo: self.topAnchor),
cellContainer.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -1*((self.frame.size.width)/2)),
cellContainer.bottomAnchor.constraint(equalTo: self.bottomAnchor),
cellContainer.leftAnchor.constraint(equalTo: self.leftAnchor)
])
}
}
However, as it can be seen from the attached image, the below line of code used above did not return the correct width as the total width was not split equally in half:
-1*((self.frame.size.width)/2)
Any idea what did I do wrong here, how can I obtain the true width of the custom Cell, which in my mind should be equal to the width of the Table it is going to be placed inside?
Regards,
Shadi Hammoudeh.
Do base the constraint on the frame. Use the proper constraint.
Change:
cellContainer.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -1*((self.frame.size.width)/2))
to:
cellContainer.rightAnchor.constraint(equalTo: cellContainer.superview.centerXAnchor)

Invalidate and Redraw CAShapeLayer inside collectionViewCell after device orientation change

I have a custom collection view cell that is adding a dashed border to the cell layer with the following code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = appDealCollectionView.dequeueReusableCell(withReuseIdentifier: "appDealCollectionCell", for: indexPath) as! AppDealCollectionViewCell
if let codes = discountCodes {
cell.discountCodeTitle.text = codes[indexPath.row].codeMessageOne
cell.discountCode.text = codes[indexPath.row].code
let yourViewBorder = CAShapeLayer()
yourViewBorder.strokeColor = UIColor.black.cgColor
yourViewBorder.lineWidth = 2
yourViewBorder.lineDashPattern = [10, 10]
yourViewBorder.frame = cell.bounds
yourViewBorder.fillColor = nil
yourViewBorder.path = UIBezierPath(roundedRect: cell.bounds, cornerRadius: 6).cgPath
cell.layer.addSublayer(yourViewBorder)
}
return cell
}
This code works perfectly fine on the initial loading of the view. However, when the orientation changes, the cell size changes. The above code does correctly draw the new border CAShapeLayer, but the previously drawn border layer is still present which were drawn based on the old size.
The result is two different border layers being present at the same time overlapping each other with different dimensions.
How do I invalidate any previously drawn CAShapeLayers? Where is the invalidation done? In cellForItemAt? Or possibly inside the custom "AppDealCollectionViewCell" itself?
Since cells are reusable, every call of cellForRowAtIndexPath will add another instance of CAShapeLayer onto cell. That is why you are having several borders overlapping each other. Also CALayer doest not support neither auto layout nor autoresizingMask, so you have to update size of your CAShapeLayer manually.
You should create subclass of UITableViewCell, then create instance of CAShapeLayer and store pointer to it in class property variable. Once layout cycle occurs, in layoutSubviews function you need to update frame of CAShapeLayer.
The final implementation looks like that:
class BorderedTableViewCell: UITableViewCell {
lazy var borderLayer = CAShapeLayer()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupBorderLayer()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupBorderLayer()
}
private func setupBorderLayer() {
borderLayer.strokeColor = UIColor.black.cgColor
borderLayer.lineWidth = 2
borderLayer.fillColor = nil
borderLayer.lineDashPattern = [10, 10]
layer.addSublayer(borderLayer)
}
private func updateBorderLayer() {
borderLayer.frame = bounds
borderLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 6).cgPath
}
override func layoutSubviews() {
super.layoutSubviews()
updateBorderLayer()
}
}
I hope this helps.

ContentView for tableViewCell not auto-resizing correctly?

I have added 2 labels to my cell and setup these constraints with snapkit, issue is I cant get the cell to expand correctly, it stays at its default height:
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(contentView.snp.top)
make.bottom.equalTo(descriptionLabel.snp.top)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.equalTo(contentView.snp.bottom)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
I mapped the four edges as you can see, however I know height isnt implied by these, how can I apply a height when the content is by nature, dynamic, and could be various heights...
setup for the labels looks like this:
lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.textColor = .green
titleLabel.textAlignment = .center
contentView.addSubview(titleLabel)
return titleLabel
}()
lazy var descriptionLabel: UILabel = {
let descriptionLabel = UILabel()
descriptionLabel.textColor = .dark
descriptionLabel.textAlignment = .center
descriptionLabel.numberOfLines = 0
contentView.addSubview(descriptionLabel)
return descriptionLabel
}()
Give the table view an estimatedRowHeight, and set its rowHeight to UITableViewAutomaticDimension. Now the cells will be self-sizing. Well, if a label is pinned by all four sides to the content view, and if the cell is self-sizing, then that's all you have to do: the label will automatically change its height to accommodate its text, and the cell will automatically change size to accommodate the label.
First of all I think that you should add subviews to contentView in subclassed UITableViewCell class initializer method.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(titleLabel)
self.contentView.addSubview(descriptionLabel)
}
Secondly, make sure that in your viewDidLoad method (probably in your ViewController) these two lines are added:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
Of course, you should change estimatedRowHeight to accommodate your needs.
One more thing worth mentioning - you can create these constraints easier (using power of SnapKit):
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.left.right.equalTo(contentView)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.left.right.equalTo(contentView)
}