Swift Table View Cell Value Failed to display vertically - ios

I am new to swift . I am creating table view cell programatically with the sub title . I defined the two label and two function to get the value form API and display it into label control but the problem is the values of label are displayed into correct position . I want to display the title and then below the title is sub title values . Here is the screen shot .
Here is the code table view cell.
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.rovers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: StoryCell.identifier , for: indexPath) as? StoryCell
else{ return UITableViewCell()}
let row = indexPath.row
let title = viewModel.getTitle(by: row)
cell.configureCell(title:title)
let Id = viewModel.getId(by: row)
cell.configureCell(Id: Id)
return cell
}
}
Here is code for configure the label .
import UIKit
class StoryCell: UITableViewCell {
static let identifier = "StoryCell"
private lazy var storyTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
private lazy var storyIdLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpUI()
setUpUIID()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(title: String) {
storyTitleLabel.text = "Status :\(title)"
}
func configureCell(Id: Int) {
storyIdLabel.text = "Id: \(String(Id))"
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
private func setUpUI() {
contentView.addSubview(storyTitleLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyTitleLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyTitleLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
storyTitleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyTitleLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}
private func setUpUIID() {
contentView.addSubview(storyIdLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyIdLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyIdLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
storyIdLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyIdLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}
}

I made some changes in your code check below. Now it should be fine.
private func setUpUI() {
contentView.addSubview(storyTitleLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyTitleLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyTitleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyTitleLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}
private func setUpUIID() {
contentView.addSubview(storyIdLabel)
let safeArea = contentView.safeAreaLayoutGuide
storyIdLabel.topAnchor.constraint(equalTo: storyTitleLabel.topAnchor).constant = 5
storyIdLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
storyIdLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyIdLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
}

Related

UITableView CustomCell Reuse (ImageView in CustomCell)

I'm pretty new to iOS dev and I have an issue with UITableViewCell.
I guess it is related to dequeuing reusable cell.
I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse). The problem is when I scroll down, some of the cells are automatically tapped. I found out why this is happening, but still don't know how to fix it appropriately.
Below are my codes,
ViewController
import UIKit
struct CellData {
var title: String
var done: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.textLabel?.text = model.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
}
}
TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"
let likeImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(likeImage)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImage.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
if likeImage.image == UIImage(systemName: "heart.fill"){
likeImage.image = UIImage(systemName: "heart")
likeImage.tintColor = .darkGray
} else {
likeImage.image = UIImage(systemName: "heart.fill")
likeImage.tintColor = .systemRed
}
}
}
This gif shows how it works now.
enter image description here
I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).
I would be so happy if anyone can help this!
You've already figured out that the problem is cell reuse.
When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:
cell.textLabel?.text = model.title
you also need to tell the cell whether to show the empty or filled heart image.
And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.
That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure.
Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:
struct CellData {
var title: String
var done: Bool
}
class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
let model = models[indexPath.row]
cell.myLabel.text = model.title
// set the "heart" to true/false
cell.isLiked = model.done
// closure
cell.callback = { [weak self] theCell, isLiked in
guard let self = self,
let pth = self.tableView.indexPath(for: theCell)
else { return }
// update our data
self.models[pth.row].done = isLiked
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class ShinTableViewCell: UITableViewCell {
// we'll use this closure to communicate with the controller
var callback: ((UITableViewCell, Bool) -> ())?
static let identifier = "TableViewCell"
let likeImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// we'll load the heart images once in init
// instead of loading them every time they change
var likeIMG: UIImage!
var unlikeIMG: UIImage!
var isLiked: Bool = false {
didSet {
// update the image in the image view
likeImageView.image = isLiked ? likeIMG : unlikeIMG
// update the tint
likeImageView.tintColor = isLiked ? .systemRed : .darkGray
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// make sure we load the heart images
guard let img1 = UIImage(systemName: "heart"),
let img2 = UIImage(systemName: "heart.fill")
else {
fatalError("Could not load the heart images!!!")
}
unlikeIMG = img1
likeIMG = img2
// add label and image view
contentView.addSubview(myLabel)
contentView.addSubview(likeImageView)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImageView.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
// let's use the "built-in" margins guide
let g = contentView.layoutMarginsGuide
// image view bottom constraint
let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
// this will avoid auto-layout complaints
bottomConstraint.priority = .required - 1
NSLayoutConstraint.activate([
// constrain label leading
myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// center the label vertically
myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view trailing
likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain image view to 30 x 30
likeImageView.widthAnchor.constraint(equalToConstant: 30),
likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
// constrain image view top
likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
// activate image view bottom constraint
bottomConstraint,
])
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
// toggle isLiked (true/false)
isLiked.toggle()
// inform the controller, so it can update the data
callback?(self, isLiked)
}
}

Swift UISwitch Button with Label

I am receiving data from API . The data field is displayed with two Label control, Id and status . Here is the screenshot .
I have another view where I created switch button and label programatically. I want to change the status to false when user turn on to switch button but it now updating the value . Here is the code and function i defined .
class DetailsViewController : UIViewController{
#IBOutlet private weak var tableView: UITableView!
var changeStatus: ((Bool, String) -> Void)?
var identifier = ""
private let switchControl: UISwitch = {
let switchControl = UISwitch()
switchControl.translatesAutoresizingMaskIntoConstraints = false
switchControl.isOn = false
return switchControl
}()
#IBOutlet weak var customSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(switchControl)
switchControl.addTarget(self, action: #selector(changeSwitchControl), for: .valueChanged)
let safeArea = view.safeAreaLayoutGuide
switchControl.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
switchControl.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
setUpUI()
}
#objc
private func changeSwitchControl() {
changeStatus?(switchControl.isOn, identifier)
}
}
Here is code in View Controller .
class ViewController: UIViewController {
// ! Mark is means it not null the value will in story board
// use lazy property to tell compiler instance value of the datasource self class and execuate the controller code to set the value of tableview datasource
private let viewModel = ViewModel()
private var subcriber = Set<AnyCancellable>()
private var storiesTrue = [String]()
#Published private(set) var stories = [Rover]()
private var datasourceStories = [Rover]()
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false// adding constrains
tableview.dataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(StoryCell.self, forCellReuseIdentifier: StoryCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
setUpBinding()
tableView.dataSource = self
tableView.delegate = self
self.tableView.rowHeight = 44;
// display the second view controller using push methods
/*let detail = DetailsViewController()
detail.name "Mohammad"
navigationController?.pushViewController(detail, animated: true)*/
}
private func setUpUI() {
view.backgroundColor = .white
view.addSubview(tableView)// adding hierracy key like adding into story board
// creating constrains with safe area
let safeArea = view.safeAreaLayoutGuide
tableView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
}
private func setUpBinding(){
viewModel
.$rovers
.receive(on : RunLoop.main)
.sink {[weak self]_ in
self?.tableView.reloadData()
}
.store(in: &subcriber)
viewModel.getStories()
}
private func getStatus(by identifier: String) -> Bool {
return storiesTrue.contains(identifier)
}
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.rovers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: StoryCell.identifier , for: indexPath) as? StoryCell
else{ return UITableViewCell()}
let row = indexPath.row
let Id = viewModel.getId(by: row)
let title = viewModel.getTitle(by: row)
let identifier = viewModel.getIdentifier(by: indexPath.row)
let status = getStatus(by: identifier) ? "active" : "false"
cell.configureCell(Id: Id,title: title,statusString: status)
return cell
}
}
extension ViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getStories()
}
}
extension ViewController : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let second = DetailsViewController()
navigationController?.pushViewController(second, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44;
}
}
Here is the view controller to defined the UI properties .
class StoryCell: UITableViewCell {
static let identifier = "StoryCell"
private lazy var storyIdLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
public lazy var storyTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
private lazy var statusStory: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(Id: Int,title: String,statusString: String) {
storyIdLabel.text = "Id: \(String(Id))"
storyTitleLabel.text = "Status :\(title)"
statusStory.text = "Status:\(statusString)"
}
/* func configureCell(Id: Int) {
storyIdLabel.text = "Id: \(String(Id))"
}*/
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
private func setUpUI() {
contentView.addSubview(storyTitleLabel)
// constraints
let safeArea = contentView.safeAreaLayoutGuide
storyTitleLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
storyTitleLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
storyTitleLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
contentView.addSubview(storyIdLabel)
let safeArea1 = contentView.safeAreaLayoutGuide
storyIdLabel.topAnchor.constraint(equalTo: storyTitleLabel.topAnchor).constant = 5
storyIdLabel.bottomAnchor.constraint(equalTo: safeArea1.bottomAnchor).isActive = true
storyIdLabel.leadingAnchor.constraint(equalTo: safeArea1.leadingAnchor).isActive = true
storyIdLabel.trailingAnchor.constraint(equalTo: safeArea1.trailingAnchor).isActive = true
/* contentView.addSubview(statusStory)
let safeArea2 = contentView.safeAreaLayoutGuide
statusStory.topAnchor.constraint(equalTo: statusStory.topAnchor).isActive = true
statusStory.bottomAnchor.constraint(equalTo: safeArea2.bottomAnchor).isActive = true
statusStory.leadingAnchor.constraint(equalTo: safeArea2.leadingAnchor).isActive = true
statusStory.trailingAnchor.constraint(equalTo: safeArea2.trailingAnchor).isActive = true*/
}
}
You have to use tableView.reloadData() after updating info that you want to show. Without running this function, data in table view won't be updated.

UITableView Custom Cell Height Not Working

I'm learning how to use UIKit programmatically, and I created a custom UITableViewCell, but my row height isn't registering.
I keep getting this error
[Warning] Warning once only: Detected a case where constraints ambiguously
suggest a height of zero for a table view cell's content view.
We're considering the collapse unintentional and using standard height instead.
I can't seem to understand/find where the issue with my code is, even after googling the error.
ViewController:
import UIKit
class ViewController: UIViewController {
let contacts = ContactAPI.getContacts()
let tableView = UITableView()
var safeArea: UILayoutGuide!
//let contentView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
safeArea = view.layoutMarginsGuide
setUpTable()
setUpNavigation()
}
func setUpTable(){
view.addSubview(tableView)
//populate with data
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "cell")
//turn off autoresizing
tableView.translatesAutoresizingMaskIntoConstraints = false
//Layout Configs
tableView.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func setUpNavigation(){
navigationItem.title = "Contacts"
self.navigationController?.navigationBar.barTintColor = .gray
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
}
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
//cell.textLabel?.text = contacts[indexPath.row].name
cell.contact = contacts[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
}
}
CustomCell:
class TableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImage)
subView.addSubview(nameLabel)
subView.addSubview(jobTitleLabel)
self.contentView.addSubview(subView)
self.contentView.addSubview(flagImage)
profileImage.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImage.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImage.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImage.heightAnchor.constraint(equalToConstant:70).isActive = true
subView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
subView.leadingAnchor.constraint(equalTo:self.profileImage.trailingAnchor, constant:10).isActive = true
subView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
subView.heightAnchor.constraint(equalToConstant:40).isActive = true
nameLabel.topAnchor.constraint(equalTo:self.subView.topAnchor).isActive = true
nameLabel.leadingAnchor.constraint(equalTo:self.subView.leadingAnchor).isActive = true
nameLabel.trailingAnchor.constraint(equalTo:self.subView.trailingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.nameLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.subView.leadingAnchor).isActive = true
jobTitleLabel.topAnchor.constraint(equalTo:self.nameLabel.bottomAnchor).isActive = true
jobTitleLabel.leadingAnchor.constraint(equalTo:self.subView.leadingAnchor).isActive = true
flagImage.widthAnchor.constraint(equalToConstant:26).isActive = true
flagImage.heightAnchor.constraint(equalToConstant:26).isActive = true
flagImage.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-20).isActive = true
flagImage.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
//Profile Image
let profileImage: UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill
img.translatesAutoresizingMaskIntoConstraints = false
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
//Name
let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .gray
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
//Job Title
let jobTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = .gray
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
//Container for Name and Job Title
let subView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true
return view
}()
//Country Flag
let flagImage: UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill
img.translatesAutoresizingMaskIntoConstraints = false
img.layer.cornerRadius = 13
img.clipsToBounds = true
return img
}()
var contact:Contact? {
didSet{
guard let contactItem = contact else {return}
if let name = contactItem.name{
profileImage.image = UIImage(named: name)
nameLabel.text = name
}
if let jobTitle = contactItem.jobTitle{
jobTitleLabel.text = "\(jobTitle)"
}
if let country = contactItem.country{
flagImage.image = UIImage(named: country)
}
}
}
}
You haven't implemented the UITableViewDelegate
In your setupTable() add the line
tableView.delegate = self
Then change your extension from
extension ViewController: UITableViewDataSource {
to extension ViewController: UITableViewDataSource, UITableViewDelegate {

How to add subview to stackview in table view cell?

I am in need to add the label as subview to UIStackView in table view cell.
I have created label as
let nameLabel=UILabel()
nameLabel.text=names[indexPath.row]
Where name is an array which is a type of String
My code is
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource
{
let names=["Amutha","Priya","Amuthapriya","Priyasri","Kavisha"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell
{
let cell=tableView.dequeueReusableCell(withIdentifier: "cell") as! ViewCell
for name in names
{
let nameLabel=UILabel()
nameLabel.text=name
cell.nameStackView!.addSubview(nameLabel)
}
return cell
}
}
Why I am getting a null pointer exception when I add a label to stackview?
Any help will be much appreciated.
Change
cell.nameStackView!.addSubview(nameLabel)
To
cell.nameStackView!.addArrangedSubview(nameLabel)
You can use below code to add UIStackView as per your need.
let titleLabel = UILabel()
let subtitleLabel = UILabel()
lazy var titleStackView: UIStackView = {
titleLabel.textAlignment = .center
titleLabel.text = "Good Morning"
titleLabel.textColor = UIColor.white
titleLabel.font = UIFont(name: "ProximaNova-Regular", size: 12.0)
subtitleLabel.textAlignment = .center
subtitleLabel.text = "--"
subtitleLabel.textColor = UIColor.white
subtitleLabel.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
subtitleLabel.font = UIFont(name: "DublinSpurs", size: 20.0)
let stackView = UIStackView(arrangedSubviews: [subtitleLabel])
stackView.axis = .vertical/.horizontal
return stackView
}()
Also try replacing
for name in names
{
let nameLabel=UILabel()
nameLabel.text=name
cell.nameStackView!.addSubview(nameLabel)
}
with
let nameLabel=UILabel()
nameLabel.text = names[indexPath.row]
cell.nameStackView!.addSubview(nameLabel)
If you have nameStackView in storyboard or xib, make sure the IBOutlet is connected properly. If you have created the nameStackView programmatically make sure it is initialised.
And remove all existing labels from the stackview, else you'll have duplicate labels on each scroll
class ViewCell: UITableViewCell {
//Make sure IBOutlet is connected properly
#IBOutlet weak var nameStackView: UIStackView!
}
ViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! ViewCell
for name in names
{
cell.nameStackView.arrangedSubviews.forEach { cell.nameStackView.removeArrangedSubview($0) }
let nameLabel=UILabel()
nameLabel.text=name
cell.nameStackView.addSubview(nameLabel)
}
return cell
}
Try below code
let nameLabel = UILabel()
cell.nameStackView.addArrangedSubview(nameLabel)
Try initializing the stackview and label in your ViewCell class.
class ViewCell: UITableViewCell {
let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
return label
}()
let nameStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
return stackView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super .init(style: style, reuseIdentifier: reuseIdentifier)
self.configureStackView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureStackView()
{
addSubview(nameStackView)
nameStackView.translatesAutoresizingMaskIntoConstraints = false
nameStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
nameStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
nameStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
nameStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
nameStackView.addArrangedSubview(nameLabel)
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.topAnchor.constraint(equalTo: nameStackView.topAnchor,
constant:10).isActive = true
nameLabel.leadingAnchor.constraint(equalTo: nameStackView.leadingAnchor,
constant:10).isActive = true
nameLabel.trailingAnchor.constraint(equalTo: nameStackView.trailingAnchor,
constant:-10).isActive = true
nameLabel.bottomAnchor.constraint(equalTo: nameStackView.bottomAnchor,
constant:-10).isActive = true
}
}
This code is in case you didn't create your stackview in storyboard.
Now in your ViewController class, add the following code in your cellForRowAt method.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell
{
let cell=tableView.dequeueReusableCell(withIdentifier: "cell") as! ViewCell
cell.nameLabel.text = names[indexPath.row]
return cell
}
Hope, this solution works for you.

UITableViewCell delegate not working

Conditions:
Swift 4, Xcode 9.3
Target: iOS 11.3
UI Done Programatically
Using Constraints
Episode is an object that holds the source as a String
Situation:
Here is my custom cell: EpisodeCell.swift
import UIKit
protocol EpisodeCellDelegate {
func didTapPlayButton(url: String)
}
class EpisodeCell: UITableViewCell {
var delegate: EpisodeCellDelegate?
var episode: Episode!
let cellView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.init(hex: "#EBE4D3")
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let episodeTitle: UILabel = {
let label = UILabel()
label.textColor = .darkGray
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let playButton: UIButton = {
let btn = UIButton.init(type: .custom)
btn.setTitle("PLAY", for: .normal)
btn.setTitleColor(.gray, for: .normal)
btn.isUserInteractionEnabled = true
btn.addTarget(self, action: #selector(playPressed), for: .touchUpInside)
return btn
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
private func setup(){
self.accessoryType = .none
self.addSubview(cellView)
cellView.addSubview(episodeTitle)
cellView.addSubview(playButton)
cellView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
cellView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
cellView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
cellView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
episodeTitle.topAnchor.constraint(equalTo: cellView.topAnchor, constant: 10).isActive = true
episodeTitle.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 10).isActive = true
episodeTitle.trailingAnchor.constraint(equalTo: cellView.trailingAnchor).isActive = true
episodeTitle.centerYAnchor.constraint(equalTo: cellView.centerYAnchor).isActive = true
playButton.translatesAutoresizingMaskIntoConstraints = false
playButton.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: -10).isActive = true
playButton.centerYAnchor.constraint(equalTo: cellView.centerYAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("initCoder has not been implemented")
}
#objc func playPressed() {
self.delegate?.didTapPlayButton(url: episode.source)
}
}
And here is how I implemented on my View Controller with the tableview: EpisodesViewController.swift
extension EpisodesViewController: EpisodeCellDelegate {
func didTapPlayButton(url: String) {
print("WOAH: \(url)")
}
}
extension EpisodesViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "episodeCell") as! EpisodeCell
cell.episode = series.episodes![indexPath.row]
cell.episodeTitle.text = ep.episodeName
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (series.episodes?.count)!
}
}
Problem:
I'm having difficulty in working the button to be tapped in a custom table view cell. My tableview conforms to the protocol but it doesn't work.
You should do lazy initialisation for control inside the tableviewcell. Below code does the magic for me. Just change the below part of the code alone
lazy var cellView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var episodeTitle: UILabel = {
let label = UILabel()
label.textColor = .darkGray
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
lazy var playButton: UIButton = {
let btn = UIButton.init(type: .custom)
btn.setTitle("PLAY", for: .normal)
btn.setTitleColor(.gray, for: .normal)
btn.isUserInteractionEnabled = true
btn.addTarget(self, action: #selector(playPressed), for: .touchUpInside)
return btn
}()
You shouldn't have the action that a cell performs inside the cell. Cells can be reused, and a cell that was in row 1 can end up in row 4 at any time. Instead of your playPressed() routine, use the traditional didSelectRowAtIndexPath call, which uses the IndexPath, not the cell itself, to determine the action to take.

Resources