Swift UITableView messing up after reloadData() - ios

I'm developing an iOS app where I want to reload information of a UITableView from a different controller. In order to do that, I have a reference of the controller MainViewController with the UITableView in another controller called (AirlinesController).
I have an issue reloading the data of the UITableView of the first controller where it messes up pretty much:
MainViewController
Each cell of the table you can see leads to AirlinesController:
AirlinesController
So after the user clicks on the "Apply" button, the UITableView of MainViewController reloads using mainView.reloadData(). In this example I would want to see in the MainViewController that the cell with the title "Embraer" has a green label with the text "Completed" and the cell with the title "Airbus" has a yellow label with the title "Employee" but this is the result I get after reloading the table:
Why did the last cell of the table change one of its labels color to yellow?
This is the code I'm using:
MainViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! CellController
let items = self.gameView.data!.levels
cell.model.text = items[indexPath.row].name
if items[indexPath.row].id < self.gameView.game!.level.id {
cell.cost.text = "Complete"
cell.cost.textColor = UIColor.systemGreen
cell.accessoryType = .none
cell.isUserInteractionEnabled = false
} else if items[indexPath.row].id == self.gameView.game!.level.id {
cell.cost.text = "Employee"
cell.cost.textColor = UIColor.systemYellow
cell.accessoryType = .none
cell.isUserInteractionEnabled = false
} else {
cell.cost.text = "\(items[indexPath.row].XP) XP"
}
return cell
}
AirlinesController
#IBAction func apply(_ sender: Any) {
if (mainView.game.XP >= level.XP) {
let new_salary = Int(Float(mainView.game.salary) * level.salaryMultiplier)
let new_XP = Int(Float(mainView.game.XPSalary) * level.XPMultiplier)
mainView.game.salary = new_salary
mainView.game.XPSalary = new_XP
mainView.salaryLabel.text = "\(new_salary) $"
mainView.XPLabel.text = "\(new_XP) XP"
mainView.workingFor.text = "Currently working for \(level.name)"
mainView.game.level = Level(id: level.id, name: level.name, XP: 0, XPMultiplier: 1, salaryMultiplier: 1, aircrafts: [Aircraft]())
mainView.saveGame()
DispatchQueue.main.async {
self.mainView.levelItems.reloadData()
self.navigationController?.popViewController(animated: true)
}
} else {
let alert = UIAlertController(title: "Error", message: "Not enough experience to apply!", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel, handler: nil))
self.present(alert, animated: true)
}
}
How can I fix this?

This is because of the reuse of cells.
Set the default color in else condition.
} else {
cell.cost.textColor = UIColor.gray
cell.cost.text = "\(items[indexPath.row].XP) XP"
}
Another way, you can set the default style property inside the prepareForReuse method of UITableViewCell
class TableViewCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
// Set default cell style
}
}

TableViewCell are reused all the time. My advice is that you have to set the default color every time.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! CellController
cell.cost.textColor = ...your default text color....
...
return cell

Related

button, label is getting hide when i scroll table view up and down

I have a tableview, and in each cell I have one button called drop down. So when user presses any option in my drop down - the hidden elements like one more drop down, one name label, one save button will be visible. So again when user presses my save button again those elements will be hidden. Now the issues is when I select my button in two or three cells and if I scroll up and down automatically which and all cell showing the elements that and all getting hide. I need to show which and all cell is clicked and showed the elements.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartDetailsCell", for: indexPath) as! CartDetailsCell
cell.selectionStyle = UITableViewCellSelectionStyle.none
let notClicked = !selectedIndexPaths.contains(indexPath)
print(notClicked)
cell.noOfQtyOuterView.isHidden = notClicked
cell.saveDataButnOtlet.isHidden = notClicked
cell.noOfQtyButnOutlet.isHidden = notClicked
}
#IBAction func dropDownButnClick(_ sender: Any) {
guard let button = sender as? UIButton else {
return
}
let indexPath = IndexPath(item: button.tag, section: 0)
let cell = self.tableView.cellForRow(at: indexPath) as! CartDetailsCell
dropDown.anchorView = button
dropDown.dataSource = ["Edit", "Cancel"]
dropDown.selectionAction = { [unowned self] (index: Int, item: String) in
switch index {
case 0:
cell.noOfQtyOuterView.isHidden = false
cell.saveDataButnOtlet.isHidden = false
cell.noOfComboOuterViewButn.isHidden = false
case 2:
}
}
Once the button is hidden it will never be un-hidden until you explicitly make it unhidden.
"Now the issues is when i select my button in two or three cells and if i scroll up and down automatically which and all cell showing the elements that and al getting hide"
let cell = tableView.dequeueReusableCell(withIdentifier: "CartDetailsCell", for: indexPath) as! CartDetailsCell
As you are using the cell with the hidden button is reused, it will make the button remain hidden for remaining cells
I suggest to use following pattern, will save you time and you'll have a more reusable and pretty code:
protocol CartDetailsCellDelegate: class {
func didTouchDropDownButton(in cell: CartDetailsCell)
....
}
final class CartDetailsCell: UITableViewCell {
....
weak var delegate: CartDetailsCellDelegate?
#IBAction func didTouchDropDownButton(_ sender: UIButton) {
delegate?.didTouchDropDownButton(in: self)
}
...
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.delegate = self
...
}
extension ViewController: CartDetailsCellDelegate {
func didTouchDropDownButton(in cell: CartDetailsCell) {
// Do your stuff here, you have the cell, don't have to play with tags
}
}

Facebook native Ads are not clickable again in UITableView Cells after scroll

I have implemented facebook native Ads in UITableView, for first 1-2 times it clickable but when I scroll tableview and come again back to the same cell, now Ads are not clicking, I am using swift 3.2
Below is the cell implementation.
let ad = adsManager.nextNativeAd
let cell = self.tableHome.dequeueReusableCell(withIdentifier: "HomeAdsTableViewCell", for: indexPath) as! HomeAdsTableViewCell
cell.message.text = ad?.body
cell.title.text = ad?.title
cell.callToActionButton.setTitle(ad?.callToAction, for: .normal)
if let pic = ad?.coverImage {
cell.postImage.setImageWithIndicator(imageUrl:pic.url.absoluteString)
}
ad?.registerView(forInteraction: cell.postView, with: self)
cell.selectionStyle=UITableViewCellSelectionStyle.none
return cell
//create a new object of nativead in your class//
var previousNativead : FBNativeAd?
let ad = adsManager.nextNativeAd
self.previousNativead?.unregisterView()
let cell = self.tableHome.dequeueReusableCell(withIdentifier: "HomeAdsTableViewCell", for: indexPath) as! HomeAdsTableViewCell
cell.message.text = ad?.body
cell.title.text = ad?.title
cell.callToActionButton.setTitle(ad?.callToAction, for: .normal)
if let pic = ad?.coverImage {
cell.postImage.setImageWithIndicator(imageUrl:pic.url.absoluteString)
}
previousNativead = ad
ad?.registerView(forInteraction: cell.postView, with: self)
cell.selectionStyle=UITableViewCellSelectionStyle.none
return cell
I suggest you follow the steps here. He told me about putting a facebook ad between cells.
https://www.appcoda.com/facebook-ads-integration/
It's like you do not make mistakes in the place you turn the idiot.
You should adapt this part in the link to your own.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if adsCellProvider != nil && adsCellProvider.isAdCellAtIndexPath(indexPath, forStride: UInt(adRowStep)) {
return adsCellProvider.tableView(tableView, cellForRowAtIndexPath: indexPath)
}
else {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellSample", forIndexPath: indexPath) as! SampleCell
cell.lblTitle.text = sampleData[indexPath.row - Int(indexPath.row / adRowStep)]
return cell
} }
You should also use this method on screen refreshes and turns.
func configureAdManagerAndLoadAds() {
if adsManager == nil {
adsManager = FBNativeAdsManager(placementID: "PLACEMENT_ID", forNumAdsRequested: 5)
adsManager.delegate = self
adsManager.loadAds()
}}
override func viewWillAppear(animated: Bool) {
configureAdManagerAndLoadAds()}
Finally you should check the relevant fields, which may not be compatible with content swift 3.
First in your viewDidLoad() check that you add
override func viewDidLoad() {
super.viewDidLoad()
adsManager = FBNativeAdsManager(placementID: "YOUR_PLACEMENT_ID_HERE", forNumAdsRequested: "YourNumAds")
adsManager.delegate = self
adsManager.loadAds()
}
then to comply with the FBNativeAdsManagerDelegate, you will need to add the following method, nativeAdsLoaded().
func nativeAdsLoaded() {
adsCellProvider = FBNativeAdTableViewCellProvider(manager: adsManager, forType: FBNativeAdViewType.genericHeight300)
adsCellProvider.delegate = self
if self.tableview != nil {
self.tableview.reloadData()
}
}
the last thing you should do is to registerView directly to the cell not to postView
ad?.registerView(forInteraction: cell, with: self)
hope that will help you.

IOS Swift how can I highlight a cell on tap with TableView recycling

I have a TableView and a label inside of it called Post. I have a Gesture Tap function and when a user taps that label then the TableView label that was tapped changes color. The issue is with the tableView recycling if I go down the tableView then other cells are also highlighted that were not clicked . How can I fix it so that only the clicked cell is highlighted ? This is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfileTVC", for: indexPath) as! UserProfileTVC
cell.post.text = Posts[indexPath.row]
let post_tap = UITapGestureRecognizer(target: self, action: #selector(UserProfileC.post_tapped(sender:)))
cell.post.addGestureRecognizer(post_tap)
return cell
}
func post_tapped(sender:UITapGestureRecognizer) {
let point = CGPoint(x: 0, y: 0)
let position: CGPoint = sender.view!.convert(point, to: self.TableSource)
if self.TableSource.indexPathForRow(at: position) != nil {
sender.view?.backgroundColor = UIColor.blue
}
}
Again the code works and it highlights the correct TableCell label the issue is when scrolling down the tableView other tableCells label also get highlighted without being clicked .
I have updated the code with the sample given below but it is still giving the same results
if let existingRecognizerView = cell.viewWithTag(101) as UIView? {
existingRecognizerView.backgroundColor = UIColor.white
} else {
let post_tap = UITapGestureRecognizer(target: self, action: #selector(UserProfileC.post_tapped(sender:)))
post_tap.view?.tag = 101
cell.post.addGestureRecognizer(post_tap)
}
In your cellForRowAtIndexPath function you are dequeuing a cell that already has a UITapGestureRecognizer on it with a blue background. You will need to add a tag to its view so that you can get access to it when dequeuing and remove the background color.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfileTVC", for: indexPath) as! UserProfileTVC
cell.post.text = Posts[indexPath.row]
// Check if we already have a GestureRecognizer view
if let existingRecognizerView = cell.post.viewWithTag(101) {
existingRecognizerView.backgroundColor = UIColor.white
} else {
let post_tap = UITapGestureRecognizer(target: self, action: #selector(UserProfileC.post_tapped(sender:)))
cell.post.addGestureRecognizer(post_tap)
post_tap.view.tag = 101
}
return cell
}
func post_tapped(sender:UITapGestureRecognizer) {
let point = CGPoint(x: 0, y: 0)
let position: CGPoint = sender.view!.convert(point, to: self.TableSource)
if self.TableSource.indexPathForRow(at: position) != nil {
sender.view?.backgroundColor = UIColor.blue
}
}
*Note: coded from a mobile device... not tested for syntactical or functional errors.

Reusing cell doesn't work well - TableView

I have a problem about my cell's button.
In my tableView each row is composed by: an image, some labels and a button.
The button has a checkmark image. When it is clicked, the button's image changes.
The problem is that also another button's image changes without reason.
This mistake happens because my cell is reused.
I have tried to use prepareForReuse method in TableViewCell but nothing happens. I've also tried with selectedRowAt but I didn't have any results. Please help me.
Image 1:
Image 2:
This is my func in my custom Cell:
override func prepareForReuse() {
if checkStr == "uncheck"{
self.checkBook.setImage(uncheck, for: .normal)
} else if checkStr == "check"{
self.checkBook.setImage(check, for: .normal)
}
}
func isPressed(){
let uncheck = UIImage(named:"uncheck")
let check = UIImage(named: "check")
if self.checkBook.currentImage == uncheck{
checkStr == "check"
} else self.checkBook.currentImage == check{
checkStr == "uncheck"
}
}
In my tableView:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell: ListPropertyUserCell = tableView.cellForRow(at: indexPath) as! ListPropertyUserCell
let uncheck = UIImage(named:"uncheck")
let check = UIImage(named: "check")
if selectedCell.checkBook.imageView?.image == uncheck{
selectedCell.checkStr = "check"
} else if selectedCell.checkBook.imageView?.image == check{
selectedCell.checkStr = "uncheck"
}
}
From the information in your post, this looks like a cell reuse issue. The problem is that the tableView reuses the cells rather than creating new ones, to maintain performance. If you haven't reset the cell's state, the reused cell will be remain configured in the old state.
For a quick fix, you can implement the prepareForReuse method on UITableViewCell.
However, you'll need to store which cell is 'checked' in your view controller if you want the checkbox to be selected after scrolling the tableView. You can store this yourself, or use the tableView's didSelectRowAtIndexPath method.
Try to do it like this:
var checkBook = UIImageView()
if self.checkBook.image == UIImage(named: "check"){
self.checkBook.image = UIImage(named: "uncheck")
}
else{
self.checkBook.image = UIImage(named: "check")
}
If you're using the click on the entire cell, you can override the setSelected func in your custom cell just like that.
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.checkBook.image = UIImage(named: "check")
} else {
self.checkBook.image = UIImage(named: "uncheck")
}
}
UITableViewCell is reusable. You can't store state of view in cell. You should setup cell in
func tableView(UITableView, cellForRowAt: IndexPath)
method of your data source
The easiest way to achieve that is to implement
func tableView(UITableView, didSelectRowAt: IndexPath)
func tableView(UITableView, didDeselectRowAt: IndexPath)
methods of UITableViewDelegate
Then you can add/remove indexPath to some array in these methods and in cellForRowAtIndexPath setup cell.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("YourTableViewCell") as! YourTableViewCell
if array.contains(indexPath) {
cell.checkBook.image = UIImage(named: "check")
} else {
cell.checkBook.image = UIImage(named: "uncheck")
}
return cell
}
Try my code . here selectindex is use for get selected cell index and selectedindex is NSMutableArray that i store all selected cell value.
var selectindex : Int?
var selectedindex : NSMutableArray = NSMutableArray()
#IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("LikeCell", forIndexPath: indexPath)
let like: UIButton = (cell.viewWithTag(2) as! UIButton)// like button
let comment: UIButton = (cell.viewWithTag(3) as! UIButton) // comment button
comment.setBackgroundImage(UIImage(named: "chat.png"), forState: UIControlState.Normal) // comment button set
like.addTarget(self, action: #selector(self.CloseMethod(_:event:)), forControlEvents: .TouchDown)
comment.addTarget(self, action: #selector(self.CloseMethod1(_:event:)), forControlEvents: .TouchDown)
return cell
}
// This is my like button action method.
#IBAction func CloseMethod(sender: UIButton, event: AnyObject) {
let touches = event.allTouches()!
let touch = touches.first!
let currentTouchPosition = touch.locationInView(self.tableview)
let indexPath = self.tableview.indexPathForRowAtPoint(currentTouchPosition)!
selectindex = indexPath.row
if selectedindex.containsObject(selectindex!) {
sender.setBackgroundImage(UIImage.init(named: "like (1).png"), forState: .Normal)
selectedindex.removeObject(selectindex!)
}else{
sender.setBackgroundImage(UIImage.init(named: "like.png"), forState: .Normal)
selectedindex.addObject(selectindex!)
}
}
I faced this problem recently, and did not find much about it. What solve, after much searching, was to use:
override func prepareForReuse() {
btnAdd.setImage(nil, for: .normal) //here I use to change to none image
super.prepareForReuse()
}
just put this method inside your custom UITableViewCell, and set which item you want to realese stats.

tableViewCell accessory type issue in didSelectRowAtIndexPath

I have a tableView with list of persons. I want to select multiple cells. I've created a dictionary to store selected cells (screenshot).
var checkedSubjects: [Person: Bool] = [Person: Bool]()
Then when I select a cell it shows a checkmark near the cell and save it in my array (screenshot).
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: SearchTableViewCell = tableView.dequeueReusableCellWithIdentifier("CELL", forIndexPath: indexPath) as! SearchTableViewCell
cell.tintColor = UIColor(hex: 0x3f51b5)
cell.subjectNameLabel.text = subjects[indexPath.row].name
cell.subjectDescriptionLabel.text = "(\(subjects[indexPath.row].type))"
let person = Person(id: subjects[indexPath.row].id, name: subjects[indexPath.row].name, type: subjects[indexPath.row].type)
if checkedSubjects[person] != nil {
cell.accessoryType = checkedSubjects[person]! ? .Checkmark : .None
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
let index = indexPath.row
let person = Person(id: subjects[index].id, name: subjects[index].name, type: subjects[index].type)
if tableView.cellForRowAtIndexPath(indexPath)!.accessoryType == .Checkmark {
tableView.cellForRowAtIndexPath(indexPath)!.accessoryType = .None
checkedSubjects[person] = false
counter--
} else {
tableView.cellForRowAtIndexPath(indexPath)!.accessoryType = .Checkmark
checkedSubjects[person] = true
counter++
}
if counter > 0 {
saveBtn.enabled = true
let text = counter == 1 ? "Add \(counter) person" : "Add \(counter) persons"
saveBtn.setTitle(text, forState: UIControlState.Normal)
} else {
saveBtn.enabled = false
saveBtn.setTitle("Choose persons", forState: UIControlState.Normal)
}
}
But when I press this cell one more time I want it to return to default view. It removes checkmark but the text doesn't take empty space (screenshot). The trailing constraint of the label is set to container margin.
I've tried to reloadData() of tableView in didSelectRowAtIndexPath but it doesn't helped.
Any ideas how I can solve this issue?
I think the problem here is that you're trying to manipulate the physical cell in your didSelectRow implementation. This is the wrong way to go about things. Do not attempt to change or even read the accessory type of a cell in your didSelectRow implementation! Instead, operate there entirely on the model (checkedSubjects) and reload the affected row, so that the view picks up changes to the model (because cellForRow will be called).

Resources