I am facing an issue with UITableView.
I want to dynamically fill its cells with data fetched from a remote database, so it takes some times before the data arrived.
Here is the code:
class MailBoxViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var users: [NSDictionary] = []
override func viewDidLoad() {
super.viewDidLoad()
// call to rest API code to get all users in [NSDictionary]
(...)
// set table view delegate and data source
self.tableView.delegate = self
self.tableView.dataSource = self
}
// set number of sections within table view
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// set number of rows for each section
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return self.users.count
}
return 0
}
// set header title for each section
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Users"
}
}
// set cell content for each row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// deque reusable cell
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as UITableViewCell
// set item title
if indexPath.section == 0 {
cell.textLabel?.text = self.users[indexPath.row]["firstname"] as? String
}
return cell
}
}
The problem is that when tableView functions are called to set number of rows for each section and to set cell content for each row, my [NSDictionary] users is still empty.
How could I do to set rows and cells only after my call to rest API code to get all users in [NSDictionary] is finished?
Thank you for any feedback.
Regards
When you get the response from the API, you should set self.users = arrayOfUsersYouReceivedFromServer and then call self.tableView.reloadData(). This
After users is populated, call tableView.reloadData(). This will call your data source methods again.
When you're done fetching the users call tableView.reloadData().
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'm using Xcode 7.0, Swift 2
I'm basically trying to create a custom class that will build a UITable, then in the ViewController I make a new object of the table and load it into self.view;
The problem I'm having is that the function func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell isn't being called at all from within the custom class. I've been looking for a solution for 3 days now and I've tried rebuilding the App and code several times with no luck.
Please note, if I use the same code (that is everything required to build the table; excluding init functions, etc) in the ViewController.swift file, it works fine.
I know the problem is with the cellForRowAtIndexPath function because it will not print out the statement I set in that block of code when it runs. All other functions are called, but for some reason this isn't being called. Not sure if I overlooked something here. Any help would be appreciated. Thanks in advance.
class sideTest: NSObject, UITableViewDelegate, UITableViewDataSource {
let tesTable: UITableView = UITableView()
var items: [String]?
var mView: UIView = UIView()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("The number of rows is: \(self.items!.count)")
return self.items!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("\nLets create some cells.")
let sCell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell!
sCell.textLabel?.text = self.items![indexPath.row]
sCell.textLabel?.textColor = UIColor.darkTextColor()
return sCell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You selected cell #\(indexPath.row)!")
}
func tblSetup() {
self.tesTable.frame = CGRectMake(0, 0, 320, mView.bounds.height)
self.tesTable.delegate = self
self.tesTable.dataSource = self
self.tesTable.backgroundColor = UIColor.cyanColor()
// load cells
self.tesTable.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.tesTable.reloadData()
print("Currenlty in tblSetup.\nCurrent rows is: \(self.items!.count)")
}
//Init
override init() {
super.init()
self.items = nil
self.tblSetup()
}
init(sourceView: UIView , itemListAsArrayString: [String]) {
super.init()
self.items = itemListAsArrayString
self.mView = sourceView
self.tblSetup()
}
}
Here is the code from ViewController.swift; Please do note that the table gets built, but the cells do not populate, even if I manually enter cell info by doing: sCell.textLabel?.text = "test cell"
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let myTable: sideTest = sideTest(sourceView: self.view, itemListAsArrayString: ["Cell 1", "Cell 2", "Cell 3"])
self.view.addSubview(myTable.tesTable)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Again, any help is greatly appreciated. Thanks.
Your view controller don't have a strong reference to your sideTest var.
Once your view did load finished,your sideTest is nil.Although you have a tableview(by add subview), but you no longer have a data source.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {}
is called after view did load. That cause the problem.
change your view controller to:
var tb :sideTest?
override func viewDidLoad() {
super.viewDidLoad()
let myTable: sideTest = sideTest(sourceView: self.view, itemListAsArrayString: ["Cell 1", "Cell 2", "Cell 3"])
print(myTable.tesTable.frame)
tb=myTable
self.view.addSubview(myTable.tesTable)
}
change your cellforrowatindexpath to:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("create cells")
var cell :UITableViewCell?
if let sCell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell"){
cell=sCell
}else{
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
}
cell!.textLabel?.text = self.items![indexPath.row]
cell!.textLabel?.textColor = UIColor.darkTextColor()
return cell!
}
this will fix most of the problems.
Your code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let myTable: sideTest = sideTest(sourceView: self.view, itemListAsArrayString: ["Cell 1", "Cell 2", "Cell 3"])
self.view.addSubview(myTable.tesTable)
}
I would think that the myTable variable goes out of scope and is released when viewDidLoad finishes, so there is no data source or delegate after that. Did you verify that the self.view.addSubview(myTable.tesTable) retains it? Try moving declaration of myTable outside of the function level (to property level) or add a diagnostic print to deinit..
I am writing a note taking app, just for reference. I have arrays set up, and a table that feeds off the arrays with the following code:
import UIKit
import Foundation
var tableData = ["Pancake Recipe", "Costume Party", "Camping Supplies"]
var tableSubtitle = ["Some Milk and some Flour", "Let's dress up like Jen", "Tenting with Lucy"]
class ViewController: UIViewController {
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"cell")
cell.textLabel!.text = tableData.reverse()[indexPath.row]
cell.detailTextLabel!.text = tableSubtitle.reverse()[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
var listTitle = "Notes"
self.title = listTitle
UIApplication.sharedApplication().statusBarStyle = .LightContent
}
override func viewDidAppear(animated: Bool) {
println(tableSubtitle)
}
}
A user creates a new title and subtitle for the cell on a different page, and these are added to the arrays (the tableData and tableSubtitle arrays). I know the adding of the data works correctly, because when I watch the console it prints both the updated arrays perfectly.
When I then return to the main view controller, I am presented with an extra cell (as I wanted) but instead of the new content that I want, it is instead just a duplicate of the 'Pancake Recipe' cell.
Do I need to refresh the content of the cells when the view loads again? If so, how can I do this?
Thanks :)
For reference, here is a picture of the Table View after data has been added to both the arrays twice, and I have then returned to the Table View, despite the fact both the arrays now contain two extra and distinct entries (checked using println(tableData) and println(tableSubtitle)
The provided code does not provide much information to find the issue, probably the issue will be with data adding code.
For refreshing the table view use:
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
self.yourTableView.reloadData()
}
EDIT Answer below in this post
I'm trying to set up a UITableView controller in storyboard, with a separate datasource, and I've hit a wall. The data source doesn't seem to respond to changes or push it's 'updates' to the table view. I've tried implementing the data source in the MainMenuTableViewController which worked fine.
This is my MainMenuTableViewController
override func viewDidLoad() {
super.viewDidLoad()
sharedLightsManager.delegate = self
sharedLightsManager.loadNetworkContext()
dataSource = MainMenuTableViewDataSource(sharedLightsManager: sharedLightsManager)
tableView.dataSource = dataSource
tableView.delegate = dataSource
title = "test"
}
//This method fires each time a change happens
func updateLights(){
lights = sharedLightsManager.localNetworkContext.allLightsCollection.lights
tableView.reloadData()
}
MainMenuDataSource:
class MainMenuTableViewDataSource: NSObject, UITableViewDataSource, UITableViewDelegate
{
let reuseIdentifier = "tableViewCell"
var sharedLightsManager: SharedLightsManager?
var lights = []
init(sharedLightsManager: SharedLightsManager)
{
self.sharedLightsManager = sharedLightsManager
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lights.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("tableViewCell", forIndexPath: indexPath) as UITableViewCell
var lights = sharedLightsManager!.localNetworkContext.allLightsCollection.lights
var light = LFXLight()
if lights.count == 0 {
println("Lights array still loading...")
} else {
light = lights[indexPath.row] as LFXLight
}
return cell
}
}
and here is my outlets:
I've just figured it out. A bit embarrassing. It was due to the lights array not having any objects in it, so obv. lights.count would return 0, therefore no rows...
The data source will not push updates unless the UITableView is told to reloadData. If you change the numberOfRows value, it will not update unless the tableView is notified through methods like insertRowAtIndexPath, reloadData, deleteRowAtIndexPath etc.