Here is my implementation of tableView(_:cellForRowAt:):
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.section
let weekDay = WeekDays.day(at: index)
if self.availability.numberOfTimeslots(for: weekDay) == 0 {
let cell = NotSelectedCell(style: .default, reuseIdentifier: nil)
return cell
}
return UITableViewCell()
}
Here is my code for my custom table view cell:
class NotSelectedCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.backgroundColor = .red
self.textLabel?.numberOfLines = 0
self.textLabel?.textAlignment = .center;
self.textLabel?.text = "Not Available"
}
}
I've also tried initializing custom cell cell = NotSelectedCell() the result is the same. The content isn't shown. dataSource or viewDelegate aren't the problem as I'm working with UITableViewController.
Here's an image
The problem is awakeFromNIB "prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file." But you're instantiating this programmatically, so that method isn't called. You could theoretically move the code to init(style:reuseIdentifier:), make sure to call super in your implementation, and do any additional customization after that point.
But, you generally wouldn't programmatically instantiate cells when using static cells. (It's the point of static cells, that IB takes care of everything for you.) You generally don't implement UITableViewDataSource at all when using static cells.
I would advise using dynamic table and have two cell prototypes, one with reuse identifier of "NotAvailable" and one with "Available" (or whatever identifiers you want). Then programmatically instantiate the cell with the appropriate identifier. (By the way, this also has the virtue that your cell with "NotAvailable" can be designed entirely in IB, and no code is needed, for that cell at least.) This way, the storyboard takes care of instantiating the appropriate cell.
So, here I have two cell prototypes in my dynamic table, one for "not available" and one for "available":
Then the code would look at the model to figure out which to instantiate:
// for the complicated cell where I want to show details of some window of availability, add IBOutlets for that cell's labels
class AvailableCell: UITableViewCell {
#IBOutlet weak var startLabel: UILabel!
#IBOutlet weak var stopLabel: UILabel!
#IBOutlet weak var doctorLabel: UILabel!
}
// some super simple model to represent some window of availability with a particular doctor in that office
struct Availability {
let start: String
let stop: String
let doctor: String
}
class ViewController: UITableViewController {
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
let available = ...
override func numberOfSections(in tableView: UITableView) -> Int {
return days.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return available[days[section]]?.count ?? 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return days[section]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// see if there are any available windows for the given day, if not, return "not available" cell
guard let availabilities = available[days[indexPath.section]] else {
return tableView.dequeueReusableCell(withIdentifier: "NotAvailable", for: indexPath)
}
// otherwise, proceed with the more complicated "Available" cell where I have to populate various labels and the like
let cell = tableView.dequeueReusableCell(withIdentifier: "Available", for: indexPath) as! AvailableCell
let availability = availabilities[indexPath.row]
cell.startLabel.text = availability.start
cell.stopLabel.text = availability.stop
cell.doctorLabel.text = availability.doctor
return cell
}
}
And that would yield:
Now, clearly, I just whipped up a super primitive model, and didn't do any UI design in the "available" cell prototype other than inserting three labels. But it illustrates the idea: If your dynamic table has multiple unique cell designs, just implement cell prototypes for each with unique identifiers and instantiate the appropriate one. And this way, you enjoy full cell reuse, minimize how much visual design you have to do programmatically, etc.
You are not supposed to use the cellForRow:atIndexPath method when using static cells. The cells are static, so the loading flow is different. What i'd suggest is to connect the cells individually from the interface builder to your view controller.
STILL, if you want to do it this way you have to get your cells by calling "super" since that's the class who is actually generating your static cells.
UITableView with static cells without cellForRowAtIndexPath. How to set clear background?
EDIT:
I just noticed that this is wrong:
if self.availability.numberOfTimeslots(for: weekDay) == 0 {
let cell = NotSelectedCell(style: .default, reuseIdentifier: nil)
return cell
}
You have to use the "dequeueReusable" method or something. Then again, these are STATIC Cells, so you should just be linking the cells directly from the interface builder.
I know this particular question has been asked and answered previously in SO but cross checked those answers and still not able to fix this issue. Can be a silly mistake but unable to nail it.
Cross checked :
Cell Identifierid
datasource and delegate added through Interface builder
Code :
var sensorFields = [Sensor]()
override func viewDidLoad() {
super.viewDidLoad()
readParseDataFromCSV(file: "csvFile")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(self.sensorFields.count)// has count 120
return self.sensorFields.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SensorTableCell") as! SensorTableViewCell
cell.lblSensorName.text = sensorFields[indexPath.row].labelName
cell.lblSensorValue.text = sensorFields[indexPath.row].labelValue
print(sensorFields[indexPath.row].labelName) // doesn't seem to enter to this code block
print(sensorFields[indexPath.row].labelValue)
return cell
}
func readParseDataFromCSV(file:String){
let filepath = Bundle.main.path(forResource: file, ofType: "csv")!
do {
let csv = try CSV(contentsOfURL: filepath)
let rows = csv.rows
for row in rows{
let sensorlblName = row["data column 1"]
let sensorlblValue = row["data column 2"]
let sensor = Sensor(labelName: sensorlblName!, labelValue: sensorlblValue!)
sensorFields.append(sensor)
}
} catch {
print(error.localizedDescription)
}
}
class SensorTableViewCell: UITableViewCell {
#IBOutlet weak var lblSensorName: UILabel!
#IBOutlet weak var lblSensorValue: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Is there anything missed out , using other tableviews in the app already which is working perfectly apart from this one.
Using Xcode 8.1 and Swift 3.0
Edit :
Added reloadData()
Seems like
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
is not firing too.
Please help.
Thanks in advance.
Just appending data to your sensorFields array has no impact on the cells displayed in your tableView.
You need to call reloadData on the tableView so it knows the data did change and repopulates the cells with the new data.
for row in rows {
let sensorlblName = row["data column 1"]
let sensorlblValue = row["data column 2"]
let sensor = Sensor(labelName: sensorlblName!, labelValue: sensorlblValue!)
sensorFields.append(sensor)
}
tableView.reloadData()
I would also prefer
// newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered
// Swift 2
func dequeueReusableCellWithIdentifier(identifier: String, forIndexPath indexPath: NSIndexPath) -> UITableViewCell
// Swift 3
func dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell
to
// Used by the delegate to acquire an already allocated cell, in lieu of allocating a new one.
// Swift 2
func dequeueReusableCellWithIdentifier(identifier: String) -> UITableViewCell?
// Swift 3
func dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?
because dequeueReusableCellWithIdentifier(identifier: String) does not guarantee returning a UITableViewCell when the cell with identifier was not yet allocated. ("[...] acquire an already allocated cell, in lieu of allocating a new one.")
And make sure your IBOutlets are connected! (lblSensorName and lblSensorValue)
check this
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell = tableView.dequeueReusableCellWithIdentifier("SensorTableCell") as UITableViewCell!
if !cell {
cell = UITableViewCell(style:.Default, reuseIdentifier: "SensorTableCell")
}
}
I'm making a simple table view app to display and play all the iOS System sounds.
I have all of the sounds and ID's in a a dictionary(I now realize this was a bad way to do this) in the form of [ID(Int):Name(String)], the problem is that when I load my view it loads well, but if I scroll down the cells originally on top change. Same when scrolling from the bottom to the top.
For example, the view loads in and I can click and hear the various sounds from any of the cells I click on. Lets say the first cell is "SMS-Sound1" and the seconds is "SMS-Sound2". Now when I scroll down to where those cells are out of view and then scroll back to the the top they are named something different(Still from my data dictionary).
How would I fix this problem so that it loads the tableview and then the tableview data doesn't change?
Edit: I thought the problem could be in the fact that the for in loop was executed around 300,000 times but thats not the case, made an array of the IDS so it was only executed around 1000 times total and the problem persists
My Code:
Cell set up
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("soundCell", forIndexPath: indexPath)
let button = cell.viewWithTag(3) as! UILabel //UILabel in "SoundCell"
for i: Int in 999..<4100 {
//Lowest id sound is 1000, highest is 4095
if (sounds[i] != nil) && loadedSoundStrings.contains(sounds[i]!) == false {
button.text = sounds[i]
loadedSoundStrings.append(sounds[i]!)
cell.tag = i
break
}
}
return cell
}
Rows/sections
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sounds.count
}
Variables:
let sounds =
[ 1000:"new-mail.caf",
1001:"mail-sent.caf",
1002:"Voicemail.caf",
1003:"ReceivedMessage.caf",
1004:"SentMessag.caf",
1005:"alarm.caf",
1006:"low-power.caf",
1007:"sms-received1.caf",
1008:"sms-received2.caf",
1009:"sms-received3.caf",
1010:"sms-received4.caf",
1011:"-(SMSReceived_Vibrate)",
1012:"sms-received1.caf",
1013:"sms-received5.caf",
1014:"sms-received6.caf",
1015:"Voicemail.caf",
1016:"tweet_sent.caf",
1020:"Anticipate.caf",
1021:"Bloom.caf",
1022:"Calypso.caf",
1023:"Choo_Choo.caf",
1024:"Descent.caf",
1025:"Fanfare.caf",
1026:"Ladder.caf",
1027:"Minuet.caf",
1028:"News_Flash.caf",
1029:"Noir.caf",
1030:"Sherwood_Forest.caf",
1031:"Spell.caf",
1032:"Suspense.caf",
1033:"Telegraph.caf",
1034:"Tiptoes.caf",
1035:"Typewriters.caf",
1036:"Update.caf",
1050:"ussd.caf",
1051:"SIMToolkitCallDropped.caf",
1052:"SIMToolkitGeneralBeep.caf",
1053:"SIMToolkitNegativeACK.caf",
1054:"SIMToolkitPositiveACK.caf",
1055:"SIMToolkitSMS.caf",
1057:"Tink.caf",
1070:"ct-busy.caf",
1071:"ct-congestion.caf",
1072:"ct-path-ack.caf",
1073:"ct-error.caf",
1074:"ct-call-waiting.caf",
1075:"ct-keytone2.caf",
1100:"lock.caf",
1101:"unlock.caf",
1102:"-(FailedUnlock)",
1103:"Tink.caf",
1104:"Tock.caf",
1105:"Tock.caf",
1106:"beep-beep.caf",
1107:"RingerChanged.caf",
1108:"photoShutter.caf",
1109:"shake.caf",
1110:"jbl_begin.caf",
1111:"jbl_confirm.caf",
1112:"jbl_cancel.caf",
1113:"begin_record.caf",
1114:"end_record.caf",
1115:"jbl_ambiguous.caf",
1116:"jbl_no_match.caf",
1117:"begin_video_record.caf",
1118:"end_video_record.caf",
1150:"vc~invitation-accepted.caf",
1151:"vc~ringing.caf",
1152:"vc~ended.caf",
1153:"ct-call-waiting.caf",
1154:"vc~ringing.caf",
1200:"dtmf-0.caf",
1201:"dtmf-1.caf",
1202:"dtmf-2.caf",
1203:"dtmf-3.caf",
1204:"dtmf-4.caf",
1205:"dtmf-5.caf",
1206:"dtmf-6.caf",
1207:"dtmf-7.caf",
1208:"dtmf-8.caf",
1209:"dtmf-9.caf",
1210:"dtmf-star.caf",
1211:"dtmf-pound.caf",
1254:"long_low_short_high.caf",
1255:"short_double_high.caf",
1256:"short_low_high.caf",
1257:"short_double_low.caf",
1258:"short_double_low.caf",
1259:"middle_9_short_double_low.caf",
1300:"Voicemail.caf",
1301:"ReceivedMessage.caf",
1302:"new-mail.caf",
1303:"mail-sent.caf",
1304:"alarm.caf",
1305:"lock.caf",
1306:"Tock.caf",
1307:"sms-received1.caf",
1308:"sms-received2.caf",
1309:"sms-received3.caf",
1310:"sms-received4.caf",
1311:"-(SMSReceived_Vibrate)",
1312:"sms-received1.caf",
1313:"sms-received5.caf",
1314:"sms-received6.caf",
1315:"Voicemail.caf",
1320:"Anticipate.caf",
1321:"Bloom.caf",
1322:"Calypso.caf",
1323:"Choo_Choo.caf",
1324:"Descent.caf",
1325:"Fanfare.caf",
1326:"Ladder.caf",
1327:"Minuet.caf",
1328:"News_Flash.caf",
1329:"Noir.caf",
1330:"Sherwood_Forest.caf",
1331:"Spell.caf",
1332:"Suspense.caf",
1333:"Telegraph.caf",
1334:"Tiptoes.caf",
1335:"Typewriters.caf",
1336:"Update.caf",
1350:"-(RingerVibeChanged)",
1351:"-(SilentVibeChanged)",
4095:"-(Vibrate)"]
var loadedSoundStrings = [String]()
You are instantiating all of the sounds for every single row when you actually want to only instantiate the sound for the rows that are loaded. To fix your order issue change your
cellForRowAtIdexPath
to this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("soundCell", forIndexPath: indexPath)
let button = cell.viewWithTag(3) as! UILabel //UILabel in "SoundCell"
button.text = sounds[i]
cell.tag = indexPath.row
return cell
}
This gives you 1 sound per cell since you have NumberOfRowsInSection set to sounds.count Cell for row will be called for every sound you have.
If I understand your code correctly, you're going about it the wrong way. You have a dictionary of sounds that you load once. The cellForRowAtIndexPath function should be returning one tableViewCell with details for the one sound.
UITableView automatically discards cells that are off screen to conserve memory, and will reuse them for newly visible cells. That's why you call dequeueReusableCellWithIdentifier. Therefore you should just be doing:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("soundCell", forIndexPath: indexPath)
let button = cell.viewWithTag(3) as! UILabel //UILabel in "SoundCell"
//Lowest id sound is 1000, highest is 4095
let i = indexPath.row + 1000
button.text = sounds[i]
cell.tag = i
return cell
}
Since you are hardcoding the sound number range I have done the same.
A table view works best with an array, as an array has a defined order and you can quickly access a given element; a for loop in cellForRowAtIndexPath is seldom a good thing.
You have a couple of issues, however, as your sounds identifiers don't start from 0, you can't use the identifier as a direct index into the array, but also the identifiers aren't sequential, so you can't even use a simple offset (adding a constant value to the row number).
I think that the best solution is not to rely directly on intrinsic types as you are for your dictionary, but rather, create a struct for each sound and store an array of them. Something like this:
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDatasource
struct Sound {
var id:Int
var fileName:String
}
var sounds=[Sound]()
func loadSounds() {
let soundsDict =
[1000:"new-mail.caf",
1001:"mail-sent.caf",
1002:"Voicemail.caf",
1003:"ReceivedMessage.caf",
1004:"SentMessag.caf",
1005:"alarm.caf",
1006:"low-power.caf",
1007:"sms-received1.caf",
1008:"sms-received2.caf",
...
]
for (id,fileName) in soundsDict {
self.sounds.append(Sound(id: id, fileName: fileName))
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("soundCell", forIndexPath: indexPath)
let button = cell.viewWithTag(3) as! UILabel //UILabel in "SoundCell"
button.text=self.sounds[indexPath.row].fileName
return cell
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sounds.count
}
}
I have a TableViewController (lets call TVC1) with a row that says "OD" (which stands for Outer Diameter).
Upon selecting this row, a bunch of rows in a new TableViewController (lets call TVC2) containing the various OD (casingOD in my code) shows. What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection. My code for this currently fails...Could anyone help point me in the right direction? If you require TVC1 code i'll happily post it, i'm just trying to save any unneccessary code reading for you folks :)
My TVC2 code is as follows:
import UIKit
class CasingSelectionTableViewController: UITableViewController {
var selectedData: Data?
let casingOD = ["114.3", "127.0", "139.7", "168.3" , "177.8", "193.7", "219.1", "244.5", "247.6", "273.1", "298.4", "298.4", "339.7", "406.4", "473.0", "508"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
switch selectedData! {
case .OuterDiameter:
print(casingOD)
case .Weight:
print(casingWeight114) // I deleted the casingWeight114 line of code as its not required for this question
case .InnerDiameter:
print(id114) // I deleted the id114 line as its not required for this question
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return casingOD.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var casingSpec: UITableViewCell!
if selectedData == Data.OuterDiameter {
casingSpec = tableView.dequeueReusableCellWithIdentifier("selectedCasingSpec", forIndexPath: indexPath)
let casingODSpec = casingOD[indexPath.row]
casingSpec.textLabel?.text = casingODSpec
return casingSpec
} else {
return casingSpec
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selection: UITableViewCell!
selection.textLabel?.text = indexPath.row as! String
}
What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection.
First of all you'll need to implement a way for TVC2 to notify TVC1 that a value has been selected.
A common way to do such thing is by using delegation. You can define a delegate protocol like this:
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
Then add a var delegate: TVC2Delegate? property to TVC2.
You'll then make TVC1 comform to TVC2Delegate by implementing that method in TVC1.
When presenting TVC2 from TVC1 remember to set it as the delegate for TVC2.
// In TVC1
tvc2.delegate = self
To connect TVC1 and TVC2 you could add a bit o logic to your tableView(tableView:,didSelectRowAtIndexPath:) method call the delegate with the selected value
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = indexPath.row as! String
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
Finally, in TVC1's implementation of the delegate method you can take care of dismissing TVC2 if needed.
Update:
This is how the final implementation of these bits might look like:
// In TVC1
class TVC1: UITableViewController, TVC2Delegate {
// ...
// Implement the method(s) of TVC2Delegate
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String) {
// Do whatever you need to do with the outerDiameter parameter
}
}
// In TVC2
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
class CasingSelectionTableViewController: UITableViewController {
var delegate: TVC2Delegate?
// ...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = casingOD[indexPath.row]
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
}
Use the delegate approach as suggested in the answer by #Mokagio. And in case you're having issue in getting the string, here is the answer
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! UITableViewCell
let stringValue = cell.textLabel.text //You can get this from your datasource as well)
//call the delegate
}
I'd like to get started using swift to make a small list based application. I was planning on using two table view controllers to display the two lists, and was wondering if it were possible to have them share a common data source.
Essentially the data would just be an item name, and two integers representing the amount of the item owned vs needed. When one number increases, the other decreases, and vice versa.
I figured this might be easiest to do using a single data source utilized by both table view controllers.
I did some googling on shared data sources and didn't find anything too useful to help me implement this. If there are any good references for me to look at please point me in their direction!
You can create one data source class and use it in both view controllers:
class Item {
}
class ItemsDataSource: NSObject, UITableViewDataSource {
var items: [Item] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
//setup cell
// ...
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
}
class FirstViewController : UITableViewController {
var dataSource = ItemsDataSource()
override func viewDidLoad() {
self.tableView.dataSource = dataSource
self.tableView.reloadData()
}
}
class SecondViewController : UITableViewController {
var dataSource = ItemsDataSource()
override func viewDidLoad() {
self.tableView.dataSource = dataSource
self.tableView.reloadData()
}
}
use singleton design pattern, it means both tables will get data source from the same instance
class sharedDataSource : NSObject,UITableViewDataSource{
static var sharedInstance = sharedDataSource();
override init(){
super.init()
}
//handle here data source
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
}
}
var tableOne = UITableView();
var tableTwo = UITableView();
tableOne.dataSource = sharedDataSource.sharedInstance;
tableTwo.dataSource = sharedDataSource.sharedInstance;
The first argument to the delegate method is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
At that point, your one Datasource delegate can decide which table view is wanting a cell, for example, and return results accordingly.