I have a tableView displaying [Double]. Very Simple. But I also wanna display the average of the onscreen numbers on every row, and the difference between this number and the average.
Because I need to re-calculated the average every time a new row appears, I'm thinking about accessing tableView.visibleCells in cellForRowAt: indexPath method, and then update the average of this row and every other rows on screen, because the average of onscreen rows should be the same for all the onscreen rows.
But then I got this error message [Assert] Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed. Make a symbolic breakpoint at UITableViewAlertForVisibleCellsAccessDuringUpdate to catch this in the debugger and see what caused this to occur. Perhaps you are trying to ask the table view for the visible cells from inside a table view callback about a specific row?
While this is loud and clear, I'm wondering what is the correct way or workaround for this?
Code is very simple
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
print(tableView.visibleCells.count) // THIS LINE PRODUCE ERROR
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
}
What I have tried:
I've tried didEndDisplaying and willDisplay, when I added print(tableView.visibleCells.count) to either of them, same error message was given back.
Answer:
You can use UITableView's delegate functions to calculate this.
tableView(_:willDisplay:forRowAt:) is called every time before cell becomes visible, so you can recalculate your average value at this moment. Also, there is tableView(_:didEndDisplaying:forRowAt:) which fires when cell goes off display and also can be used to recalculate.
Documentation:
tableView(_:willDisplay:forRowAt:)
tableView(_:didEndDisplaying:forRowAt:)
UPD:
For calculation use tableView.indexPathsForVisibleItems
Example:
import UIKit
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
}
private func calculate() {
let count = tableView.indexPathsForVisibleRows?.count
let sum = tableView.indexPathsForVisibleRows?
.map { data[$0.row] }
.reduce(0) { $0 + $1 }
if let count = count, let sum = sum {
print(sum / Double(count))
}
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
}
Related
I am asked to do optimise my swift code especially when it comes to UI Delegate methods.
Scenario: I have UITableView in five UIViewControllers, in each view controller I am using UITableView delegate methods due to which my code is repeating. So how can I optimise this thing?
Below is the structure of code: You can see delegate method code is repeating
class FirstViewController: UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var userArray = [UserModel]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
// register cell according to model (Dynamic Cells)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell01", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
class SecondViewController: UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var productArray = [ProductModel]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
// register cell according to model (Dynamic Cells)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell01", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
sorry to bother programmers again,
I'm writing a simple app, and one of the view controllers has a table.
I'm new to all of this so I found some videos and programmed the table. This is the code above classes (honestly I still don't understand what it does):
extension ViewController3: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension ViewController3: UITableViewDataSource
{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
And this is the class:
class ViewController3: UIViewController {
#IBOutlet var tView: UITableView!
let names = [
"Alex",
"Andrew",
"Mary",
"Henry"]
override func viewDidLoad() {
super.viewDidLoad()
tView.delegate = self
tView.dataSource = self
}
}
It works fine but the task is when the orientation of the phone/simulator is horizontal, to SHOW the table not in one row, but 2x2 (like 4 cells - 2 rows, 2 lines). I don't know where to search it, I found some tips about placing elements in this way but it's not for a table. And I don't know whether its possible or not. Thank you all in advance
I'm updating an app of mine, and I have used this method in other areas of the app and it works, but for some reason, it's not working on a tableView.
The tableView is inside of a ViewController (CurrencyViewController)
#IBOutlet weak var tableView: UITableView!
tableView.dataSource = self
extension CurrencyViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currencies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "currencyCell")! as! CurrencyTableViewCell
cell.name.text = currencies[indexPath.row].currencyName
cell.name.textColor = Styles.whiteColor()
cell.symbol.text = currencies[indexPath.row].currencyCode
cell.symbol.textColor = Styles.whiteColor()
cell.backgroundColor = Styles.mainColor();
return cell
}
}
The tableView itself works, it's just not updating the height of the row.
Did something change with the update of Swift?
You don't seem to have conformed to the UITableViewDelegate ?
extension CurrencyViewController: UITableViewDataSource, UITableViewDelegate
If dataSource (and delegate) is connected in IB delete
tableView.dataSource = self
You must also adopt UITableViewDelegate in the extension, connecting the delegate is not sufficient
extension CurrencyViewController: UITableViewDataSource, UITableViewDelegate {
I am trying to make a FAQ view controller using table view, I need little bit fix in the UI. here is the display of my FAQ VC right now
(please ignore the red line)
as we can see, basically there are 2 row height, 80 & 160. if the row is tapped (selected) the row height will expand from 80 (yellow line) to 160 (purple line).
I want to make the row height under the last question is still 80 not 160. I have tried but I can't set the row below the last question. here is my code I use
class FAQVC: UIViewController {
#IBOutlet weak var retryButton: DesignableButton!
#IBOutlet weak var tableView: UITableView!
var FAQs = [FAQ]()
var selectedIndexs: [IndexPath: Bool] = [:]
override func viewDidLoad() {
super.viewDidLoad()
getFAQData()
}
}
extension FAQVC : UITableViewDelegate, UITableViewDataSource {
// MARK: - Table View Delegate and Datasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if self.cellIsSelected(indexPath: indexPath) {
return 160
} else {
return 80
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let isSelected = !self.cellIsSelected(indexPath: indexPath)
selectedIndexs[indexPath] = isSelected
tableView.beginUpdates()
tableView.endUpdates()
}
func cellIsSelected(indexPath: IndexPath) -> Bool {
if let number = selectedIndexs[indexPath] {
return number
} else {
return false
}
}
}
Please put this code in your viewDidLoad() method of your controller.
YOUR_TABLE_VIEW.tableFooterView = UIView(frame: .zero)
this will remove extra separators.
Quickest way to do this would be to add an extra empty cell at the end with row height 80
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count + 1
}
Also, make sure you make a change to cellForRowAt method to accommodate this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < FAQs.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
return UITableViewCell()
}
EDIT :
I just read that you don't really require separators after the last cell. In that case look here https://spin.atomicobject.com/2017/01/02/remove-extra-separator-lines-uitableview/
The table cells contained in the first view are properly represented, but the table cells contained in the second view do not appear. I can’t understand why.
HelpController.swift
import UIKit
class HelpController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: - Initialize
#IBOutlet weak var menuTable: UITableView!
let helpMenu = ["a","b","c"]
override func viewDidLoad() {
super.viewDidLoad()
//datasource link
menuTable.delegate = self
menuTable.dataSource = self
self.menuTable?.tableFooterView = UIView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table View Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return helpMenu.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "HelpMenuCell")
cell.textLabel?.text = helpMenu[indexPath.row]
return cell
}
}
When i check it with print (helpmenu.count), it return 3. It seems to work well until numberOfRowsInSection, but cellForRowAt does not work.
and this is my first view InfosController.swift
// MARK: - Table View Data Source
// get cell count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if UserDefaults.standard.value(forKey: "userid") != nil {
// sign in state
return memberMenu.count
} else {
// sign out state
return nonMemberMenu.count
}
}
// change cell text
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "InfosMenuCell")
if UserDefaults.standard.value(forKey: "userid") != nil {
// sign in state
cell.textLabel?.text = memberMenu[indexPath.row]
} else {
// sign out state
cell.textLabel?.text = nonMemberMenu[indexPath.row]
}
return cell
}
This is the code included in the first view that works properly. This works well, but I’m confused because it does not work well in the second view(HelpController).
ps.
The problem in HelpController, In controlle self.menuTable?.tableFooterView = UIView()
So if You are having footer view then You need to write the delegate function heightForFooterInSection and viewForFooterInSection
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
}
Make sure below things
Make sure you added Delegate & DataSource to tableView.
Check your helpMenu count is not equal to
Zero.
If numberOfRowsInSection returns Zero, then probably cellForRowAt
indexPath won't call.
Please check the method declaration once the correct syntax is
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell { }