Dynamic UITableView row height using UIStackView? - ios

Surprised this isn't working out of the box, as this seems to be an important use case for stack views. I have a UITableViewCell subclass which adds a UIStackView to the contentView. I'm adding labels to the stack view in tableView(_cellForRowAtIndexPath:) and the tableview is set to use dynamic row heights, but it doesn't appear to work, at least in Xcode 7.3. I was also under the impression that hiding arranged subviews in a stack view was animatable, but that seems broken as well.
Any ideas on how to get this working correctly?
class StackCell : UITableViewCell {
enum VisualFormat: String {
case HorizontalStackViewFormat = "H:|[stackView]|"
case VerticalStackViewFormat = "V:|[stackView(>=44)]|"
}
var hasSetupConstraints = false
lazy var stackView : UIStackView! = {
let stack = UIStackView()
stack.axis = .Vertical
stack.distribution = .FillProportionally
stack.alignment = .Fill
stack.spacing = 3.0
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(stackView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
if !hasSetupConstraints {
hasSetupConstraints = true
let viewsDictionary: [String:AnyObject] = ["stackView" : stackView]
var newConstraints = [NSLayoutConstraint]()
newConstraints += self.newConstraints(VisualFormat.HorizontalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
newConstraints += self.newConstraints(VisualFormat.VerticalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
addConstraints(newConstraints)
}
super.updateConstraints()
}
private func newConstraints(visualFormat: String, viewsDictionary: [String:AnyObject]) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat(visualFormat, options: [], metrics: nil, views: viewsDictionary)
}
class ViewController: UITableViewController {
private let reuseIdentifier = "StackCell"
private let cellClass = StackCell.self
override func viewDidLoad() {
super.viewDidLoad()
configureTableView(self.tableView)
}
private func configureTableView(tableView: UITableView) {
tableView.registerClass(cellClass, forCellReuseIdentifier: reuseIdentifier)
tableView.separatorStyle = .SingleLine
tableView.estimatedRowHeight = 88
tableView.rowHeight = UITableViewAutomaticDimension
}
private func newLabel(title: String) -> UILabel {
let label = UILabel()
label.text = title
return label
}
// MARK: - UITableView
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 4
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44.0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! StackCell
cell.stackView.arrangedSubviews.forEach({$0.removeFromSuperview()})
cell.stackView.addArrangedSubview(newLabel("\(indexPath.section)-\(indexPath.row)"))
cell.stackView.addArrangedSubview(newLabel("Second Label"))
cell.stackView.addArrangedSubview(newLabel("Third Label"))
cell.stackView.addArrangedSubview(newLabel("Fourth Label"))
cell.stackView.addArrangedSubview(newLabel("Fifth Label"))
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! StackCell
for (idx, view) in cell.stackView.arrangedSubviews.enumerate() {
if idx == 0 {
continue
}
view.hidden = !view.hidden
}
UIView.animateWithDuration(0.3, animations: {
cell.contentView.layoutIfNeeded()
tableView.beginUpdates()
tableView.endUpdates()
})
}
}

It seems that for this to work the constraints need to be added in the init of the UITableViewCell and added to the contentView instead of cell's view.
The working code looks like this:
import UIKit
class StackCell : UITableViewCell {
enum VisualFormat: String {
case HorizontalStackViewFormat = "H:|[stackView]|"
case VerticalStackViewFormat = "V:|[stackView(>=44)]|"
}
var hasSetupConstraints = false
lazy var stackView : UIStackView! = {
let stack = UIStackView()
stack.axis = UILayoutConstraintAxis.Vertical
stack.distribution = .FillProportionally
stack.alignment = .Fill
stack.spacing = 3.0
stack.translatesAutoresizingMaskIntoConstraints = false
stack.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
return stack
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(stackView)
addStackConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addStackConstraints() {
let viewsDictionary: [String:AnyObject] = ["stackView" : stackView]
var newConstraints = [NSLayoutConstraint]()
newConstraints += self.newConstraints(VisualFormat.HorizontalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
newConstraints += self.newConstraints(VisualFormat.VerticalStackViewFormat.rawValue, viewsDictionary: viewsDictionary)
contentView.addConstraints(newConstraints)
super.updateConstraints()
}
private func newConstraints(visualFormat: String, viewsDictionary: [String:AnyObject]) -> [NSLayoutConstraint] {
return NSLayoutConstraint.constraintsWithVisualFormat(visualFormat, options: [], metrics: nil, views: viewsDictionary)
}
}

Related

Rows in table view do not expand the width of it and are stacked Swift

I have spent an hour trying to figure out what I'm doing wrong but no success. below is the code and an image of the result. All the rows appear one on top of the other in the first row of the table. and the row does not expand the width of the table as I have set it in the constraints. What am I doing wrong? Thank you.
The table view class:
class TableViewListType: UITableView {
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
translatesAutoresizingMaskIntoConstraints = false
allowsSelection = true
allowsMultipleSelection = false
allowsSelectionDuringEditing = true
allowsMultipleSelectionDuringEditing = true
dragInteractionEnabled = false
backgroundColor = .clear
separatorColor = .white
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
indicatorStyle = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The row classes for the table view:
class SuperRowForProfileAttributesTable: UITableViewCell {
//MARK: - Properties.
internal let firstLabel: LabelForRowInList = {
let label = LabelForRowInList(frame: .zero)
return label
}()
internal let secondLabel: LabelForRowInList = {
let label = LabelForRowInList(frame: .zero)
return label
}()
internal let thirdLabel: LabelForRowInList = {
let label = LabelForRowInList(frame: .zero)
return label
}()
internal let fourthLabel: LabelForRowInList = {
let label = LabelForRowInList(frame: .zero)
return label
}()
//MARK: - Init.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
translatesAutoresizingMaskIntoConstraints = false
clipsToBounds = true
backgroundColor = .clear
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Functions.
internal func setupViews() {
addSubview(firstLabel)
addSubview(secondLabel)
addSubview(thirdLabel)
addSubview(fourthLabel)
let firstConstraints = [
firstLabel.topAnchor.constraint(equalTo: topAnchor),
firstLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
firstLabel.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1/3),
firstLabel.widthAnchor.constraint(equalTo: widthAnchor)
]
NSLayoutConstraint.activate(firstConstraints)
let secondConstraints = [
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor),
secondLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
secondLabel.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1/3),
secondLabel.widthAnchor.constraint(equalTo: widthAnchor)
]
NSLayoutConstraint.activate(secondConstraints)
let thirdConstraints = [
thirdLabel.topAnchor.constraint(equalTo: secondLabel.bottomAnchor),
thirdLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
thirdLabel.trailingAnchor.constraint(equalTo: centerXAnchor),
thirdLabel.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1/3),
]
NSLayoutConstraint.activate(thirdConstraints)
let fourthConstraints = [
fourthLabel.topAnchor.constraint(equalTo: secondLabel.bottomAnchor),
fourthLabel.leadingAnchor.constraint(equalTo: centerXAnchor),
fourthLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
fourthLabel.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1/3)
]
NSLayoutConstraint.activate(fourthConstraints)
}
}
class RowForExperienceInProfileTable: SuperRowForProfileAttributesTable {
//MARK: - Properties.
internal var valueForExperienceRow: ExperienceModelForProfileAttributes! {
didSet {
firstLabel.text = valueForExperienceRow.jobTitle
secondLabel.text = valueForExperienceRow.companyName
thirdLabel.text = valueForExperienceRow.startedWork
fourthLabel.text = valueForExperienceRow.finishedWork
}
}
}
class RowForSkillInProfileTable: SuperRowForProfileAttributesTable {
//MARK: - Properties.
internal var valueForSkillRow: SkillsModelForProfileAttributes! {
didSet {
firstLabel.text = valueForSkillRow.skill
}
}
//MARK: - Init.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupViews() {
addSubview(firstLabel)
let firstConstraints = [
firstLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
firstLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
firstLabel.widthAnchor.constraint(equalTo: widthAnchor, constant: 0),
firstLabel.heightAnchor.constraint(equalTo: heightAnchor, constant: 0)
]
NSLayoutConstraint.activate(firstConstraints)
}
}
class RowForEducationInProfileTable: SuperRowForProfileAttributesTable {
//MARK: - Properties.
internal var valueForEducationRow: EducationModelForProfileAttributes! {
didSet {
firstLabel.text = valueForEducationRow.institutionName
secondLabel.text = valueForEducationRow.degreeName
thirdLabel.text = valueForEducationRow.startedStudy
fourthLabel.text = valueForEducationRow.finishedStudy
}
}
}
The VC:
fileprivate var experienceForProfile = [ExperienceModelForProfileAttributes(jobTitle: "Tester", companyName: "Testing Company", startedWork: "May 2019", finishedWork: "October 2019"), ExperienceModelForProfileAttributes(jobTitle: "Welder", companyName: "Welding Company", startedWork: "January 2018", finishedWork: "May 2020")]
fileprivate var skillsForProfile = [SkillsModelForProfileAttributes]()
fileprivate var educationForProfile = [EducationModelForProfileAttributes]()
fileprivate lazy var tableForAttributes: TableViewListType = {
let table = TableViewListType(frame: .zero, style: .plain)
table.delegate = self
table.dataSource = self
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.isHidden = true
tableForAttributes.register(RowForExperienceInProfileTable.self, forCellReuseIdentifier: firstIDForTable)
tableForAttributes.register(RowForSkillInProfileTable.self, forCellReuseIdentifier: secondIDForTable)
tableForAttributes.register(RowForEducationInProfileTable.self, forCellReuseIdentifier: thirdIDForTable)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var numberOfRows = 0
switch selectedMimicIndex {
case 0: numberOfRows = experienceForProfile.count
case 1: numberOfRows = skillsForProfile.count
case 2: numberOfRows = educationForProfile.count
default: print("nu such rows for attributes table in own profile")
}
return numberOfRows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch selectedMimicIndex {
case 0: let cell = tableView.dequeueReusableCell(withIdentifier: firstIDForTable, for: indexPath) as! RowForExperienceInProfileTable
cell.valueForExperienceRow = experienceForProfile[indexPath.row]
return cell
case 1: let cell = tableView.dequeueReusableCell(withIdentifier: secondIDForTable, for: indexPath) as! RowForSkillInProfileTable
cell.valueForSkillRow = skillsForProfile[indexPath.row]
return cell
case 2: let cell = tableView.dequeueReusableCell(withIdentifier: thirdIDForTable, for: indexPath) as! RowForEducationInProfileTable
cell.valueForEducationRow = educationForProfile[indexPath.row]
return cell
default: return UITableViewCell()
}
}
The selectedMimicIndex value is changed the didSelectItem() function of the collection view that I have; a cv that controls what cells the table displays. reloadData() is called here after the Int value is changed when the user changes the selected cv cell.
Also notice that the second row is much taller that it should be; ignores the height that I have specified.
For every label you need to set and add it to contentView
firstLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(firstLabel)
also the most bottom label to bottom of cell
fourthLabel.bottomAnchor.constraint(equalTo:self.contentView.bottomAnchor, constant:-20)

TableView inside a custom UITableViewCell not appearing for all of the cells of that custom cellviewtype

I am trying to create a table of services such that, if service has a couple of sub-services, then the cell associated with that service then has another table view showing those sub-services under the said service.
I tried implementing such a table by looking at the example shown in the link: table within a tableviewcell
I am posting related source codes associated with the tableview
BookingServiceChargeViewCell.swift
import UIKit
import PineKit
import SwiftMoment
class BookingServiceChargeViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
var service : Service? = nil
var subServices : [Service] = []
let content = PineCardView()
var cover = UIImageView()
let serviceName = PineLabel.Bold(text: " ... ")
var itemIndex = -1
var chosen = false
var parentView : OnboardingChosenServicesViewController? = nil
var anchor = UIView()
let table = UITableView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
layout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layout() {
self.addSubview(content)
content.snp.makeConstraints { (make) in
make.top.left.right.equalTo(self).inset(5)
make.bottom.equalTo(self)
}
layoutContent()
}
func layoutContent() {
content.addSubviews([cover, serviceName])
self.cover.image = UIImage(named: "gray-card")
cover.clipsToBounds = true
cover.snp.makeConstraints { (make) in
make.width.equalTo(content).multipliedBy(0.15)
make.left.equalTo(content).offset(10)
make.top.equalTo(content.snp.top).offset(15)
make.size.equalTo(50)
}
serviceName.textColor = UIColor.black
serviceName.font = Config.Font.get(.Bold, size: 17.5)
serviceName.snp.makeConstraints { (make) in
make.centerY.equalTo(cover)
make.left.equalTo(cover.snp.right).offset(20)
}
table.delegate = self
table.dataSource = self
table.register(BookingSubServicesChargeViewCell.self, forCellReuseIdentifier: "cell")
table.separatorStyle = .none
self.content.addSubview(table)
table.snp.makeConstraints { (make) in
make.top.equalTo(self.cover.snp.bottom).offset(15)
make.left.equalTo(self.cover.snp.right).offset(10)
make.right.equalTo(self.content.snp.right).offset(-10)
make.height.equalTo(450)
}
}
func configure(_ service: Service, subServices: [Service], index: Int, parentView: OnboardingChosenServicesViewController) {
self.service = service
self.subServices = subServices
self.itemIndex = index
self.parentView = parentView
if (self.service!.defaultImage != nil){
ImageLoader.sharedLoader.imageForUrl(urlString: self.service!.defaultImage!) { (image, url) in
self.cover.image = image
}
}
self.serviceName.text = self.service!.name!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.subServices.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! BookingSubServicesChargeViewCell
cell.configure(self.subServices[indexPath.row], index: indexPath.row, parentView: self.parentView!)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
}
BookingSubServicesChargeViewCell.swift
import UIKit
import PineKit
import SwiftMoment
class BookingSubServicesChargeViewCell: UITableViewCell {
var service : Service? = nil
let content = PineCardView()
var cover = UIImageView()
let serviceName = PineLabel.Bold(text: " ... ")
var itemIndex = -1
var chosen = false
var parentView : OnboardingChosenServicesViewController? = nil
var anchor = UIView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
layout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layout() {
self.addSubview(content)
content.snp.makeConstraints { (make) in
make.top.left.right.equalTo(self).inset(5)
make.bottom.equalTo(self)
}
layoutContent()
}
func layoutContent() {
content.addSubviews([cover, serviceName])
self.cover.image = UIImage(named: "gray-card")
cover.clipsToBounds = true
cover.snp.makeConstraints { (make) in
make.width.equalTo(content).multipliedBy(0.15)
make.left.equalTo(content).offset(10)
make.top.equalTo(content.snp.top).offset(15)
make.size.equalTo(50)
}
serviceName.textColor = UIColor.black
serviceName.font = Config.Font.get(.Bold, size: 17.5)
serviceName.snp.makeConstraints { (make) in
make.centerY.equalTo(cover)
make.left.equalTo(cover.snp.right).offset(20)
}
self.anchor = self.serviceName
}
func configure(_ service: Service, index: Int, parentView: OnboardingChosenServicesViewController) {
self.service = service
self.itemIndex = index
self.parentView = parentView
if (self.service!.defaultImage != nil){
ImageLoader.sharedLoader.imageForUrl(urlString: self.service!.defaultImage!) { (image, url) in
self.cover.image = image
}
}
self.serviceName.text = self.service!.name!
}
}
Here a couple of screenshots of the situation that has arisen:-
As you can see in the screenshots, some of the tables which have sub-services are not being shown, but sometimes they are being shown.
Can someone tell me what is it that I am missing here? What changes am I supposed to make in the source codes shown above?
FYI: I am not using storyboards in any way and I do not know what nib files are. I have constructed this programmatically and I am hoping that I can get a code snippet based solution as soon as possible.
Thanks.
In BookingServiceChargeViewCell class, call self.table.reloadData() at the end of configure method as below.
func configure(_ service: Service, subServices: [Service], index: Int, parentView: OnboardingChosenServicesViewController) {
self.service = service
self.subServices = subServices
self.itemIndex = index
self.parentView = parentView
if (self.service!.defaultImage != nil){
ImageLoader.sharedLoader.imageForUrl(urlString: self.service!.defaultImage!) { (image, url) in
self.cover.image = image
}
}
self.serviceName.text = self.service!.name!
self.table.reloadData()
}

iOS setting UIImageView constraints programmatically in UITableViewCell gets messed up while scrolling the tableview

I change the height of a UIImageView inside UITableViewCell programmatically.So when there is an image url, height is 100.0 and when image url is nil, height is 0.0 .
This is the code i use in my tableViewCell class :
func setConstraints(_height:CGFloat){
self.commentImage.addConstraint(NSLayoutConstraint(item: self.commentImage,attribute: .height,relatedBy: .equal,toItem: self.commentImage,attribute: .width,multiplier: _height / 287.0,constant: 0))
self.commentImage.updateConstraints()
self.commentImage.layoutIfNeeded()
}
if let img = comment.image {
let imgUrl = URL(string: img)
self.commentImage.sd_setImage(with: imgUrl, placeholderImage: UIImage(named: "PlaceHolder Shop"))
setConstraints(_height: 100.0)
}else {
self.commentImage.image = nil
setConstraints(_height: 0.0)
}
Now the problem is that when I scroll the tableview, some of the rows that has no image url, get the height of 100.0 for UIImageView, which leaves a blank area, And if I scroll tableView very fast, sometimes the images in rows are gone.
What should I do to solve this problem?What am I missing here?
CustomTableViewCell with stackview
class CustomTableViewCell: UITableViewCell {
let titleLbl = UILabel()
let stackView = UIStackView()
let imgView = UIImageView()
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
titleLbl.translatesAutoresizingMaskIntoConstraints = false
addSubview(titleLbl)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.alignment = .fill
stackView.distribution = .fill
addSubview(stackView)
imgView.translatesAutoresizingMaskIntoConstraints = false
let imgViewHeight = imgView.heightAnchor.constraint(equalToConstant: 100)
imgViewHeight.priority = .defaultLow
imgViewHeight.isActive = true
imgView.addConstraint(imgViewHeight)
stackView.addArrangedSubview(imgView)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[titleLbl]|", options: [], metrics: nil, views: ["titleLbl":titleLbl,"stackView":stackView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[titleLbl(30)][stackView]|", options: [.alignAllLeading,.alignAllTrailing], metrics: nil, views: ["titleLbl":titleLbl,"stackView":stackView]))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ViewController with automatic height tableView
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 130
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 15
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomTableViewCell
cell.titleLbl.text = "Row \(indexPath.row)"
// set cell.imgView.image
cell.imgView.isHidden = cell.imageView?.image == nil
return cell
}
}
UITableViewCells are recycled. Your setConstraints method is not actually setting the constraints; its adding a new constraint over an over again. To do this correctly you can just set the constraints in Interface builder. Find the height constraint in the size inspector and double click it to select it in the document outline. Option drag from the constraint to your tableViewCell to create an IBOutlet called heightConstraint. Change your code as follows:
func setConstraints(_height:CGFloat){
heightConstraint.constant = height
commentImage.setNeedsLayout()
}

Creating a TableView Programmatically with multiple cell in swift

As I am creating a TableView programmatically with multiple cell and in each cell having UICollectionView, UITableView, UITableView.... but I am not able to find the error and every time when I run the program it Shows
" Command failed due to signal: Segmentation fault: 11".
Has any one created this type of UI using coding.
New in Swift so forgive for errors.
// ViewController.swift
// Json Parsing in Swift
//
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UITableViewController {
var dataArray = Array<JSON>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request(.GET, "http://104.131.162.14:3033/api/ios/detail").validate().responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
print("JSON: \(json)")
var trafficJson = json["traffic_partners"]
trafficJson["type"] = "Traffic"
self.dataArray.append(trafficJson)
var newsJson = json["news"]
newsJson["type"] = "News"
self.dataArray.append(newsJson)
var categoryJson = json["category"]
categoryJson["type"] = "Category"
self.dataArray.append(categoryJson)
var topFreeApps = json["top_free_apps"]
topFreeApps["type"] = "TopApps"
self.dataArray.append(topFreeApps)
var topSites = json["top_sites"]
topSites["type"] = "TopSites"
self.dataArray.append(topSites)
var trendingVideos = json["tranding_video"]
trendingVideos["type"] = "TrendingVideos"
self.dataArray.append(trendingVideos)
var sports = json["sports"]
sports["type"] = "Sports"
self.dataArray.append(sports)
var jokes = json["jokes"]
jokes["type"] = "jokes"
self.dataArray.append(jokes)
print(self.dataArray[0]["detail"][0].object)
print(self.dataArray[2]["detail"].object)
self.tableView.reloadData()
}
case .Failure(let error):
print(error)
}
}
tableView.registerClass(MyCell.self, forCellReuseIdentifier: "cellId")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("cellId", forIndexPath: indexPath) as! MyCell
myCell.nameLabel.text = dataArray[indexPath.row]["type"].string
if (dataArray[indexPath.row]["type"].string == "News") {
myCell.newsArray = dataArray[indexPath.row]["detail"].arrayObject
}
myCell.myTableViewController = self
return myCell
}
}
class MyCell: UITableViewCell {
var newsArray :NSMutableArray=[]
var myTableViewController: ViewController?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let newsTableView: UITableView = {
let newsTV = UITableView(frame:UIScreen.mainScreen().bounds, style: UITableViewStyle.Plain)
newsTV.registerClass(NewsTableViewCell.self, forCellReuseIdentifier: "NewsTableViewCell")
return newsTV
}()
func tableView(newsTableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsArray.count
}
func tableView(newsTableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = newsTableView.dequeueReusableCellWithIdentifier("cellId", forIndexPath: indexPath) as! NewsTableViewCell
myCell.nameLabel.text = newsArray[indexPath.row]["title"].string
myCell.myTableViewController = myTableViewController
return myCell
}
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Sample Item"
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFontOfSize(14)
return label
}()
func setupViews() {
addSubview(newsTableView)
addSubview(nameLabel)
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": nameLabel]))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": newsTableView]))
}
func handleAction() {
}
}

Custom UITableViewCell programmatically using Swift

Hey all I am trying to create a custom UITableViewCell, but I see nothing on the simulator. Can you help me please.
I can see the label only if I var labUserName = UILabel(frame: CGRectMake(0.0, 0.0, 130, 30));
but it overlaps the cell. I don't understand, Auto Layout should know the preferred size/minimum size of each cell?
Thanks
import Foundation
import UIKit
class TableCellMessages: UITableViewCell {
var imgUser = UIImageView();
var labUserName = UILabel();
var labMessage = UILabel();
var labTime = UILabel();
override init(style: UITableViewCellStyle, reuseIdentifier: String) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
imgUser.layer.cornerRadius = imgUser.frame.size.width / 2;
imgUser.clipsToBounds = true;
contentView.addSubview(imgUser)
contentView.addSubview(labUserName)
contentView.addSubview(labMessage)
contentView.addSubview(labTime)
//Set layout
var viewsDict = Dictionary <String, UIView>()
viewsDict["image"] = imgUser;
viewsDict["username"] = labUserName;
viewsDict["message"] = labMessage;
viewsDict["time"] = labTime;
//Image
//contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[image(100)]-'", options: nil, metrics: nil, views: viewsDict));
//contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[image(100)]-|", options: nil, metrics: nil, views: viewsDict));
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[username]-[message]-|", options: nil, metrics: nil, views: viewsDict));
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[username]-|", options: nil, metrics: nil, views: viewsDict));
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[message]-|", options: nil, metrics: nil, views: viewsDict));
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Let's make a few assumptions:
You have an iOS8 project with a Storyboard that contains a single UITableViewController. Its tableView has a unique prototype UITableViewCell with custom style and identifier: "cell".
The UITableViewController will be linked to Class TableViewController, the cell will be linked to Class CustomTableViewCell.
You will then be able to set the following code (updated for Swift 2):
CustomTableViewCell.swift:
import UIKit
class CustomTableViewCell: UITableViewCell {
let imgUser = UIImageView()
let labUserName = UILabel()
let labMessage = UILabel()
let labTime = UILabel()
override func awakeFromNib() {
super.awakeFromNib()
imgUser.backgroundColor = UIColor.blueColor()
imgUser.translatesAutoresizingMaskIntoConstraints = false
labUserName.translatesAutoresizingMaskIntoConstraints = false
labMessage.translatesAutoresizingMaskIntoConstraints = false
labTime.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imgUser)
contentView.addSubview(labUserName)
contentView.addSubview(labMessage)
contentView.addSubview(labTime)
let viewsDict = [
"image": imgUser,
"username": labUserName,
"message": labMessage,
"labTime": labTime,
]
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[image(10)]", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[labTime]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[username]-[message]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[username]-[image(10)]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[message]-[labTime]-|", options: [], metrics: nil, views: viewsDict))
}
}
TableViewController.swift:
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Auto-set the UITableViewCells height (requires iOS8+)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
cell.labUserName.text = "Name"
cell.labMessage.text = "Message \(indexPath.row)"
cell.labTime.text = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .ShortStyle)
return cell
}
}
You will expect a display like this (iPhone landscape):
This is the update for swift 3 of the answer Imanou Petit.
CustomTableViewCell.swift:
import Foundation
import UIKit
class CustomTableViewCell: UITableViewCell {
let imgUser = UIImageView()
let labUerName = UILabel()
let labMessage = UILabel()
let labTime = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
imgUser.backgroundColor = UIColor.blue
imgUser.translatesAutoresizingMaskIntoConstraints = false
labUerName.translatesAutoresizingMaskIntoConstraints = false
labMessage.translatesAutoresizingMaskIntoConstraints = false
labTime.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imgUser)
contentView.addSubview(labUerName)
contentView.addSubview(labMessage)
contentView.addSubview(labTime)
let viewsDict = [
"image" : imgUser,
"username" : labUerName,
"message" : labMessage,
"labTime" : labTime,
] as [String : Any]
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[image(10)]", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[labTime]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[username]-[message]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[username]-[image(10)]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[message]-[labTime]-|", options: [], metrics: nil, views: viewsDict))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Settigns.swift:
import Foundation
import UIKit
class Settings: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var myTableView: UITableView!
private let sections: NSArray = ["fruit", "vegitable"] //Profile network audio Codecs
private let fruit: NSArray = ["apple", "orange", "banana", "strawberry", "lemon"]
private let vegitable: NSArray = ["carrots", "avocado", "potato", "onion"]
override func viewDidLoad() {
super.viewDidLoad()
// get width and height of View
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let navigationBarHeight: CGFloat = self.navigationController!.navigationBar.frame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
myTableView = UITableView(frame: CGRect(x: 0, y: barHeight+navigationBarHeight, width: displayWidth, height: displayHeight - (barHeight+navigationBarHeight)))
myTableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell") // register cell name
myTableView.dataSource = self
myTableView.delegate = self
//Auto-set the UITableViewCells height (requires iOS8+)
myTableView.rowHeight = UITableViewAutomaticDimension
myTableView.estimatedRowHeight = 44
self.view.addSubview(myTableView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// return the number of sections
func numberOfSections(in tableView: UITableView) -> Int{
return sections.count
}
// return the title of sections
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section] as? String
}
// called when the cell is selected.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Num: \(indexPath.row)")
if indexPath.section == 0 {
print("Value: \(fruit[indexPath.row])")
} else if indexPath.section == 1 {
print("Value: \(vegitable[indexPath.row])")
}
}
// return the number of cells each section.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return fruit.count
} else if section == 1 {
return vegitable.count
} else {
return 0
}
}
// return cells
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
if indexPath.section == 0 {
cell.labUerName.text = "\(fruit[indexPath.row])"
cell.labMessage.text = "Message \(indexPath.row)"
cell.labTime.text = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .short, timeStyle: .short)
} else if indexPath.section == 1 {
cell.labUerName.text = "\(vegitable[indexPath.row])"
cell.labMessage.text = "Message \(indexPath.row)"
cell.labTime.text = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .short, timeStyle: .short)
}
return cell
}
}
In Swift 5.
The custom UITableViewCell:
import UIKit
class CourseCell: UITableViewCell {
let courseName = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Set any attributes of your UI components here.
courseName.translatesAutoresizingMaskIntoConstraints = false
courseName.font = UIFont.systemFont(ofSize: 20)
// Add the UI components
contentView.addSubview(courseName)
NSLayoutConstraint.activate([
courseName.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
courseName.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20),
courseName.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
courseName.heightAnchor.constraint(equalToConstant: 50)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The UITableViewController:
import UIKit
class CourseTableViewController: UITableViewController {
private var data: [Int] = [1]
override func viewDidLoad() {
super.viewDidLoad()
// You must register the cell with a reuse identifier
tableView.register(CourseCell.self, forCellReuseIdentifier: "courseCell")
// Change the row height if you want
tableView.rowHeight = 150
// This will remove any empty cells that are below your data filled cells
tableView.tableFooterView = UIView()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "courseCell", for: indexPath) as! CourseCell
cell.courseName.text = "Course name"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}

Resources