I'm using firebase. My code works but my issue is when I press the accept button, I want that index to be deleted from the collectionView and I want the table to reload right away right after. I can't use the slide to delete because I need it for another function.
This is in my cell class thats why I did not use self. to call the collectionView. This function is being called by a addTarget() method. I'm thinking maybe passing an indexPath as a parameter, but I don't know how to pass an indexPath.item as a parameter and also how to pass a parameter inside an addTarget() method.
func handleAccept(_ sender: UIButton) {
print("button pressed")
guard let artId = art?.artId else {return}
let approvedRef =
FIRDatabase.database().reference().child("approved_art")
approvedRef.updateChildValues([artId: 1])
let pendingRef =
FIRDatabase.database().reference().child("pending_art").child("\(artId)")
pendingRef.removeValue { (error, ref) in
if error != nil {
return
}
let layout = UICollectionViewFlowLayout()
let pendingArtCollectionViewController =
PendingArtsCollectionViewController(collectionViewLayout: layout)
DispatchQueue.main.async {
pendingArtCollectionViewController.collectionView?.reloadData()
}
}
}
You don't need to pass indexpath as argument, you can get the indexpath of the tableView in button action with the following code :
func handleAccept(_ sender: UIButton) {
let center = sender.center
let rootViewPoint = sender.superview!!.convert(center!, to: self.myTableView)
let currentIndexpath = myTableView.indexPathForRow(at: rootViewPoint)
}
Related
I have a tableview which shows a custom cell.
Inside the cell is a button.
Once the button is clicked, a network call is made and the tableview should reload.
I tried this, but I get Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value at vc.reloadData().
#IBAction func acceptRequestPressed(_ sender: UIButton) {
DatabaseManager.shared.AnswerFriendRequest(emailFriend: friendNameLabel.text!, answer: true) { success in
if success {
let vc = FriendRequestViewController()
vc.reloadData()
}else {
print ("error at answer")
}
}
}
The problem is this line:
let vc = FriendRequestViewController()
After that, vc is the wrong view controller — it is just some view controller living off emptily in thoughtspace, whereas the view controller you want is the already existing view controller that's already in the view controller hierarchy (the "interface").
Few pointers:
You can have a closure upon the data call completion in your class that has the table view.
Eg:
// Custom Table Cell Class
class CustomCellClass: UITableViewCell {
let button = UIButton()
// All other UI set up
// Create a closure that your tableView class can use to set the logic for reloading the tableView
public let buttonCompletion: () -> ()
#IBAction func acceptRequestPressed(_ sender: UIButton) {
DatabaseManager.shared.AnswerFriendRequest(emailFriend: friendNameLabel.text!, answer: true) { [weak self] success in
if success {
// Whatever logic is provided in cellForRow will be executed here. That is, tableView.reloadData()
self?.buttonCompletion()
}else {
print ("error at answer")
}
}
}
}
// Class that has tableView:
class SomeViewController: UIViewController {
private let tableView = UITableView()
.
.
// All other set up, delegate, data source
func cellForRow(..) {
let cell = tableView.dequeueTableViewCell(for: "Cell") as! CustomCellClass
cell.buttonCompletion = {[weak self] in
tableView.reloadData()
}
}
I am new in swift and I want to get the value of label from tableview on button click
I am using code like this but it is getting crash
in cellforrowatindexpath
cell.btnsubmit.tag = indexPath.row
cell.btnsubmit.addTarget(self, action: #selector(buttonSelected), for: .touchUpInside)
#objc func buttonSelected(sender: UIButton){
print(sender.tag)
let cell = sender.superview?.superview as! PatientUpdateVCCell
surgery_date = cell.surgeryDateTextField.text!
discharge_date = cell.dischargeDateTextField.text!
follow_up_duration = cell.lblfolowup.text!
follow_up_date = cell.firstFollowUpTextField.text!
patient_status = cell.patientStatusTextView.text!
}
but it is getting crash. How can I achieve this
crash
Could not cast value of type 'UITableViewCellContentView' (0x11a794af0) to 'appname.PatientUpdateVCCell' (0x10ae74ae0).
According to your crash last superView is contentView then it's superView is the needed cell , so You need
let cell = sender.superview!.superview!.superview as! PatientUpdateVCCell
Target/action is pretty objective-c-ish. And view hierarchy math is pretty cumbersome.
A swiftier way is a callback closure which is called in the cell and passes the cell.
In the cell add a callback property and an IBAction. Connect the action to the button
var callback : ((UITableViewCell) -> Void)?
#IBAction func buttonSelected(_ sender: UIButton) {
callback?(self)
}
In cellForRow rather than the tag assign the closure
cell.callback = { currentCell in
self.surgery_date = currentCell.surgeryDateTextField.text!
self.discharge_date = currentCell.dischargeDateTextField.text!
self.follow_up_duration = currentCell.lblfolowup.text!
self.follow_up_date = currentCell.firstFollowUpTextField.text!
self.patient_status = currentCell.patientStatusTextView.text!
}
And delete the action method in the controller
I have 5 buttons within a UIStackView, and I want to find out which index is being selected, and later compare those indexes. My code right now gives me an Array.Index. I've tried both subviews and arrangedSubviews. Is there anyway I can turn this into an Integer? I can't figure it out. Thanks!!
if let selectedIndex = stackview.subviews.index(of: sender) {
}
// UPDATE
I kinda got what I wanted with:
let int = stackview.subviews.distance(from: stackview.subviews.startIndex, to: selectedIndex)
I'm still not sure if this is the most efficient way, but it does the job for now.
index(of:) return Int.
Also you should find your button in the arrangedSubviews, not in the subviews
Assuming your stack view contains only buttons, and each button is connected to this #IBAction, this should work:
#IBAction func didTap(_ sender: Any) {
// make sure the sender is a button
guard let btn = sender as? UIButton else { return }
// make sure the button's superview is a stack view
guard let stack = btn.superview as? UIStackView else { return }
// get the array of arranged subviews
let theArray = stack.arrangedSubviews
get the "index" of the tapped button
if let idx = theArray.index(of: btn) {
print(idx)
} else {
print("Should never fail...")
}
}
I would add a tag to each of your buttons (button.tag = index) then check the tag of your sender.
So then you can wire up each of your buttons to the same function with a sender parameter, then check if sender.tag == index.
I was using a function as an #IBAction but now I want to use it as a normal function. But the problem is when I try to call the function it is asking me for the sender and is expecting a UIButton as a parameter.
How can I remove that sender so it doesn't affect my function?
Here is my function:
func addProductToCartButton(_ sender: UIButton) {
// Start animation region
let buttonPosition : CGPoint = sender.convert(sender.bounds.origin, to: self.productsTableView)
let indexPath = self.productsTableView.indexPathForRow(at: buttonPosition)!
let cell = productsTableView.cellForRow(at: indexPath) as! ProductTableViewCell
let imageViewPosition : CGPoint = cell.productImageView.convert(cell.productImageView.bounds.origin, to: self.view)
let imgViewTemp = UIImageView(frame: CGRect(x: imageViewPosition.x, y: imageViewPosition.y, width: cell.productImageView.frame.size.width, height: cell.productImageView.frame.size.height))
imgViewTemp.image = cell.productImageView.image
animationProduct(tempView: imgViewTemp)
// End animation region
}
Here is where I need to call the function:
func didTapAddToCart(_ cell: ProductTableViewCell) {
let indexPath = self.productsTableView.indexPath(for: cell)
addProductToCartButton( expecting UIBUTTON parameter)
}
I was trying to set the sender as nil but is not working. Do you have any idea?
Approach 1 (Recommended):
You can make that argument as optional:
#IBAction func addProductToCartButton(_ sender: UIButton?)
{
// Do your stuff here
}
Now you can call it like:
addProductToCartButton(nil)
Approach 2 (Not Recommended)
If you don't want to make the argument as optional, you can call it like:
addProductToCartButton(UIButton()) // It's not recommended
Approach 3 (Recommended)
Just write another utility function and add the code in it (Add the code written inside the IBAction to this function). Instead of calling IBAction from another function, call this utility function.
You need to refactor your code. The current implementation of addProductToCartButton uses the sender (the button) to determine an index path. And then the rest of the code is based on that index path.
You then have your didTapAddToCart method which attempts to call addProductToCartButton but you don't have the button at this point but it does have an index path.
I would create a new function that takes an index path as its parameter. Its implementation is most of the existing code in addProductToCartButton.
Here's the new function (which is mostly the original addProductToCartButton code):
func addProduct(at indexPath: IndexPath) {
let cell = productsTableView.cellForRow(at: indexPath) as! ProductTableViewCell
let imageViewPosition : CGPoint = cell.productImageView.convert(cell.productImageView.bounds.origin, to: self.view)
let imgViewTemp = UIImageView(frame: CGRect(x: imageViewPosition.x, y: imageViewPosition.y, width: cell.productImageView.frame.size.width, height: cell.productImageView.frame.size.height))
imgViewTemp.image = cell.productImageView.image
animationProduct(tempView: imgViewTemp)
// End animation region
}
Then redo addProductToCartButton as:
func addProductToCartButton(_ sender: UIButton) {
// Start animation region
let buttonPosition : CGPoint = sender.convert(sender.bounds.origin, to: self.productsTableView)
let indexPath = self.productsTableView.indexPathForRow(at: buttonPosition)!
addProduct(at: indexPath)
}
And finally, update didTapAddToCart:
func didTapAddToCart(_ cell: ProductTableViewCell) {
let indexPath = self.productsTableView.indexPath(for: cell)
addProduct(at: indexPath)
}
Extending Midhun MPs answer you can make your function call even simpler by providing a default value of nil:
#IBAction func addProductToCartButton(_ sender: UIButton? = nil) {
// Do your stuff here
}
Then you can call the function like this:
addProductToCartButton()
Well simplest approach for this type of logic is, pass nil value as parameter, it would be like this,
addProductToCartButton(nil)
Just make sure that you are not using any button property in your function. But if you are using button property then just simple add a check in your function like this,
func addProductToCartButton(_ sender: UIButton) {
if sender != nil {
//Do Something
}
}
Hope this will solve your issue, Thanks for reading this.
I'm really close to figuring this out. So in the iOS mail app when you click on the two arrow keys it takes you to the previous/next mail. Its on the top right
I've managed to pass the indexPath value to my second viewcontroller and print in in the console. I can also increase and decrease from it.
if segue.identifier == "DetailVC" {
let detailVC = segue.destination as! DetailVC
let indexPath = self.collectionViewIBO.indexPathsForSelectedItems?.last!
detailVC.index = indexPath
}
EDIT
This is where I'm pulling the data from. It reads the values from my model. I cannot assign an indexPath to it however. I can only do that from the previous view controller
var monster: Monsters!
I've attempted to implement the "previous" functionality using this code. My view styling are in the displayDataForIndexPath() function and the function is called from my view will appear
#IBAction func monsPreviousIBO(_ sender: Any) {
self.index = IndexPath(row: self.index.row - 1, section: self.index.section)
displayDataForIndexPath()
}
But all it does is decrease the IndexPath. For some reason the data doesn't actually reload with my function. I'm missing some important puzzle piece here to achieving the same functionality.
EDIT The code in my displayDataForIndex is as follows
func displayDataForIndexPath() {
if index.row == 0 {
self.monsPreviousIBO.removeFromSuperview()
}
var monsterName = (String(format: "%03d", monster.speciesId!))
self.navigationItem.title = monster.name!
let gif = UIImage(gifName: monsterName)
self.gifIBO.setGifImage(gif, manager: gifManager)
gifIBO.contentMode = .center
guard monster.legendary! != true else {
// Value requirements not met, do something
monsterStatusLegend()
return
}
guard monster.subLegend! != true else {
// Value requirements not met, do something
monsterStatusSub()
return
}
guard monster.isMega! != true else {
// Value requirements not met, do something
monsterStatusMega()
return
}
}
you display all data depending on monster but you never change the monster depending which indexPath you used.
add some code to populate the monster from indexPath
monster = getMonster(index.row)
or in your case
#IBAction func monsPreviousIBO(_ sender: Any) {
self.index = IndexPath(row: self.index.row - 1, section: self.index.section)
monster = mons[self.index.row]
displayDataForIndexPath()
}
or better in the displayDataForIndexPath() add this line:
func displayDataForIndexPath() {
if index.row == 0 {
self.monsPreviousIBO.removeFromSuperview()
}
monster = mons[self.index.row]
//....
NOTE some suggestions:
i would change the line for the button then it is enabled if the indexpath gets >0:
self.monsPreviousIBO.isEnabled = (index.row != 0)
just save the row as monsterIndex = indexPath.row and then deal only with the index and not indexPath.
you don't need to save the current monster as monster if you use the monster only in displayDataForIndexPath - then you can get the current monster just there and have it a local variable in this function:
var monster = mons[self.index.row]