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 { }
Related
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()
}
}
I have a table view with two sections and want to allow rows to be re-ordered for just one of the sections. I've found a lot of information about this, but can't restrict it to the single section. Here is my entire code - it's very simple, with one table view. I've put this together from various sources - thanks to those who have contributed to this topic.
To reproduce this in Storyboard, add a table view to the view controller, add some constraints, set number of Prototype Cells to 1, and set its Identifier to 'cell'.
The table view has two sections - 'Fruit' and 'Flowers'. Press and hold on any cell allows that cell to be moved, so this part works ok.
I want to restrict it so that I can only move in the first section.
Also, if I'm dragging a cell and move it from one section to another, it gives an error and the program crashes. I'd like it just to reject the move and send the cell back to its original position.
Thanks for any help. Ian
import UIKit
import MobileCoreServices
var fruitList = ["Orange", "Banana", "Apple", "Blueberry", "Mango"]
var flowerList = ["Rose", "Dahlia", "Hydrangea"]
// this all works
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITableViewDragDelegate, UITableViewDropDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
tableView.dropDelegate = self
tableView.dragInteractionEnabled = true
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Fruit"
} else {
return "Flowers"
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return fruitList.count
} else {
return flowerList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = fruitList[indexPath.row]
} else {
cell.textLabel?.text = flowerList[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
var string: String
if indexPath.section == 0 {
string = fruitList[indexPath.row]
} else {
string = flowerList[indexPath.row]
}
guard let data = string.data(using: .utf8) else { return [] }
let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypePlainText as String)
return [UIDragItem(itemProvider: itemProvider)]
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}
}
Just in case anyone finds their way here, thanks to the link #Paulw11 has provided, this is the extra bit of code needed. Replace
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
with
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let string = fruitList[sourceIndexPath.row]
fruitList.remove(at: sourceIndexPath.row)
fruitList.insert(string, at: destinationIndexPath.row)
}
Obviously this is just a simple example program, but I've now adapted this code to a complex situation and it works perfectly.
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
}
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/
When I take var type variable then Xcode show warning
variable is never mutated
If I take let type variable then don't show any result!
import UIKit
class ViewController: UIViewController,UITableViewDataSource{
let people = [
("Pankaj","Dhaka"),
("Asish","Madaripur"),
("Anup","Narail")
]
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "People"
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let (personName , personLocation) = people[indexPath.row]
cell.textLabel?.text = personName
cell.textLabel?.text = personLocation
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
You cannot use a table view cell initialized with the default initializer UITableViewCell()
Reuse the cell, add an identifier (e.g. PeopleCell) in Interface Builder.
let cell = tableView.dequeueReusableCell(withIdentifier: "PeopleCell", for: indexPath)
And make sure that datasource and delegate of the table view are connected in Interface Builder, too.