I have an issue that has been vexing me for a while and I feel like I am so close. Here is the situation. I have a tableview that holds different calculus videos and I would like to give my students the ability to download the videos for playing offline. That part works fine. The problem I am having is that when the user clicks on the download button, the download button should hide and show an activity indicator as well as a progress bar (all of which are in a custom cell). I have two problems:
1) The download button does not automatically hide and show the activity view unless I scroll away from it and come back to it.
2) If I do scroll down all of a sudden random cells will not have the download button anymore.
This must be due to the fact that cells are being reused but I thought I was doing things properly although obviously there is a mistake. I will post the relevant code snippets below:
cellForRowAtIndexPath Code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("VideoCell", forIndexPath: indexPath) as! TableViewCell
let currentVideo = videos[indexPath.section][indexPath.row]
cell.downloadButton.addTarget(self, action: "downloadButtonPressed:", forControlEvents: .TouchUpInside)
cell.video = currentVideo
return cell
}
Custom Table View Cell Code:
class TableViewCell: UITableViewCell {
#IBOutlet weak var videoTitleLabel: UILabel!
#IBOutlet weak var progressView: UIProgressView!
#IBOutlet weak var customAccessoryView: UIView!
#IBOutlet weak var downloadButton: UIButton!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
let userDefaults = NSUserDefaults.standardUserDefaults()
var video: Video? {
didSet {
updateUI()
}
}
private func updateUI() {
guard let video = video else {
print("video should not be nil")
return
}
videoTitleLabel.text = video.title
if video.downloadStatus.isDownloading {
progressView.hidden = false
progressView.progress = video.downloadStatus.downloadProgress
downloadButton.hidden = true
activityIndicator.hidden = false
activityIndicator.startAnimating()
} else {
progressView.hidden = true
activityIndicator.stopAnimating()
}
if video.downloadStatus.isSaved {
activityIndicator.stopAnimating()
progressView.hidden = true
downloadButton.hidden = true
}
}
Download request
func downloadButtonPressed(sender: UIButton) {
let buttonPosition = sender.convertPoint(CGPointZero, toView: self.tableView)
guard let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition) else {
print("Error getting index path from button press")
return
}
let section = indexPath.section
let row = indexPath.row
print("Button pressed at section \(section) and row \(row)")//correct row and section
//Code to save Video to Documents directory goes here
let currentVideo = videos[section][row]
guard !currentVideo.downloadStatus.isSaved else {
print("Video is already saved")
return
}
guard let url = currentVideo.url else {
print("Video not found...url is invalid")
return
}
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, url, destination: destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
let progress = Float(totalBytesRead) / Float(totalBytesExpectedToRead)
currentVideo.downloadStatus.isDownloading = true
currentVideo.downloadStatus.downloadProgress = progress
}.response { request,response,data, error in
if let error = error {
print("Failed with error: \(error)")
} else {
print("Downloaded file successfully")
currentVideo.downloadStatus.isDownloading = false
currentVideo.downloadStatus.isSaved = true
self.saveDownloadStatusInDefaultsForVideo(currentVideo, isSaved: true)
}
print("Files currently in the documents directory:")
self.printDocumentsDirectoryContents() //file is there
}
}
In your class TableViewCell you're setting the property hidden of downloadButton to true under certain circumstances.
If you want the button to be visible by default set the property explicitly to false when reusing the cell
Related
I am trying to display IPFS image in swift using UIImageView
. When I run my code in xCode/Simulator the image shows fine.
. When I run my code in Xcode/My iPhone device connected via USB works. I do see the image
But when run the app on my phone (after app is installed via Xcode) the IPFS image is not shows for the UIImageView - what am I doing wrong?
func tableView(_ myTableViewAccount: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TableViewCellAccount = myTableViewAccount.dequeueReusableCell(withIdentifier: "aCell") as! TableViewCellAccount
guard (self.accountsArray.count > 0) else { return cell }
print("accountArray=\(self.accountsArray) indexPath=\(indexPath.row)")
let count = self.accountsArray.count
if (indexPath.row < count) {
guard (self.accountsArray[indexPath.row].name != nil) else { return cell }
cell.accountImgView?.frame.size = CGSize(width: 50, height: 50)
cell.accountImgView.center = view.center
cell.accountImgView.layer.cornerRadius = 18
cell.accountImgView?.clipsToBounds = true
let activityIndicator = UIActivityIndicatorView()
activityIndicator.frame = cell.accountImgView.bounds
cell.accountImgView.addSubview(activityIndicator)
activityIndicator.backgroundColor = UIColor.white
activityIndicator.startAnimating()
let front_img_url = "https://cloudflare-ipfs.com/ipfs/QmYFDgVBMrRfEm5JpVSWeSDAfTUpboEiL8rZyGym24MNVu"
let frontImageURL = URL(string: front_img_url)
if frontImageURL != nil {
DispatchQueue.main.async {
let dataProdFrontImg = try? Data(contentsOf: frontImageURL!)
if let data_front_img = dataProdFrontImg { //fromn img
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
let accountImage = UIImage(data: data_front_img)
cell.accountImgView.image = accountImage
}
}
}
}
return cell
}
... Cell Table is defined like this
class TableViewCellAccount: UITableViewCell {
#IBOutlet weak var accountImgView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
I moved away from UIImage View instead, I used
#IBOutlet weak var accountImgView: WKWebView?
that fixed my issue
I am creating a Tableview inside which is Tableviewcell and on cell there is a label and sound button. For each label there is a sound on button click. When I click for the first time on btn1 the sound plays and the button image changes to "pause" when I click again the same button sound stops and the image changes to "play" works perfectly in this manner but when I click for the first time on one button let suppose btn1 and without clicking it again (stoping sound) I click on btn2, the sound of the btn1 stops and the image of btn1 nor btn2 changes. I want that when I click on btn 2,3, or 4 the previous sound should stop, the image of previous button (means all buttons except the current) should change to "play" and the current clicked button should change to "pause" and the sound of the previous click should stop and the current clicked should play.
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var titleLable: UILabel!
#IBOutlet weak var sound: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
class ViewController: UIViewController , UITableViewDataSource , UITableViewDelegate , GADInterstitialDelegate {
var countsNumberOfButtonClicks = 0
var countsNumberOfInfoBtnClicks = 0
var isFirstTime = false
var player : AVAudioPlayer! = nil
var titleAlert: String!
#IBOutlet weak var myTableView: UITableView!
var toggleState = 1
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell.
{
let myCell = self.myTableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as! TableViewCell
myCell.titleLable.text = self.Duck[indexPath.row]
myCell.sound.tag = indexPath.row
myCell.sound.addTarget(self, action: #selector(self.playSound), forControlEvents: .TouchUpInside)
return myCell
}
#IBAction func playSound(sender: UIButton) {
if toggleState == 1 {
let fullName: String = self.Duck[sender.tag]
let fullNameArr = fullName.componentsSeparatedByString(" ")
let path = NSBundle.mainBundle().pathForResource(fullNameArr[0], ofType:"wav", inDirectory: "sounds")
let fileURL = NSURL(fileURLWithPath: path!)
do {
player = try AVAudioPlayer(contentsOfURL: fileURL)
player.prepareToPlay()
} catch {
print("Problem in getting File")
}
player.play()
sender.setImage(UIImage(named: "pause.png"), forState: UIControlState.Normal)
print("toggle state 1")
toggleState = 2
}
else {
player.pause()
toggleState = 1
sender.setImage(UIImage(named: "play.png"), forState: UIControlState.Normal)
print("Toggle state else")
}
Simulator result
In you class, declare a variable,
var currentlyPlaying : UIButton?
And wherever you play the sound, store the button which played the sound in this variable and whenever this variable is about to be change, reset the previous ones image and store the new one.
#IBAction func playSound(sender: UIButton) {
if currentlyPlaying != sender || toggleState == 1 {
//Set image in previous
currentlyPlaying?.setImage(UIImage(named: "play.png")
if player != nil{
player.pause()
}
let fullName: String = self.Duck[sender.tag]
let fullNameArr = fullName.componentsSeparatedByString(" ")
let path = NSBundle.mainBundle().pathForResource(fullNameArr[0], ofType:"wav", inDirectory: "sounds")
let fileURL = NSURL(fileURLWithPath: path!)
do {
player = try AVAudioPlayer(contentsOfURL: fileURL)
player.prepareToPlay()
} catch {
print("Problem in getting File")
}
player.play()
sender.setImage(UIImage(named: "pause.png"), forState: UIControlState.Normal)
//Store new
currentlyPlaying = sender
print("toggle state 1")
if sender != currentlyPlaying{
toggleState = 2
}
myTableView.reloadData()
}
else {
player.pause()
toggleState = 1
sender.setImage(UIImage(named: "play.png"), forState: UIControlState.Normal)
print("Toggle state else")
}
I have an UIViewController with a UITableView inside. In that table I have few rows and each row have few buttons. When the user click a button then the icon of the button is changed. I managed to save the status of the buttons in an object but I have issues with a bug which is selecting randomly other buttons when I check the selected button.
Here is my code:
// MODEL
class ChecklistItem: NSObject, NSCoding {
// Recorded values
var vehiclePass = 0 // 0 = unchecked, 1 = pass, 2 = fail
var trailerPass = 0 // 0 = unchecked, 1 = pass, 2 = fail
var vehicleComment = String()
var trailerComment = String()
}
// Set the cell of UITableView.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Constants.checklistCell, for: indexPath) as! ChecklistCell
let itemCategory = itemSections[indexPath.section]
let item = itemCategory.checklistItems[indexPath.row]
titleForSelectedQuestion = item.descript
cell.delegate = self
cell.configCell(item)
// 0 = questions is not checked yet ( white icon ), 1 = question was checked as PASS ( green icon ), 2 = question was checked as FAIL ( red icon )
if item.vehiclePass == 0 {
// vehicle pass = white icon & vehicle fail = white icon
cell.vehiclePassButton.setImage(UIImage(named: Constants.whiteTickIcon), for: UIControl.State.normal)
cell.vehicleFailButton.setImage(UIImage(named: Constants.whiteCrossIcon), for: UIControl.State.normal)
}
if item.trailerPass == 0 {
// trailer pass = white icon & trailer fail = white icon
cell.trailerPassButton.setImage(UIImage(named: Constants.whiteTickIcon), for: UIControl.State.normal)
cell.trailerFailButton.setImage(UIImage(named: Constants.whiteCrossIcon), for: UIControl.State.normal)
}
if item.vehiclePass == 1 {
// vehicle pass = green icon & vehicle fail = white icon
cell.vehiclePassButton.setImage(UIImage(named: Constants.greenTickIcon), for: UIControl.State.normal)
cell.vehicleFailButton.setImage(UIImage(named: Constants.whiteCrossIcon), for: UIControl.State.normal)
}
if item.vehiclePass == 2 {
// vehicle pass = white icon & vehicle fail = red icon
cell.vehiclePassButton.setImage(UIImage(named: Constants.whiteTickIcon), for: UIControl.State.normal)
cell.vehicleFailButton.setImage(UIImage(named: Constants.redCrossIcon), for: UIControl.State.normal)
}
if item.trailerPass == 1 {
// trailer pass = green icon & trailer fail = white icon
cell.trailerPassButton.setImage(UIImage(named: Constants.greenTickIcon), for: UIControl.State.normal)
cell.trailerFailButton.setImage(UIImage(named: Constants.whiteCrossIcon), for: UIControl.State.normal)
}
if item.trailerPass == 2 {
// trailer pass = white icon & trailer fail = red icon
cell.trailerPassButton.setImage(UIImage(named: Constants.whiteTickIcon), for: UIControl.State.normal)
cell.trailerFailButton.setImage(UIImage(named: Constants.redCrossIcon), for: UIControl.State.normal)
}
print("For section \(indexPath.section) row: \(indexPath.row) - vehicle status = \(item.vehiclePass)")
print("For section \(indexPath.section) row: \(indexPath.row) - trailer status = \(item.trailerPass)")
return cell
}
}
extension ChecklistVC: ChecklistCellDelegate{
// Check if user pressed Pass or Fail btn for Vehicle/Trailer and scroll to next question
func tappedOnVehicleOrTrailerButtons(vehiclePassBtn: UIButton, vehicleFailBtn: UIButton, trailerPassBtn: UIButton, trailerFailBtn: UIButton, selectedCell: ChecklistCell) {
let indexPath = questionsTableView.indexPath(for: selectedCell)
let item = itemSections[indexPath?.section ?? 0].checklistItems[indexPath?.row ?? 0]
if vehiclePassBtn.isSelected {
item.vehiclePass = 1
}
if vehicleFailBtn.isSelected {
item.vehiclePass = 2
}
if trailerPassBtn.isSelected {
item.trailerPass = 1
}
if trailerFailBtn.isSelected {
item.trailerPass = 2
}
displayChecksLeft()
questionsTableView.reloadData()
print("For section \(indexPath?.section ?? 0) row: \(indexPath?.row ?? 0) - vehicle status = \(item.vehiclePass)")
print("For section \(indexPath?.section ?? 0) row: \(indexPath?.row ?? 0) - trailer status = \(item.trailerPass)")
if (vehiclePassBtn.isSelected || vehicleFailBtn.isSelected) && (trailerPassBtn.isSelected || trailerFailBtn.isSelected) {
displayChecksLeft()
scrollDown()
}
if (vehicleFailBtn.isTouchInside || trailerFailBtn.isTouchInside) {
self.lastIndexPath = indexPath
self.performSegue(withIdentifier: Constants.goChecklistAddComment, sender: nil)
}
}
}
Here is the code for the Cell:
protocol ChecklistCellDelegate {
func tappedOnVehicleOrTrailerButtons(vehiclePassBtn: UIButton, vehicleFailBtn: UIButton, trailerPassBtn: UIButton, trailerFailBtn: UIButton, selectedCell: ChecklistCell)
func tapGestureOnCell(_ selectedCell: ChecklistCell)
}
class ChecklistCell: UITableViewCell {
// Interface Links
#IBOutlet weak var questionName: UILabel!
#IBOutlet weak var vehiclePassButton: UIButton!
#IBOutlet weak var vehicleFailButton: UIButton!
#IBOutlet weak var trailerPassButton: UIButton!
#IBOutlet weak var trailerFailButton: UIButton!
#IBOutlet weak var vehicleCommentLabel: UILabel!
#IBOutlet weak var trailerCommentLabel: UILabel!
#IBOutlet weak var tagIconImageView: UIImageView!
#IBOutlet weak var tagNameLabel: UILabel!
#IBOutlet weak var defectImageView: CustomImageView!
// Constraints Links
#IBOutlet weak var vehicleCommentHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var trailerCommentHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var tagIconHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var tagNameBottomConstraint: NSLayoutConstraint!
#IBOutlet weak var defectImageHeightConstraint: NSLayoutConstraint!
// Properties
var delegate: ChecklistCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let longTapGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.tapEdit(sender:)))
addGestureRecognizer(longTapGesture)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
// Detect when the user press Long Tap on any cell
#objc func tapEdit(sender: UITapGestureRecognizer) {
delegate?.tapGestureOnCell(self)
}
// Config the cell for Defect and Damage Check
func configCell(_ checklistItem: ChecklistItem){
questionName.text = checklistItem.descript
defectImageView.image = UIImage(named: String(checklistItem.imagesPath?.last ?? String()))
vehicleCommentLabel.text = checklistItem.vehicleComment
trailerCommentLabel.text = checklistItem.trailerComment
tagIconImageView.image = UIImage(named: Constants.tagIcon)
}
// Function to change the icon when the user press Pass or Fail Btn
func changeIconOnBtnTap(checkedBtn: UIButton, uncheckedBtn: UIButton){
if checkedBtn.isSelected == true {
checkedBtn.isSelected = true // user can't uncheck the button
uncheckedBtn.isSelected = false // the unchecked button is white now
}else {
checkedBtn.isSelected = true
uncheckedBtn.isSelected = false
}
}
// Check if the user press Pass or Fail btn for Vehicle or Trailer
#IBAction func passOrFailBtnTapped(_ sender: UIButton) {
sender.isSelected = true
delegate?.tappedOnVehicleOrTrailerButtons(vehiclePassBtn: vehiclePassButton,
vehicleFailBtn: vehicleFailButton,
trailerPassBtn: trailerPassButton,
trailerFailBtn: trailerFailButton,
selectedCell: self)
}
}
Here is a capture with the bug:
Thanks for reading this.
The problem is here tappedOnVehicleOrTrailerButtons
if vehiclePassBtn.isSelected {
item.vehiclePass = 1
}
if vehicleFailBtn.isSelected {
item.vehiclePass = 2
}
if trailerPassBtn.isSelected {
item.trailerPass = 1
}
if trailerFailBtn.isSelected {
item.trailerPass = 2
}
you entirely depend on the buttons isSelected property which state can be dequeued to change the model values , but you should check the current model value and act accordingly
Since you send this parameter , selectedCell: ChecklistCell use it like the other method
let indexPath = tableView.indexPath(for:cell)!
let itemCategory = itemSections[indexPath.section]
let item = itemCategory.checklistItems[indexPath.row]
// then check the value in item and go on
another thing also don't reload the whole table here
questionsTableView.reloadData()
but only the affected indexPath
questionsTableView.reloadRows([indexPath],for:.none)
and put it at end of the function
Here is the fix if someone want to implement the same functionality:
// Config the cell for Defect and Damage Check
func configCell(_ checklistItem: ChecklistItem){
questionName.text = checklistItem.descript
// Detect when user press Pass or Fail on any button and execute the assigned function to change the icon
vehiclePassButton.addTarget(self, action: #selector(vehiclePassButtonTapped), for: UIControl.Event.touchUpInside)
vehicleFailButton.addTarget(self, action: #selector(vehicleFailButtonTapped), for: UIControl.Event.touchUpInside)
trailerPassButton.addTarget(self, action: #selector(trailerPassButtonTapped), for: UIControl.Event.touchUpInside)
trailerFailButton.addTarget(self, action: #selector(trailerFailButtonTapped), for: UIControl.Event.touchUpInside)
}
#objc func vehiclePassButtonTapped(){
changeIconOnBtnTap(checkedBtn: vehiclePassButton, uncheckedBtn: vehicleFailButton)
}
#objc func vehicleFailButtonTapped(){
changeIconOnBtnTap(checkedBtn: vehicleFailButton, uncheckedBtn: vehiclePassButton)
}
#objc func trailerPassButtonTapped(){
changeIconOnBtnTap(checkedBtn: trailerPassButton, uncheckedBtn: trailerFailButton)
}
#objc func trailerFailButtonTapped(){
changeIconOnBtnTap(checkedBtn: trailerFailButton, uncheckedBtn: trailerPassButton)
}
// Function to change the icon when the user press Pass or Fail Btn
func changeIconOnBtnTap(checkedBtn: UIButton, uncheckedBtn: UIButton){
if checkedBtn.isSelected{
checkedBtn.isSelected = true // user can't uncheck the button
uncheckedBtn.isSelected = false // the unchecked button is white now
} else{
checkedBtn.isSelected = false
}
checkedBtn.isSelected = false // reset button status
uncheckedBtn.isSelected = false
}
Thanks Sh_Khan for trying to help me !!!
I am working on a "to do" app in Swift and using Firebase as my backend.
On the first tap on my checkbox to signal that a task has been done, the UI updates and the variable should become true (a checkmark appears) but firebase and the local instance of the bool value for that item do not update to false. After a second tap, the UI continues to have normal functionality (the checkmark disappears); but firebase and the local instance both update to true. Following taps from then on are reversed (True for no checkmark and false for checkmark). When I stop the simulator in Xcode and re-run, the values and UI that load in are correct. It is not until I try and tap on the checkmark that I get the incorrect functionality again. Firebase only updates after the second tap and change in UI. I have include just the code that pertains to the checkbox. I think the problem happens in the doneHit function but I am not quite sure why its happening.
Please help. If there is an easier way to go about this, that would be helpful too.
protocol TaskCellDelegate {
func doneHit(cell : TaskCell)
}
class TaskCell : UITableViewCell {
var delegate : TaskCellDelegate?
#IBOutlet weak var label: UILabel!
#IBOutlet weak var checkBox: CheckBox!
#IBOutlet weak var detailLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
checkBox.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
}
func buttonClicked(sender:UIButton) {
delegate?.doneHit(self)
}
}
class CheckBox: UIButton {
//images
let checkedImage = UIImage(named: "checkedbox")
let unCheckedImage = UIImage(named: "uncheckedbox")
//bool propety
var isChecked:Bool = false{
didSet {
if isChecked == true{
self.setImage(checkedImage, forState: .Normal)
}
else {
self.setImage(unCheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
}
func buttonClicked(sender:UIButton) {
if(sender == self){
if isChecked == false {
isChecked = true
} else {
isChecked = false
}
}
}
}
func doneHit(cell:TaskCell) {
if let ip = tableView.indexPathForCell(cell) {
var task = tasks[ip.row]
task.done = cell.checkBox.isChecked
if task.done == true {
task.completedBy = "Completed by: \(self.user)"
cell.label.textColor = UIColor.grayColor()
}
else {
task.completedBy = ""
cell.label.textColor = UIColor.blackColor()
}
let taskNameRef = self.ref.childByAppendingPath("tasks/\(task.title)")
let completedByData = ["completedBy": "\(self.user)"]
let doneData = ["done": task.done]
taskNameRef.updateChildValues(completedByData)
taskNameRef.updateChildValues(doneData)
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! TaskCell
// Configure the cell...
cell.selectionStyle = .None
let idx = tasks[indexPath.row]
if let label = cell.viewWithTag(1) as? UILabel {
label.text = idx.title
if idx.done == true {
label.textColor = UIColor.grayColor()
} else {
label.textColor = UIColor.blackColor()
}
if let checkBox = cell.viewWithTag(2) as? CheckBox {
checkBox.isChecked = idx.done
}
if let userCompleted = cell.viewWithTag(3) as? UILabel {
if idx.done == true {
userCompleted.text = "Completed By: \(idx.completedBy)"
}
else {
userCompleted.text = ""
}
}
}
print("Task.done is: \(idx.done)")
print("isChecked is:\(cell.checkBox.isChecked)")
cell.delegate = self
return cell
}
Figured it out myself after 3 days. I scrapped the Checkbox class and turned the checkbox into a UIImageView and added a gesture recognizer. The rest was just moving the Checkbox logic into the TaskTVC class under the doneHit method.
I have UITableViewCells that contains a custom button, which is an image. When the button is pressed, the image changes. However, when I scroll up and down the tableView, the cell that is tapped no longer displays the right image, or the "tapped" image is displaying elsewhere. I understand this problem is related to the cells being dequeued and reused. What I've tried to do is create a delegate in my custom cell class. The delegate calls a method in the tableViewController class. I am doing this with the code
self.delegate?.pressedButtonInCell?(self)
However this is not working..why?
=======custom cell class====
import UIKit
#objc protocol BoostCellDelegate{
optional func pressedButtonInCell(cell:BoostCell)
optional func displayAlert (title:String , error: String)
}
class BoostCell: UITableViewCell {
var delegate:BoostCellDelegate?
var boosted = false
var boostedButton = UIImage(named: "boostButtons.png")
#IBOutlet var userImage: UIImageView!
#IBOutlet var name: UILabel!
#IBOutlet var createdAt: UILabel!
#IBOutlet var boostButton: UIButton!
#IBAction func boostPressed(sender: UIButton) {
let objectId = sender.titleLabel!.text!
var query = PFQuery(className:"boostPost")
query.getObjectInBackgroundWithId(objectId) {
(boostPost: PFObject!, error: NSError!) -> Void in
if error != nil {
NSLog("%#", error)
self.delegate?.displayAlert?("Something's gone wrong", error: "Might be the bad reception")
} else {
if boostPost != nil{
if boostPost["boostedBy"] != nil {
var boostedByArray : NSArray = boostPost["boostedBy"] as NSArray
if boostedByArray.containsObject(PFUser.currentUser().username){
println("username already boosted")
}
else{
var boostNumber = boostPost["boostNumber"] as Int
boostNumber++
boostPost["boostNumber"] = boostNumber
boostPost.addObject(PFUser.currentUser().username, forKey:"boostedBy")
boostPost.saveInBackground()
println("new username added to boosted")
self.delegate?.pressedButtonInCell?(self)
}
}
else if boostPost["boostedBy"] == nil{
var boostNumber = boostPost["boostNumber"] as Int
boostNumber++
boostPost["boostNumber"] = boostNumber
boostPost.addObject(PFUser.currentUser().username, forKey:"boostedBy")
boostPost.saveInBackground()
println("new username added to boosted")
self.delegate?.pressedButtonInCell?(self)
=====================================code in tableViewController class =========
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as BoostCell
cell.name.text = userNewNames[indexPath.row]
cell.content.text = userNewContent[indexPath.row]
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
cell.createdAt.text = userCreatedAt[indexPath.row]
cell.delegate = self
if userBoostedBy[indexPath.row].count == 2 {
cell.boostedBy.text = "1 boost"
}
else if userBoostedBy[indexPath.row].count > 2 {
var count = userBoostedBy[indexPath.row].count - 1
cell.boostedBy.text = "\(count) boosts"
}
else {
cell.boostedBy.text = "0 boost"
}
if (userButtonState[indexPath.row] == true) {
cell.boostButton.setImage(self.boostedButton, forState: UIControlState.Normal)
}
else {
cell.boostButton.setImage(self.unBoostedButton, forState: UIControlState.Normal)
}
userImagesFiles[indexPath.row].getDataInBackgroundWithBlock({ (imageData:NSData!, error:NSError!) in
if error == nil {
let image = UIImage(data: imageData)
cell.userImage.image = image
}
})
cell.boostButton.setTitle(objectIds[indexPath.row], forState: UIControlState.Normal)
func pressedButtonInCell(cell:BoostCell)
{
cell.boostButton.setImage(self.boostedButton, forState: UIControlState.Normal)
}
// function to display the alert message
func displayAlert (title:String , error: String){
var alert = UIAlertController(title: title, message: error, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: {
action in
}))
self.presentViewController(alert, animated: true, completion: nil)
}
return cell
}
It looks like your cellForRowAtIndexPath method uses userButtonState[indexPath.row] to determine whether to change the image for the boostButton. So, you just need to amend your pressedButtonInCell method to set userButtonState for the relevant row:
func pressedButtonInCell(cell:BoostCell)
{
cell.boostButton.setImage(self.boostedButton, forState: UIControlState.Normal)
if let indexPath = self.tableView.indexPathForCell(cell) {
userButtonState[indexPath.row] = true
}
}
(This assumes self.tableView points to your table view; if not amend to provide the correct reference). Now, when you press the button on a row, the relevant element of the array will be set to true. If the row then scrolls off screen, when it scrolls back on again, the tableView:cellForRowAtIndexPath: method will check userButtonState, find it to be true and so update the image for the button. Conversely, if userButtonState is false, the image is reset (just in case the re-used cell happened to have the boostedButton image).