I've come across a very strange issue suddenly in an app I'm building - the tableViewCells in my tableView are not showing up at all, even though the tableView methods are firing (I've checked using print statements within them). The app does not crash, but everything within the table view cell is just not showing up anymore, even though all data variables have been updated.
Below are the tableView methods I've used:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = selectedValidInputs.count
return count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = carbonDetailsTable.dequeueReusableCell(withIdentifier: "CarbonDetailsCell") as! CarbonTableViewCell
cell.itemName.text = selectedValidInputs[indexPath.row]
cell.carbonValue.text = carbonValues[indexPath.row]
cell.tip.text = tips[indexPath.row]
cell.hashtag.text = tags[indexPath.row]
return cell
}
Covering some questions that may be asked
I have a Controller that conforms to UIViewController, UITableViewDelegate, UITableViewDataSource, so override methods are not required
I have made sure the identifier for the CarbonTableViewCell (the custom UITableViewCell class) matches the one being used in .dequeueReusableCell method
I have verified all the variable connections from the storyboard to the code
I have not set the number of sections within the tableView, but the code had been working for a month without that too, so don't think the problem is associated with that.
I'm very new to coding altogether, so any help and feedback would be much appreciated!
Note that the page is just empty. Even the 'cellBg' variable below, which is just a view and has a background color, does not show up and neither does the app crash - which is a bit weird.
Edit: Since this is being suggested by everyone, just updating here. On printing the results, I get all values: When the input value is "apple" for example, all the values are shown:
count: 1
selectedValidInputs: ["apple (1 kg)"]
carbonValues: ["550 g CO2, same as 22 plastic bags"]
tips: ["Avoid wastage that adds unnecessary methane to the air with decomposition!"]
tags: ["#very-low-carbon-impact"]
When I add carbonDetailsTable.register(CarbonTableViewCell.self, forCellReuseIdentifier: "CarbonDetailsCell") to viewDidLoad(), I get a crash with the msg shown in attached image
Number of sections is equal 0, ‘selectedValidInputs’ array is empty or you have not placed all of the code that is initialising it with values.
1) Try to implement also
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
2) Try to implement also
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
And comment the estimatedRowHeight and rowHeight setters, to confirm if the cell's autolayout is not broken and table view is unable to determine row height.
(estimatedRowHeight is basically used just for tableView indicator bar and estimating the content size of tableView)
Print your Arrays which you are passing in tableview in CarbonDetailsViewController, and check data is exact as you passed in previous viewcontroller.
Related
I have a UITableView cell with a stack view inside. When the cell is tapped the data source changes and the table view is reloaded. The stack view will now have more views inside and the cell is bigger. However sometimes when I scroll the table there is jerky behaviour. It's almost like the cell size was calculated wrong or something (even though it looks fine). Once the tableview has jerked once it is fine and doesn't do it again until I tap the cell and it adds more stack views.
I am using UITableViewAutomaticDimension on the table view. I have tried removing the cell and the table doesn't jerk. It's defiantly the stack view causing issues.
I set my estimated row height to as close as possible to the calculated height tableView.estimatedRowHeight = 270. No affect. I have also tried implementing the delegate and it makes no difference. I have tried many combinations or sizes and the result is the same. Any idea on what I am doing wrong here? Do stack views in cells just suck?
I think you are on the right track about estimatedRowHeight causing trouble. I encounter this jerking problem and in pretty much every tableView with varying element size. What usually does the job is "caching" cell heights and returning them in delegate, something like:
class MyViewController: UIViewController {
fileprivate var cachedCellHeights = [IndexPath: CGFloat]()
//your code here
}
extension MyViewController: UITableViewDelegate {
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cachedCellHeights[indexPath] = cell.frame.height
}
public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cachedCellHeights[indexPath] {
return height
}
return 270
}
}
It should work as long as you configure your cell (i.e. add new views to stack view) in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath).
The same applies to section headers.
NOTE: I know this is a re-post; I posted the same question a few days ago sans-code and, understandably, it was closed. I edited the question to add some relevant snippets but the question was not reopened, so I am reposting here. If this isn't the proper way to do this, let me know!
I have an application with two modes, and the settings screen is slightly different for each mode (one additional section in one of them, a few differences in number of rows, different UserDefaults keys, etc.). In the past I implemented this with a crapton of switch and if statements, but in an effort to make things more maintainable I'm investigating ways on splitting the two modes into their own separate classes of something. Initially I considered making two separate UITableViewController subclasses, however I was having trouble thinking of how that would work with storyboards and such. I then thought to use two separate UITableView subclasses, and select which one to show based on the mode in viewDidLoad.
However, I'm having issues with this approach. I have it set up so that the controller's cellForRow method calls the TableView's cellForRow method, but that's where things break. When trying to do a dequeueReusableCell, the app crashes with the ever vague "EXC_BAD_INSTRUCTION" error on that line.
Here's some relevant code:
ViewController.swift
...
override func viewDidLoad()
{
super.viewDidLoad()
...
tableView = SRScaleSettingsTableView()
}
...
override func tableView(_ tableView: UITableView?, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
return (tableView?.cellForRow(at: indexPath))!
}
SRScaleSettingsTableView.swift
override func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
{
...
switch indexPath.section
{
case 0:
...
let switchCell = dequeueReusableCell(withIdentifier: "SwitchCell") as! SRSwitchCell
^ Debugger breaks on that line with EXC_BAD_INSTRUCTION
...
return switchCell
...
}
}
Any ideas on what would cause this? Is my approach even correct; is there a better way to do this?
You can keep a single UITableView class (you likely don't need to subclass UITableView at all) and a single UIViewController subclass. Create two different classes that implement the UITableViewDataSource protocol (and possibly also UITableViewDelegate). These two classes can implement the various delegate/datasource methods (e.g. cellForRowAtIndexPath, numberOfRowsInSection, didSelectRow) in completely different ways for the different modes your app needs to run in.
protocol SettingsSource: UITableViewDelegate, UITableViewDataSource {
}
class SettingsSourceForModeA: NSObject, SettingsSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)...
}
class SettingsSourceForModeB: NSObject, SettingsSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)...
}
class SettingsViewController: UIViewController {
#IBOutlet tableView: UITableView!
var source: SettingsSource! {
didSet {
tableView.dataSource = source
tableView.delegate = source
}
}
override func viewDidLoad() {
super.viewDidLoad()
// whatever logic is appropriate to determine the mode
if appIsRunningInModeA() {
source = SettingsSourceForModeA()
} else {
source = SettingsSourceForModeB()
}
}
}
The key detail in the above code is the source variable in SettingsViewController - the value of source is based on which mode the app is running in, and it determines what class will be used as the data source for the table view.
The storyboard setup is simple: one scene SettingsViewController, and a single stock UITableView in that scene.
Note that the SettingsViewController above is a UIViewController subclass, not UITableViewController, since the data source and delegate protocols are being implemented in separate classes and determined at runtime. This will require you to manually wire up the tableView outlet in your storyboard. However, you do not wire up the UITableView's dataSource and delegate outlets in the storyboard. Instead, it's done at runtime as shown in the sample code above.
Note that you may not have any need to implement UITableViewDelegate, in which case you can just ignore the references to UITableViewDelegate and its methods in the sample code above. Or, if you the UITableViewDelegate implementation (such as the didSelectRow method) is identical for the two modes your app can run in, you may be able to implement that in your view controller class, in which case you can wire up the delegate outlet of your table view directly to your view controller, in the storyboard.
You have a misunderstanding of how the UITableView and UITableViewController work together. A UITableView needs a UITableViewDataSource to provide it the details of the underlying data (number of sections, number of rows and actual cells, etc). This is what the UITableViewController does (it conforms to the UITableViewDataSource). So if you call the cellForRow for the tableView then it will call it's data sources cellForRow method to obtain that.
So in your code when you do this:
return (tableView?.cellForRow(at: indexPath))!
Your table view calls its data source which is your UITableViewController and that calls the table view cellForRow and so on. You have just entered a recursive loop which is eventually killed off with the error you see.
As for your overall approach I would go down the two UITableViewControllers route as that separates out the different logics between the two making it easier to both understand and maintain and also allowing for more reuse as well.
As for how that works with storyboards it depends greatly on how you switch between the two modes but in essence you can setup segues to switch between the two controllers.
I created a table view controller and from drop down in Xcode selected content to be static cells. I placed buttons in those cells but when I run I get an empty table. I didn't enter any code in the TableViewController class as I thought this was not needed.
How can this be fixed?
This is the way it looks in Xcode and then when it runs in simulator:
You may miss some stuff:
1) Check whether your tableView has set the class named as that on in your code. You find the input field to type the name of the class in Attributes inspector of the tableView.
2) Check whether you implemented methods, that comform to UITableViewDelegate and UITableViewDataSource Protocol.
func numberOfSections(in tableView: UITableView) -> Int {
// I see you only have 1 section now
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//you should return appropriate number
return 3
}
3) Check whether your table is properly connected from Storyboard to your UIViewController =>
if your tableView is inside UIViewController, check whether you set delegate and datasource for tableView in your controller (CTRL drag from table to FileOwner - rounded yellow icon in storyboard scene frame.)
using static cells, it's not necessary to implement numberOfSections and numberOfRowsInSection. By default, at runtime you get the static cell(s) as implemented in the designer.
However, if you do implement these methods and, by error, return 0: No cell is shown at runtime.
Following up on the answer above:
1) Make sure the tableview is hooked up as both the datasource and delegate.
2) Make sure these two lines of code are modified to your table. Default is to return 0 (an empty table) so you need to modify them accordingly. You can also choose to comment them out completely (what I did).
func numberOfSections(in tableView: UITableView) -> Int {
// I see you only have 1 section now
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//you should return appropriate number
return 3
}
So, here`s my problem:
I want to create a table to display some flight data. The idea is that each cell represents a trip (i.e all flights necessary to go from city A to B). Each flight has 3 properties which I would divide in separate labels, composing the line. Because of that, the cell must adjust its height accordingly.
This is what I`ve done so far:
My view controller class:
#IBOutlet weak var dispTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
dispTable.dataSource = self
dispTable.delegate = self
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("DispCell") as? DispTableViewCell {
let flight1: Flight = Flight()
let flight2: Flight = Flight()
curFlightsIda = [Flight]()
Flight1.Num = "1221"
Flight2.Num = "1331"
curFlightsIda.append(Flight1)
curFlightsIda.append(Flight2)
for voo in curFlightsIda {
let lbl = UILabel(frame: CGRectMake(0, 0, 100, 50))
lbl.textAlignment = NSTextAlignment.Center
lbl.text = voo.Num
cell.contentView.addSubview(lbl)
}
return cell
} else {
return DispTableViewCell()
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
The flight.num there is just for tests. I was able to create the label programmatically, but I feel that the way Im going, the layout wont work properly.
So, how can I create the labels representing the lines and place them correctly? Am I on the right track? I searched around and found about XIBs, but I'm not sure if it's better..? Honestly I`m completely lost and any help would be deeply appreciated.
I`m not sure if this helps, but it would be something like this:
TRIP CELL:
Flight1Num Flight1Departure Flight1Duration
Flight2Num Flight2Departure Flight2Duration
Flight3Num Flight3Departure Flight3Duration
Don't.
Why:
cellForRowAtIndexPath gets called for every cell (also during scrolling). If you are trying to do too much, it won't scroll smooth. Furthermore cells are reused (as dequeue indicates it). iOS only creates the number of visible cells instances plus one or two extra cells. If a cell is reused, you would need to remove the already added labels before adding new ones which leads to the next problem: How can you identify them (either search for all subviews and check if it is an instance of UILabel or use a tag and find them by tag.... anyway: If you are trying to do too much, it won't scroll smooth.
Instead:
Define the labels in your template cell 'DispCell' or create different template cell in your storyboard (and dequeue the correct one by name). There you can use storyboard layout constraints in order to position your labels correctly.
If you have a flexible number of curFlightsIda, you can either think about a max number (and hide empty labels) or maybe it works out to use a textfield instead of a label.
I am a new to iOS development using Swift.
I am trying to understand how functions are called in a view controller that controls a table view.
In the examples I am looking at, the view controller runs three functions, all called 'table view', and each function does something unique such as returning how many rows are in a section, or using reusable cells.
But I just can't see when or how these functions are called.
Are they called when the user navigates to the view? If so, how? And how come these different functions all have the same name (i.e. func tableView ())?
Here is some sample code:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dwarves.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier(simpleTableIdentifier)
as? UITableViewCell
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: simpleTableIdentifier)
}
cell!.textLabel.text = dwarves[indexPath.row]
cell!.textLabel.font = UIFont .boldSystemFontOfSize(15)
return cell!
}
I just can't see when or how these functions are called.
A UITableView calls these methods on its delegate or dataSource to get information about what it should display, and communicate when certain actions occur. For a UITableViewController, the controller itself is both the delegate and the data source. So you won't see these methods get called unless you set a breakpoint within them.
Are they called when the user navigates to the view? If so, how?
When the user navigates to the view, the default implementation of UITableViewController sets the table view's delegate and data source properties to self. The table view itself calls these methods lazily when it needs information to create, size, layout, and display table cells appropriately.
And how come these different functions all have the same name? I.e func tableView ()
They don't. In both Swift and Objective-C, argument names are part of the method name. For example, this method:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80
}
is named tableView(_:heightForRowAtIndexPath:).