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.
Related
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'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)
}
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]
For the life of me I can't seem to figure this out. I must be completely overthinking this...
I have a UIScrollView that will hold a between 1-3 UIImageView(w UIImage) inside my tableView.
TableViewCell:
I'm calling...
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let currentPage = (CGFloat(imageScrollView.contentOffset.x) / CGFloat(userImageView!.frame.size.width))
pageControl.currentPage = Int(currentPage)
}
to find out what page the user is currently looking at. My question is how do I pass the currentPage number to my new ViewController?
TableViewController:
In my CellForRowAtIndex I'm calling to find out which button was clicked.
cell.tapToViewButton.tag = indexPath.row
cell.tapToViewButton.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
I'm using
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "toViewImageVC"){
let viewImageVC: ViewImageViewController = segue.destinationViewController as! ViewImageViewController
//passed [UIImage] correctly
//HOW TO PASS CURRENT PAGE????----
}
}
I've tried several things like using the UIScrollViewDelegate in my TableView, using creating a method in my tableViewCell that returns an Int() which is my current page but I can't access it through tableView cause that only loads the cell info.
This seems simple but for some reason I can't wrap my head around this!!!
In your ViewImageViewController add a page parameter, like:
var pageNumber = 0
Then in prepareForSegue call:
viewImageVC.pageNumber = pageControl.currentPage
I'm trying to implement a like button function on my app, similar to facebook or instagram, through parse. I tried using this code below and it works. When the user taps the button on an object(or in my case messages), the like goes up by 1 point. However, when the user quits the app and launches it they can like the same object again, meaning they can like as many times as they want. Do I need to edit something in this code or try a whole different method?
#IBAction func likeButton(sender: UIButton) {
sender.enabled = false
sender.userInteractionEnabled = false
sender.alpha = 0.5
//get the point in the table view that corresponds to the button that was pressed
//in my case these were a bunch of cells each with their own like button
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = objectAtIndexPath(hitIndex)
//this is where I incremented the key for the object
object!.incrementKey("count")
object!.saveInBackground()
self.tableView.reloadData()
NSLog("Top Index Path \(hitIndex?.row)")
}
Here is the way in which I would approach the problem:
First, I would make disabling the button into its own function like so:
func disableButton(button: UIButton){
button.enabled = false
button.userInteractionEnabled = false
button.alpha = 0.5
}
(And when the user presses the like button it is disabled like so:)
#IBAction func likeButton(sender: UIButton) {
disableButton(sender)
//get the point in the table view that corresponds to the button that was pressed
//in my case these were a bunch of cells each with their own like button
let hitPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let hitIndex = self.tableView.indexPathForRowAtPoint(hitPoint)
let object = objectAtIndexPath(hitIndex)
//this is where I incremented the key for the object
object!.incrementKey("count")
object!.saveInBackground()
self.tableView.reloadData()
NSLog("Top Index Path \(hitIndex?.row)")
}
Then I would make an attribute of users their liked posts, which is an array of strings of the objectIds of these posts. (Since users would probably end up liking many posts, you really should use relations, but arrays are easier to understand. The documentation for relationships between PFObjects is here.) Then, when creating the cell for each post, where cell is the cell you are creating, post is the current post in the list and cell.likeButton is the like button from the cell:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = YourCell()
let post = posts[indexPath.row]
if (PFUser.currentUser!["likedPosts"] as! [String]).contains(post.objectId){
cell.disableButton(cell.likeButton)
}
//Setup rest of cell