I'm really confused about this problem of checkbox on collectionView.
I'm already create a class component for 'checkbox' : UIbutton and after that i used
with 'collectionview' so my problem when i click on some checkbox of item
i found another item has checking after :
if i check checkbox of item 1 the other item 6 has checking.
class CheckBox: UIButton {
// Images
let checkedImage = UIImage(named: "checkbox")! as UIImage
let uncheckedImage = UIImage(named: "uncheckbox")! as UIImage
// Bool property
var isChecked: Bool = false {
didSet{
if isChecked == true {
self.setImage(checkedImage, forState: .Normal)
} else if isChecked == false {
self.setImage(uncheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
func buttonClicked(sender: UIButton) {
if sender == self {
println(self)
if isChecked == true {
isChecked = false
} else if isChecked == false {
isChecked = true
}
}
}
}
extension TrackersController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.vehicles.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier,forIndexPath:indexPath) as! VehicleCell
let vehicles: [Vehicle] = dataSource.vehiclesInGroup(indexPath.section)
let vehicle = vehicles[indexPath.row]
cell.txt_mat.text = vehicle.name
cell.txt_imei.text = vehicle.imei
cell.check_veh.addTarget(self, action: "selectVeh:", forControlEvents: UIControlEvents.TouchUpInside)
cell.check_veh.tag = vehicle.id_tracker!.toInt()!
cell.check_veh.enabled = vehicle.subscription!
return cell
}
func selectVeh(button: CheckBox) {
if(button.isChecked){
vehIds.append(button.tag)
}else if(!button.isChecked){
var index = find(vehIds, button.tag)
vehIds.removeAtIndex(index!)
// JLToast.makeText(String(button.tag)).show()
}
}
Image of item 1
Image of item 6
You should store the selected indices in an array and later in cellForRowAtIndexPath method you need to check if indexPath is already there in array or not to show/hide the checkbox.
The appearance of the checkbox is due to the reusing of the cell. The cell's content is loaded every time when cell reappears on screen. So you need to update the content accordingly in cellForRowAtIndex method.
You need take one bool variable in Vehicle modal class (Ex:isSelected) and set value for that bool variable like below in selectVeh method
func selectVeh(button: UIButton) {
button.selected = !button.selected
let vehicle:Vehicle = vehicles[button.tag]
vehicle.isSelected = button.selected;
vehicles.replaceObjectAtIndex(button.tag, withObject: vehicle)
}
in cellForItemAtIndexPath add below code
cell.check_veh.selected = vehicle.isSelected
Related
I have a UITableView. Its cell contains a label that will display a question, a yes button and a no button. The goal is to view questions one by one.
First I call the API to get the questions in the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsSelection = false
getQuestions(baseComplainID: "1") { (questions, error) in
self.questions = questions
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
In the cellForRowAt method I display them one by one:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else {
fatalError("Fatal Error")
}
cell.yesButton.isHidden = false
cell.noButton.isHidden = false
if indexPath.row + 1 == displayNumber {
cell.questionLabel.text = questions[indexPath.row].question_name
} else {
cell.yesButton.isHidden = true
cell.noButton.isHidden = true
}
cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)
return cell
}
and this is the action being executed on clicking yes or no:
#objc func action(sender: UIButton){
let indexPath = self.tableView.indexPathForRow(at: sender.convert(CGPoint.zero, to: self.tableView))
let cell = tableView.cellForRow(at: indexPath!) as? TableViewCell
cell?.yesButton.isEnabled = false
cell?.noButton.isEnabled = false
if sender == cell?.yesButton {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
} else {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
}
displayNumber += 1
self.tableView.reloadData()
}
Here I just change the background color of the button and increment the display number to display the next question.
All of this works perfect EXCEPT when I scroll, the data gets overridden and sometimes I find the question label empty and the questions replaces each other. I know this is normal due to the cell reusability but I don't know how to fix it.
Any suggestions please?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableViewCell else {
fatalError("Fatal Error")
}
cell.yesButton.isHidden = false
cell.noButton.isHidden = false
if indexPath.row + 1 == displayNumber {
cell.questionLabel.text = questions[indexPath.row].question_name
} else {
cell.yesButton.isHidden = true
cell.noButton.isHidden = true
}
cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)
return cell
}
i feel like your issue lies here in cellForRowAt function.
you have this written
if indexPath.row + 1 == displayNumber { your code here }
but i am unsure as to why you need this.
you should be doing something like this inside cellForRowAt
let data = self.questions
data = data[indexPath.row]
cell.questionLabel.text = data.question_name
you should not be adding 1 to your indexPath.row
You're going to need to keep track of your yes's no's and neither's for each cell. I'd tack an enum onto another data structure along with your questions. Your primary problem was that you were only keeping track of your question. You need to keep track of your answer as well. That way, when you load a cell, you can configure each button with the colors that you want in cellForRow(at:)
struct QuestionAndAnswer {
enum Answer {
case yes
case no
case nada
}
var question: Question
var answer: Answer
}
And try not to reload your whole tableView when a button is pressed. tableView.reloadData() is expensive and distracting to the user. You should only be reloading the row that changed when a button was pressed.
Add callbacks on your cell so that you know which cell the corresponding buttons belong to. Notice how in the onYes and onNo callbacks we keep track of your "yes" or "no" selection then immediately reload the row below. When the row is reloaded, we finally know which color to make the button.
class AnswerCell: UITableViewCell {
#IBOutlet weak var yesButton: UIButton!
#IBOutlet weak var noButton: UIButton!
var onYes: (() -> Void)) = {}
var onNo: (() -> Void)) = {}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ...
cell.yesButton.backgroundColor = qAndA.answer == .yes ? .green : .white
cell.noButton.backgroundColor = qAndA.answer == .no ? .green : .white
cell.onYes = {
questionsAndAnswers[indexPath.row].answer = .yes
tableView.reloadRows(at: [indexPath], with: .fade)
}
cell.onNo = {
questionsAndAnswers[indexPath.row].answer = .no
tableView.reloadRows(at: [indexPath], with: .fade)
}
// ...
}
Well, assume you have 10 questions, so a very simple and workaround fix is to declare a new array which has 10 elements as follow
var questionIsLoaded = Array(repeating:true , count 10)
the previous line will declare an array with 10 elements each element is bool which in our case will be true
then declare a function that handles if the question is loaded or not as follows, so if the question is loaded thus, the question with its indexPath should be marked as true and as a result, the yes and no buttons should be hidden else, the buttons should be shown
func handleQuestionIfLoaded(cell:yourCellType, indexPath:IndexPath) {
if questionIsLoaded[indexPath.row] , indexPath.row + 1 == displayNumber { {
questionIsLoaded[indexPath.row] = false
cell.questionLabel.text = questions[indexPath.row].question_name
cell.yesButton.isHidden = questionIsLoaded[indexPath.row]
cell.noButton.isHidden = questionIsLoaded[indexPath.row]
} else {
cell.yesButton.isHidden = questionIsLoaded[indexPath.row]
cell.noButton.isHidden = questionIsLoaded[indexPath.row]
}
cell.yesButton.addTarget(self, action: #selector(action), for: .touchUpInside)
cell.noButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
then replace the body of cellForRowAt with the function above, then your action function will be as follows
#objc func action(sender: UIButton){
let indexPath = self.tableView.indexPathForRow(at: sender.convert(CGPoint.zero, to: self.tableView))
let cell = tableView.cellForRow(at: indexPath!) as? TableViewCell
cell?.yesButton.isEnabled = questionIsLoaded[indexPath.row]
cell?.noButton.isEnabled = questionIsLoaded[indexPath.row]
if sender == cell?.yesButton {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
} else {
sender.setTitleColor(.black, for: .normal)
sender.backgroundColor = .green
}
displayNumber += 1
self.tableView.reloadData()
}
Now, your cells depend on an external dependency which is the array you have declared earlier, this means that when the cells are dequeued, they will be reused according to if the question is loaded or not by asking the array's element at the specific indexPath at first if the element is true or false
I have a checkbox (UIButton) and a label in a UITableViewCell. I want to change the label's text (color + strikethrough) when I click on the checkbox.
This is for a Recipe Application. After a cooking step is done, the user can "check" it as done.
This is my current cellForRowAt Function for the tableView:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == groceryTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: groceryTableViewCell, for: indexPath) as! GroceryItemTableViewCell
cell.amoutLabel.text = indexPath.item % 2 == 0 ? "50 g" : "500 ml"
cell.itemLabel.text = indexPath.item % 2 == 0 ? "Cheese" : "Milk"
cell.selectionStyle = .none
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: cookingStepTableViewCell, for: indexPath) as! CookingStepTableViewCell
cell.cookingStepDescription.text = indexPath.item % 2 == 0 ? "Test 123..." : "Test 321..."
cell.selectionStyle = .none
cell.delegate = self
return cell
}
}
And this is my Button addTarget Function, which is delegated from the TableViewCell Class to the actual ViewController Class:
func cookingStepDone(description: String, isDone: Bool) {
// if isDone == true
// label textcolor is gray + strikethrough
// if isDone == false
// no change...
}
I want that cell.cookingStepDescription label is changed if "isDone" is true (= click on the checkbox)
Assuming that the button outlet is taken in cell class. so declare a action method in the cellForRowAtIndexpath i.e like this.
cell.yourDoneBtn?.addTarget(self, action: #selector(self.cookingStepDone), for: .touchUpInside)
Now in your action function:
#objc func cookingStepDone(sender: UIButton)
{
let location = self.yourTableViewName?.convert(sender.bounds.origin, from:sender)
let indexPath = self.yourTableViewName?.indexPathForRow(at: location!)
if let cell = self.yourTableViewName.cellForRow(at: indexPath!) as? yourTableViewCell // i.e groceryTableViewCell or CookingStepTableViewCell
{
if isDone == true
{
// Set your cell label textcolor to gray + strikethrough
}
else
{
// no change
}
}
DispatchQueue.main.async
{
self.yourTableView.reloadData() // reload your table view
}
}
Set your bool value where ever needed.
You can do this using below approach
define an Array, in you cookingStepDone method add indexPath to the array and if indexPath already in Array remove it and reload the tableView. and in cellForRowAtIndexpathmethod, check if the Array contains the indexPath. if contains make text strikeThrough else make normal.
What if you create a new class whose superclass would be UITableViewCell and inside that class you add in your #IBOutlets (UIButton and UILabel) and an #IBAction (buttonWasTapped)?
Something Like:
class RecipeTableViewCell: UITableViewCell {
#IBOutlet var myButton : UIButton!
#IBOutlet var myLabel : UILabel!
#IBAction func didTouchButton(sender : UIButton)
{
myLabel.textColor = UIColor.green;
}
}
Checkout this code : RecipeTableViewCell
class RecipeTableViewCell: UITableViewCell {
#IBOutlet var myButton : UIButton!
#IBOutlet var myLabel : UILabel!
var buttonClick : (() -> Void)? = nil
override func awakeFromNib() {
myButton.addTarget(self, action: #selector(didTouchButton(sender:)), for: .touchUpInside)
}
#IBAction func didTouchButton(sender : UIButton)
{
if let action = buttonClick {
action()
}
}
}
In cellForRowAt
let cell = tableView.dequeueReusableCell...
// Your code ...
cell.buttonClick = {
//access your label and data from here
cell.yourLbl.text = yourModel[indexPath.row].text
}
I am used button inside UICollectionView cell, and I want to button can change color just two indexes and other can't change color if click other buttons uicolor.clear
and I want to like this, so how to use sender.backgroundColor
func collectionView(_: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellB", for: indexPath) as! BandingCollectionViewCell
cell.bBandingCell.addTarget(self, action: #selector(masterAction3(_:)), for: .touchUpInside)
return cell
}
{
#objc func masterAction3(_ sender: UIButton) {
var indexPath = collectionView.indexPath(for: ((sender.superview?.superview) as! BandingCollectionViewCell))
if sender.isSelected {
sender.isSelected = false
switch indexPath?.row {
case 0:
print("0")
sender.backgroundColor = UIColor.clear
case 1:
print("1")
sender.backgroundColor = UIColor.clear
default:
print("default")
sender.backgroundColor = UIColor.blue
}
} else {
sender.isSelected = true
switch indexPath?.row {
case 0:
print("0")
sender.backgroundColor = UIColor.blue
case 1:
print("1")
sender.backgroundColor = UIColor.blue
default:
print("default")
sender.backgroundColor = UIColor.clear
}
}
}
I'd do it this way:
Use custom UICollectionViewCell subclass (without button because collection view cell handles selection itself)
in this cell class override isSelected property like this:
override var isSelected: Bool {
didSet {
// set color according to state
self.backgroundColor = self.isSelected ? .blue : .clear
}
}
In class which controls your collectionView perform collectionView.allowsMultipleSelection = true
In your UICollectionViewDelegate implement method (which will prevent selection of more than two cells at a time):
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return (collectionView.indexPathsForSelectedItems?.count ?? 0) < 2
}
This way you don't need a button inside the cell.
When you select a cell, isSelected will be set. And you can customise your cell like this.
class YourCollectionViewCell: UICollectionViewCell {
override var isSelected: Bool {
didSet {
self.contentView.backgroundColor = isSelected ?.blue : .clear
}
}
}
NB: No need to add actions manually. Remove your selector method
I'm adding UIButton in UITableView, I'm trying to change UIButton background color, UIButton title color and UIButton image color on table view cell selection and same vice versa.
MY Code is
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let propertyCell = tablePropertyList.cellForRowAtIndexPath(indexPath) as! PropertyListTableCell
propertyCell.buttonDownload.selected = true
propertyCell.buttonDownload.setTitleColor(utility!.uicolorFromHex(0xf8f8f8), forState: UIControlState.Selected)
let image:UIImage = UIImage(named: "DownloadSelected")!
propertyCell.buttonDownload.setImage(image, forState: UIControlState.Selected)
propertyCell.buttonDownload.backgroundColor = utility!.uicolorFromHex(0x006747)
propertyCell.buttonDownload.layer.borderColor = utility!.uicolorFromHex(0x006747).CGColor
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath)
{
let propertyCell = tablePropertyList.cellForRowAtIndexPath(indexPath) as! PropertyListTableCell
propertyCell.buttonDownload.selected = false
propertyCell.buttonDownload.setTitleColor(utility!.uicolorFromHex(0x006747), forState: UIControlState.Normal)
let image:UIImage = UIImage(named: "Download")!
propertyCell.buttonDownload.setImage(image, forState: UIControlState.Normal)
propertyCell.buttonDownload.backgroundColor = utility!.uicolorFromHex(0xf8f8f8)
propertyCell.buttonDownload.layer.borderColor = utility!.uicolorFromHex(0xf8f8f8).CGColor
}
//on UIButton Action
MY Code is
func downloadViewAction(sender: UIButton)
{
sender.selected = true
sender.setTitleColor(utility!.uicolorFromHex(0xf8f8f8), forState: UIControlState.Selected)
let image:UIImage = UIImage(named: "DownloadSelected")!
sender.setImage(image, forState: UIControlState.Selected)
sender.backgroundColor = utility!.uicolorFromHex(0x006747)
sender.layer.borderColor = utility!.uicolorFromHex(0x006747).CGColor
print("inside ownload action view")
let splitView:UISplitViewController = DevelopmemtSplitViewController()
if let path = tablePropertyList.indexPathForSelectedRow {
let selectedproperty = propertyArray[path.row]
self.showActivityIndicator(splitView.view, message: "Downloading properties for "+selectedproperty)
}
}
awakeFromNib method of cell class can be used to change titlecolor, image of button based on state. Please refer following code
class PropertyListTableCell : UITableViewCell
{
func awakeFromNib()
{
propertyCell.buttonDownload.setTitleColor(utility!.uicolorFromHex(0x006747), forState: UIControlState.Normal)
propertyCell.buttonDownload.setTitleColor(utility!.uicolorFromHex(0xf8f8f8), forState: UIControlState.Selected)
}
}
func downloadViewAction(sender: UIButton)
{
sender.selected = true //automatically changes titlecolor of button
}
If I'm getting it right. You have a button inside every PropertyListTableCell and you want to change the way that button looks depending on the selection state of the cell.
If so, overwrite the setSelected(_:animated:) function in your PropertyListTableCell class, this way, whenever a cell is selected or deselect the button appearance will change automatically.
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: true);
if (selected) {
self.buttonDownload.backgroundColor = utility!.uicolorFromHex(0x006747)
self.buttonDownload.layer.borderColor = utility!.uicolorFromHex(0x006747).CGColor
}
else {
propertyCell.buttonDownload.backgroundColor = utility!.uicolorFromHex(0xf8f8f8)
propertyCell.buttonDownload.layer.borderColor = utility!.uicolorFromHex(0xf8f8f8).CGColor
}
self.buttonDownload.selected = true;
}
As commented Savitha above, the button image and the title color can be defined just once in the awakeFromNib (for example).
And there is no need to change any visual in the button action function because it's done when selecting the cell:
func downloadViewAction(sender: UIButton) {
let cell = sender as! as! PropertyListTableCell;
let indexPath = tableView.indexPathForCell(cell);
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .None);
print("inside ownload action view")
let selectedproperty = propertyArray[path.row]
self.showActivityIndicator(splitView.view, message: "Downloading properties for "+selectedproperty)
}
Hello i have a collection view in which i have a checkbox in every cell.
I use this checkbox https://github.com/zhouhao27/WOWCheckbox.
All cells have their checkbox but the problem as the title says is that when i tap on the checkbox all checkboxes are checked.
Actually when i tap the first all odds checkboxes (1-3-5-7-...) are checked and when i tap the second then all checkboxes are checked.
I connected the view to my cell file i change it to WOWCheckbox as the documentation says.
I didn't change anything else.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Interest Cell", forIndexPath: indexPath) as! ThirdTabCell
cell.check1.tag = indexPath.row
cell.check1.addTarget(self, action: #selector(ThirdTab.follow(_:)), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func follow(sender:WOWCheckbox!) {
print("check")
}
When i use this code when i tap a checkbox it prints check only once.
I believe i somehow i have to declare which check i tap but i don't know how to use it.
// func follow(sender:WOWCheckbox!) {
// sender.tag//will get the which row you checked
//do logic based on tag
//}
//Create model
class checkList{
var item = ""
var checked = false
}
//Controller
class SampleViewController: ViewController,WOWCheckboxDelegate{
var List = [checkList]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...10 {
let object = checkList()
object.item = i.description
object.checked = false
List.append = obj
}
_collectionview.reloadData()
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Interest Cell", forIndexPath: indexPath) as! ThirdTabCell
cell.check1.tag = indexPath.row
if List[indexPath.row].checked {
cell.check1.isChecked = true
}
else{
cell.check1.isChecked = false
}
return cell
}
//delegate function by WOWCheckbox
func didSelectCheckbox(checkbox : WOWCheckbox) {
if checkBox.tag == 0{
//first row
for i ...< List.count {
if i % 2 != 0 {
List[i].checked = true
}
else{
List[i].checked = false
}
collectionView.reloadData()
return
}
}
//second row
else if checkBox.tag == 1{
for obj in List {
obj.checked = true
}
collectionView.reloadData()
return
}
else {
if list[checkbox.tag].isChecked {
checkBox.isChecked = false
list[checkbox.tag].checked = false
}
else{
list[checkbox.tag].checked = true
checkBox.isChecked = true
}
}
}
}
i didn't tested please check the code and change according to your requirement