I am making a forum app similar to stack overflow. There is a UITableView displaying the answers to a question, and each cell (which contains an answer) also contains a 'correct answer' tick button. Only one answer can be selected as the correct answer at any given time. The correct answer is marked with a green tick, whilst the others are marked with a grey tick (I have two identical images but with different colors). I am trying to set it so when the user clicks on a tick button that is grey, the currently green button turns grey, and the grey button that was tapped turns green.
#IBOutlet weak var answeredTick: UIButton!
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
cell.answeredTick.tag = indexPath.row
}
#IBAction func answerTickPressed(sender: AnyObject) {
guard let answeredTick = sender as? UIButton else { return }
//indexOfOfficialAnswerId is the currently registered official ID
if let buttonToMakeGrey = answeredTick.viewWithTag(self.indexOfOfficialAnswerId) as? UIButton {
print("success!")
buttonToMakeGrey.setImage(UIImage(named: "greyTick.png"), forState: UIControlState.Normal)
} else {
print("no success")
}
}
However it always prints "no success". The value of indexOfOfficialAnswerId is correct during the if statement. I am able to make answerTicks that are grey turn green, but i cant make the green one grey. Why is this?
UPDATE: It appears that i can make the green tick grey if i click on the green tick, but not if i click on any of the grey ticks
You can find cell with green tick as so:
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.indexOfOfficialAnswerId inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
And then use your:
cell.answeredTick.setImage(UIImage(named: "greyTick.png"), forState: UIControlState.Normal)
Sorry for Objective-C code
Try reloading that cell after setting that button image or changing its state as selected. I guess that might be the problem .
Related
Screenshot of the app:
I have an 'x' button to delete a TableViewCell which is in a table within a table. On click of the button, I would like to remove the cell, so I need to know the 2 indexes of the button click, the row if the first table view, and then within that tableview the row of the cell which the button was clicked. All I have is the sender.
So to be a bit clearer, in the screenshot, if someone clicks the x under the ford fiesta, I need to get indexpath 0 for the "subtableview" and 1 for the tableview, and that way I know to delete this element from the table datasource.
I do it successfully by doing:
var cell = sender.superview
while (cell != nil) && !((cell?.isKind(of: CustomCell.self))!) {
cell = cell?.superview
}
let tbl = cell?.superview as! UITableView
let indexPath = tbl.indexPath(for: (cell as? UITableViewCell)!
)
The stupid thing is I have to do it twice, once to find the index of the cell within the "sub"tableview, and then again to find the index of the "subtableview" within the tableview.
Is there a better way to do this? Isnt there a way to get the buttonClick to get the didSelectRowAt to fire and add the sender object to it (so I know that a button was clicked as opposed to the cell being selected)?
EDIT I forgot to mention that the first tableview opens and closes on click, so the main tableview has 2 different cell types, one closed (so no nested tableview) and then onselect of a row from that tableview, the cell is replaced with a detailed cell which has another tableview inside it, thats why sectioned tableview isnt a solution (to the best of my knowledge, I'm new here)
One way to do it is to use closures. You set up your cell with a closure and then call it. Pretty much like this:
class CellWithClosure: UITableViewCell {
var button: UIButton = UIButton()
var closureForButton: (Void) -> Void
func setupCell(closureForButton: #escaping (Void) -> Void) {
self.closureForButton = closureForButton
button.addTarget(self, action: #selector(buttonAction), for: UIControlEvents.touchUpInside)
}
#objc func buttonAction() {
closureForButton()
}
}
In my swift app I have a UITableView with one static cell and many dynamic cells.
Static cell contains different fields, such as labels, map (taken from MapKit) and a button, that indicates whether user voted up or not.
Now, when user presses the button, I want to change its color, possibly without refreshing anything else.
So far my code looks like this:
var currentUserVote:Int = 0
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).row == 0 {
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
fetchScore(cell.score)
let voteUpImage = UIImage(named: "voteUp");
let tintedVoteUpImage = voteUpImage?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
cell.voteUpButton.setImage(tintedVoteUpImage, for: UIControlState())
checkUsersVote() { responseObject in
if(responseObject == 1) {
cell.voteUpButton.tintColor = orangeColor
} else if (responseObject == -1){
cell.voteUpButton.tintColor = greyColor
} else {
cell.voteUpButton.tintColor = greyColor
}
self.currentUserVote = responseObject
}
//map handling:
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: latitude, longitude: longitude)
centerMapOnLocation(initialLocation, map: cell.mapView, radius: regionRadius)
//cell.mapView.isScrollEnabled = false
cell.mapView.delegate = self
.
.
.
return cell
} else {
//handle dynamic cells
}
}
So in the method above I'm checking if user voted already and based on that I'm setting different color on the button. I'm also centering the map on a specific point.
Now, since it's a static cell, I connected IBAction outlet to that button:
#IBAction func voteUpButtonAction(_ sender: AnyObject) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
}
and the vote method works as follows:
func vote(_ vote: Int){
let indexPath = IndexPath(row: 0, section: 0)
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
switch(vote) {
case 1:
cell.voteUpButton.tintColor = orangeColor
case 0:
cell.voteUpButton.tintColor = greyColor
case -1:
cell.voteUpButton.tintColor = greyColor
default:
cell.voteUpButton.tintColor = greyColor
}
tview.beginUpdates()
tview.reloadRows(at: [indexPath], with: .automatic)
tview.endUpdates()
currentUserVote = vote
//sending vote to my backend
}
My problem is, that when user taps the button, he invokes the method vote, then - based on the vote, the button changes color, but immediately after that method cellForRow is called and it changes the color of the button again. Also, it refreshes the map that's inside of it.
What I want to achieve is that when user taps the button, it should immediately change its color and that's it. Map stays untouched and the button is not changed again from cellForRow.
Is there a way of refreshing only that particular button without calling again cellForRow?
First of all, you confuse static and dynamic cells. You can use static cells only in the UITableViewController and you can't use static and dynamic cell at the same time.
I strongly recommend you not to use cell for storing map and button. All elements from the cell will be released after scrolling it beyond the screen.
I can advise you use TableViewHeaderView for this task. In this case you will be able set button and map view as #IBOutlet.
(See more about adding tableview headerView. You can also set it from interface builder.)
Another way is change tableView.contentInset and set your view with map and button as subview to tableView. This method is used when you need create Stretchy Headers.
It should be quite easy, simply do it in your button handler. The sender argument there is the button object that caused the action. When you were connecting it from IB there was a dropdown to select sender type, you may have missed it and the whole thing would have been obvious with UIButton type there.
Simply change your handler like this :
#IBAction func voteUpButtonAction(_ sender: UIButton) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
sender.backgroundColor = yourFavouriteColor
}
Another approach would be to create an IBOutlet for your button, since its from a static cell, and then you would be able to reference it from any place in your view controller.
In this call:
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I see it calls checkUsersVote() which I'm guessing should get the updated value set in the vote() call. Could the problem be that you aren't doing this
currentUserVote = vote
until after reloadRows() is called?
I am programmatically creating cells and adding a delete button to each one of them. The problem is that I'd like to toggle their .hidden state. The idea is to have an edit button that toggles all of the button's state at the same time. Maybe I am going about this the wrong way?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("verticalCell", forIndexPath: indexPath) as! RACollectionViewCell
let slide = panelSlides[indexPath.row]
cell.slideData = slide
cell.slideImageView.setImageWithUrl(NSURL(string: IMAGE_URL + slide.imageName + ".jpg")!)
cell.setNeedsLayout()
let image = UIImage(named: "ic_close") as UIImage?
var deleteButton = UIButton(type: UIButtonType.Custom) as UIButton
deleteButton.frame = CGRectMake(-25, -25, 100, 100)
deleteButton.setImage(image, forState: .Normal)
deleteButton.addTarget(self,action:#selector(deleteCell), forControlEvents:.TouchUpInside)
deleteButton.hidden = editOn
cell.addSubview(deleteButton)
return cell
}
#IBAction func EditButtonTap(sender: AnyObject) {
editOn = !editOn
sidePanelCollectionView.reloadData()
}
I think what you want to do is iterate over all of your data by index and then call cellForItemAtIndexPath: on your UICollectionView for each index. Then you can take that existing cell, cast it to your specific type as? RACollectionViewCell an then set the button hidden values this way.
Example (apologies i'm not in xcode to verify this precisely right now but this is the gist):
for (index, data) in myDataArray.enumerated() {
let cell = collectionView.cellForRowAtIndexPath(NSIndexPath(row: index, section: 0)) as? RACollectionViewCell
cell?.deleteButton.hidden = false
}
You probably also need some sort of isEditing Boolean variable in your view controller that keeps track of the fact that you are in an editing state so that as you scroll, newly configured cells continue to display with/without the button. You are going to need your existing code above as well to make sure it continues to work as scrolling occurs. Instead of creating a new delete button every time, you should put the button in your storyboard and set up a reference too and then you can just use something like cell.deleteButton.hidden = !isEditing
I'm writing a to do list app, and when I click a UIButton to add a new task, I want a new TableViewCell to pop up, that contains a UITextField. However, when I set this UITextfield to first responder, with the following code:
let cellIdentifier = "TaskTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: newIndexPath) as! TaskTableViewCell
cell.nameLabel.becomeFirstResponder()
However when I do this, a keyboard pops up but a cursor does not, and when I type, I can see autocorrect options, but no characters ever appear in the textfield.
Wondering what's going on? Thanks!
You should create IBOutlet for textField and IBAction for button. And it should look like this:
class TaskTableViewCell: UITableViewCell
{
#IBAction func buttonPressed()
{
self.myTextField.becomeFirstResponder()
}
}
I have a tableView with custom tableViewCell. Each cell has an image on the left (see screen 1). The custom tableViewCell has been subclassed. Every row has a different image which depends on indexPath.row
If I click on the image(s) the screen, the images should change to tick boxes so user can multiselect (see screen 2). If I click back on the image with a tick, it should revert back to old image on screen 1.
Issue :
I know how to change the image to tick mark but don't know how to go back to old image (pls remember every row has a different image - so the "else" part in my below code will not make sense).
Below is my code to change and revert the image
#IBAction func imageTap(sender: AnyObject) {
let imageView : UIImageView! = sender.view! as! UIImageView
if imageView.image != UIImage(named: "Oval 5"){
imageView.image = UIImage(named: "Oval 5")
awardBadge.hidden = false
}
else // Need to figure out how to go back to same image
{
imageView.image = UIImage(named: "Oval 1")
awardBadge.hidden = true
}
}
Add a boolean named "isTicked", or something like that, to your cell and set it to the right value at selecting / deselecting. Based on this value, you can set the right image. Let the cell do the rest.
Comparing images makes it unnecessarily complex and less performing.
You should track selected items. The easiest way would be to define an array of booleans in the view controller's subclass.
Define the delegate for you UITableViewCell's subclass like:
protocol MYCustomCellDelegate {
func didSelectedCell(cell: MYCustomCell)
}
And handle it in your controller:
func didSelectedCell(cell: MYCustomCell)
{
let indexPath = tableView.indexPath(cell: cell)
selectedItems[indexPath.row] = !selectedItems[indexPath.row]
tableView.reloadItemsAtIndexPaths([indexPath])
}
In the cellForItemAtIndexPath method set the state of you cell according to the selectedItems[indexPath.row]. If it is selected show the checkbox, if it is not - image:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as! MYCustomCell
cell.setTitle(titles[indexPath.row])
cell.setSelected(selectedItems[indexPath.row])
cell.delegate = self
return cell
}
i think you approach would be different, why don't you use the custom image button with 2 different images which represents selected and normal state. available states are normal/highlighted/selected/disabled. when we have custom image button, since you are using the static images, you would use your images for default state and tick image for selected state. all you have to do is just use the inbuilt property selected.