I have a UITableView that I'm using to show an array of custom objects. Each object has several properties including a Boolean property that indicates if this item is new or not.
My UITableViewCell content view is defined in the storyboard and has an initial layout similar to this:
In my UITableViewController, when I dequeue my cells, I call a method on my UITableViewCell that configures the data to be displayed in the cell before I return it. One of the properties that I check is the .isNew property that I mentioned previously. If this value is true, then I am creating a UIButton and inserting it as a subview in the cell's content view so I end up with something like this:
Just for context, this button will show a "new" image to indicate that this item is new. I am also hooking up a method that will fire when the button is tapped. That method is also defined in my UITableViewCell and looks like this:
#objc func newIndicatorButtonTapped(sender: UIButton!) {
// call delegate method and pass this cell as the argument
delegate?.newIndicatorButtonTapped(cell: self)
}
I have also created a protocol that defines a delegate method. My UITableViewController conforms to this and I see that code fire when I tap on the button in my cell(s). Here's is the delegate method (defined in an extension on my UITableViewController):
func newIndicatorButtonTapped(cell: UITableViewCell) {
if let indexPath = self.tableView.indexPath(for: cell) {
print(indexPath.row)
}
}
I see the row from the indexPath print out correctly in Xcode when I tap on the cell. When my user taps on this button, I need to remove it (the button) and update the constraint for my UILabel so that is aligned again with the leading edge of the content view as shown in the first mockup above. Unfortunately, I seem to be running into an issue with cell recycling because the UIButton is disappearing and re-appearing in different cells as I scroll through them. Do I need to reset the cell's layout/appearance before it gets recycled or am I misunderstanding something about how cell recycling works? Any tips would be much appreciated!
What you may be thinking is that you get a "fresh" cell, but when a cell gets re-cycled that means it gets re-used.
You can see this very easily by changing the text color of a basic cell.
For example:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyID", for: indexPath) as! MyCustomCell
if indexPath.row == 3 {
cell.theLabel.textColor = .red
}
return cell
}
As you would expect, when the table first loads the text color will change for the 4th row (row indexing is zero-based).
However, suppose you have 100 rows? As you scroll, the cells will be re-used ... and each time that original-4th-cell gets re-used, it will still have red text.
So, as you guessed, yes... you need to "reset" your cell to its original layout / content / colors / etc each time you want to use it:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyID", for: indexPath) as! MyCustomCell
if indexPath.row == 3 {
cell.theLabel.textColor = .red
} else {
cell.theLabel.textColor = .black
}
return cell
}
You may want to consider to have the button hidden and then change the layout when it is clicked.
Firing the action from the cell to the tableView with a protocol and then reseting the layout at cell reuse is a good way to do it
Doing it in a cell fully programatic would be like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
cell.isNew = indexPath.row == 0 ? true : false
cell.layoutIfNeeded()
return cell
}
And the cell class needs to be similar to: (you can do what you need by changing the Autolayout constraint, manipulating the frame directly or using a UIStackView)
class Cell: UITableViewCell {
var isNew: Bool = false {
didSet {
if isNew {
button.isHidden = true
leftConstraint.constant = 20
} else {
button.isHidden = false
leftConstraint.constant = 100
}
self.setNeedsLayout()
}
}
var button: UIButton!
var label: UILabel!
var leftConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
button = UIButton(type: .system)
button.setTitle("Click", for: .normal)
self.contentView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: 50).isActive = true
button.heightAnchor.constraint(equalToConstant: 10).isActive = true
button.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 20).isActive = true
button.topAnchor.constraint(equalTo: self.topAnchor, constant: 20).isActive = true
label = UILabel()
label.text = "Label"
self.contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.widthAnchor.constraint(equalToConstant: 200).isActive = true
label.heightAnchor.constraint(equalToConstant: 20).isActive = true
label.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
leftConstraint = label.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 100)
leftConstraint.isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Related
I’m coding a mock messaging application in Swift Playgrounds for iPad. So far, I have a UITableViewController with a custom cell, whose background color changes every other cell. Now, I’d like to add a sticky footer of some kind to the bottom of the screen that will have a text field and a send button so that the user can send messages. I’m not quite sure how to approach this. Any ideas? Here’s what I have so far:
import UIKit
import PlaygroundSupport
class ViewController: UITableViewController {
let textMessages = [
"Here's my very first message",
"I'm going to message another long message that will word wrap",
"I'm going to message another long message that will word wrap, I'm going to message another long message that will word wrap, I'm going to message another long message that will word wrap",
"Somejfjfidiskkejejsjsjsjdjjdj blah blah blah"
]
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Messages"
navigationController?.navigationBar.prefersLargeTitles = true
tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "cell_1")
tableView.separatorStyle = .none
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textMessages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_1", for: indexPath) as! ChatMessageCell
cell.selectionStyle = .none
if indexPath.row % 2 == 1{
cell.messageLabel.text = textMessages[indexPath.row]
//cell.setupConstraints(side: 1)
cell.bubbleBackgroundView.backgroundColor = UIColor(white: 0.9, alpha: 1)
return cell
}else{
cell.messageLabel.text = textMessages[indexPath.row]
//cell.setupConstraints(side: 0)
cell.bubbleBackgroundView.backgroundColor = .blue
return cell
}
}
//let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ChatMessageCell
// cell.textLabel?.text = "We want to provide a longer string that is actually going to wrap onto the next line and maybe even a third line."
// cell.textLabel?.numberOfLines = 0
}
class ChatMessageCell: UITableViewCell {
let messageLabel = UILabel()
let bubbleBackgroundView = UIView()
var leadingAnchorConstant = CGFloat()
func setupConstraints(side: Int){
if side == 1{
leadingAnchorConstant = frame.size.width - 176
}else{
leadingAnchorConstant = 32
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
bubbleBackgroundView.backgroundColor = .yellow
let gradient = CAGradientLayer()
bubbleBackgroundView.layer.shadowOpacity = 0.35
bubbleBackgroundView.layer.shadowRadius = 6
bubbleBackgroundView.layer.shadowOffset = CGSize(width: 0, height: 0)
bubbleBackgroundView.layer.shadowColor = UIColor.black.cgColor
bubbleBackgroundView.layer.cornerRadius = 25
bubbleBackgroundView.translatesAutoresizingMaskIntoConstraints = false
addSubview(bubbleBackgroundView)
addSubview(messageLabel)
// messageLabel.backgroundColor = .green
messageLabel.text = "We want to provide a longer string that is actually going to wrap onto the next line and maybe even a third line."
messageLabel.numberOfLines = 0
messageLabel.translatesAutoresizingMaskIntoConstraints = false
// lets set up some constraints for our label
let constraints = [messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 32),
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32),
messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -32),
messageLabel.widthAnchor.constraint(equalToConstant: 250),
bubbleBackgroundView.topAnchor.constraint(equalTo: messageLabel.topAnchor, constant: -16),
bubbleBackgroundView.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor, constant: -16),
bubbleBackgroundView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
bubbleBackgroundView.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 16),
]
NSLayoutConstraint.activate(constraints)
// messageLabel.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
PlaygroundPage.current.liveView = ViewController()
I think you are looking for these tableview delegate methods to set up a custom footer:
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
//let cell = tableView.dequeueReusableCell(withIdentifier: "footer_cell") as! FooterCell
// Set up cell
let cell = UITableViewCell()
cell.textLabel?.text = "Footer"
cell.backgroundColor = .white
return cell
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 40.0 // Set height of footer
}
You can use custom tableview cells the same way you use them in cellForRow - I just set it up with simple default cells to produce a working sample - you can also create UIView() to return to this method
By default it will stick to bottom of screen above tableview - if this isn't the case or you want it to stick to bottom of table you can change this setting in attributes inspector if using storyboard:
Style - Plain -> Sticks to bottom of view on top of tableview
Style - Grouped -> Sticks to bottom of tableview no matter how tall
Although another option probably even better without the use of a footer is to use a UITableView on UIViewController - this will give you the space to add your TextView, Buttons and whatever else you need directly on the View Controller under the UITableView
Left is UITableViewController - Right is UIViewController with UITableView
Up to you how you want to play it but I'd recommend the second option to provide the most flexibility on a ViewController
Hope this helps!
I am using a tableview where the first cell contains two buttons. When the first button is activated, the second should be disabled (you should not be able to press it). It all works fine until I start scrolling down the tableview. If I scroll down as far as possible while still being able to see the buttons in first cell, and I activate the first button, I am still able to press the other one. Other things also stops working, but I guess that it is cause by the same thing. Do you have any idea on what happens? See the gif below to see what is going on
I have uploaded the source code on this link, if you need it
https://github.com/Rawchris/scroll-down
I hope you can help :)
Table view cells are reused - which means a couple things:
in general (particularly for your case) you should only add UI elements when you initialize the cell. Otherwise, new ones get added over and over and over.
you need to maintain "row" information, usually in your data source. In this example, you want at least an array of Bool values indicating whether the button in the row should be enabled or not when the cell is reused.
Change your View Controller class to this:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
// this would be replaced with your real data
// test with 20 rows
// start with all rows having button enabled
var buttonStatus: [Bool] = Array(repeating: true, count: 20)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Configure the button
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(200)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return buttonStatus.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
cell.selectionStyle = UITableViewCell.SelectionStyle.none
cell.setButtonEnabled(buttonStatus[indexPath.row], with: "Row \(indexPath.row)")
cell.callback = { b in
// update data source with enabled state of button
self.buttonStatus[indexPath.row] = b
}
return cell
}
}
and change your cell class to this:
class TableViewCell: UITableViewCell {
var callback: ((Bool) -> ())?
var button = DropDownBtn()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
button = DropDownBtn.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
button.setTitle("Button1", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
//Add Button to the View Controller
self.addSubview(button)
//button Constraints
button.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: 30).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
//Set the drop down menu's options
button.dropView.dropDownOptions = ["Option1", "Option2", "Option3", "Option4"]
self.clipsToBounds = false
self.contentView.clipsToBounds=false
}
func setButtonEnabled(_ b: Bool, with title: String) {
button.isUserInteractionEnabled = b
// update the UI - green enabled, red disabled
button.backgroundColor = b ? UIColor(red: 0.0, green: 0.6, blue: 0.0, alpha: 1.0) : .red
// update the title so we can see what row we're on
button.setTitle(title, for: [])
}
#IBAction func deactivate(_ sender: Any) {
// toggle the enabled property of "button"
button.isUserInteractionEnabled = !button.isUserInteractionEnabled
// tell the controller the status changed
callback?(button.isUserInteractionEnabled)
// update the UI - green enabled, red disabled
button.backgroundColor = button.isUserInteractionEnabled ? UIColor(red: 0.0, green: 0.6, blue: 0.0, alpha: 1.0) : .red
}
}
This will demonstrate using an array to track the Bool enabled state for the dropDown button in each row. It also changes the button's background color to Green when enabled, Red when disabled. And, it sets the Title of the dropDown button to make it easy to see which rows you're looking at when you scroll up and down.
I have a table view controller with a section cell and on click it expands and other subsections are showed. A section cell will always have title and a button and it may or may not have a description, on expanding the cell with no description, I am applying a centerYanchor on the title of the section cell so that it's aligned accordingly to the expand icon.
On expanding the cells which have a description it works as expected, also the section with no description has centerYanchor applied to it and works properly.
Now the problem that I am facing is as soon as I expand a cell with no description, the cells with description starts to behave weirdly on expanding.
As you can see the first two cells with description opened properly and other cells with no description is also aligned with the button.
In this case I opened the third cell first and on opening the first cell, even though it had description the centerYanchor and hiden logic is being applied to it.
Here is the code for tableViewController
override func numberOfSections(in tableView: UITableView) -> Int {
return tableData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableData[section].opened == true{
return tableData[section].sectionData.count + 1
} else{
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "anotherCell", for: indexPath) as? tableCell else {
fatalError("The dequeued cell has thrown some error.")
}
cell.cellTitle.text = tableData[indexPath.section].title
cell.cellDescription.text = tableData[indexPath.section].description
cell.setData = tableData[indexPath.section].opened
return cell
} else{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "subSectionCell", for: indexPath) as? subSectionTableViewCell else {
fatalError("The dequeued cell has thrown some error.")
}
cell.subSectionTitle.text = tableData[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if tableData[indexPath.section].opened == true {
tableData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none)
}
else{
tableData[indexPath.section].opened = true
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none)
}
}
}
Here is the code for hiding and applying the centerYanchor to the cell
var setData: Bool = false {
didSet{
setupCell()
}
}
func setupCell() {
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
}
if setData{
button.setImage(UIImage(named: "down"), for: .normal)
} else{
button.setImage(UIImage(named: "right"), for: .normal)
}
}
Please suggest me on how I should fix this, and if you have any doubts ask in the comments.
Cell Data Struct
struct cData {
var title = String()
var description = String()
var identifier = Int()
var opened = Bool()
var sectionData = [String]()
}
Cells constraints
Here is my suggested layout.
Yellow is the cell's contentView; orange is the View the contains the other elements; labels have cyan background.
Embed the labels in a UIStackView:
Give the "arrow button" a centerY constraint to the Description label, with Priority: 751 AND give it a centerY constraint to the Title label, with Priority: 750. That will automatically center it on the Description label when it is visible, and on the Title label when Description is hidden.
Then change your cell's setupCell() func as follows:
func setupCell() {
// set all subviews background colors to white
//[contentView, view, cellTitle, cellDescription, button].forEach {
// $0?.backgroundColor = .white
//}
// hide if no text, otherwise show
cellDescription.isHidden = (cellDescription.text == "")
if setData{
button.setImage(UIImage(named: "down"), for: .normal)
} else{
button.setImage(UIImage(named: "right"), for: .normal)
}
}
During dev, I like to use contrasting colors to make it easy to see the layout. If you un-comment the .forEach block, everything will get a white background. After I have my layout correct, I go back to Storyboard and set the background colors to white (or clear, or however I really want them) and remove the color setting from the code.
Looks like a cell reuse issue here:
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
}
Your cells are being reused, but you're never actually showing the cellDescription if it's available:
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
} else {
cellDescription.isHidden = false
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = false
}
All I'm doing in my cellForRowAt is setting UITableViewCell's (no subclass) imageView?.image and textLabel?.text values (and fonts and colors). Setting an image via SF Symbols and text according to my model. To go into more detail, there are only three possible cell kinds: a workspace, the "Add Workspace" button, and the archive.
In the following screenshot, I've demonstrated with green lines how these views don't quite line up in a logical fashion. Text is misaligned between all three types of cells, and (annoyingly) the SF Symbols image for that boxy icon with an "add" (+) indicator is slightly wider than the standard image.
Can anyone help me with an easy fix for this that I'm simply just missing? I've already tried setting the imageViews' aspect ratios to 1:1 with constraints. That didn't affect anything.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
if indexPath.section == 0 {
cell = tableView.dequeueReusableCell(withIdentifier: "workspace", for: indexPath)
if indexPath.row == addWorkspaceRow {
cell.imageView?.image = .addWorkspace
cell.imageView?.tintColor = cell.tintColor
cell.textLabel?.text = "Add Workspace"
cell.textLabel?.textColor = cell.tintColor
cell.accessoryType = .none
} else {
let workspace = model.workspaces[indexPath.row]
cell.imageView?.image = .workspace
cell.imageView?.tintColor = .label
cell.textLabel?.text = workspace.displayName
cell.textLabel?.textColor = .label
cell.accessoryType = .disclosureIndicator
}
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "archive", for: indexPath)
cell.imageView?.image = .archive
cell.imageView?.tintColor = .archive
cell.textLabel?.text = "Archive"
cell.textLabel?.textColor = .label
cell.accessoryType = .disclosureIndicator
}
cell.textLabel?.font = .preferredFont(forTextStyle: .body)
return cell
}
I meet the same things, and want to solve it without using custom cell. But it is hard to config the constraint without subclass it.
Eventually, I turn to use custom cell, it is simple to use, and in case you may need to add more stuff in your tableView cell, custom cell is the better place to go.
My code, yeah I'm making a music player now;)
class LibraryTableCell: UITableViewCell {
var cellImageView = UIImageView()
var cellLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: "libraryTableCell")
cellImageView.translatesAutoresizingMaskIntoConstraints = false
cellImageView.contentMode = .scaleAspectFit
cellImageView.tintColor = .systemPink
contentView.addSubview(cellImageView)
cellLabel.translatesAutoresizingMaskIntoConstraints = false
cellLabel.font = UIFont.systemFont(ofSize: 20)
contentView.addSubview(cellLabel)
NSLayoutConstraint.activate([
cellImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
cellImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
cellImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
cellImageView.widthAnchor.constraint(equalToConstant: 44),
cellLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
cellLabel.leadingAnchor.constraint(equalTo: cellImageView.trailingAnchor, constant: 10),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class LibraryViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView = UITableView()
let cellTitles = ["Playlists", "Artists", "Albums", "Songs", "Genres"]
let imageNames = ["music.note.list", "music.mic", "square.stack", "music.note", "guitars"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
title = "Library"
navigationController?.navigationBar.prefersLargeTitles = true
tableView.delegate = self
tableView.dataSource = self
tableView.register(LibraryTableCell.self, forCellReuseIdentifier: "libraryTableCell")
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.rowHeight = 48
view.addSubview(tableView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20),
tableView.heightAnchor.constraint(equalTo: g.heightAnchor, multiplier: 0.6)
])
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellTitles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "libraryTableCell", for: indexPath) as? LibraryTableCell else {
fatalError("Unable to dequeue libraryTableCell")
}
cell.accessoryType = .disclosureIndicator
let imageName = imageNames[indexPath.row]
cell.cellImageView.image = UIImage(systemName: imageName)
let title = cellTitles[indexPath.row]
cell.cellLabel.text = title
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.separatorInset = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 8)
}
}
Changing contentMode property of UIImageView will do the trick:
cell.imageView?.contentMode = .scaleAspectFit
It's happening because the images themselves are not the correct dimensions.
Your image in tableviewCell is wrong contentMode. It is reason due to wrong layout of your label.
You have option to fix that problem:
Option1:
open file XIB tableviewCell and change content mode of UIImage
Option 2:
you can set by code:
yourUImage.contentMode = .scaleAspectFit
I searched a long time for solution for the problem you're describing, but sadly I haven't found any. The problem seems to be the native UIImageView in the UITableViewCell. There are essentially two workarounds:
A) Resize all images you use as table view cell icons to a certain width (e.g. 30 and height 44). Draw this space and center the icon (while keeping the original size)
B) Get used to Apple's "You can use our pre-built solution but if you want to change it even a tiny bit you have to re-build everything from the bottom up"-philosophy and just use custom cells for everything you do in the app. With a custom cell, you could simply set the imageView width to a certain value.
I wonder how they solved that problem in the iOS Mail app.
Not able to set properties to custom cell for table view.
Also, not able to assign value to custom cell in cellForRowAt function.
I am using swift 5 and xcode 10.2
import UIKit
class ContainerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let CellId = "CellId"
let tableView : UITableView = {
let tv = UITableView()
tv.allowsSelection = false
tv.separatorStyle = .none
return tv
}()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellId, for: indexPath) as! Cell
//cell.labelView.textColor = .red -- tried to set color here
cell.labelView.text = "Label \(indexPath.row)" // this code doesn't seems to work
return cell
}
}
//- Custom Cell Class
class Cell: UITableViewCell{
let cellView : UIView = {
let view = UIView()
view.backgroundColor = UIColor.red // -- this property doesn't reflect. Also, i want to add shadow to this view any suggestions?
return view
}()
let labelView: UILabel = {
let label = UILabel()
label.textColor = UIColor.black // -- this property doesn't reflect
label.font = UIFont.boldSystemFont(ofSize: 16) // -- this property doesn't reflect
return label
}()
func setUpCell() {
//-- Also tried to set properties here but no luck
//backgroundColor = UIColor.yellow
//labelView.textColor = UIColor.black
addSubview(cellView)
cellView.addSubview(labelView)
//cellView.backgroundColor = .green
}
}
I also want to add constraint to this new custom cell.
try using contentView.addSubview(cellView) rather than addSubview(celliIew)
Since you want to use autolayout constraints, make sure you set cellView. translatesAutoResizingMaskIntoConstraints = false
(Same for the label view)
and then you can start
NSLayoutConstraints.activate([
cellView.topAnchor.constraint(equalTo: contentView.topAnchor),
cellView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
cellView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
cellView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
... Setup label constraints ...
)]