UITapGestureRecognizer with UIImageView in Table View Cell not working Swift - swift

In my Table View Controller I'm requesting data from my backend. Then adding the data to an array with a view model for each row from the request.
let getNotifications = GETNotificationsByUserID(user_id: user_id)
getNotifications.getNotifications { notifications in
self.notifications = notifications.map { notification in
let ret = NotificationViewModel()
ret.mainNotification = notification
return ret
}
class NotificationViewModel {
var mainNotification: Notifications? {}
}
struct Notifications: Codable {
var supporter_picture:String?
}
In the same table view controller, I'm then adding each item from the array to a variable in my table view cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath) as! NotificationCell
cell.user_image.isUserInteractionEnabled = true
let item = self.notifications[indexPath.item]
cell.viewModel = item
return cell
}
Downloading the image from the data from my table view cell variable and setting my UIImageView image.
UITableViewCell
class NotificationCell: UITableViewCell {
var viewModel: NotificationViewModel? {
didSet {
if let item = viewModel {
self.username.text = item.mainNotification?.supporter_username
item.supporterImageDownloader = DownloadImage()
item.supporterImageDownloader?.imageDidSet = { [weak self] image in
self?.user_image.image = image
}
if let picture = item.mainNotification?.supporter_picture {
item.supporterImageDownloader?.downloadImage(urlString: picture)
} else {
self.user_image.image = UIImage(named: "profile-placeholder-user")
}
}
}
}
}
The UIImageView is created from a lazy closure variable. The lazy closure variable also holds a UITapGestureRecognizer.
UITableViewCell
lazy var user_image: UIImageView = {
let image = UIImageView()
let gesture = UITapGestureRecognizer()
gesture.addTarget(self, action: #selector(userImageClicked(_:)))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
image.addGestureRecognizer(gesture)
image.isUserInteractionEnabled = true
return image
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.isUserInteractionEnabled = true
addSubview(user_image)
user_imageContraints()
}
func user_imageContraints() {
user_image.translatesAutoresizingMaskIntoConstraints = false
user_image.topAnchor.constraint(equalTo:topAnchor, constant: 8).isActive = true
user_image.leadingAnchor.constraint(equalTo:leadingAnchor, constant: 8).isActive = true
user_image.heightAnchor.constraint(equalToConstant: 40).isActive = true
user_image.widthAnchor.constraint(equalToConstant: 40).isActive = true
}
#objc func userImageClicked(_ sender: UITapGestureRecognizer) {
print("image clicked")
}
For some reason when my UIImageView is clicked it doesn't do anything.

The problem is this line:
addSubview(user_image)
Change it to:
contentView.addSubview(user_image)
You must never add any views directly to a cell — only to its contentView.

Related

Table View Design overlapping

I am new to swift .I want to display the records with image view in table view cell . I have defined the property with leadingAnchor , trailingAnchor, widthAnchor, heightAnchor with content view . But when I run the app it overlapping the view .
Here is the code in cell .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the result .
This is rather tricky because it seems your constraints are fine, assuming that your tableview height is 100, but the screenshot tableview cells seem a little shorter than 100. Let's assume the cell height is 100 correct.
I suggest you try configuring the imageView (and other views) in override func layoutSubViews(), which is a function that renders whenever the contentView's bound change. It should also be noted that better practice is where the imageSize is relative to the cell/contentView's frame instead of hardcoded values.
So it should look like
import UIKit
class PeopleCell: UITableViewCell {
let profileImageView:UIImageView = {
let img = UIImageView()
return img
}()
override func layoutSubviews() {
super.layoutSubviews()
profileImageView.translatesAutoresizingMaskIntoConstraints = false profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
//You may want to try with other type of contentMode such as aspectFit, etc
profileImageView.contentMode = .scaleAspectFill
profileImageView.layer.cornerRadius = self.frame.width / 2
profileImageView.clipsToBounds = true
}
//If above doesn't work, you may want to look into the imageConfiguration function you made and ensure that contentMode is applied properly.
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
self?.profileImageView.contentMode = .scaleAspectFill
}
}
If all of the above code doesn't work, try to find the profileImageView size values by using breakpoints or ViewHierarchy within Xcode. To check the height of image or cell itself, they should be sufficient for you to find clues to resolve the issue.
All the best.

Duplicating UIViews in StackView when scrolling TableView Swift

Issue: When I scroll the tableView, my configureCell() method appends too many views to the stackView inside the cell.
When the cell is first displayed, and I press the UIButton, the stack is unhidden and first shows the right amount of views, after scrolling, the amount is duplicated.
prepareForReuse() is empty right now. I want to keep the stackView unHidden after scrolling.
I set the heightAnchor for the UIView as it is programmatic layout.
Expected Outcome: User taps on the button, the cell stackView is unhidden and the cell expands to chow the uiviews related to the cell.
When I call it in the cellForRowAt, nothing happens. Because im not sure how to modify the method for IndexPath.row.
protocol DataDelegate: AnyObject {
func displayDataFor(_ cell: TableViewCell)
}
class TableViewCell: UITableViewCell {
#IBOutlet weak var stackView: UIStackView! {
didSet {
stackView.isHidden = true
}
}
#IBOutlet button: UIButton!
var model = Model()
var detailBool: Bool = false
#IBAction func action(_ sender: Any) {
self.claimsDelegate?.displayClaimsFor(self)
detailBool.toggle()
}
func configureCellFrom(model: Model) {
if let addedData = model.addedData {
if addedData.count > 1 {
for data in addedData {
let dataView = DataView()
dataView.heightAnchor.constraint(equalToConstant: 65).isActive = true
dataView.dataNumber.text = data.authDataNumber
self.stackView.addArrangedSubview(dataView)
}
}
}
}
}
How would I call this in cellForRowAt, so its only created the correct amount of uiviews and not constantly adding?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.dataDelegate = self
cell.configureCellFrom(model: model[indexPath.row])
//if let addedData = model.addedData {
//if addedData.count > 1 {
//for data in addedData {
//let dataView = DataView()
//dataView.heightAnchor.constraint(equalToConstant: 65).isActive = true
//dataView.dataNumber.text = data.authDataNumber
//self.stackView.addArrangedSubview(dataView)
// }}}
cell.layoutIfNeeded()
cell.selectionStyle = .none
return cell
}
extension ViewController: DataDelegate {
func displayDataFor(_ cell: TableViewCell) {
if tableView.indexPath(for: cell) != nil {
switch cell.detailBool {
case true:
UIView.performWithoutAnimation {
tableView.beginUpdates()
cell.detailArrow.transform = CGAffineTransform(rotationAngle:.pi)
cell.stackView.isHidden = false
tableView.endUpdates()
}
case false:
UIView.performWithoutAnimation {
tableView.beginUpdates()
cell.stackView.isHidden = true
cell.detailArrow.transform = CGAffineTransform.identity
tableView.endUpdates()
}
}
}
}
}
If I understand correctly, you would like to create an expandable TableView? If yes you can do it a lot of different ways, but you have to change your approach totally. Please refer LBTA approach:
LBTA video
My favourite the Struct approach, where you create a struct and you can save the complication with the 2D array:
Struct stackoverflow link

Add textfiled and Send Button (mail to) in xcode

i'm build a simple app that allows you to reserve information equipment or a seat in a classroom. In my table view i insert an image and some text. How can i add a text field and a button in the bottom that send me a mail with the summary of the booking?
This is the code that i build until now.
TableViewController
import UIKit
struct CellData {
let image : UIImage?
let message : String?
}
class TableViewController: UITableViewController {
var data = [CellData] ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
data = [CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D"),CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D"),CellData.init(image: imageLiteral(resourceName: "printer"), message: "Stampante 3D")]
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "custom")
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 200
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "custom") as! CustomCell
cell.mainImage = data[indexPath.row].image
cell.message = data[indexPath.row].message
cell.layoutSubviews()
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
}
Custom Cell
import Foundation
import UIKit
class CustomCell: UITableViewCell {
var message : String?
var mainImage : UIImage?
var messageView : UITextView = {
var textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = false
return textView
}()
var mainImageView : UIImageView = {
var imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(mainImageView)
self.addSubview(messageView)
mainImageView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
mainImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
mainImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
mainImageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
mainImageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
messageView.leftAnchor.constraint(equalTo: self.mainImageView.rightAnchor).isActive = true
messageView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
messageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
messageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
if let message = message {
messageView.text = message
}
if let image = mainImage{
mainImageView.image = image
} }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Thanks!
I think you are referring to create a chat like window, if this is correct, one way to resolve this is add handlers for the keyboard events, in order to move the top views. In this case you can start with the following ones:
First you need to add some observers to the Notification center to listen when the keyboard is shown or when is hidden.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
Then, you need to create the functions to be triggered when the events occurs. As you can see in the following code, the view frame of the view is modified accordingly to the keyboardSize.
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y -= keyboardSize.height
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.origin.y += keyboardSize.height
}
}
So, just for clarification, you need to create a second view below the table, in which you will add the textfield and the send button.

Implementing UIStackView inside UICollectionViewCell

I am trying to display user ratings on the collectionview as follows with using two images, half.png and full.png.
But even though I hard coded with 1 star to test, but sometimes it shows more than 1 star during the scroll. It keeps increasing.
I wonder how to handle this issue?
CollectionViewCell
import UIKit
class CollectionCell: UICollectionViewCell {
#IBOutlet var ratingView: UIStackView!
var rating : Float!
{
didSet {
self.ratingView.addArrangedSubview(addRating())
}
}
func addRating() -> UIStackView
{
let stackView = UIStackView()
stackView.axis = .horizontal
let oneStarImage = UIImage(named: "full_star")
let halfStarImage = UIImage(named: "half_star")
//hard coded
var value = 1.0
while true {
value -= 1
if value >= 0 {
print("Add 1 star")
let imageView = UIImageView()
imageView.image = oneStarImage
stackView.addArrangedSubview(imageView)
} else if value == -0.5 {
print("Add half a star")
let imageView = UIImageView()
imageView.image = halfStarImage
stackView.addArrangedSubview(imageView)
break
}
else {
break
}
}
return stackView
}
}
CollectionViewController
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! CollectionViewCell
cell.rating = 1.0
return cell
}
As UICollectionViewCell is reused, every time you set the rating, a new view is added to the UIStackView. Therefore, you should reset the UIStackView before adding the images. Also, it maybe unnecessary to create a UIStackView every time you add a rating. You can reuse the ratingView instead. So, the complete code should be:
var rating : Float!
{
didSet {
self.addRating()
}
}
func addRating()
{
// Empty the ratingView first
ratingView.arrangedSubviews.forEach { $0.removeFromSuperview() }
let oneStarImage = UIImage(named: "full_star")
let halfStarImage = UIImage(named: "half_star")
//hard coded
var value = 1.0
while true {
value -= 1
if value >= 0 {
print("Add 1 star")
let imageView = UIImageView()
imageView.image = oneStarImage
ratingView.addArrangedSubview(imageView)
} else if value == -0.5 {
print("Add half a star")
let imageView = UIImageView()
imageView.image = halfStarImage
ratingView.addArrangedSubview(imageView)
break
}
else {
break
}
}
}
And also you can use for reused UICollectionViewCell or UITableViewCell,
override func prepareForReuse() {
super.prepareForReuse()
self.ratingView = UIStackView()
}
override func to prepare cell to reuse. However you should re init your components that you assign values in cell.

Expandable cells in Swift table - cell reuse/dequeue?

I'm building a custom interface for the user to enter preference settings in my app. I'm using expandable rows following an example I found at AppCoda. I've reworked that example to use Swift 3/4 and to use cell information from code rather than read from a plist.
I'm having a problem with the way some cell content appears on the screen. The rows that expand and collapse contain textfields to allow user entry. There are four such rows in the example code below.
When an entry is made in one of those cells, it may or may not cause the last-entered value to appear in all four cells when they are expanded. The 'extra' text will even overwrite the information that belongs there.
I've tried everything I can think of to get rid of this offending text but I'm banging my head against the wall. What am I missing?
FWIW, I am now looking at similar solutions elsewhere. Here's one I like quite a bit:
https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
This one looks interesting but is not in Swift:
https://github.com/singhson/Expandable-Collapsable-TableView
Same comment:
https://github.com/OliverLetterer/SLExpandableTableView
This looks very interesting - well supported - but I haven't had time to investigate:
https://github.com/Augustyniak/RATreeView
A similar request here:
Expand cell when tapped in Swift
A similar problem described here, but I think I'm already doing what is suggested?
http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
Here is my table view controller code. I believe the problem is in the...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
... function, but for the life of me I can't see it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
test = defineCellProps() // This loads my hard-coded cell properties into array "test"
configureTableView()
}
func configureTableView() {
loadCellDescriptors()
tblExpandable.delegate = self
tblExpandable.dataSource = self
tblExpandable.tableFooterView = UIView(frame: CGRect.zero)
tblExpandable.register(UINib(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "idCellNormal")
tblExpandable.register(UINib(nibName: "TextfieldCell", bundle: nil), forCellReuseIdentifier: "idCellTextfield") // There are additional cell types that are not shown and not related to the problem
}
func loadCellDescriptors() { // Puts the data from the "test" array into the format used in the original example
for section in 0..<ACsections.count {
var sectionProps = findDict(matchSection: ACsections[section], dictArray: test)
cellDescriptors.append(sectionProps)
}
cellDescriptors.remove(at: 0) // Removes the empty row
getIndicesOfVisibleRows()
tblExpandable.reloadData() // The table
}
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors { // cellDescriptors is an array of sections, each containing an array of cell dictionaries
var visibleRows = [Int]()
let rowCount = (currentSectionCells as AnyObject).count as! Int
for row in 0..<rowCount { // Each row is a table section, and array of cell dictionaries
var testDict = currentSectionCells[row]
if testDict["isVisible"] as! Bool == true {
visibleRows.append(row)
} // Close the IF
} // Close row loop
visibleRowsPerSection.append(visibleRows)
} // Close section loop
} // end the func
func getCellDescriptorForIndexPath(_ indexPath: IndexPath) -> [String: AnyObject] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptor = (cellDescriptors[indexPath.section])[indexOfVisibleRow]
return cellDescriptor as [String : AnyObject]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
cell.textLabel?.text = nil
cell.detailTextLabel?.text = nil
cell.textField?.placeholder = nil
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
if primaryTitle as! String == "" {
cell.textField.placeholder = currentCellDescriptor["secondaryTitle"] as? String
cell.textLabel?.text = nil
} else {
cell.textField.placeholder = nil
cell.textLabel?.text = primaryTitle as? String
}
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = "some text"
}
cell.detailTextLabel?.text = "some text"
// This next line, when enabled, always puts the correct row number into each cell.
// cell.textLabel?.text = "cell number \(indexPath.row)."
}
cell.delegate = self
return cell
}
Here is the CustomCell code with almost no changes by me:
import UIKit
protocol CustomCellDelegate {
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell)
}
class CustomCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let bigFont = UIFont(name: "Avenir-Book", size: 17.0)
let smallFont = UIFont(name: "Avenir-Light", size: 17.0)
let primaryColor = UIColor.black
let secondaryColor = UIColor.lightGray
var delegate: CustomCellDelegate!
override func awakeFromNib() {
super.awakeFromNib() // Initialization code
if textLabel != nil {
textLabel?.font = bigFont
textLabel?.textColor = primaryColor
}
if detailTextLabel != nil {
detailTextLabel?.font = smallFont
detailTextLabel?.textColor = secondaryColor
}
if textField != nil {
textField.font = bigFont
textField.delegate = self
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() { // I added this and it did not help
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if delegate != nil {
delegate.textfieldTextWasChanged(textField.text!, parentCell: self)
}
return true
}
}
OMG, I'm slapping my palm to my forehead. There is one very important line missing from this code from above:
override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
Can you see what's missing?
textField?.text = nil
That's all it took! I was mucking about with the label but not the textfield text itself.