Custom xib cells are not appearing on UITableView swift - ios

my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}

Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}

In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}

Related

Using a completion handler in another class/extension

I have a class, LocationViewController, which needs to implement a TableView. I have a function getParsedTestingLocation() which uses a completion handler from another function to get some data.
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func getParsedTestingLocations(completion: #escaping ([TestingLocation]?, Error?) -> (Void)) {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
completion(testLocations, nil)
} catch {
print(error)
}
}
})
}
}
I want to use the value testLocations within getParsedTestingLocations() in an external extension in this file. Here are the extensions I have:
extension LocationViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "empty cell"
return cell
}
}
Within all 3 tableView() functions I want to get the values stored in testLocations in the completion handler in these functions. How could I do this?
Actually you don't need a completion handler. Reload the table view inside the completion closure of the API call
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var locationData = [TestingLocation]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
self.locationData = try decoder.decode([TestingLocation].self, from: testLocationsData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
})
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let location = locationData[indexPath.row]
cell.textLabel?.text = location.whatEverStringYouWantToDisplay
return cell
}
}
Replace whatEverStringYouWantToDisplay with the real struct member name.
You don't need a completionHandler in getParsedTestingLocations in this case as the function already calls a function which has completionHandler. Just use a variable
class LocationViewController: UIViewController {
private lazy var locationArr = [TestingLocation]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
self.locationArr = testLocations
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
//Show Alert
}
}
})
}
}
extension LocationViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = locationArr[indexPath.row].variable
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me! \(locationArr[indexPath.row])")
}
}

Load tableview from a common class

I have this certain class which handles the loading of a tableview from any viewcontroller. It is given as..
class TableViewConfig<ItemsType, CellType:UITableViewCell>: NSObject, UITableViewDataSource, UITableViewDelegate {
var emptyDataSet: Bool {
didSet {
if emptyDataSet {
tableView.tableFooterView = UIView()
}
}
}
var items: [ItemsType] {
didSet {
tableView.dataSource = self
tableView.delegate = self
tableView.reloadData()
}
}
// MARK: - Private Properties
typealias CellClosure = (_ item: ItemsType, _ cell: CellType) -> Void
// Tableview Config
private var tableView: UITableView
private var cellIdentifier: String
private var configureCellClosure: CellClosure
// Delegate
private var indexPathClosure: ((IndexPath?) -> Void)?
// MARK: - Public Methods
public func selectedRow(_ callBack: #escaping (IndexPath?) -> Void) {
indexPathClosure = callBack
}
// MARK: - Inialization
init(_ tableView: UITableView,
items: [ItemsType],
cellIdentifier identifier: String,
configClosure config:#escaping CellClosure) {
self.tableView = tableView
self.cellIdentifier = identifier
self.items = items
self.configureCellClosure = config
self.emptyDataSet = false
}
// MARK: UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath) as! CellType
cell.tag = indexPath.row
configureCellClosure(items[indexPath.row], cell)
return cell
}
private func item(at indexpath: IndexPath) -> ItemsType {
return items[indexpath.row]
}
// MARK: UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let callback = indexPathClosure {
callback (indexPath)
}
}
}
This class will handle the loading of a tableview with data from any viewcontroller.
Now my problem is I want to use this class and show a tableview in my viewcontroller. How can I do that..? Hope someone can help...
you have to do something like yourViewController Tableview.delegate = TableViewConfig

swift: tableview does not work after reloadData

i have a tableview in a viewcontroller and because i need to reuse most of the code for another table i created an extra class:
class StatisticsViewDelegate: NSObject, UITableViewDelegate, UITableViewDataSource {
var defaultList:[String]
var infolist:[String] = []
var tableView:UITableView
var controller:UIViewController?
init(defaultList:[String], view:UITableView, controller:UIViewController?) {
self.defaultList = defaultList
self.controller = controller
tableView = view
super.init()
tableView.delegate = self
tableView.dataSource = self
loadTable()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infolist.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "infocell", for: indexPath) as! TableViewCell
// [fill cell]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// [...]
}
func loadTable() {
DispatchQueue.global(qos: .userInitiated).async {
//[...]
// in this case:
self.infolist = self.defaultList
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
and in my UITViewController in the viewDidLoad():
delegate = StatisticsViewDelegate(defaultList: defaultList, view: tableView, controller:self)
delegate is a member of the ViewController
now when i run it, the function func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) never gets called. The func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) gets called however(before and after the reload) and returns the correct number(in my case 4). The TableView isn't visible at all. Where is my error?
Maybe you can use the subclassing strategy to resolve your problem. There are many reference passed to your class and if you forgot to clean that up you will be have memory leaks in your hand. So I'll suggest the simple example as below. You can modify as you like and let me know if that was what you are after. If not please pardon me.
//This will be parent class that will handle all table methods, so you need to write only once the delegates and stuffs
class MyCommonTableController: UITableViewController {
var infoList = [String]()
// MARK: - TableView Delegate and Datsource Impl
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoList.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 55.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = infoList[indexPath.row]
return cell
}
}
The first class that is directly subclassing the from above MyCommonTableController
//In here we just have to know the data and set the infoList from parent
class TheTableViewController: MyCommonTableController {
let defaultList = ["Data1","Data2","Data3"] //....etc
override func viewDidLoad() {
super.viewDidLoad()
//this is were I will set those
infoList = defaultList
//reload the table
tableView.reloadData()
}
}
The second class that is directly subclassing the from above MyCommonTableController. Same process goes here
class TheSecondTableViewController: MyCommonTableController {
let defaultList = ["List1","List2","List3"] //....etc
override func viewDidLoad() {
super.viewDidLoad()
//this is were I will set those
infoList = defaultList
//reload the table
tableView.reloadData()
}
}
And now you are not repeating and table methods. You can also get the reference of table and use in your norma table view
#IBOutlet weak var theTable: UITableView!
let defaultList = ["List1","List2","List3"] //....etc
let commonTable = MyCommonTableController()
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
commonTable.infoList = defaultList
commonTable.tableView = theTable
}

Dependency injection in uitableviewcell nibs

I'm trying to build a pretty complex table. One cell of this table imports another table. In this imported table I show different rows depending on the situation. The imported table and all the importable table cells are organized into own nib files with own controllers for every cell. As a good programmer I'm trying to use dependency injection throughout the project. The problem now is that when I use the usual way of registering the nibs in viewDidLoad()
let cellNib = UINib(nibName: "BatchConsumptionCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "BatchConsumptionCell")
and then use them as dequeueReusableCell(withIdentifier: for:) in tableView(tableView: cellForRowAt:)
let batchConsumptionCell = tableView.dequeueReusableCell(withIdentifier: "BatchConsumptionCell", for: indexPath) as! BatchConsumptionCell
batchConsumptionCell.setConsumption(consumption: consumption)
return batchConsumptionCell
I'm not able to inject the dependencies in time.
In the BatchConsumptionCell all I do in tableView(tableView: cellForRowAt:) works fine. This function is called after dequeueReusableCell(withIdentifier: for:) is executed. But as soon as I'm trying to make the tableView(tableView: numberOfRowsInSection:) dynamic I run into problems. This functions seems to be called before dequeueReusableCell(withIdentifier: for:) so the dependencies are not injected at this point.
I tried to override the init(nibName: bundle:) initializer in my BatchConsumptionCell, but this is a UITableViewCell so I was not able to override this.
How do I approach this? Is there any way I could inject the dependencies as the nib and its controller are initialized? Or do I organize my cells all wrong? Any ideas would be much appreciated.
For more clarity here is my code:
ConsumptionDetailViewController
import UIKit
class ConsumptionDetailViewController: UITableViewController {
// MARK: - Properties
var moc: NSManagedObjectContext!
var consumption: Consumption!
// MARK: - Outlet Properties
#IBOutlet weak var labelDate: UILabel!
#IBOutlet weak var labelTime: UILabel!
...
// MARK: - Default Methods
override func viewDidLoad() {
super.viewDidLoad()
let cellNib = UINib(nibName: "BatchConsumptionCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "BatchConsumptionCell")
updateTableFields(selectedConsumption: consumption)
}
// MARK: - UI Update Methods
func updateTableFields(selectedConsumption: Consumption) {
labelId.text = selectedConsumption.wtId
...
}
// MARK: - BatchAddEditDelegate Methods
func didFinishEditing(consumption: Consumption) {
updateTableFields(selectedConsumption: consumption)
}
// MARK: - TableView Methods
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
...
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
...
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
...
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 1 && indexPath.row == 0 {
let batchConsumptionCell = tableView.dequeueReusableCell(withIdentifier: "BatchConsumptionCell", for: indexPath) as! BatchConsumptionCell
batchConsumptionCell.setConsumption(consumption: consumption)
return batchConsumptionCell
}
return super.tableView(tableView, cellForRowAt: indexPath)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
...
}
}
BatchConsumptionCell
import UIKit
class BatchConsumptionCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
var consumption: Consumption!
var batchConsumptionCount: Int!
#IBOutlet weak var tableView: UITableView!
override func awakeFromNib() {
super.awakeFromNib()
var cellNib = UINib(nibName: "BatchConsumptionBasicCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "BatchConsumptionBasicCell")
cellNib = UINib(nibName: "BatchConsumptionMultiCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "BatchConsumptionMultiCell")
self.tableView.delegate = self
self.tableView.dataSource = self
}
func setConsumption(consumption: Consumption) {
self.consumption = consumption
self.batchConsumptionCount = consumption.batchConsumptions?.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if batchConsumptionCount == 1 { // <-- This does not work
return 3
} else {
return batchConsumptionCount
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if batchConsumptionCount == 1 { // <-- This works fine
let batchConsumptionBasicCell = tableView.dequeueReusableCell(withIdentifier: "BatchConsumptionBasicCell", for: indexPath) as! BatchConsumptionBasicCell
let bc = consumption.batchConsumptions?.allObjects[0] as! BatchConsumption
if indexPath.row == 0 {
batchConsumptionBasicCell.configure(title: "Batch", detail: (bc.batch?.wtId)!)
} else if indexPath.row == 1 {
batchConsumptionBasicCell.configure(title: "Weight", detail: String(describing: bc.weight!))
} else if indexPath.row == 2 {
batchConsumptionBasicCell.configure(title: "Price", detail: String(format:"%.2f", bc.price))
}
} else if batchConsumptionCount >= 2 {
let batchConsumptionMultiCell = tableView.dequeueReusableCell(withIdentifier: "BatchConsumptionMultiCell", for: indexPath) as! BatchConsumptionMultiCell
return batchConsumptionMultiCell
}
return UITableViewCell()
}
}
See the comments // <-- This works fine and
// <-- This does not work in BatchConsumptionCell
You should also register nib to the BatchConsumptionCell class, and implement necessary table view's data source & delegate methods:
class BatchConsumptionCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var consumption: Consumption!
var batchConsumptionCount: Int!
override func awakeFromNib() {
super.awakeFromNib()
var cellNib = UINib(nibName: "BatchConsumptionBasicCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "BatchConsumptionBasicCell")
cellNib = UINib(nibName: "BatchConsumptionMultiCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "BatchConsumptionMultiCell")
self.tableView.delegate = self
self.tableView.dataSource = self
}
func setConsumption(consumption: Consumption) {
self.consumption = consumption
self.batchConsumptionCount = consumption.batchConsumptions?.count
tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return consumption == nil ? 0 : 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if batchConsumptionCount == .some(1) { return 3 }
return batchConsumptionCount ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch batchConsumptionCount {
case .some(1):
let batchConsumptionBasicCell = tableView.dequeueReusableCell(withIdentifier: "BatchConsumptionBasicCell", for: indexPath) as! BatchConsumptionBasicCell
let bc = consumption.batchConsumptions?.allObjects[0] as! BatchConsumption
if indexPath.row == 0 {
batchConsumptionBasicCell.configure(title: "Batch", detail: (bc.batch?.wtId)!)
} else if indexPath.row == 1 {
batchConsumptionBasicCell.configure(title: "Weight", detail: String(describing: bc.weight!))
} else if indexPath.row == 2 {
batchConsumptionBasicCell.configure(title: "Price", detail: String(format:"%.2f", bc.price))
}
return batchConsumptionBasicCell
default:
let batchConsumptionMultiCell = tableView.dequeueReusableCell(withIdentifier: "BatchConsumptionMultiCell", for: indexPath) as! BatchConsumptionMultiCell
return batchConsumptionMultiCell
}
}
}

Image added to UITableViewCell appears in wrong rows when scrolling

I'm adding an image to a table view row (actually, I seem to be adding it to the row's cell) when selecting it (and removing when selecting it again). The table view consists of prototype cells.
This works but when I scroll around and get back to the row I had previously selected, the image would be in another row. Also, the image appears in other rows as well.
My guess is this happens because the cells are re-used when scrolling.
Here's the code of a little sample project:
import UIKit
class MyTableViewController: UITableViewController {
// Using integers for simplicity, should work with strings, too.
var numbers = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
numbers.append(i)
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath: indexPath)
cell.textLabel?.text = "\(numbers[indexPath.row] + 1)"
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numbers.count
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if let myImage = curCell.viewWithTag(10) as? MyImage {
myImage.removeFromSuperview()
} else {
let myImage = myImage()
myImage.tag = 10
cell.addSubview(myImage)
}
}
I need to have the image stay in the correct row, also when coming back to this view controller. What's the correct way to tackle this?
Any advice much appreciated!
EDIT: I've tried to implement matt's answer but I seem to be missing something, as the problem is still the same.
EDIT 2: Updated, working as intended now.
import UIKit
class ListItem {
var name: String!
var showsImage = false
init(name: String) {
self.name = name
}
}
class MyTableViewController: UITableViewController {
var listItems = [ListItem]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
let listItem = ListItem(name: "row \(i)")
listItems.append(listItem)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath: indexPath)
cell.textLabel?.text = "\(listItems[indexPath.row].name)"
if listItems[indexPath.row].showsImage {
let myImage = myImage
myImage.tag = 10
cell.addSubview(myImage)
} else {
if let myImage = cell.viewWithTag(10) as? myImage {
myImage.removeFromSuperview()
listItems[indexPath.row].showsImage = false
}
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listItems.count
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if let myImage = cell.viewWithTag(10) as? myImage {
myImage.removeFromSuperview()
listItems[indexPath.row].showsImage = false
} else {
listItems[indexPath.row].showsImage = true
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
}
}
EDIT 3: As matt suggested, here's an alternative solution to the code above which subclasses UITableViewCell instead of using a tag for the image.
import UIKit
class MyTableViewCell: UITableViewCell {
var myImage = MyImage()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
myImage.hidden = true
addSubview(myImage)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ListItem {
var name: String!
var showsImage = false
init(name: String) {
self.name = name
}
}
class MyTableViewController: UITableViewController {
var listItems = [ListItem]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
let listItem = ListItem(name: "row \(i)")
listItems.append(listItem)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = MyTableViewCell(style: .Default, reuseIdentifier: "TestCell")
cell.textLabel?.text = "\(listItems[indexPath.row].name)"
cell.myImage.hidden = !(listItems[indexPath.row].showsImage)
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! MyTableViewCell
listItems[indexPath.row].showsImage = cell.myImage.hidden
cell.myImage.hidden = !cell.myImage.hidden
}
}
The problem is that cells are reused in other rows. When they are, cellForRowAtIndexPath is called again. But when it is, you are supplying no information about the image for that row.
The solution: fix your model (i.e. the info you consult in cellForRowAtIndexPath) so that it knows about the image. In didSelect, do not modify the cell directly. Instead, modify the model and reload the cell.

Resources