Values Repeat When Scrolling in TableView - swift

When I tap a button in a custom cell and then scroll down (or up) another cell button is also tapped. I see that it's tapped because the button outlet that I created for the button is disabled.
My cellForRowAtIndexPath has a reuseIdentifier for the cell:
var cell: FeedTableViewCell? = tableView.dequeueReusableCellWithIdentifier("MusicCell") as? FeedTableViewCell
Considering I have the degueueReusableCellWithId in the cellForRowAtIndexPath do I need a prepareForReuse? When I add the prepareForReuse in my custom cell file, the cell just goes back to the default values (obviously because I reset it to the default values). Problem is I want it to keep the value of each indexPath.row.
This is how I'm querying the values:
override func queryForTable() -> PFQuery {
var query:PFQuery = PFQuery(className:"Music")
if(objects?.count == 0)
{
query.cachePolicy = PFCachePolicy.CacheThenNetwork
}
query.orderByAscending("videoId")
return query
}
This is the numberOfRowsInSection and cellForRowAtIndexPath
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects!.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell: FeedTableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? FeedTableViewCell
if(cell == nil) {
cell = NSBundle.mainBundle().loadNibNamed("FeedTableViewCell", owner: self, options: nil)[0] as? FeedTableViewCell
}
if let pfObject = object {
//I took out the irrelevant methods. I can add them if that makes a difference...
var votes:Int? = pfObject["votes"] as? Int
if votes == nil {
votes = 0
}
cell?.votesLabel?.text = "\(votes!)"
}
I'm registering this in the viewDidLoad above the super.viewDidLoad()
tableView.registerNib(UINib(nibName: "FeedTableViewCell", bundle: nil), forCellReuseIdentifier: cellIdentifier)
This is my button query in the customCell:
#IBAction func heartButton(sender: AnyObject) {
if(parseObject != nil) {
if var votes:Int? = parseObject!.objectForKey("votes") as? Int {
votes!++
parseObject!.setObject(votes!, forKey: "votes")
parseObject!.saveInBackground()
votesLabel?.text = "\(votes!)"
}
}
heartOutlet.enabled = false
}
Any help and suggestions mean a lot.
Thank you.
REFRENCE LINKS I USED:
I referred to several links but they were in objective-c and didn't help:
UICollectionView Tap Selects More Than One Cell
How to use prepareForReuse method
I also referred to the docs, and that didn't help much.

From the code you have posted, it is clear that you are not setting the enabled property of the UIButton with respect to the DataSource(The array and its objects you are using to load the tableview, that is the elements in objects array). Whatever objects that array contains, add a property to determine if the condition for the button should be true or false, and then in cellForRowAtIndexPath set the enabled property of the button according to that. When the button is clicked, add a callback to the ViewController(using a delegate) and set the property there.
Sample Code
In custom cell class:
protocol CellButtonDelegate
{
func buttonClicked(cell : PFTableViewCell)
}
public var delegate : CellButtonDelegate?
public var buttonEnabled : Bool?
{
get
{
return heartOutlet.enabled
}
set
{
heartOutlet.enabled = newValue
}
}
#IBAction func heartButton(sender: AnyObject) {
if(parseObject != nil) {
if var votes:Int? = parseObject!.objectForKey("votes") as? Int {
votes!++
parseObject!.setObject(votes!, forKey: "votes")
parseObject!.saveInBackground()
votesLabel?.text = "\(votes!)"
}
}
delegate?.buttonClicked(self)
}
In ViewController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var cell: FeedTableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? FeedTableViewCell
if(cell == nil) {
cell = NSBundle.mainBundle().loadNibNamed("FeedTableViewCell", owner: self, options: nil)[0] as? FeedTableViewCell
}
if let pfObject = object {
//I took out the irrelevant methods. I can add them if that makes a difference...
var votes:Int? = pfObject["votes"] as? Int
if votes == nil {
votes = 0
}
cell?.buttonEnabled = objects[indexPath.row].isEnabled //The new property you need to add. true by default
cell?.delegate = self //Make sure you implement the delgate
cell?.votesLabel?.text = "\(votes!)"
return cell?
}
func buttonClicked(cell : PFTableViewCell)
{
//Here, get the indexPath using the cell and assign the new property in the array.
}
Please note that the above code is rough. Just get the idea from the code and implement it as per your requirement.

Related

How to implement a completion handler for Firebase update before table.reloadData is called in a different class?

Classes: ViewControllerTableViewCell and homepage ///the cell class is for cells on homepage
Problem: A button press triggers both 1) an IBAction that updated the firebase database (on ViewControllerTableViewCell) and 2) a model(on ViewControllerTableViewCell and homepage) that makes UI changes via table.reloadData.
The problem is the reloadData causes the wrong row to be recognized and updates in firebase for the wrong row. I checked this by removing reloadData and then the correct row is updated in firebase through the IBAction. Presumably this is because reloadData is executed before the database is done and then the row it returns are wrong. I am only having this problem with Xcode 13 btw.
class ViewControllerTableViewCell:
#IBAction func buttonPressed(_ sender: UIButton) {
...
**ref.child("users").child(self.postID).updateChildValues(updates)**
}
#objc func likeButtonTapped() {
// Calls the corresponding function of the delegate, which is set to the view controller.
delegate?.didTapLikeButton(on: row, didLike: !model.didLike)
}
override func awakeFromNib() {
like?.addTarget(self, action: #selector(likeButtonTapped), for: .touchUpInside)
}
func setup(with model: CellModel) {
}
class homepage
//in cellforRowAt {
cell.setup(with: model)
}
// in viewdidLoad {
setupmodels()
}
private func setupModels() {
....
models.append(model)
}
extension homepage: ViewControllerTableViewCellDelegate {
// Called from the cell's button action through the delegate property.
func didTapLikeButton(on row: Int, didLike: Bool) {
// Updates the corresponding cell's model so that it will update the cell when
// the table view reloads with the updated models.
models[row].didLike = didLike
**table.reloadData**
print("BBBBBBBBB")
}
//Added after first answer
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? ViewControllerTableViewCell {
let immy = cell.viewWithTag(1) as? UIImageView
let like = cell.viewWithTag(3) as? UIButton
let person: Userx = people[indexPath.row]
let text = person.Education
cell.lblName.text = person.Education
cell.lblName.text = text?.uppercased()
let person5 = colorArray[indexPath.row]
let person6 = colorArray1[indexPath.row]
let person7 = colorArray2[indexPath.row]
cell.lblName?.layer.masksToBounds = true
cell.lblName?.backgroundColor = person5
like?.backgroundColor = person6
immy?.backgroundColor = person7
cell.lblName.baselineAdjustment = .alignCenters
cell.postID = self.people[indexPath.row].postID
if let PhotoPosts = person.PhotoPosts {
let url = URL(string: PhotoPosts)
print(PhotoPosts, "sttdb")
immy?.sd_setImage(with: url)
}
cell.row = indexPath.row
// Assigns the delegate property to this view controller.
cell.delegate = self
cell.delegate2 = self
let model = models[indexPath.row]
let modell = modelss[indexPath.row]
like?.isUserInteractionEnabled = true
cell.setup(with: model)
cell.setup1(with: modell)
return cell
}
return UITableViewCell()
}
As suggested in comments,
on didTapLikeButton, update changes to your current model.
Instead of tableView.reloadData(), Reload the tableView row
tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .automatic)
I am considering you have only 1 section in tableView, if not then get the section or the indexPath and pass it in the above code.

Ho to create single tableview for two UISegment Control with two different selection check mark using Swift?

I am maintaining UISegmentControl and Search with a single tableview. Here, I am loading the tableview data from a JSON (language list).
Now I have two segment buttons like Source language and Target language and both segments tableviews also have same data. Here, whenever user selects source language a particular row is check marked and if then user clicks target language segment, the same check mark shows. I need to maintain separate data selections, also, I am going to use search bar.
Can you please provide me a solution for two different segment controller buttons but maintaining a single tableview and its data and UI look the same. Checkmark selection should be different and persistent.
My Code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "languagecell", for: indexPath) as! LangCustomCell
let item = langData[indexPath.row]
cell.flag_img.sd_setImage(with:url, placeholderImage: UIImage(named: "usa.png"))
cell.language_label.text = item.languageName
cell.language_label.textColor = UIColor.gray
cell.selectionStyle = .none
//configure you cell here.
if(indexPath.row == selectedIndex) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
Create two separate variables to store selected languages for from and to.
In tableView didSelectRowAt method check save in appropriate variable based on the selectedSegmentIndex. In TableView cellForRowAt check the selected languages with current language. If selectedSegmentIndex and selected language matches use .checkmark else use .none
And create two arrays with type [Language]. In searchBar textDidChange method filter the languages array and reload the tableView.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
struct Language: Equatable {
var title: String
var icon: UIImage?
}
var allLanguages = [Language]()
var filteredLanguages = [Language]()
var selectedFromLanguage:Language?
var selectedToLanguage:Language?
let segmentedControl = UISegmentedControl()
let tableView = UITableView()
let searchBar = UISearchBar()
override func viewDidLoad() {
super.viewDidLoad()
allLanguages = [Language(title: "English", icon: UIImage(named:"uk"))]
filteredLanguages = allLanguages
// add constraints segmentedControl, tableView, searchBar in view
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredLanguages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = filteredLanguages[indexPath.row].title
cell.imageView?.image = filteredLanguages[indexPath.row].icon
if segmentedControl.selectedSegmentIndex == 0 && selectedFromLanguage == filteredLanguages[indexPath.row] {
cell.accessoryType = .checkmark
} else if segmentedControl.selectedSegmentIndex == 1 && selectedToLanguage == filteredLanguages[indexPath.row] {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
// MARK: - Table view Delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if segmentedControl.selectedSegmentIndex == 0 {//from
selectedFromLanguage = filteredLanguages[indexPath.row]
} else {//to
selectedToLanguage = filteredLanguages[indexPath.row]
}
tableView.reloadData()
}
// MARK: - Search bar Delegate
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
filteredLanguages = allLanguages
} else {
filteredLanguages = allLanguages.filter({ $0.title.localizedCaseInsensitiveContains(searchText) })
}
tableView.reloadData()
}
}
Use computed properties like this to persist the selected languages
var selectedFromLanguage:Language? {
get {
if let data = UserDefaults.standard.value(forKey: "fromLanguage") as? Data,
let language = try? JSONDecoder().decode(Language.self, from: data) {
return language
}
return nil
}
set {
if let data = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(data, forKey: "fromLanguage")
}
}
}
var selectedToLanguage:Language? {
get {
if let data = UserDefaults.standard.value(forKey: "toLanguage") as? Data,
let language = try? JSONDecoder().decode(Language.self, from: data) {
return language
}
return nil
}
set {
if let data = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(data, forKey: "toLanguage")
}
}
}
setup an action for your UISegmentControl:
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
// do what you need with your tableView
case 1:
// do what you need with your tableView
default:
return
}
}
when the index change setup your tableView and reload your data

TableView Data Not Reloading Swift

I am working on restaurant app where i need to get all restaurant type..i successfully get all data but tableview not reloading..
var arrSubMenu = [ResataurantType]()
//TableView Datasource And Delegate Method
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(arrSubMenu.count)
return arrSubMenu.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: leftMenuTableViewCell =
tableView.dequeueReusableCell(withIdentifier: "tableCell") as!
leftMenuTableViewCell
cell.name.text = self.arrSubMenu[indexPath.row].type
return cell
}
func getRestaurantType() {
let manager = AFHTTPSessionManager()
manager.requestSerializer = AFJSONRequestSerializer()
manager.get(RESTAURANTTYPE, parameters: nil, progress: nil, success: {
(operation, responseObj) in
if let objDic = responseObj as? [String:Any] {
if let objArray = objDic["RESTAURANT_TYPE"] as? NSArray {
for objType in objArray {
let ObjRestaurant = ResataurantType()
if let objString = objType as? String {
ObjRestaurant.type = objString
}
self.arrSubMenu.append(ObjRestaurant)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}) { (operation, error) in
print(error)
}
}
I call this function in ViewDidLoad() but still i can't polulate tableview with record
-These can be possible reason from my person experience
TableView's dataSource don't set to self.
-Verification :- Break point on tableView Data Source & Delegate methods.
your arrSubMenu array don't contain the single value.
-Verification:- Break point & print the arrSubMenu before reloading the tableView.
You have just to enter:
First a IBOutlet:
#IBOutlet var tableView : UITableView
In viewDidLoad:
tableView.dataSource = self //OR connection between tableView in storyboard and tableView in swift class
When are you calling getRestaurantType() ? Could it be that it is called before the tableview's datasource is assigned ? That could make the tableview appear empty although the underlying data is present. And, unless you call reloadData() at some other point in the program, it will not refresh itself.
Replace below at cell for row at index method
let cell =
tableView.dequeueReusableCell(withIdentifier: "tableCell" ,for: indexPath)as!
leftMenuTableViewCellenter

when I search through search controller, indexPath doesn't change with original array

I did implement search page in my app. I put PFObjects in postsArray, when user searching something, searched object goes in filteredArray. that is what I tried to do.
tableView shows result, but when I tap on it, it show postArray indexPath which is indexPath before I search it. For example, original(postsArray) first item is red, and I searched yello, when I tap yellow tableView Cell, it shows red post.
here is my code for this.
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.myTable.tableHeaderView = self.resultSearchController.searchBar
self.myTable.reloadData()
self.bringAllDatafromParse()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
self.filterdArray.removeAllObjects()
let normalizedSearchText =
searchController.searchBar.text!.lowercaseString
for posts in self.postsArray {
var title = ""
var tag = ""
if let titleText = posts["titleText"] as? String {
title = titleText
}
if let tagText = posts["tagText"] as? String {
tag = tagText
}
let results = "\(title) \(tag)"
if results.lowercaseString.rangeOfString(normalizedSearchText) != nil {
self.filterdArray.addObject(posts)
}
}
self.myTable.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if self.resultSearchController.active{
return self.filterdArray.count
}else
{
return self.postsArray.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as! SearchTVCE
//cell.textLabel!.text = searchResults[indexPath.row]
var postObjects : PFObject!
if self.resultSearchController.active{
postObjects = self.filterdArray.objectAtIndex(indexPath.row) as! PFObject
}else {
postObjects = self.postsArray.objectAtIndex(indexPath.row) as! PFObject
}
//솔드
cell.soldLabel.hidden = true
if (postObjects.objectForKey("sold") as! Bool) == true {
cell.soldLabel.hidden = false
}
// 제목
cell.titleLabel.text = (postObjects.objectForKey("titleText") as! String)
+ " : " + (postObjects.objectForKey("tagText") as! String)
return cell
}
My question is how can I get right indexPath after I get result from search bar.
And after I search and tap on it, it goes another view. but the searchbar doesn't disappear unless I tap on cancel bar button. How do I fix this?
Your code looks good.
However, you are not showing your didSelectRowAtIndexPath function which might give clue for your problem.
My guess is - you are getting index path correct as you are always linking self.filterdArray on tableview. As I understand, everything on UI looks good but only when you tap on first cell, new view controller is loaded with the data that belongs to first cell of non-filtered array. I would advise you to put a check on your didSelectRowAtIndexPath and see if data is being fetched correctly. It should look something like this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if (self.resultSearchController.active) {
myData = (self.filterdArray[indexPath.row])!
} else {
myData = (self.postsArray[indexPath.row])!
}
self.performSegueWithIdentifier("MySegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "MySegue") {
let myViewController = segue.destinationViewController as MyViewController
myViewController.data = self.myData
}
}
And to your other question:
searchbar doesn't disappear unless I tap on cancel bar button. How do
I fix this?
Just call : searchController.active = false

Checkmark disappears when returning to tableview

I have a static cell table view with no segue. This is the same as iPhones Setting->Sounds->Text Tones. There's no problem implementing the checkmark and playing that system sound from another ViewController. When returning to the sound setting ViewController there is no checkmark. I am saving the indexPath.row and indexPath.section in the user defaults. I am retrieving this and storing them in variables. How do I use these variables that now have the indexPath "one for row and one for section" to indicate the row that was previously selected. I have tried the solution on the web, videos and StackoverFlow and i just can't seem to get this.
var rowSelected:Int = 0
var rowSection:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
if let soundIsNotNill = defaults.objectForKey("rowSelectedKey") as? Int{
self.rowSelected = defaults.objectForKey("rowSelectedKey") as! Int}
if let soundIsNotNill = defaults.objectForKey("rowSectionKey") as? Int{
self.rowSection = defaults.objectForKey("rowSectionKey") as! Int}
}
override func viewWillAppear(animated: Bool) {
println(" VDL rowSelected \(rowSelected)")
println(" VDL rowSection \(rowSection)")
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let tappedItem = indexPath.row
rowSelected = tappedItem
let section = indexPath.section
rowSection = section
for row in 0..<13 {
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section)) {
cell.accessoryType = row == tappedItem ? .Checkmark : .None
}
}
println("didSelectRow rowSelected \(rowSelected)")
println("didSelectRow rowSection \(rowSection)")
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(section, forKey: "rowSectionKey")
defaults.setObject(tappedItem, forKey: "rowSelectedKey")
defaults.synchronize()
saveSound()
}
One way to preserve the table's selection state is to give it a restoration identifier string. You can do this either by setting the table view's restorationIdentifier property, or setting it in the table's identity inspector in Interface Builder.