ReloadData() Not Refreshing TableView - ios

I have a text field that when I type a word in, and then press a button is supposed to add the word the the tableview. I know the array is being updated because after the button is pressed, the array, with its new value print fine in the console. I've tried reloadData() in several places but it's not doing anything. Here is my code:
import UIKit
class Arraytest: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var textField: UITextField?
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"]
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
**print(guy)**
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
***tableView.reloadData()***
return cell
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
Let's say I type John in the text field, here is what the console prints:
Jack
Andy
Guy
Bruce
Nick
James
Dominick
John
I know the array works fine, but not why the tableView won't reload when everyone claims reloadData() works(I'm sure it does, and I'm just making an easy mistake!)..Any ideas?
EDIT: Ok it turns out that you do have to drag the IBOutlet from the tableview. The reason I didn't do this earlier was because I watched a video and his worked without making the connection.

You should learn more about table view & how it works. Please see Apple Documentation Creating and Configuring a Table View
Here the method cellForRowAt is a data source of table view and it's get fired from tableview automatically when it's need to populate cell data. You couldn't manually call this method. Implementing this method inside #IBAction func addButton() does nothing. Function always needs to call from another method. So you should remove cellForRowAt inside #IBAction func addButton().
Solution: Get an table view outlet from storyboard. If need help see here Connect the UI to Code
#IBOutlet weak var tableView: UITableView?
Then set tableview datasource and delegate in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.dataSource = self;
tableView?.delegate = self;
}
And finally update #IBAction func addButton() as below
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
print(guy)
}
tableView?.reloadData()
}
Full source may look like this:
import UIKit
class Arraytest: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var textField: UITextField?
#IBOutlet weak var tableView: UITableView?
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"]
override func viewDidLoad() {
super.viewDidLoad()
tableView?.dataSource = self;
tableView?.delegate = self;
}
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
**print(guy)**
}
tableView?.reloadData()
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
return cell
}
}

Your code has two very big mistakes (assuming that your tableview delegates are connected to your viewcontroller correctly).
First your a missing an IBOulet reference to your tableview. Create one and you can call the reloadData() method in that outlet.
Second, you should not call the public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell delegate method inside your IBAction. It is a delegate method, it is already listening for an event or command that will fire its logic, in this case reloadData().
So your IBaction should only take the value from the textfield and add it to the array, to finally call reloadData(), which will call public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
this code works... hope it helps:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var textField: UITextField?
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"]
// MARK: tableview delegate methods
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "Cell")
cell.textLabel?.text = names[indexPath.row]
return cell
}
// MARK: ibactions
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
tableview.reloadData()
}
}

You have to remove TableView delegate method from
#IBAction func addButton(_ sender: Any) {
}
method & put it outside of it. Create a property of this tableView form storyboard like that
#IBOutlet weak var tableView: UITableView!
In viewDidload method set datasource & delegate of it like that
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.datasource = self
self.tableView.delegate = self
}
Hope it helps.

You can achieve this by adding a property observer to the array of names. The didset method will run if there is any changes to the value.
class Arraytest: UIViewController, UITableViewDelegate UITableViewDatasource {
var names = ["Jack", "Andy", "Guy", "Bruce", "Nick", "James", "Dominick"] {
didSet {
self.tableView.reloadData();
}
}
}
The tableview func inside the addButton func doesnt run.. remove it and keep it like this:
#IBAction func addButton(_ sender: Any) {
let name : String = textField!.text!
names.append(name)
textField?.text! = ""
for guy in names{
**print(guy)**
}
}

You have to take your tableview func outside the #IBAction func addButton, as it's part of the uitableviewdatasource and is required to display your cell. After that, all you need is to put tableView.reloadData() after the for guy in names{ ... } loop.

Drag a connection from a split view controller to your view controller and use reloadData().

Related

Prevent tableview from being reused (MVVM )

I know how to preserve the action we have done on UITableView, after scrolling back and forth.
Now Iam doing a simple UITableView on MVVM
which has a Follow button . like this.
Follow button changes to Unfollow after click and resets after scrolling.
Where and How to add the code to prevent this?
Here is the tableview Code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Vm.personFollowingTableViewViewModel.count
}
var selectedIndexArray:[Int] = []
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: FollowList_MVVM.PersonFollowingTableViewCell.identifier , for: indexPath) as? PersonFollowingTableViewCell else{
return UITableViewCell()
}
cell.configure(with: Vm.personFollowingTableViewViewModel[indexPath.row])
cell.delegate = self
return cell
}
and configure(with: ) function
#objc public func didTapButton(){
let defaultPerson = Person(name: "default", username: "default", currentFollowing: true, image: nil)
let currentFollowing = !(person?.currentFollowing ?? false)
person?.currentFollowing = currentFollowing
delegate?.PersonFollowingTableViewCell(self, didTapWith: person ?? defaultPerson )
configure(with: person ?? defaultPerson)
}
func configure(with person1 : Person){
self.person = person1
nameLabel.text = person1.name
usernameLabel.text = person1.username
userImageview.image = person1.image
if person1.currentFollowing{
//Code to change button UI
}
custom delegate of type Person is used
I guess your main issue is with Button title getting changed on scroll, so i am posting a solution for that.
Note-: Below code doesn’t follow MVVM.
Controller-:
import UIKit
class TestController: UIViewController {
#IBOutlet weak var testTableView: UITableView!
var model:[Model] = []
override func viewDidLoad() {
for i in 0..<70{
let modelObject = Model(name: "A\(i)", "Follow")
model.append(modelObject)
}
}
}
extension TestController:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TestTableCell
cell.dataModel = model[indexPath.row]
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
extension TestController:Actions{
func followButton(cell: UITableViewCell) {
let indexPath = testTableView.indexPath(for: cell)
model[indexPath!.row].buttonTitle = "Unfollow"
testTableView.reloadRows(at: [indexPath!], with: .automatic)
}
}
class Model{
var name: String?
var buttonTitle: String
init(name: String?,_ buttonTitle:String) {
self.name = name
self.buttonTitle = buttonTitle
}
}
Cell-:
import UIKit
protocol Actions:AnyObject{
func followButton(cell:UITableViewCell)
}
class TestTableCell: UITableViewCell {
#IBOutlet weak var followButtonLabel: UIButton!
#IBOutlet weak var eventLabel: UILabel!
var dataModel:Model?{
didSet{
guard let model = dataModel else{
return
}
followButtonLabel.setTitle(model.buttonTitle, for: .normal)
eventLabel.text = model.name
}
}
weak var delegate:Actions?
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
}
#IBAction func followAction(_ sender: Any) {
delegate?.followButton(cell:self)
}
}
To convert this into MVVM approach, there are few things you need to change and move out.
The loop I have in viewDidLoad shouldn’t be there. That will be some API call, and should be handled by viewModel, and viewModel can delegate that to other repository to handle or handle itself. Upon receiving response viewModel update its state and communicate with View (in our case tableView) to re-render itself.
Code in extension where I am updating model object shouldn’t be in controller (model[indexPath!.row].buttonTitle = "Unfollow"), that has to be done by viewModel, and once the viewModel state changes it should communicate with view to re-render.
The interaction responder (Button action) in Cell class, should delegate action to viewModel and not controller.
Model class should be in its own separate file.
In short viewModel handles the State of your View and it should be the one watching your model for updates, and upon change it should ask View to re-render.
There are more things you could do to follow strict MVVM approach and make your code more loosely coupled and testable. Above points might not be 100% correct I have just shared some basic ideas i have. You can check article online for further follow up.
The above answer works . But I have gone through what suggested by #Joakim Danielson to find what exactly happens when you are updating the View and Why it is not updating on ViewModel
So I made an update to delegate function
ViewController delegate function
func PersonFollowingTableViewCell1( _ cell: PersonFollowingTableViewCell, array : Person, tag : Int)
Here, I called the array in the Viewmodel and assigned the values of array in func argument to it.
like ViewModel().Vmarray[tag].currentFollow = array[tag].currentFollow

how to add label text to tableViewCell

I'm practicing creating an app where I have a label that gets its text from an UITextField when the user presses a button. Now, I added another button and a tableview and I want to be able to "save" the label's text to the table cells with the same mechanism of stopwatch's laps.
So, to be clear, I want the button to transfer the label's text to the table view cells each time I press it.
After your save button, you need to store the texts somewhere and reload the table. (Or insert it with animation)
class ViewController: UIViewController {
#IBOutlet private var textField: UITextField!
#IBOutlet private var tableView: UITableView!
var texts: [String] = [] {
didSet { tableView.reloadData() }
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SimpleCell")
tableView.dataSource = self
}
#IBAction func saveButtonTapped(_ sender: UIButton) {
guard let newText = textField.text else { return }
self.texts.append(newText)
}
}
And in tableView dataSource methods:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return texts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SimpleCell", for: indexPath)!
cell.textLabel?.text = texts[indexPath.row]
return cell
}
}

Allowing the user to create a tableViewCell with text from another viewController?

I'm creating an app, in which one of the functions is, that the user should be able to write a person's name and an answer to a question - and then when pressing the save-button he/she should be redirected to the previous controller again, which not have created a tableViewCell with this data as title. (Later on you can ofcourse click this cell and see the data in third viewcontroller.)
My way of tackling this was to let the "save" button save the name and the answer by using NSUserDefault. Then connecting a segue to the button at the same time to make it redirect the user to the previous controller - and finally to have the tableView in the previous controller refer to the newly created NSUserDefault-key in the cell.textfield.
I have two questions.
Why does this not work? My code from both viewControllers are underneeth. I don't get why it doesn't work.
If I do get this to work: How do I implement the effect, that every time you enter the "Creating viewController", in which you can write the name and the answer - the user gets the option of saving a NEW person and adding a NEW cell, instead of overriding the old one, which I'm afraid will happen if I get the current approach to work...
Code in the "Creating viewController", where you can write the name and the answer:
class CreateNewPerson: UIViewController {
let defaults = UserDefaults.standard
#IBOutlet weak var Question: UILabel!
#IBOutlet weak var ExtraIdentifier: UILabel!
#IBOutlet weak var PersonName: UITextField!
#IBOutlet weak var PersonAnswer: UITextField!
#IBOutlet weak var PersonExtraIdentifier: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
showDiaryIdentifiers () }
func showDiaryIdentifiers () {
let DiaryQuestion = self.defaults.string(forKey: "DiaryQuestionKey")
let ExtraIdentifer = self.defaults.string(forKey: "RandomIdentifierKey")
self.Question.text = DiaryQuestion
self.ExtraIdentifier.text = ExtraIdentifer
}
#IBAction func SavePerson () {
self.defaults.setValue(self.PersonName.text, forKey: "PersonNameKey")
self.defaults.setValue(self.PersonAnswer.text, forKey: "PersonAnswerKey")
self.defaults.setValue(self.PersonExtraIdentifier.text, forKey: "PersonExtraIdentiferKey")
} }
Code in the other viewController:
class AllPersonsInYourDiary: UIViewController, UITableViewDelegate, UITableViewDataSource {
let defaults = UserDefaults.standard
#IBOutlet weak var ShowingDiaryName: UILabel!
#IBOutlet weak var ShowingDiaryQuestion: UILabel!
#IBOutlet weak var ShowingExtraIdentifer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
showDiaryIdentifiers()
self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func showDiaryIdentifiers () {
let DiaryName = self.defaults.string(forKey: "DiaryNameKey")
let DiaryQuestion = self.defaults.string(forKey: "DiaryQuestionKey")
let ExtraIdentifer = self.defaults.string(forKey: "RandomIdentifierKey")
self.ShowingDiaryName.text = DiaryName
self.ShowingDiaryQuestion.text = DiaryQuestion
self.ShowingExtraIdentifer.text = ExtraIdentifer
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = self.defaults.string(forKey: "PersonNameKey")
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
In this code, I guess what is not working is the cellForRowAt method. What am I getting wrong? Right now it's not creating any cells at all.
Also, I know I should notr1 return 1 row and 1 section. It's just for now. I know I should in the end return Something.count - but I haven't yet figured out what this something is...
Thanks!
You already created a table view with only one row.
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
When returning to previous controller just reload tableview like(Make sure before reloading datasource have contain new data.)
tableView.reloadData()
If I understand correctly that you need the user to enter a set of values and then use these values to populate a table view in another view controller, then what you wanna do is:
1- create 2 dictionaries, an optional dictionary in AllPersonsInYourDiary that would carry the new values and one in your CreateNewPerson something like this let dic = [[String: String]]().
2- Instantiate the view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "#yourSegueIdentifier" {
let vc = segue.destination as! AllPersonsInYourDiary
vc.dic = self.dic
}
}
3- in your AllPersonsInYourDiary view controller, override the functions like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dic.count
}
and populate the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = dic[indexPath.row]["#whateverKeyForValue"]
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}

How can I display multiple string values to multiple labels in a custom TableView Cell in swift ios?

var leadername = ["1","2","3","4"]
var districts = ["Delhi","Kerala"]
override func viewDidLoad() {
leadTableSetup()
super.viewDidLoad()
}
func leadTableSetup(){
LeadTableView.delegate = self
LeadTableView.dataSource = self
self.LeadTableView.register(UINib(nibName: "LeaderBoardTableViewCell", bundle: nil), forCellReuseIdentifier: "leadCell")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 14
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "leadCell") as! LeaderBoardTableViewCell
// Set text from the data model
cell.areaLbl.text = districts[indexPath.row]
cell.leaderNameLbl.text = leadername[indexPath.row]
return cell
}
I have declared two strings and I need to display these strings in the labels in my custom collection view cell that I have created. How can I achieve this? I need to display "leadername" string in one label and "districts" label in another label.
Go with this demo, Shared Demo
After the demo, If you still face any problem then let me know.
Now Listen Here
I think you need output something like this,
Follow the steps: -
Create a new viewcontroller(says, CustomTableVC) in your storyboard and one UITableView(give constraints and delegate to its own class), take outlet of UItableView (says, tblMyCustom)
Now press CLT+N for new file and do like this below image, Subclass - UItableViewCell and also tick on XIB option.
Open our xib file, add new UIView (says myView, as you see highted in below image), in this myView add two labels
Now take outlet of these two labels in its customCell class
class CustomTableCell: UITableViewCell {
#IBOutlet var lblLeaderNo: UILabel!
#IBOutlet var lblDistict: 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
}
}
Now back to your Viewcontroller Class
import UIKit
class CustomTableVC: UIViewController , UITableViewDelegate, UITableViewDataSource{
#IBOutlet var tblMyCustom: UITableView!
var leaderno : [String]!
var distict : [String]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tblMyCustom.register(UINib(nibName: "CustomTableCell", bundle: nil), forCellReuseIdentifier: "customCell")
self.leaderno = ["1", "2", "3", "4"]
self.distict = ["Delhi","Kerala", "Haryana", "Punjab"]
// above both array must have same count otherwise, label text in null
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return leaderno.count;
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
var customCell: CustomTableCell! = tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableCell
customCell.lblLeaderNo.text = self.leaderno[indexPath.row]
customCell.lblDistict.text = self.distict[indexPath.row]
return customCell
}
}
above all is code of VC, it is not getting settle down here in single code format, I dont know why.
Now, follow these steps you get output as i show you image in starting of the procedure.

UITextField and UITableView on a single view controller

I'm trying to make a view controller that has one text field that populates the tableview below, ideally the user will be able to continue to add to the tableview without jumping between two views.
I previously had it working with the text field on one view that populates a UITableView and used prepareForSegue to push the data to the table, but I haven't been able to get it to work with just one view.
Can anyone please point out where I'm going wrong or push me to a tutorial / documentation to help?
Edit: Clarity
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
#IBOutlet weak var textField: UITextField!
var items: [String] = ["Pls", "work", "pls", "work", "pls"]
var foodGroup: FoodGroup = FoodGroup(itemName:"")
//var foodGroup: [FoodGroup] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = self.items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("Selected cell #\(indexPath)")
}
func addFood(sender: AnyObject!) {
if (countElements(self.textField.text) > 0) {
self.foodGroup = FoodGroup(itemName: self.textField.text)
}
}
#IBAction func addFoodToList() {
let source = FoodGroup
let foodGroup:FoodGroup = source.foodGroup
if foodGroup.itemName != "" {
self.foodGroup.append(foodGroup)
self.tableView.reloadData()
}
}
}
It seems like your intention here is to have your dataSource be an array of FoodGroup objects. If this is indeed the case you can get rid of your foodGroup instance variable and update your items definition to be like so:
var items = [FoodGroup]()
then in addFoodToList:
if self.textField.text != "" {
let foodGroup = FoodGroup(itemName: self.textField.text)
self.items.append(foodGroup)
self.tableView.reloadData()
}
and finally in cellForRowAtIndexPath:
var cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
let foodGroup = self.items[indexPath.row] as FoodGroup
cell.textLabel.text = foodGroup.itemName
return cell
Also I don't quite see the intention of your the addFood(sender: AnyObject!) function. Looks like cruft. I would get rid of it. Good luck!

Resources