UITableView cellForRowAtIndexPath function not updating after reload function - swift

I am trying to reload data in my tableview after getting the data from a delegate method. But the issue is that all the data is not coming through to the tableView function cellForRowAtIndexPath. I have set the variable self.restNames to hold the values from the delegate method which comes around 4 values but not all of them show up in the function cellForRowAtIndexPath. When I change the tabs and go back to the tableView, some data does come through but not all of it.Will appreciate any help on this. Apologies if I have missed something, I am new to Swift and haven't raised much questions in StackOverflow.
Regards,
Saurabh
Below is my the code for the tableViewController
class RestTableViewController: UITableViewController,getDistanceTime {
var RestTable = [Restaurant]()
let label = UILabel()
let menuUrl = "menu url"
let listProjectUrl = "url"
override func viewDidLoad() {
super.viewDidLoad()
let headerView = self.tableView
headerView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
headerView.tableHeaderView?.backgroundColor = UIColor.redColor()
self.tableView.tableHeaderView = self.tableView.tableHeaderView
self.tableView.tableHeaderView?.frame = CGRectMake(0, 30, self.view.frame.width, 40)
let token = keychain[""]
let menuData = Alamofire.request(Method.GET, self.menuUrl, headers: ["Authorization":"JWT \(token!)"])
menuData.responseJSON{ response in
let data = JSON(response.result.value!)
var localmenu = [menu]()
for (_,item) in data{
let menuOne = menu(place: item["place"].string!, types: item["types"].string!, name: item["name"].string!, price: item["price"].string!)
localmenu.append(menuOne)
}
self.menus = localmenu
}
}
// Delegate method
func loadWithDisTime(distance: String,name: String)
{
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.userDistanceFromLocation = distance
//This will hold 4 values
self.restNames = name
self.tableView.reloadData()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.getDistance = getDistanceTimeVC()
self.getDistance.delegate = self
let restCount = self.RestTable.count
if restCount == 0
{
let alertView = UIAlertController(title: "There are no restaurants near your location", message: "Press Okay to go back", preferredStyle: UIAlertControllerStyle.Alert)
let alertAction = UIAlertAction(title: "Okay", style: UIAlertActionStyle.Cancel, handler: nil)
alertView.addAction(alertAction)
self.presentViewController(alertView, animated: true, completion: nil)
}
else
{
for item in 0...restCount - 1
{
self.restLat = self.RestTable[item].lat
self.restLng = self.RestTable[item].lng
self.getDistance.getDistance(self.userLat, userLng: self.userLng, restLat: self.restLat, restLng: self.restLng,restName: self.RestTable[item].name)
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.RestTable.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! RestTableViewCell
//Not all values coming through here
print(self.restNames)
if self.RestTable[indexPath.row].name == self.restNames
{
cell.textLabel?.text = "\(indexPath.row + 1) - \(self.RestTable[indexPath.row].name) - \(self.userDistanceFromLocation)"
cell.backgroundColor = UIColor.whiteColor()
cell.textLabel?.textColor = UIColor.blackColor()
cell.textLabel?.font = UIFont(name: "Avenir-Heavy", size: 15)
}
return cell
}

In this block leave only reload table view
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.tableView.reloadData()
}

Related

How to deselect all buttons when one of them is selected

I have a tableview and inside one of my rows I have buttons. I want to deselect all buttons when one is selected and change the background color to the red when button is selected. I saw lots of example but I couldn't do it in my own code.
func configure(_ modelArray : [UnitAndColors], colorTitle:String, packingTitle:String) {
self.unitStackView.removeAllArrangedSubviews()
self.arrayUnitsAndColor?.removeAll()
self.arrayUnitsAndColor = modelArray
let filteredArray = self.arrayUnitsAndColor?.unique { $0.colorDesc }
var i : Int = 0
filteredArray?.forEach { units in
let vw = self.createUnits(units, tag: i)
self.unitStackView.addArrangedSubview(vw)
vw.snp.makeConstraints {
$0.size.equalTo(40.0)
}
i = i + 1
}
}
func createUnits(_ model : UnitAndColors, tag: Int) -> UIStackView {
let stackViewMain = UIStackView()
stackViewMain.axis = .horizontal
stackViewMain.spacing = 4
let labelPackingUnit = UIButton()
labelPackingUnit.backgroundColor = Colors().colorWhite
labelPackingUnit.tag = tag
labelPackingUnit.setTitleColor(Colors().colorRed, for: .normal)
labelPackingUnit.addTarget(self, action: #selector(selectUnit(_:)), for: .touchUpInside)
labelPackingUnit.titleLabel?.font = UIFont.fontBold16
labelPackingUnit.contentHorizontalAlignment = .center
labelPackingUnit.setBorder(width: 1, color: Colors().colorRed)
labelPackingUnit.setCornerRound(value: 20.0)
labelPackingUnit.setTitle(model.unitDesc, for: .normal)
stackViewMain.addArrangedSubview(labelPackingUnit)
labelPackingUnit.snp.makeConstraints {
$0.size.equalTo(40)
}
return stackViewMain
}
#objc private func selectButton(_ sender : UIButton) {
let tag : Int = sender.tag
guard let model : UnitAndColors = self.arrayUnitsAndColor?.filter({ $0.colorDesc == selectedColorName })[tag] else { return }
selectedUnit = model.unitDesc ?? ""
delegate?.changePrice(selectedPrice: model.modelPrice, arrayUnitsAndColor ?? [])
}
It's pretty frustrating to understand what's happening in your code, but I think that's what you're looking for:
#objc private func selectButton(_ sender : UIButton) {
let tag : Int = sender.tag
guard let model: UnitAndColors = self.arrayUnitsAndColor?.filter({ $0.colorDesc == selectedColorName })[tag] else { return }
selectedUnit = model.unitDesc ?? ""
for stackView in unitStackView.arrangedSubviews as? [UIStackView] ?? [] {
guard let button = stackView.arrangedSubviews.first as? UIButton else {
return
}
button.isSelected = false
button.backgroundColor = .clear
}
selectedUnit.isSelected = true
selectedUnit.backgroundColor = .red
delegate?.changePrice(selectedPrice: model.modelPrice, arrayUnitsAndColor ?? [])
}
var selectedRows: [Int] = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.allowMultipleSelection = true
}
func selectAll(){
for i in 0..<totalRows.count{
selectedRows.append(i)
let indexPath = IndexPath(row: i, section: 0)
self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
}
}
func deselectAll(){
for i in 0..<totalRows.count{
selectedRows.append(i)
let indexPath = IndexPath(row: i, section: 0)
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
selectedRows.append(indexPath.row)
print(selectedRows)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let index = selectedRows.firstIndex(of: indexPath.row) {
selectedRows.remove(at: index)
}
print(selectedRows)
if selectedRows.isEmpty{
}
}
}
class TableViewCell: UITableViewCell{
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if (selected) {
print("selected")
contentView.backgroundColor = .red
} else {
contentView.backgroundColor = .red
}
}
}

how to call/show action sheet for taking or choose photo from gallery on Main VC after tapped on TableViewCell Image(swift)

Here I Try MVVM pattern to achieve TableView but for showing alert I face problems , it compiles successfully but not showing alert.
[Result] on the tapped on profile pic which is in tableview cell, I want to show alert
TableViewCell
import Foundation
import UIKit
class ParentsImageCell : UITableViewCell {
weak var myVC : ProfileOfParentsDetailsViewController?
var parentProfileVC = ProfileOfParentsDetailsViewController()
#IBOutlet weak var imageProfile : UIImageView!
var items : ParentProfileViewModelItem? {
didSet {
guard let items = items as? ParentProfileViewModelProfileItem else {
return
}
imageProfile?.image = UIImage(named: items.profileImg)
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
override func awakeFromNib() {
super.awakeFromNib()
imageProfile?.layer.cornerRadius = 62
imageProfile?.clipsToBounds = true
imageProfile?.contentMode = .scaleAspectFill
imageProfile?.backgroundColor = UIColor.lightGray
//Add Tapped Gesture
imageProfile.isUserInteractionEnabled = true
let gesture = UITapGestureRecognizer(
target: self,
action: #selector(didTappedChangeProfilePic))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
imageProfile.addGestureRecognizer(gesture)
}
#objc private func didTappedChangeProfilePic(){
print("tapped on imageView")
presentPhotoActionSheet()
}
override func prepareForReuse() {
super.prepareForReuse()
imageProfile?.image = nil
}
}
extension ParentsImageCell : UIImagePickerControllerDelegate ,UINavigationControllerDelegate {
func presentPhotoActionSheet(){
let actionSheet = UIAlertController(title: "Profile Picture", message: "How would you write to select a picture", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: nil))
actionSheet.addAction(UIAlertAction(title: "Take Photo", style: .default, handler: {[weak self] _ in
self?.presentCamera()
}))
actionSheet.addAction(UIAlertAction(title: "Choose Photo", style: .default, handler: { [weak self]_ in
self?.presentPhotoPicker()
}))
myVC?.present(actionSheet , animated: true)
}
func presentCamera(){
let vc = UIImagePickerController()
vc.sourceType = .camera
vc.delegate = self
vc.allowsEditing = true
myVC?.present(vc , animated: true)
}
func presentPhotoPicker(){
let vc = UIImagePickerController()
vc.sourceType = .photoLibrary
vc.delegate = self
vc.allowsEditing = true
myVC?.present(vc , animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info:
[UIImagePickerController.InfoKey : Any]) {
guard let selectedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else {
return
}
self.imageProfile.image = selectedImage
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
ViewModel
class ParentProfileViewModel: NSObject {
var items = [ParentProfileViewModelItem]()
var reloadSections: ((_ section: Int) -> Void)?
override init() {
super.init()
guard let data = dataFromFile("ServerData"),
let profile = Profile(data: data) else {
return
}
// initialization code will go here
if let profile = profile.pictureUrl {
let profileItem = ParentProfileViewModelProfileItem(profileImg: profile)
items.append(profileItem)
}
if let name = profile.fullName {
let nameItem = ParentProfileViewModelNameItem(name: name)
items.append(nameItem)
}
if let email = profile.email {
let emailItem = ParentProfileViewModelEmailItem(email: email)
items.append(emailItem)
}
let coach = profile.coach
if !coach.isEmpty {
let coachItem = ParentProfileViewModelCoachItem(coach: coach)
items.append(coachItem)
}
let candidate = profile.candidate
if !candidate.isEmpty {
let candidateItem = ParentProfileViewModelCandidateItem(candidate: candidate)
items.append(candidateItem)
}
}
}
//MARK:- TableviewDatasource & Delegates
extension ParentProfileViewModel: UITableViewDataSource {
//Number of section
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
//Number of RowInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let item = items[section]
guard item.isCollapsible else {
return item.rowCount
}
if item.isCollapsed {
return 0
} else {
return item.rowCount
}
}
//Cell for row at indexpath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we will configure the cells here
let item = items[indexPath.section]
switch item.type {
case .profileImg:
let vc = ProfileOfParentsDetailsViewController()
if let cell = tableView.dequeueReusableCell(withIdentifier: ParentsImageCell.identifier, for: indexPath) as? ParentsImageCell {
cell.items = item
cell.myVC = vc
return cell
}
case .fullName:
if let cell = tableView.dequeueReusableCell(withIdentifier: ParentsFulNameCell.identifier, for: indexPath) as? ParentsFulNameCell {
cell.items = item
return cell
}
case .email:
if let cell = tableView.dequeueReusableCell(withIdentifier: ParentsEmailCell.identifier, for: indexPath) as? ParentsEmailCell {
cell.items = item
return cell
}
case .candidate:
if let item = item as? ParentProfileViewModelCandidateItem, let cell = tableView.dequeueReusableCell(withIdentifier: CandidatCell.identifier, for: indexPath) as? CandidatCell {
let candidate = item.candidate[indexPath.row]
cell.item = candidate
return cell
}
case .coach:
if let item = item as? ParentProfileViewModelCoachItem, let cell = tableView.dequeueReusableCell(withIdentifier: ParentCoachCell.identifier, for: indexPath) as? ParentCoachCell {
cell.item = item.coach[indexPath.row]
return cell
}
}
return UITableViewCell()
}
}
ViewController
import Foundation
import UIKit
class ProfileOfParentsDetailsViewController: UIViewController {
let viewModel = ParentProfileViewModel()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
viewModel.reloadSections = { [weak self] (section: Int) in
self?.tableView?.beginUpdates()
self?.tableView?.reloadSections([section], with: .fade)
self?.tableView?.endUpdates()
}
tableView?.dataSource = viewModel
tableView.delegate = viewModel
tableView?.estimatedRowHeight = 250
tableView?.rowHeight = UITableView.automaticDimension
tableView?.register(ParentsImageCell.nib, forCellReuseIdentifier: ParentsImageCell.identifier)
tableView?.register(ParentsEmailCell.nib, forCellReuseIdentifier: ParentsEmailCell.identifier)
tableView?.register(ParentsFulNameCell.nib, forCellReuseIdentifier: ParentsFulNameCell.identifier)
tableView?.register(CandidatCell.nib, forCellReuseIdentifier: CandidatCell.identifier)
tableView?.register(ParentCoachCell.nib, forCellReuseIdentifier: ParentCoachCell.identifier)
tableView?.register(ParentsHeaderView.nib, forHeaderFooterViewReuseIdentifier: ParentsHeaderView.identifier)
}
I try to get called alert Sheet , but I Failed and also comment about my approach towards MVVM
I try to called the tableview data source and delegate in VM
I try to get called alert Sheet , but I Failed and also comment about my approach towards MVVM
I try to called the tableview data source and delegate in VM

fetch data from firebase and use textfield for preview and update purposes

I want to use the same objects of one ViewController for saving into Firebase and for fetching saved data to preview and update if necessary.
Initially I used textfield in static cells it worked pretty well, but fail to insert text in textfield in dynamic cell.
When I call print function for the textfield in console it prints out correct value, but doesn't show anything on screen of simulator. I even tried to use simple strait text string to put it into textfield, but unsuccessful.
here is related code from TextMessageViewController, which i use for sending data to Firebase through textfields in dynamical tablecells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TextInputTableViewCell = receiverEmailTableView.dequeueReusableCell(withIdentifier: "ReceiverEmail") as! TextInputTableViewCell!
cell.recepientEmailTF.delegate = self
cell.recepientEmailTF.tag = indexPath.row
return cell
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
if MyGlobalVariables.emails.count <= 3 {
print("tag master = \(textField.tag)")
switch textField.tag {
case 0:
if MyGlobalVariables.emails.endIndex == 0 {
MyGlobalVariables.emails.append(textField.text!)
}
MyGlobalVariables.emails[0] = textField.text!
case 1:
if MyGlobalVariables.emails.endIndex == 1 {
MyGlobalVariables.emails.append(textField.text!)
}
MyGlobalVariables.emails[1] = textField.text!
case 2:
if MyGlobalVariables.emails.endIndex == 2 {
MyGlobalVariables.emails.append(textField.text!)
}
MyGlobalVariables.emails[2] = textField.text!
default:
print("exceeded")
}
DispatchQueue.main.async {
self.receiverEmailTableView.reloadData()
}
} else {
print("exceeded emails limit, add alert")
}
}
Portion of code from TextPreviewViewController from where I want to get firebase data and add it to texfields. This viewcontroller is connected to preview viewcontroller in storyboard
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let edit = UITableViewRowAction(style: .default, title: "Edit") { (action, indexPath) in
let newMessageVC = self.storyboard?.instantiateViewController(withIdentifier: "TextMessage") as? TextMessageViewController
newMessageVC?.modalPresentationStyle = .overCurrentContext
self.present(newMessageVC!, animated: true, completion: {
let updateButton = newMessageVC?.saveOrUpdateButton
updateButton?.titleLabel?.text = "Update"
let messageBody = newMessageVC?.messageTV
let dateField = newMessageVC?.tergetDateTF
let action = MyGlobalVariables.refMessages.child(MyGlobalVariables.uidUser!)
// CONCERN POINT: from here->
let cell1: TextInputTableViewCell = newMessageVC?.receiverEmailTableView.dequeueReusableCell(withIdentifier: "ReceiverEmail") as! TextInputTableViewCell!
cell1.recepientEmailTF.delegate = self
cell1.recepientEmailTF.allowsEditingTextAttributes = true
let texfielf = cell1.recepientEmailTF
MyGlobalVariables.emails.removeAll()
MyGlobalVariables.emails = ["","",""]
// cell1.recepientEmailTF.text = "Suka blyat" <- even this simple text doesnt appear
MyGlobalVariables.emails[0].append(self.messages[indexPath.row].email1!)
texfielf?.text = MyGlobalVariables.emails[0]
//cell1.recepientEmailTF.text = MyGlobalVariables.emails[0] <- this code also doesnt work
MyGlobalVariables.emails[1].append(self.messages[indexPath.row].email2!)
texfielf?.text = self.messages[indexPath.row].email2!
MyGlobalVariables.emails[2].append(self.messages[indexPath.row].email3!)
texfielf?.text = self.messages[indexPath.row].email3!
DispatchQueue.main.async {
newMessageVC?.receiverEmailTableView.reloadData()
}
//CONCERN POINT: ->up to here
messageBody?.text = self.messages[indexPath.row].message!
dateField?.text = self.messages[indexPath.row].setupDate!
if let autoID2 = self.messages[indexPath.row].autoID {
MyGlobalVariables.messageForUpdate1.append(autoID2) }
})
}
return [edit]
}
My UITableViewCell class
public class TextInputTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var recepientEmailTF: UITextField!
override public func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override public func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}}
I would appreciate any help or advices.

Index out of range during tableView.reloadData

I am having performance issues and index out of range errors during some refreshes. Not all, but I am struggling to troubleshoot it.
Basically, the app will collect data from an RSS feed and display it in a tableview. This is my first time using the libraries that I have, and my first time trying to properly make tableview app.
I believe the refresh error has to do with where I am refresh() as well as where I am calling projects.removeAll(). I have tried moving them to other places and I still get the refresh issue.
I am not sure why all of a sudden I have scrolling lag. If anyone has time to quickly review my code and give me suggestions, as well as try and help me fix the refresh section of the code (to fix the array out of bounds error) that would be amazing.
Thanks.
Here is my complete code.
//
// FirstViewController.swift
//
//
import UIKit
import SWXMLHash
import Alamofire
import SafariServices
class FirstViewController: UITableViewController, SFSafariViewControllerDelegate {
var projects = [[String]]()
var xmlToParse = String()
var postTitle = String()
var postLink = String()
var postAuthor = String()
var postThumbnail = String()
var postComments = String()
var postPublishDate = String()
var postVotes = String()
var postVotesNeg = String()
var dateFormatter = DateFormatter()
override func viewDidLoad() {
super.viewDidLoad()
httpRequest("https://www.test.com/rss") { response in
}
// set up the refresh control
self.refreshControl?.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl?.addTarget(self, action: #selector(refresh), for: UIControlEvents.valueChanged)
self.tableView?.addSubview(refreshControl!)
self.dateFormatter.dateStyle = DateFormatter.Style.short
self.dateFormatter.timeStyle = DateFormatter.Style.long
}
func setup() {
tableView.reloadData()
print("Refreshed")
}
#objc func refresh(sender:AnyObject) {
let now = NSDate()
let updateString = "Last Updated at " + self.dateFormatter.string(from: now as Date)
self.refreshControl?.attributedTitle = NSAttributedString(string: updateString)
if (self.refreshControl?.isRefreshing)!
{
self.refreshControl?.endRefreshing()
projects.removeAll()
}
httpRequest("https://www.test.com/rss") { response in
}
}
func enumerate(indexer: XMLIndexer, level: Int) {
for child in indexer.children {
let name = child.element!.name
print("\(level) \(name)")
enumerate(indexer: child, level: level + 1)
}
}
func httpRequest(_ section: String, completion: #escaping (String) -> Void) {
Alamofire.request(section, method: .get).responseString { response in
self.xmlToParse = response.result.value!
let xml = SWXMLHash.parse(self.xmlToParse)
for elem in xml["rss"]["channel"]["item"].all {
self.postTitle = elem["title"].element!.text
self.postLink = elem["link"].element!.text
self.postAuthor = elem["dc:creator"].element!.text
self.postThumbnail = (elem["media:thumbnail"].element?.attribute(by: "url")?.text)!
self.postComments = elem["comments"].element!.text
self.postPublishDate = elem["pubDate"].element!.text
self.postVotes = (elem["test:meta"].element?.attribute(by: "votes-pos")?.text)!
self.postVotesNeg = (elem["test:meta"].element?.attribute(by: "votes-neg")?.text)!
self.projects.append([self.postTitle, self.postLink, self.postAuthor, self.postThumbnail, self.postPublishDate, self.postComments, self.postVotes, self.postVotesNeg])
}
completion(response.result.value!)
self.setup()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let url = URL(string: self.projects[indexPath.row][1]) {
let vc = SFSafariViewController(url: url)
vc.delegate = self
present(vc, animated: true)
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
dismiss(animated: true)
}
func makeAttributedString(title: String, subtitle: String) -> NSAttributedString {
let titleAttributes = [NSAttributedStringKey.font: UIFont.preferredFont(forTextStyle: .headline), NSAttributedStringKey.foregroundColor: UIColor.black]
let subtitleAttributes = [NSAttributedStringKey.font: UIFont.preferredFont(forTextStyle: .subheadline), NSAttributedStringKey.foregroundColor: UIColor.gray]
let titleString = NSMutableAttributedString(string: "\(title)\n", attributes: titleAttributes)
let subtitleString = NSAttributedString(string: subtitle, attributes: subtitleAttributes)
titleString.append(subtitleString)
return titleString
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return projects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let project = projects[indexPath.row]
cell.textLabel?.attributedText = makeAttributedString(title: project[0], subtitle: "Author: " + project[2] + "\nPost Date: " + project[4] + "\nUpvotes: " + project[6] + "\nDownvotes: " + project[7] )
let imageURL = URL(string: project[3])
let data = try? Data(contentsOf: imageURL!)
if data != nil {
let image = UIImage(data: data!)
cell.imageView?.image = image
}
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

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.