this is my code here I touch on + before search It gives correct product. but the problem is after search, it gives previous product not the correct product
after searching UISearchController looks up self.tableView
search is working finely
table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure the cell...
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "MasterViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? MasterViewCell else {
fatalError("The dequeued cell is not an instance of OutletViewCell.")
}
// Fetches the appropriate meal for the data source layout.
var product = products[indexPath.row]
if isFiltering {
product = filteredProducts[indexPath.row]
} else {
product = products[indexPath.row]
}
cell.productName.text = product.productDescription
cell.availQty.text = "Avail. Qty:" + String(product.stock)
cell.productPrice.text = "Price: " + String(product.defaultSellPrice)
cell.addItem.tag = indexPath.row
cell.addItem.addTarget(self, action: #selector(buttonTapped(button:)), for: .touchUpInside)
orderDetails.forEach { detail in
// print(word.price)
if detail.key == product.id {
cell.AddedQty.text = "Qty :" + String(detail.value.qty)
}
}
return cell
}
here is buttonTapped function
#objc func buttonTapped(button: UIButton) {
// print("Button pressed " + String(button.tag))
let product=products[button.tag]
print(product.productDescription)
showAlert(product: product)
}
In your buttonTapped function, you also need to check if the data isFiltered or not
#objc func buttonTapped(button: UIButton) {
let productData = isFiltering ? filteredProducts[indexPath.row] : products[indexPath.row]
showAlert(product: productData)
}
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 checkbox and label inside a tableview and when we click checkbox the price present in label in each cell of tableview should add to another label which is present in another view
#IBAction func checkUncheckButtonAction(_ sender: UIButton) {
if let cell = sender.superview?.superview as? PrepaidPageTableViewCell
{
let indexPath = tableviewOutlet.indexPath(for: cell)
if cell.checkUncheckButtonOutlet.isSelected == false
{
cell.checkUncheckButtonOutlet.setImage(#imageLiteral(resourceName: "checked_blue"), for: .normal)
cell.checkUncheckButtonOutlet.isSelected = true
viewHeightConstraint.constant = 65
cell.amountOutlet.text = "₹ "+amount_receivable_from_customerArray[indexPath!.row]
isPrepaidOrder = false
tableviewOutlet.reloadData()
} else {
cell.checkUncheckButtonOutlet.setImage(#imageLiteral(resourceName: "unchecked_blue"), for: .normal)
cell.checkUncheckButtonOutlet.isSelected = false
self.viewHeightConstraint.constant = 0
tableviewOutlet.reloadData()
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PrepaidPageTableViewCell") as! PrepaidPageTableViewCell
cell.customerNameOutlet.text = buyer_nameArray[indexPath.row]
cell.deliverydateOutlet.text = "Delivery Date:\(dispatch_dateArray[indexPath.row])"
cell.amountOutlet.text = "₹\(amount_receivable_from_customerArray[indexPath.row])"
cell.dispatchidoutlet.text = "Dispatch ID: \(id_dispatch_summaryArray[indexPath.row])"
cell.dispatchdateOutlet.text = "Dispatch Date:\(dispatch_dateArray[indexPath.row])"
cell.checkUncheckButtonOutlet.setImage(#imageLiteral(resourceName: "unchecked_blue"), for: .normal)
cell.selectionStyle = .none
return cell
}
I want tableviewcell index on dropdown item selection. But the index is nil when I am clicking on dropdown item. Is there any way to get index on dropdown item selection? If anyone have any better solution give me some idea.
let EditDropDown = DropDown()
lazy var dropDowns: [DropDown] = {
return [
self.EditDropDown
]
}()
This is my function which I am using for DropDown List.
func setupGenderDropDown() {
let cellHeader = tableview.dequeueReusableCell(withIdentifier: "CellRIDHeader") as! SPOccupationCell
EditDropDown.anchorView = cellHeader.btnDots
EditDropDown.bottomOffset = CGPoint(x: 0, y: 40)
// You can also use localizationKeysDataSource instead. Check the docs.
EditDropDown.dataSource = [
"Edit",
"Make Default",
"Delete"
]
// Action triggered on selection
EditDropDown.selectionAction = { [weak self] (index, item) in
cellHeader.btnDots.setTitle(item, for: .normal)
if item == "Edit"
{
// I am Getting Cell Index but index is nil
let cell = self!.tableview.dequeueReusableCell(withIdentifier: "CellRIDHeader") as! SPOccupationCell
let indexPath = self!.tableview.indexPath(for: cell)
print(indexPath as Any)
let occupation_id = self!.arrayOccupation[(indexPath?.row)!].occupation_Main_id
print(occupation_id)
let next = self!.storyboard?.instantiateViewController(withIdentifier: "EditOccupationVCSID") as! EditOccupationVC
self!.navigationController?.pushViewController(next, animated: false)
next.occupationId = occupation_id
}
else if item == "Make Default"
{
print("B")
}
else if item == "Delete"
{
print("c")
}
}
}
I am assuming you are using DropDown library to show dropdown. There is a problem where you are getting cell when it is tapped so I have created a demo project (simple tableView and not with custom UITableViewCell) for you and I have added comment to explain the changes. Consider below code:
import UIKit
import DropDown
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var items: [String] = ["We", "Heart", "Swift"]
let editDropDown = DropDown() //Object name should start with small letter
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func setupGenderDropDown(cell: UITableViewCell) { //Pass your cell with argument and change type to your custom cell
//By changing cell argument with your custom cell you will get your button for anchor
editDropDown.anchorView = cell.textLabel
editDropDown.bottomOffset = CGPoint(x: 0, y: 40)
editDropDown.dataSource = [
"Edit",
"Make Default",
"Delete"
]
//Here you need to update selectionAction from their library page
editDropDown.selectionAction = { [unowned self] (index: Int, item: String) in
//Here you will get selected item and index
print("Selected item: \(item) at index: \(index)")
if item == "Edit"
{
print(item)
print(index)
}
else if item == "Make Default"
{
print("B")
}
else if item == "Delete"
{
print("c")
}
}
//This was missing in your code
editDropDown.show()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Get the selected cell this way
guard let indexPath = tableView.indexPathForSelectedRow else { return }
guard let currentCell = tableView.cellForRow(at: indexPath) else { return }
//Pass your selected cell to setupGenderDropDown method
setupGenderDropDown(cell: currentCell)
}
}
HERE you can check demo project. And it's created into Xcode 10.1
I have 2 prototype cells.
One represents all the comments from the post.
This is initially presented.
If a user writes an "#" sign, a tableview with users he can choose to link appears.
My Problem is that the cells with users never disappear.
I want them to disappear if a cell is touched or if the user deletes the # sign.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (commentTextField.text?.contains("#"))! {
let cellForUser = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! SuggestUserTableViewCell
let user = usersSuggestion[indexPath.row]
cellForUser.userSuggested = user
return cellForUser
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CommentCell", for: indexPath) as! CommentTableViewCell
let comment = comments[indexPath.row]
let user = users[indexPath.row]
cell.tapMore.tag = indexPath.row
cell.comment = comment
cell.postId = postId
cell.user = user
cell.delegate = self
return cell
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let caption = commentTextField.text
let words = caption?.components(separatedBy: CharacterSet.whitespacesAndNewlines)
for var word in words! {
if word.hasPrefix("#") {
word = word.trimmingCharacters(in: CharacterSet.punctuationCharacters)
return usersSuggestion.count
}
}
return comments.count
}
Additionally I would like to have the cell2 with the users at the bottom, currently both start at the very top of the screen.
And I don't want to let the comments ever disappear, just decrease the opaqueness of the cell.
cell2 should disappear as soon as cell touched or # sign deleted.
Thanks in advance
Update Code
#objc func textFieldDidChange() {
doSearch()
if let commentText = commentTextField.text , !commentText.isEmpty {
sendButton.setTitleColor(UIColor.blue, for: UIControlState.normal)
sendButton.isEnabled = true
return
}
sendButton.setTitleColor(UIColor.lightGray, for: UIControlState.normal)
sendButton.isEnabled = false
}
func doSearch() {
let caption = commentTextField.text
let words = caption?.components(separatedBy: CharacterSet.whitespacesAndNewlines)
for var word in words! {
if word.hasPrefix("#") {
word = word.trimmingCharacters(in: CharacterSet.punctuationCharacters)
self.usersSuggestion.removeAll()
API.User.suggestUsers(withText: word, completion: { (user) in
self.usersSuggestion.insert(user, at: 0)
self.tableView.reloadData()
})
self.usersSuggestion.removeAll()
}else {
self.usersSuggestion.removeAll()
}
}
}
Instead of having the check for "#" in CellForRowAt, I think you should add a target to your text field with the action .editingChanged so that the check will fire each time there is a change.
Hope it helps.
I am making a quiz in a tableView that has 4 Buttons (options), I tagged them on a story board like 201,202,203,204 and got all of them successfully in tableView methods. But after adding targets to buttons, I am not able to get particular buttons in buttonClicked method.
func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 }
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return questions.count }
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
(cell.viewWithTag(100) as! UILabel).text = "Q : " + (questions[indexPath.row].objectForKey("MocQuestion")! as? String)!
(cell.viewWithTag(100) as! UILabel).font = themeFont
(cell.viewWithTag(101) as! UILabel).text = questions[indexPath.row].objectForKey("Op1")! as? String
(cell.viewWithTag(102) as! UILabel).text = questions[indexPath.row].objectForKey("Op2")! as? String
(cell.viewWithTag(103) as! UILabel).text = questions[indexPath.row].objectForKey("Op3")! as? String
(cell.viewWithTag(104) as! UILabel).text = questions[indexPath.row].objectForKey("Op4")! as? String
let btn1 = (cell.viewWithTag(201) as! UIButton)
let btn2 = (cell.viewWithTag(202) as! UIButton)
let btn3 = (cell.viewWithTag(203) as! UIButton)
let btn4 = (cell.viewWithTag(204) as! UIButton)
// btn1.tag = indexPath.row * 100 + 0
// btn1.tag = indexPath.row * 100 + 1
// btn1.tag = indexPath.row * 100 + 2
// btn1.tag = indexPath.row * 100 + 3
btn1.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
btn2.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
btn3.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
btn4.addTarget(self, action: #selector(Quiz.buttonClicked(_:)),forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func buttonClicked(sender:UIButton)
{
let tag = sender.tag
print(tag)
}
If you want the indexPath to access the questions Array then you can try like this.
func buttonClicked(sender:UIButton) {
let center = sender.center
let point = sender.superview!.convertPoint(center, toView:self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)
//Now you have tag of button check for that
if (sender.tag == 201) {
print("Option A")
}
else if (sender.tag == 202) {
print("Option B")
}
else if (sender.tag == 203) {
print("Option C")
}
else {
print("Option D")
}
print(question[indexPath.row])
}
In swift 3 You can try bellow like
My table cell class
class Custom_Cell: UITableViewCell {
#IBOutlet weak var ButtonA: UIButton!
#IBOutlet weak var ButtonB: UIButton!
#IBOutlet weak var ButtonC: UIButton!
}
Set Tag in table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Custom_Cell;
cell.ButtonA.tag = indexPath.row;
cell.ButtonB.tag = indexPath.row;
cell.ButtonA.tag = indexPath.row;
//Add Action Methods to UIButtons
cell.ButtonA.addTarget(self, action: #selector(ButtonAAction), for: .touchUpInside)
cell.ButtonB.addTarget(self, action: #selector(ButtonBAction), for: .touchUpInside)
cell.ButtonA.addTarget(self, action: #selector(ButtonCAction), for: .touchUpInside)
return cell;
}
Button Action will look like ..
func ButtonAAction(_ sender: Any) {
//Get Button cell position.
let ButtonPosition = (sender as AnyObject).convert(CGPoint.zero, to: tableView)
let indexPath = tableView.indexPathForRow(at: ButtonPosition)
if indexPath != nil {
print("Cell indexPath: \(indexPath?.row)")
}
}
In order to get every ques separate button click event you can pass the unique ID as postfix or prefix e.g 20101 or 01201 of every ques as a tag of button instead of hard-coded. Then get the tag and extract ques id first now proceed for examination as per ques.