Align Button To Left Side Of UITableView Cell Programmatically Swift - ios

I'm creating a simple app using Swift 5, Xcode 11, and a ui table view controller. Inside the ui table view, I want 2 buttons: One button on the left of my table view, the other on the right. I have tried many other related/similar question's answers, but all of them failed(probably because 1. Too Old, 2. Answer written in OBJ-C).
Here's my Table View Controller:
import UIKit
#objcMembers class CustomViewController: UITableViewController {
var tag = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
// 3
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tag = tag + 1
let cell = tableView.dequeueReusableCell(withIdentifier: "themeCell", for: indexPath) as! ThemeCell
var cellButton: UIButton!
cellButton = UIButton(frame: CGRect(x: 5, y: 5, width: 50, height: 30))
cell.addSubview(cellButton)
cell.img.image = UIImage(named: SingletonViewController.themes[indexPath.row])
cell.accessoryView = cellButton
cellButton.backgroundColor = UIColor.red
cellButton.tag = tag
return cell
}
}
Here's what I'm currently getting:

add below code for apply constraint worked
let cellButton = UIButton(frame: CGRect.zero)
cellButton.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(cellButton)
cell.accessoryView = cellButton
cellButton.backgroundColor = UIColor.red
cellButton.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 5).isActive = true
cellButton.topAnchor.constraint(equalTo: cell.topAnchor, constant: 5).isActive = true
cellButton.widthAnchor.constraint(equalToConstant: 50).isActive = true
cellButton.heightAnchor.constraint(equalToConstant: 30).isActive = true

You can achieve right and left align button from constraints.
Below is my code to align view right or left.
override func viewDidLoad() {
let leftButton = UIButton(type: .custom)
leftButton.backgroundColor = UIColor.red
self.view.addSubview(leftButton)
leftButton.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = leftButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20)
let verticalConstraint = leftButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let widthConstraint = leftButton.widthAnchor.constraint(equalToConstant: 100)
let heightConstraint = leftButton.heightAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
let rightButton = UIButton(type: .custom)
rightButton.backgroundColor = UIColor.red
self.view.addSubview(rightButton)
rightButton.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraintRight = rightButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
let verticalConstraintRight = rightButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let widthConstraintRight = rightButton.widthAnchor.constraint(equalToConstant: 100)
let heightConstraintRight = rightButton.heightAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([horizontalConstraintRight, verticalConstraintRight, widthConstraintRight, heightConstraintRight])
}
[![Left and Right Aligned View Constraints][1]][1]
[1]: https://i.stack.imgur.com/tMJ8N.jpg

As the cell is getting reuse, you have to put buttons in your xib file. You should not make a button every time and add it in a cell. Try this by adding a button in xib.

class ThemeCell: UITableViewCell {
//MARK:- Initialize View
private let button : UIButton = {
let button = UIButton()
button.setTitle("Hello", .normal)
button.backgroundColor = .red
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
//MARK:- View Life Cycle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
selectionStyle = .none
}
//MARK:- User Defined Function
private func setup() {
addSubView(button)
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
button.topAnchor.constraint(equalTo: topAnchor, constant: 20).isActive = true
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20).isActive = true
}
}
You can use AutoLayout Constraints like this to setup the button. No need to call it in the cellForRow method.

Related

How to add options menu on UIBarButtonItem in swift?

I am trying to add options menu over a UIBarButtonItem similar to what we see in android for material design. Similar to this
But I am stuck on how to add it over a UIBarButtonItem. Once clicked this menu should pop up anchored to that UIBarButtonItem and clicking outside should dismiss it. How to achieve the same?
class ABMenu: UIView {
private var items:[ABMenuItem] = []
private var tableView:UITableView!
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(barButtonItem:UIBarButtonItem,items:[ABMenuItem]) {
self.init(frame: .zero)
self.items = items
self.commonInit(barButtonItem: barButtonItem)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func commonInit(barButtonItem:UIBarButtonItem){
self.frame = CGRect(x: 10, y: 20, width: 200, height: self.items.count * 40)
self.backgroundColor = .white
self.layer.cornerRadius = 5
self.layer.shadowColor = UIColor.gray.withAlphaComponent(0.7).cgColor
self.layer.shadowOffset = CGSize(width: -1.0, height: 1.0)
self.layer.shadowRadius = 0.8
self.layer.shadowOpacity = 0.5
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints=false
tableView.backgroundColor = .clear
tableView.tableFooterView=UIView()
tableView.separatorStyle = .none
tableView.delegate = self
tableView.dataSource = self
tableView.register(ABMenuItemCell.self, forCellReuseIdentifier: "itemCell")
self.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
tableView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
}
}
extension ABMenu:UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! ABMenuItemCell
cell.setup(item:self.items[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.items[indexPath.row].onTap()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
}
class ABMenuItemCell:UITableViewCell {
private var itemLabel:UILabel!
func setup(item:ABMenuItem){
for view in self.contentView.subviews {
view.removeFromSuperview()
}
self.selectionStyle = .none
itemLabel = UILabel()
itemLabel.translatesAutoresizingMaskIntoConstraints=false
itemLabel.backgroundColor = .white
itemLabel.text = item.name
itemLabel.textColor = .black
itemLabel.font = .systemFont(ofSize: 15)
itemLabel.numberOfLines = 1
itemLabel.lineBreakMode = .byTruncatingMiddle
self.contentView.addSubview(itemLabel)
itemLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 0).isActive = true
itemLabel.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 10).isActive = true
itemLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -10).isActive = true
itemLabel.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
}
struct ABMenuItem {
var name:String
var onTap:(()->Void)
}
Try creating a view for the menu, then setting the X and Y position relative to the button, and setting the view's layer.zPosition = 1
Below is an example of how this could be done. Note, I haven't tested this code yet. To implement clicking outside the menu to close it, you'll need to create a transparent view the size of the screen, positioned underneath the menu, add a UITapGestureRecognizer to it, and then link to an IBAction that removes the menu from the view.
// calculate location of menu
let openMenuButtonOrigin = openMenuButton.frame.origin
let menuWidth = 200
let menuHeight = 100
let menuX = openMenuButtonOrigin.x - menuWidth
let menuY = openMenuButtonOrigin.y + menuHeight
// create menu (this can be done in XIB/Storyboard file as well)
let menuView = UIView(frame: CGRect(x: menuX, y: menuY, width: menuWidth, height: menuHeight))
// create menu option
let shareOption = UILabel()
shareOption.text = "Share"
shareOption.topAnchor.constraint(equalTo: menuView).isActive = true
shareOption.bottomAnchor.constraint(equalTo: menuView).isActive = true
shareOption.leadingAnchor.constraint(equalTo: menuView).isActive = true
shareOption.trailingAnchor.constraint(equalTo: menuView).isActive = true
menuView.addSubview(shareOption)
// add menu to view
menuView.layer.zPosition = 1
view.addSubview(menuView)

Resizing image inside of UIButton

I have been trying to get the system images I placed inside of a UIButton to scale aspect fit, but no solutions I find online seem to work. This is what I have tried so far:
//the cells objects
var acceptButton = UIButton(type: .custom)
var declineButton = UIButton(type: .custom)
override init(frame: CGRect) {
super.init(frame: frame)
//add the contents and set its autolayout constraints
setAutoLayoutConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//function to set the autolayout constraints for the cell's contents
func setAutoLayoutConstraints() {
//create the stack view which will contain the accept and reject buttons
let buttonStackView = UIStackView()
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
buttonStackView.alignment = .fill
//add the buttons to the stack view
buttonStackView.addArrangedSubview(acceptButton)
buttonStackView.addArrangedSubview(declineButton)
//add the button stack view to the cell
self.addSubview(buttonStackView)
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
buttonStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
buttonStackView.leadingAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
buttonStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
//add the button images
let addImage = UIImage(systemName: "checkmark.circle")
acceptButton.imageView?.contentMode = .scaleAspectFit
acceptButton.setImage(addImage, for: .normal)
acceptButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
acceptButton.addTarget(self, action: #selector(addButtonClicked), for: .touchUpInside)
let declineImage = UIImage(systemName: "xmark.octagon")
declineButton.imageView?.contentMode = .scaleAspectFit
declineButton.setImage(declineImage, for: .normal)
declineButton.addTarget(self, action: #selector(declineButtonClicked), for: .touchUpInside)
}
This is what I get:
(The "test test" label is just what I added for the left half of the cell's contents, can be ignored)
I have tried multiple solutions such as setting the edge insets, setting the content mode of the button and the button's image view, using a custom button, etc. but I can't seem to get the image to resized.
NOTE: When I make the image the background image with setBackgroundImage, I am able to resize the button but I can not scale aspect fit it, only fill it horizontally and vertically which is not what I am looking to do.
Any help is appreciated, thanks!!
This is a simple solution example: add first your labelTest and after that set stackView leading anchor to labelTest trailing...
this is my tableView:
class YourViewController: UIViewController, UITabBarDelegate, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
tableView.delegate = self
tableView.dataSource = self
tableView.register(myCell.self, forCellReuseIdentifier: "cellId")
tableView.backgroundColor = .white
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! myCell
return cell
}
}
Now set my cell with arrangedSubviews and set constraints to content view (this is the correct way, not directly to cell):
class myCell: UITableViewCell {
let labelTest: UILabel = {
let label = UILabel()
label.text = "test Label"
label.backgroundColor = .red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var acceptButton = UIButton(type: .custom)
var declineButton = UIButton(type: .custom)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
acceptButton.backgroundColor = .purple
declineButton.backgroundColor = .brown
setAutoLayoutConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//function to set the autolayout constraints for the cell's contents
func setAutoLayoutConstraints() {
contentView.addSubview(labelTest)
labelTest.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
labelTest.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
labelTest.trailingAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
labelTest.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
let buttonStackView = UIStackView(arrangedSubviews: [acceptButton, declineButton])
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
//add the button stack view to the cell
contentView.addSubview(buttonStackView)
buttonStackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
buttonStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
buttonStackView.leadingAnchor.constraint(equalTo: labelTest.trailingAnchor).isActive = true
buttonStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
//add the buttons images
let addImage = UIImage(systemName: "checkmark.circle")
acceptButton.imageView?.contentMode = .scaleAspectFit
acceptButton.setImage(addImage, for: .normal)
acceptButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
acceptButton.addTarget(self, action: #selector(addButtonClicked), for: .touchUpInside)
let declineImage = UIImage(systemName: "xmark.octagon")
declineButton.imageView?.contentMode = .scaleAspectFit
declineButton.setImage(declineImage, for: .normal)
declineButton.addTarget(self, action: #selector(declineButtonClicked), for: .touchUpInside)
}
}
If you want some space to left/right simply add constant to relative constraint:
labelTest.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
buttonStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
This is the results

Multilinelabel inside multiple stackviews inside UITableViewCell

I have view hierarchy like below;
UITableViewCell ->
-> UIView -> UIStackView (axis: vertical, distribution: fill)
-> UIStackView (axis: horizontal, alignment: top, distribution: fillEqually)
-> UIView -> UIStackView(axis:vertical, distribution: fill)
-> TwoLabelView
My problem is that labels don't get more than one line. I read every question in SO and also tried every possibility but none of them worked. On below screenshot, on the top left box, there should be two pair of label but even one of them isn't showing.
My Question is that how can I achieve multiline in the first box (both for left and right)?
If I change top stack views distribution to fillProportionally, labels get multiline but there will be a gap between last element of first box and the box itself
My first top stack views
//This is the Stackview used just below UITableViewCell
private let stackView: UIStackView = {
let s = UIStackView()
s.distribution = .fill
s.axis = .vertical
s.spacing = 10
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
//This is used to create two horizontal box next to each other
private let myStackView: UIStackView = {
let s = UIStackView()
s.distribution = .fillEqually
s.spacing = 10
s.axis = .horizontal
//s.alignment = .center
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
UILabel Class:
fileprivate class FixAutoLabel: UILabel {
override func layoutSubviews() {
super.layoutSubviews()
if(self.preferredMaxLayoutWidth != self.bounds.size.width) {
self.preferredMaxLayoutWidth = self.bounds.size.width
}
}
}
#IBDesignable class TwoLabelView: UIView {
var topMargin: CGFloat = 0.0
var verticalSpacing: CGFloat = 3.0
var bottomMargin: CGFloat = 0.0
#IBInspectable var firstLabelText: String = "" { didSet { updateView() } }
#IBInspectable var secondLabelText: String = "" { didSet { updateView() } }
fileprivate var firstLabel: FixAutoLabel!
fileprivate var secondLabel: FixAutoLabel!
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required public init?(coder: NSCoder) {
super.init(coder:coder)
setUpView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setUpView()
}
func setUpView() {
firstLabel = FixAutoLabel()
firstLabel.font = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.bold)
firstLabel.numberOfLines = 0
firstLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
secondLabel = FixAutoLabel()
secondLabel.font = UIFont.systemFont(ofSize: 13.0, weight: UIFont.Weight.regular)
secondLabel.numberOfLines = 1
secondLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
addSubview(firstLabel)
addSubview(secondLabel)
// we're going to set the constraints
firstLabel .translatesAutoresizingMaskIntoConstraints = false
secondLabel.translatesAutoresizingMaskIntoConstraints = false
// pin both labels' left-edges to left-edge of self
firstLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0).isActive = true
secondLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0).isActive = true
// pin both labels' right-edges to right-edge of self
firstLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0).isActive = true
secondLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0).isActive = true
// pin firstLabel to the top of self + topMargin (padding)
firstLabel.topAnchor.constraint(equalTo: topAnchor, constant: topMargin).isActive = true
// pin top of secondLabel to bottom of firstLabel + verticalSpacing
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: verticalSpacing).isActive = true
// pin bottom of self to bottom of secondLabel + bottomMargin (padding)
bottomAnchor.constraint(equalTo: secondLabel.bottomAnchor, constant: bottomMargin).isActive = true
// call common "refresh" func
updateView()
}
func updateView() {
firstLabel.preferredMaxLayoutWidth = self.bounds.width
secondLabel.preferredMaxLayoutWidth = self.bounds.width
firstLabel.text = firstLabelText
secondLabel.text = secondLabelText
firstLabel.sizeToFit()
secondLabel.sizeToFit()
setNeedsUpdateConstraints()
}
override open var intrinsicContentSize : CGSize {
// just has to have SOME intrinsic content size defined
// this will be overridden by the constraints
return CGSize(width: 1, height: 1)
}
}
UIView -> UIStackView class
class ViewWithStack: UIView {
let verticalStackView: UIStackView = {
let s = UIStackView()
s.distribution = .fillEqually
s.spacing = 10
s.axis = .vertical
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.white
self.layer.cornerRadius = 6.0
self.layer.applySketchShadow(color: UIColor(red:0.56, green:0.56, blue:0.56, alpha:1), alpha: 0.2, x: 0, y: 0, blur: 10, spread: 0)
addSubview(verticalStackView)
let lessThan = verticalStackView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: 0)
lessThan.priority = UILayoutPriority(1000)
lessThan.isActive = true
verticalStackView.leftAnchor.constraint(equalTo: self.leftAnchor,constant: 0).isActive = true
verticalStackView.rightAnchor.constraint(equalTo: self.rightAnchor,constant: 0).isActive = true
verticalStackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
verticalStackView.layoutMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
verticalStackView.isLayoutMarginsRelativeArrangement = true
}
convenience init(orientation: NSLayoutConstraint.Axis,labelsArray: [UIView]) {
self.init()
verticalStackView.axis = orientation
for label in labelsArray {
verticalStackView.addArrangedSubview(label)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Example Controller Class (This is a minimized version of the whole project):
class ViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let viewWithStack = BoxView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "myCell")
tableView.rowHeight = UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! TableViewCell
if (indexPath.row == 0) {
cell.setup(viewWithStack: self.viewWithStack)
} else {
cell.backgroundColor = UIColor.black
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//return 500
if ( indexPath.row == 0) {
return UITableView.automaticDimension
} else {
return 40
}
}
}
EDIT I created a minimal project then I found that my problem is that my project implements heightForRow function which overrides UITableViewAutomaticDimension so that It gives wrong height for my component. I think I should look how to get height size of the component? because I can't delete heightForRow function which solves my problem.
Example Project Link https://github.com/emreond/tableviewWithStackView/tree/master/tableViewWithStackViewEx
Example project has ambitious layouts when you open view debugger. I think when I fix them, everything should be fine.
Here is a full example that should do what you want (this is what I mean by a minimal reproducible example):
Best way to examine this is to:
create a new project
create a new file, named TestTableViewController.swift
copy and paste the code below into that file (replace the default template code)
add a UITableViewController to the Storyboard
assign its Custom Class to TestTableViewController
embed it in a UINavigationController
set the UINavigationController as Is Initial View Controller
run the app
This is what you should see as the result:
I based the classes on what you had posted (removed unnecessary code, and I am assuming you have the other cells working as desired).
//
// TestTableViewController.swift
//
// Created by Don Mag on 10/21/19.
//
import UIKit
class SideBySideCell: UITableViewCell {
let horizStackView: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fillEqually
v.spacing = 10
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForReuse() {
horizStackView.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
}
func commonInit() -> Void {
contentView.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
contentView.addSubview(horizStackView)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
horizStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
horizStackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
horizStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
horizStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
])
}
func addViewWithStack(_ v: ViewWithStack) -> Void {
horizStackView.addArrangedSubview(v)
}
}
class TestTableViewController: UITableViewController {
let sideBySideReuseID = "sbsID"
override func viewDidLoad() {
super.viewDidLoad()
// register custom SideBySide cell for reuse
tableView.register(SideBySideCell.self, forCellReuseIdentifier: sideBySideReuseID)
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 {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: sideBySideReuseID, for: indexPath) as! SideBySideCell
let twoLabelView1 = TwoLabelView()
twoLabelView1.firstLabelText = "Text for first label on left-side."
twoLabelView1.secondLabelText = "10.765,00TL"
let twoLabelView2 = TwoLabelView()
twoLabelView2.firstLabelText = "Text for second-first label on left-side."
twoLabelView2.secondLabelText = "10.765,00TL"
let twoLabelView3 = TwoLabelView()
twoLabelView3.firstLabelText = "Text for the first label on right-side."
twoLabelView3.secondLabelText = "10.765,00TL"
let leftStackV = ViewWithStack(orientation: .vertical, labelsArray: [twoLabelView1, twoLabelView2])
let rightStackV = ViewWithStack(orientation: .vertical, labelsArray: [twoLabelView3])
cell.addViewWithStack(leftStackV)
cell.addViewWithStack(rightStackV)
return cell
}
// create ViewWithStack using just a simple label
let cell = tableView.dequeueReusableCell(withIdentifier: sideBySideReuseID, for: indexPath) as! SideBySideCell
let v = UILabel()
v.text = "This is row \(indexPath.row)"
let aStackV = ViewWithStack(orientation: .vertical, labelsArray: [v])
cell.addViewWithStack(aStackV)
return cell
}
}
#IBDesignable class TwoLabelView: UIView {
var topMargin: CGFloat = 0.0
var verticalSpacing: CGFloat = 3.0
var bottomMargin: CGFloat = 0.0
#IBInspectable var firstLabelText: String = "" { didSet { updateView() } }
#IBInspectable var secondLabelText: String = "" { didSet { updateView() } }
fileprivate var firstLabel: UILabel = {
let v = UILabel()
return v
}()
fileprivate var secondLabel: UILabel = {
let v = UILabel()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required public init?(coder: NSCoder) {
super.init(coder:coder)
setUpView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setUpView()
}
func setUpView() {
firstLabel.font = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.bold)
firstLabel.numberOfLines = 0
secondLabel.font = UIFont.systemFont(ofSize: 13.0, weight: UIFont.Weight.regular)
secondLabel.numberOfLines = 1
addSubview(firstLabel)
addSubview(secondLabel)
// we're going to set the constraints
firstLabel .translatesAutoresizingMaskIntoConstraints = false
secondLabel.translatesAutoresizingMaskIntoConstraints = false
// Note: recommended to use Leading / Trailing rather than Left / Right
NSLayoutConstraint.activate([
// pin both labels' left-edges to left-edge of self
firstLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
secondLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
// pin both labels' right-edges to right-edge of self
firstLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
secondLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// pin firstLabel to the top of self + topMargin (padding)
firstLabel.topAnchor.constraint(equalTo: topAnchor, constant: topMargin),
// pin top of secondLabel to bottom of firstLabel + verticalSpacing
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: verticalSpacing),
// pin bottom of self to >= (bottom of secondLabel + bottomMargin (padding))
bottomAnchor.constraint(greaterThanOrEqualTo: secondLabel.bottomAnchor, constant: bottomMargin),
])
}
func updateView() -> Void {
firstLabel.text = firstLabelText
secondLabel.text = secondLabelText
}
}
class ViewWithStack: UIView {
let verticalStackView: UIStackView = {
let s = UIStackView()
s.distribution = .fill
s.spacing = 10
s.axis = .vertical
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.white
self.layer.cornerRadius = 6.0
// self.layer.applySketchShadow(color: UIColor(red:0.56, green:0.56, blue:0.56, alpha:1), alpha: 0.2, x: 0, y: 0, blur: 10, spread: 0)
addSubview(verticalStackView)
NSLayoutConstraint.activate([
// constrain to all 4 sides
verticalStackView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
verticalStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
verticalStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
verticalStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
])
verticalStackView.layoutMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
verticalStackView.isLayoutMarginsRelativeArrangement = true
}
convenience init(orientation: NSLayoutConstraint.Axis, labelsArray: [UIView]) {
self.init()
verticalStackView.axis = orientation
for label in labelsArray {
verticalStackView.addArrangedSubview(label)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Constraint Crash after Dismissing View Controller

I'm a bit new to Xcode and been trying to do things programatically. I have View Controller A, B, C, and D. I have a back button on C, and D. When going from D to C using self.dismiss it works fine, however when I go from C to B I am getting a crash that looks like it's a constraint issue and I have no idea why.
Again, the crash occurs when going from C to B. The error says no common ancestor for DropDownButton, but there is no DropDownButton on ViewController B, it exists on C the one I am trying to dismiss.
I would like to know more about how the view controllers dismissing and Auto Layout works, could someone point me in the right direction please?
"oneonone.DropDownButton:0x7fcfe9d30660'πŸ‡ΊπŸ‡Έ+1 βŒ„'.bottom"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal. userInfo: (null)
2018-11-09 19:56:22.828322-0600 oneonone[62728:4835265] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutYAxisAnchor
UPDATE TO QUESTIONS:
Here is View Controller C, included is the var, adding it to subview, and how I dismiss this view controller
lazy var countryCodes: DropDownButton = {
let button = DropDownButton(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
let us = flag(country: "US")
let br = flag(country: "BR")
let lightGray = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
button.backgroundColor = lightGray
button.setTitle(us + "+1 \u{2304}", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20)
button.setTitleColor(UIColor.darkGray, for: .normal)
button.uiView.dropDownOptions = [us + "+1", br + "+55", "+33", "+17", "+19"]
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
[countryCodes].forEach{ view.addSubview($0) }
setupLayout()
}
func setupLayout(){
countryCodes.translatesAutoresizingMaskIntoConstraints = false
countryCodes.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 30).isActive = true
countryCodes.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -77.5).isActive = true
countryCodes.widthAnchor.constraint(equalToConstant: 85).isActive = true // guarantees this width for stack
countryCodes.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
#objc func buttonPressed(){
self.dismiss(animated: true, completion: nil)
}
Here is the code in view controller B that (creates or presents?) View Controller C
#objc func phoneAuthButtonPressed(){
let vc = phoneAuthViewController()
self.present(vc, animated: true, completion: nil)
}
UPDATE 2: ADDING THE CUSTOM CLASS
Here is the button code that I used as a custom class following a tutorial, I believe the problem lies in here
protocol dropDownProtocol {
func dropDownPressed(string: String)
}
class DropDownButton: UIButton, dropDownProtocol {
var uiView = DropDownView()
var height = NSLayoutConstraint()
var isOpen = false
func dropDownPressed(string: String) {
self.setTitle(string + " \u{2304}", for: .normal)
self.titleLabel?.font = UIFont.systemFont(ofSize: 18)
self.dismissDropDown()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.gray
uiView = DropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
uiView.delegate = self
uiView.layer.zPosition = 1 // show in front of other labels
uiView.translatesAutoresizingMaskIntoConstraints = false
}
override func didMoveToSuperview() {
self.superview?.addSubview(uiView)
self.superview?.bringSubviewToFront(uiView)
uiView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
uiView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
uiView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = uiView.heightAnchor.constraint(equalToConstant: 0)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // animates drop down list
NSLayoutConstraint.deactivate([self.height])
if self.uiView.tableView.contentSize.height > 150 {
self.height.constant = 150
} else {
self.height.constant = self.uiView.tableView.contentSize.height
}
if isOpen == false {
isOpen = true
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut, animations: {
self.uiView.layoutIfNeeded()
self.uiView.center.y += self.uiView.frame.height / 2
}, completion: nil)
} else {
dismissDropDown()
}
}
func dismissDropDown(){
isOpen = false
self.height.constant = 0
NSLayoutConstraint.activate([self.height])
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut, animations: {
self.uiView.center.y -= self.uiView.frame.height / 2
self.uiView.layoutIfNeeded()
}, completion: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class DropDownView: UIView, UITableViewDelegate, UITableViewDataSource {
var dropDownOptions = [String]()
var tableView = UITableView()
var delegate : dropDownProtocol!
let lightGray = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
override init(frame: CGRect) {
super.init(frame: frame)
tableView.backgroundColor = lightGray
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(tableView) // can not come after constraints
tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dropDownOptions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = dropDownOptions[indexPath.row]
cell.textLabel?.font = UIFont.systemFont(ofSize: 14)
cell.textLabel?.textColor = UIColor.darkGray
cell.backgroundColor = lightGray
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.delegate.dropDownPressed(string: dropDownOptions[indexPath.row])
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
Replace your didMoveToSuperview function with this and it will work. This function also gets called when the view its removed from the superview and the superview will be nil and that's causing the crash.
override func didMoveToSuperview() {
if let superview = self.superview {
self.superview?.addSubview(dropView)
self.superview?.bringSubviewToFront(dropView)
dropView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = dropView.heightAnchor.constraint(equalToConstant: 0)
}
}
As I can see, the error says that one of countryCodes buttons is located in the different view than instructionLabel. They should have the same parent if you want them to be constrained by each other.
Hi I think the problem occurs in the didMoveToSuperView() function, because it is called also when the view is removed from it's superview. so when you try to setup the anchor to something that does no more exist it crashes.
try something like this :
if let superview = self.superview {
superview.addSubview(uiView)
superview.bringSubviewToFront(uiView)
uiView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
uiView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
uiView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
height = uiView.heightAnchor.constraint(equalToConstant: 0)
}

Problems with complex UITableViewCell

I'm trying to implement a custom complex UITableViewCell. My data source is relatively simple, but I could have some multiple elements.
class Element: NSObject {
var id: String
var titles: [String]
var value: String
init(id: String, titles: [String], value: String) {
self.id = id
self.titles = titles
self.value = value
}
}
I have an array of elements [Element] and, as you can see, for each element titles could have multiple string values. I must use the following layouts:
My first approach was to implement a dynamic UITableViewCell, trying to add content inside self.contentView at runtime. Everything is working, but it's not so fine and as you can see, reusability is not handled in the right way. Lag is terrible.
import UIKit
class ElementTableCell: UITableViewCell {
var titles: [String]!
var value: String!
var width: CGFloat!
var titleViewWidth: CGFloat!
var cellHeight: Int!
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:)")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
func drawLayout() {
titleViewWidth = (width * 2)/3
cellHeight = 46 * titles.count
for i in 0 ..< titles.count {
let view = initTitleView(title: titles[i], width: titleViewWidth, yPosition: CGFloat(cellHeight * i))
self.contentView.addSubview(view)
}
self.contentView.addSubview(initButton())
}
func initTitleView(title: String, width: CGFloat, yPosition: CGFloat) -> UIView {
let titleView: UILabel = UILabel(frame:CGRect(x:0, y:Int(yPosition), width: Int(width), height: 45))
titleView.text = title
return titleView
}
func initButton(value: String) -> UIButton {
let button = UIButton(frame:CGRect(x: 0, y: 0, width: 70, height:34))
button.setTitle(value, for: .normal)
button.center.x = titleViewWidth + ((width * 1)/3)/2
button.center.y = CGFloat(cellHeight/2)
return priceButton
}
}
And the UITableView delegate method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ElementTableCell(style: .default, reuseIdentifier: "ElementTableCell")
cell.width = self.view.frame.size.width
cell.titles = elements[indexPath.row].titles
cel.value = elements[indexPath.row].value
cell.drawLayout()
return cell
}
Now I'm thinking about a total different approach, such as using a UITableView Section for each element in elements array and a UITableViewCell for each title in titles. It could work, but I'm concerned about the right button.
Do you have any suggestion or other approach to share?
I solved changing application UI logic in order to overcome the problem. Thank you all.
Here's some code you can play with. It should work just be creating a new UITableView in a Storyboard and assigning it to BoxedTableViewController in this file...
//
// BoxedTableViewController.swift
//
import UIKit
class BoxedCell: UITableViewCell {
var theStackView: UIStackView!
var containingView: UIView!
var theButton: UIButton!
var brdColor = UIColor(white: 0.7, alpha: 1.0)
// "spacer" view is just a 1-pt tall UIView used as a horizontal-line between labels
// when there is more than one title label
func getSpacer() -> UIView {
let newView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 1))
newView.backgroundColor = brdColor
newView.translatesAutoresizingMaskIntoConstraints = false
newView.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
return newView
}
// "label view" is a UIView containing on UILabel
// embedding the label in a view allows for convenient borders and insets
func getLabelView(text: String, position: Int) -> UIView {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
let newLabel = UILabel()
newLabel.font = UIFont.systemFont(ofSize: 15.0)
newLabel.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
newLabel.textColor = .black
newLabel.layer.borderWidth = 1
newLabel.layer.borderColor = brdColor.cgColor
newLabel.numberOfLines = 0
newLabel.text = text
newLabel.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(newLabel)
newLabel.leadingAnchor.constraint(equalTo: v.leadingAnchor, constant: 8.0).isActive = true
newLabel.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -8.0).isActive = true
var iTop: CGFloat = 0.0
var iBot: CGFloat = 0.0
// the passed "position" tells me whether this label is:
// a Single Title only
// the first Title of more than one
// the last Title of more than one
// or a Title with a Title above and below
// so we can set up proper top/bottom padding
switch position {
case 0:
iTop = 16.0
iBot = 16.0
break
case 1:
iTop = 12.0
iBot = 8.0
break
case -1:
iTop = 8.0
iBot = 12.0
break
default:
iTop = 8.0
iBot = 8.0
break
}
newLabel.topAnchor.constraint(equalTo: v.topAnchor, constant: iTop).isActive = true
newLabel.bottomAnchor.constraint(equalTo: v.bottomAnchor, constant: -iBot).isActive = true
return v
}
func setupThisCell(rowNumber: Int) -> Void {
// if containingView is nil, it hasn't been created yet
// so, create it + Stack view + Button
// else
// don't create new ones
// This way, we don't keep adding more and more views to the cell on reuse
if containingView == nil {
containingView = UIView()
containingView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(containingView)
containingView.layer.borderWidth = 1
containingView.layer.borderColor = brdColor.cgColor
containingView.backgroundColor = .white
containingView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
containingView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0).isActive = true
containingView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6.0).isActive = true
containingView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -6.0).isActive = true
theStackView = UIStackView()
theStackView.translatesAutoresizingMaskIntoConstraints = false
containingView.addSubview(theStackView)
theStackView.axis = .vertical
theStackView.spacing = 4.0
theStackView.alignment = .fill
theStackView.distribution = .fill
theButton = UIButton(type: .custom)
theButton.translatesAutoresizingMaskIntoConstraints = false
containingView.addSubview(theButton)
theButton.backgroundColor = .blue
theButton.setTitleColor(.white, for: .normal)
theButton.setTitle("The Button", for: .normal)
theButton.setContentHuggingPriority(1000, for: .horizontal)
theButton.centerYAnchor.constraint(equalTo: containingView.centerYAnchor, constant: 0.0).isActive = true
theButton.trailingAnchor.constraint(equalTo: containingView.trailingAnchor, constant: -8.0).isActive = true
theStackView.topAnchor.constraint(equalTo: containingView.topAnchor, constant: 0.0).isActive = true
theStackView.bottomAnchor.constraint(equalTo: containingView.bottomAnchor, constant: 0.0).isActive = true
theStackView.leadingAnchor.constraint(equalTo: containingView.leadingAnchor, constant: 0.0).isActive = true
theStackView.trailingAnchor.constraint(equalTo: theButton.leadingAnchor, constant: -8.0).isActive = true
}
// remove all previously added Title labels and spacer views
for v in theStackView.arrangedSubviews {
v.removeFromSuperview()
}
// setup 1 to 5 Titles
let n = rowNumber % 5 + 1
// create new Title Label views and, if needed, spacer views
// and add them to the Stack view
if n == 1 {
let aLabel = getLabelView(text: "Only one title for row: \(rowNumber)", position: 0)
theStackView.addArrangedSubview(aLabel)
} else {
for i in 1..<n {
let aLabel = getLabelView(text: "Title number \(i)\n for row: \(rowNumber)", position: i)
theStackView.addArrangedSubview(aLabel)
let aSpacer = getSpacer()
theStackView.addArrangedSubview(aSpacer)
}
let aLabel = getLabelView(text: "Title number \(n)\n for row: \(rowNumber)", position: -1)
theStackView.addArrangedSubview(aLabel)
}
}
}
class BoxedTableViewController: UITableViewController {
let cellID = "boxedCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(BoxedCell.self, forCellReuseIdentifier: cellID)
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1250
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! BoxedCell
// Configure the cell...
cell.setupThisCell(rowNumber: indexPath.row)
return cell
}
}
I'll check back if you run into any problems with it (gotta run, and haven't fully tested it yet -- and ran out of time to comment it - ugh).
You can also use tableview as tableviecell and adjust cell accordingly.
u need to layout cell in func layoutsubviews after set data to label and imageview;
Yes, split ElementTableCell to section with header and cells is much better approach. In this case you have no need to create constraints or dealing with complex manual layout. This would make your code simple and make scrolling smooth.
The button you use can be easily moved to the reusable header view
Is you still want to keep it in one complete cell, where is a way to draw manually the dynamic elements, such as titles and separators lines. Manually drawing is faster as usual. Or remove all views from cell.contentView each time you adding new. But this way is much more complicated.
Greate article about how to make UITableView appearence swmoth:
Perfect smooth scrolling in UITableViews

Resources