(Edited with working solution)
So I'm trying to add a double tap gesture to an UIImageView I created in a custom UITableViewCell but can't seem to get it working.
Here is my custom UITableViewCell:
protocol CustomCellDelegate: class {
func didTapImage()
}
class CustomCell: UITableViewCell {
//change let to lazy var
lazy var userImage: UIImageView = {
let newView = UIIMageView()
newView.layer.cornerRadius = 24
newView.layer.masksToBounds = true
newView.image = UIImage(named: "samplePic")
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(myFunc))
doubleTap.numberOfTouchesRequired = 1
doubleTap.numberOfTapsRequired = 2
newView.addGestureRecognizer(doubleTap)
newView.translatesAutoresizingMaskIntoConstraints = false
return newView
}
weak var delegate: CustomCellDelegate?
#objc func myFunc() {
delegate?.didTapImage()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subTitle, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
//changed addSubView(userImage) to self.contentView.addSubView(userImage)
self.contentView.addSubView(userImage)
NSLayoutConstraint.activate([
userImage.centerYAnchor.constraint(equalTo: self.centerYAnchor),
userImage.leftAnchor.constraint(equalTo: self.leftAnchor),
userImage.widthAnchor.constraint(equalToConstant: 48),
userImage.heightAnchor.constraint(equalToConstant: 48),
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Here is my custom UITableViewController:
class customTableViewController: UITableViewController, CustomCellDelegate {
fileprivate let cellId = "cellId"
func didTapImage() {
print("Tapped Image")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: cellId)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 72
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomCell
cell.delegate = self
return cell
}
}
Any ideas as to why this isn't working? What am I doing wrong? Also how do I avoid having the same tap gestures recognizer added multiple times as cells are dequeue?
You may need
userImage.translatesAutoresizingMaskIntoConstraints = false
as you create constraints programmatically
lazy var userImage: UIImageView = {
let newView = UIIMageView()
userImage.translatesAutoresizingMaskIntoConstraints = false
newView.layer.cornerRadius = 24
newView.layer.masksToBounds = true
newView.image = UIImage(named: "samplePic")
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(myFunc))
doubleTap.numberOfTouchesRequired = 1
doubleTap.numberOfTapsRequired = 2
newView.addGestureRecognizer(doubleTap)
return newView
}()
also make it a lazy var not a computed property for being 1 instance every access , add the imageView to
self.contentView.addSubView(userImage)
and set the constraints with it
Related
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)
}
}
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.
Programmatically I created a custom UITableViewCell and tried centering two UILabels vertically inside it. But the UILabel ended up being squished. Doing the same thing in Interface Builder with a prototype cell works well. What is wrong with my code?
The Custom view cell class
import UIKit
class TopViewCell: UITableViewCell {
let df: DateFormatter = {
let df = DateFormatter()
df.dateFormat = NSLocalizedString("DATE_WEEKDAY", comment: "show date and weekday")
return df
}()
var dateLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var costLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let margin = contentView.layoutMarginsGuide
contentView.addSubview(dateLabel)
dateLabel.leadingAnchor.constraint(equalTo: margin.leadingAnchor).isActive = true
dateLabel.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
dateLabel.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
contentView.addSubview(costLabel)
costLabel.trailingAnchor.constraint(equalTo: margin.trailingAnchor).isActive = true
costLabel.topAnchor.constraint(equalTo: dateLabel.topAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
dateLabel.text = df.string(from: Date())
costLabel.text = "total: five thousand"
}
}
The Custom UITableViewController class
import UIKit
class ItemViewController: UITableViewController {
var itemStore: ItemStore!
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TopViewCell.self, forCellReuseIdentifier: "top_cell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count + 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if indexPath.row == 0 {
cell = tableView.dequeueReusableCell(withIdentifier: "top_cell", for: indexPath)
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = itemStore.allItems[indexPath.row - 1].name
cell.textLabel?.font = cell.textLabel!.font.withSize(30)
cell.detailTextLabel?.text = "$\(itemStore.allItems[indexPath.row - 1].valueInDolloar)"
}
return cell
}
}
Your TopViewCell is not auto-sizing correctly because you're setting the text in layoutSubviews(). Move those two lines to init and it will size properly:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let margin = contentView.layoutMarginsGuide
contentView.addSubview(dateLabel)
dateLabel.leadingAnchor.constraint(equalTo: margin.leadingAnchor).isActive = true
dateLabel.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
dateLabel.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
contentView.addSubview(costLabel)
costLabel.trailingAnchor.constraint(equalTo: margin.trailingAnchor).isActive = true
costLabel.topAnchor.constraint(equalTo: dateLabel.topAnchor).isActive = true
// set the text here
dateLabel.text = df.string(from: Date())
costLabel.text = "total: five thousand"
}
As a side note, you should specify the class when you use TopViewCell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "top_cell", for: indexPath) as! TopViewCell
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = itemStore.allItems[indexPath.row - 1].name
cell.textLabel?.font = cell.textLabel!.font.withSize(30)
cell.detailTextLabel?.text = "$\(itemStore.allItems[indexPath.row - 1].valueInDolloar)"
return cell
}
As another side note... you can create two prototype cells in your Storyboard.
I have a custom uitableviewcell in app, I am trying to update its length dynamically based on its subviews (labels) contents.
but it's not working.
find related code as below.
class TransactionTableViewCell: UITableViewCell{
private lazy var dateLabel: UILabel = {
let datelabel = UILabel()
datelabel.textColor = .label
datelabel.backgroundColor = .red
datelabel.lineBreakMode = .byTruncatingTail
datelabel.numberOfLines = 0
datelabel.translatesAutoresizingMaskIntoConstraints = false
return datelabel
}()
another method in same class:
func setupDefaultUI(){
self.contentView.addSubview(dateLabel)
}
override func awakeFromNib() {
super.awakeFromNib()
setupDefaultUI()
buildConstraints()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupDefaultUI()
buildConstraints()
}
func buildConstraints(){
let marginGuide = self.contentView
NSLayoutConstraint.activate([
dateLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor)
,
dateLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor),
dateLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor),
dateLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 1.0)
,
dateLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor)
])
}
and inside viewcontroller file:
private lazy var transactionTableView: UITableView = {
let tableview = UITableView.init()
tableview.backgroundView = nil
tableview.backgroundColor = .clear
tableview.rowHeight = UITableView.automaticDimension
tableview.estimatedRowHeight = 300
return tableview
}()
and in viewdidload:
transactionTableView.dataSource = Objdatasource
transactionTableView.delegate = Objdelegate
transactionTableView.register(TransactionTableViewCell.self, forCellReuseIdentifier: CellIdentifiers.transactionCell)
Remove this
dateLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 1.0)
In this tutorial (which I recently followed and worked for me), there is a function you are missing there:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
I made a UITextfield to receive the data from user.
I want to convert a value from UITextField to UILabel.
I did it in simple UIView, which has only two object, UITextField and UILabel.
This is the code that works.
class ViewController: UIViewController, UITextFieldDelegate {
let inputNumber = UITextField(frame: CGRect(x: 150.0, y: 100.0, width: 200.0, height: 50.0))
let outputNumber = UILabel(frame: CGRect(x: 150.0, y: 200.0, width: 200.0, height: 50.0))
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressed))
var result : String!
override func viewDidLoad() {
super.viewDidLoad()
calculatePrice()
}
func calculatePrice () {
priceInputLabel.keyboardType = .numberPad
priceInputLabel.clearButtonMode = .whileEditing
self.view.addSubview(priceInputLabel)
toolBarKeyBoard.sizeToFit()
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
priceInputLabel.inputAccessoryView = toolBarKeyBoard
}
#objc func donePressed() {
view.endEditing(true)
result = inputNumber.text!
let convertedNumber = (result as NSString).doubleValue
if Int(inputNumber.text!) == nil {
outputNumber.text = String("Nil")
} else {
outputNumber.text = String(Int(convertedNumber * 0.85))
}
}
}
But in other case, down below, the problem is UITextField and UILabel are in the UITableViewCell as subviews.
I made a 3 swift files. 2 files are UITableViewCell subclasses, and 1 file is a UITableView class.
1. FruitTableViewCell : UITableViewCell subclass
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressed))
var result : String!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 250, y: 7.5, width: 100, height: 30)
fruitsTextField.textColor = UIColor(red: CGFloat(242/255.0), green: CGFloat(56/255.0), blue: CGFloat(90/255.0), alpha: 1.0)
fruitsTextField.keyboardType = .numberPad
fruitsTextField.clearButtonMode = .whileEditing
toolBarKeyBoard.sizeToFit()
fruitsTextField.inputAccessoryView = toolBarKeyBoard
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
}
#objc func donePressed() {
fruitTextField.endEditing(true)
}
}
2. AnotherFruitTableViewCell : UITableViewCell subclass
class AnotherFruitTableViewCell: UITableViewCell {
var fruitsTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextLabel.backgroundColor = UIColor.brown
fruitsTextLabel.frame = CGRect(x: 250.0, y: 7.5, width: 100.0, height: 30.0)
}
}
3. TableViewController : UITableViewController class
class TableViewController: UITableViewController, UITextFieldDelegate {
let fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
let cellReuseidentifier = "cell"
let anotherCellReuseidentifier = "anotherCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
tableView.register(AnotherFruitTableViewCell.self, forCellReuseIdentifier: anotherCellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
}
The fruitsTextField and fruitsTextLabel is not in the same class like in the first example code.
So, I cannot call both instances and calculate a value in ViewController class. Of course, cannot return a calculated value.
And, I'm not sure I can return after touching a done button to get a value from UITextField to UILabel, because the cells which is the super view of subview(UITextField and UILabel) are reproduced. I'm confusing touching a done button occurs dequeueing cells again.
How can I return a value from UITextField to UILabel in UITableViewCell?
Thanks!
If I understand correctly you want to change some parameter of one cell based on action in another cell (actually the fact that these cells are of different classes is not important to that matter). ViewController will be in that case intermediary, so you need to make cells to communicate with ViewController. For communication between two objects one usually uses delegate or closure pattern.
In that case I would use closure. So, when you instantiate the cell with TextField ViewController tells Cell what to do when Done is pressed. To achieve that:
add var didEntered: ((_ text: String)->())? to FruitTableViewCell
add didEntered?(fruitsTextField.text ?? "") to #objc func donePressed()
add (the code updates the second row based on textfield value - just for a example)
cell.didEntered = {text in
self.fruitsComponents[1] = text
self.tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: UITableViewRowAnimation.none)}
to override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
On the way I corrected some mistakes to make it work, so the complete code is below.
Upd 1
import UIKit
class ViewController: UITableViewController, UITextFieldDelegate {
var fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
var fruitsLabels: [String] = ["", "", "", ""]
let cellReuseidentifier = "cell"
let anotherCellReuseidentifier = "anotherCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
tableView.register(AnotherFruitTableViewCell.self, forCellReuseIdentifier: anotherCellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
cell.didEntered = {text in
self.fruitsLabels = Array(repeating: text, count: 4)
self.tableView.reloadData()
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
cell.fruitsTextLabel.text = fruitsLabels[indexPath.row]
return cell
}
}
}
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
var result : String!
var didEntered: ((_ text: String)->())?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 250, y: 7.5, width: 100, height: 30)
fruitsTextField.backgroundColor = .yellow
fruitsTextField.textColor = UIColor(red: CGFloat(242/255.0), green: CGFloat(56/255.0), blue: CGFloat(90/255.0), alpha: 1.0)
fruitsTextField.keyboardType = .numberPad
fruitsTextField.clearButtonMode = .whileEditing
toolBarKeyBoard.sizeToFit()
fruitsTextField.inputAccessoryView = toolBarKeyBoard
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
}
#objc func donePressed() {
fruitsTextField.endEditing(true)
didEntered?(fruitsTextField.text ?? "")
}
}
class AnotherFruitTableViewCell: UITableViewCell {
var fruitsTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextLabel.backgroundColor = UIColor.brown
fruitsTextLabel.frame = CGRect(x: 250.0, y: 7.5, width: 100.0, height: 30.0)
}
}
You can use UITextField delegate methods to achieve this in your TableViewController.
In your TableViewController cellForRowAt method:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row] //"I am confused why it is here"
cell.fruitsTextField.delegate = self
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
Now add UITextField Delegate methods:
extension TableViewController : UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
print(textField.text!)
let cell = self.tableView.cellForRow(at: IndexPath(row: 1, section: 0)) as! AnotherFruitTableViewCell
cell.textLabel.text = textField.text!
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(true)
return true
}
}
Hope this helps.