Using button in custom cell from another ViewController - ios

I created button in my custom cell which is I created to another class after that I added that cell on my tableView(in my viewController), when the user click the that button which is in custom cell, I have to segue another class and also I have to pass some data but problem is I could not segue in custom cell, so how can I detect when user tap that button ? or how can I take current index of that button which user tapped ?

First you have to make one protocol on your custom table cell.
Then create one weak var in your table cell class.
After that make your button action in your custom table cell class and pass button tag from your table cell to view controller. From the button tag you can easily get which index button is clicked.
protocol CustomTableCellDelegate: AnyObject {
func btnClicked(tag: Int)
}
class CustomTableCell: UITableViewCell {
..
..
weak var customTableCellDelegate: CustomTableCellDelegate?
..
..
#IBAction func btnClicked(_ sender: UIButton) {
self.customTableCellDelegate?.btnClicked(tag: sender.tag)
}
Now in your viewcontroller (where your table cell is implementing and showing list in tableview) make one extension and give delegate to that extension.
extension YourViewController: CustomTableCellDelegate {
func btnClicked(tag: Int) {
// Here you can find the which button is clicked from the button tag. You can do segue or navigation from here.
}
}
Make sure to do this. This is important
In your cellForRowAt method, write two below lines.
cell.customTableCellDelegate = self
cell.yourbutton.tag = indexPath.row

Related

How to create a UIView from a UITableViewCell button click on a separate UIViewController?

I'm working on an iOS app in Xcode/Swift and I'm trying to add a subview UISendViewButton in my MainViewController when a UITableViewCell button is clicked in a separate UITableViewController (which itself is embedded in a UIView). Basically, the concept is that of a "send post" button like in Instagram: the user will click a paper airplane button and a separate list of friends appears (UIView -> UITableViewController). Next to the list of contacts, there is a button (customButton) that the user can click to choose which friends to send it to. What I want is to have a "Send" button (UIViewButton) appear ONLY if the user decides to click the button (customButton) next to their friends' name.
I was able to make the UITableViewController appear by embedding it within a UIView and then adding that as a subview to my MainViewController, but when I click the customButton in the UITableViewCell class, nothing happens. I would like for a new UIViewButton (in my MainViewController) to appear when I click the customButton.
So basically I wanted to know how to have these two controllers communicate. The controller which houses the UITableViewCell button is the one that is provided by Xcode's library: UITableViewController -> UITableView -> UITableViewCell which is itself embedded within a UIView.
I tried using a delegate on the UITableViewCell class below:
import UIKit
public protocol CustomCellDelegate: class {
func customButtonClick()
}
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var customButton: UIButton!
weak var delegate: CustomCellDelegate?
#IBAction func customButtonAction(_ sender: UIButton) {
delegate?.customButtonClick()
}
.. }
And corresponding code on the MainViewController here:
import UIKit
class MainViewController: UIViewController, CustomCellDelegate {
#IBOutlet var UIViewButton: UIView!
func customButtonClick() {
self.UIViewButton.frame = CGRect (x:20, y: 300, width: 369, height: 46)
self.view.addSubview(UIViewButton)
} }
I know that CustomTableViewCell is within two different controllers (CustomTableView, CustomTableViewController), so I was thinking there may be a delegate issue within one of those/I may need to add another delegate for those controllers but I'm not sure how. I've managed to change the customButton icon in CustomTableViewCell so I know that it's clickable, I just can't seem to get it to delegate or communicate with the MainViewController and have the UIViewButton appear. I'm sorry for the confusion and inconvenience, any help with this would be greatly appreciated as I'm a beginner to coding. Thanks so much!! ^__^
UPDATE:
CustomTableViewController:
public protocol ContactListTableViewControllerDelegate: class {
func showSendButton() }
class CustomTableViewController: UITableViewController, CustomCellDelegate {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "likesCell", for: indexPath) as! CustomTableViewCell
cell.delegate = self }
func customButtonClick() {
delegate?.showSendButton()
} }
MainViewController:
extension MainViewController: ContactListTableViewControllerDelegate {
func showSendButton() {
self.UISendButton.frame = CGRect (x:20, y: 300, width: 369, height: 46)
self.view.addSubview(UISendButton)
} }
I hope that your UI is similar to the image below.
For this, I would do the following
Declare a protocol that will let you know when to show or hide the 'Send' button.
For example
protocol ContactListTableViewControllerDelegate: class {
func showSendButton()
func hideSendButton()
}
Make the first view controller the delegate for the table view controller class
You have already created a protocol for picking up tap events on the button inside the cell. Optionally you need to add the cell too as a parameter, so that the delegate knows which cell was clicked.
public protocol CustomCellDelegate: class {
func customButtonClick(cell: CustomTableViewCell)
}
When creating each cell in cellForRowAtIndexPath method, make the table view controller the delegate for the cell
cell.delegate = self
Implement the delegate method in the table view controller subclass
func customButtonClick(cell: CustomTableViewCell) {
// Find the index path for the clicked cell
// You should have an array for storing the indices of selected cells
// Check if the index is already in the array. If yes, remove it from
//the array (deselection)
// If no, add the index to the array(selection)
// Check the count of the array
// If it is > 0, it means at least one cell is selected. Call the
// delegate method
// to show button
delegate?.showSendButton()
// Else call delegate method to hide send button
delegate?.hideSendButton()
}

How do I pass button action from a nested collectionView cell?

I have a MainCollectionView used for scrolling between items, inside of one of these cells I have another collectionView with cells. In that collection view, I have a button for each cell. My question is how do I pass action from my button to my MainCollectionView when it is tapped? I did create protocol for that button in the cell but I don't know how to let MainCollectionView know when my button is tapped. I can call action from my cell class but I think it is better to run it in Model which is my MainCollectionView. Below is my button protocol.
protocol ThanhCaHotTracksCellDelegate: class {
func handleSeeAllPressed()}
weak var delegate: ThanhCaHotTracksCellDelegate?
#objc func handleSeeAllButton(){
delegate?.handleSeeAllPressed()
}
LIke NSAdi said, you're on the right track, but the delegate pattern is a bit much overhead for just a single task like notifying about a button press.
I prefer using closures, because they're lightweight and helps to keep related code together.
Using Closures
This is what I'm always doing in UITableView. So this will work in UICollectionView too.
class MyTableViewCell: UITableViewCell {
var myButtonTapAction: ((MyTableViewCell) -> Void)?
#IBAction func myButtonTapped(_ sender: Any) {
myButtonTapAction?(self)
}
}
So when I dequeue my cell and cast it to MyTableViewCell I can set a custom action like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCellReuseIdentifier", for: indexPath) as! MyTableViewCell
cell.myButtonTapAction = { cell in
// Put your button action here
// With cell you have a strong reference to your cell, in case you need it
}
}
Using direct reference
When you're dequeueing your UICollectionView cell you can obtain a reference to your button by casting the cell to your cell's custom subclass.
Then just do the following
cell.button.addTarget(self, action: #selector(didTapButton(_:)), forControlEvents: .TouchUpInside)
And outside have a function:
#objc func didTapButton(_ sender: UIButton) {
// Handle button tap
}
Downside of this is that you have no direct access to your cell. You could use button.superview? but it's not a good idea since your view hierarchy could change...
You're on the right track.
Make sure MainCollectionView (or the class that contains) it implements ThanhCaHotTracksCellDelegate protocol.
Then assign the delegate as self.
Something like...
class ViewController: ThanhCaHotTracksCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
subCollectionView.delegate = self
}
}

how to reference the Tab Bar Controller from a UICollectionView Cell

I have a tab bar controller application and in one of the tabs a UI Collection View Controller with an action assigned to a button. This button does its magic and then should change the tab bar view to another one. However, I'm not able to reference it right to the tab controller.
tabBarController is the class name assigned to the controller. So, I tried:
tabBarController.selectedIndex = 3
and also creating a method directly in the tabBarController class
tabBarController.goToIndex(3)
The error says: Instance member of 'goToIndex' cannot be used on type tabBarController
Any ideia?
Thank you,
Im having a little trouble understanding what you mean by referencing it right, but hopefully this will help. Assuming tabBarController is a subclass of UITabBarController:
class MyTabBarController: UITabBarController {
/// ...
func goToIndex(index: Int) {
}
}
In one of your tab controllers (UIViewController) you can reference your UITabBarController with self.tabBarController. Note that self.tabBarController is optional.
self.tabBarController?.selectedIndex = 3
If your tab UIViewController is a UIViewController inside a UINavigationController, then you will need to reference your tab bar like this:
self.navigationController?.tabBarController
To call a function on your subclass you would need to cast the tab bar controller to your custom subclass.
if let myTabBarController = self.tabBarController as? MyTabBarController {
myTabBarController.goToIndex(3)
}
Update based on comments:
You're correct that you cant access the tabBarController inside the cell unless you made it a property on either the cell itself (not recommended) or the app delegate. Alternatively, you could use target action on your UIViewController to call a function on the view controller every time a button is tapped inside a cell.
class CustomCell: UITableViewCell {
#IBOutlet weak var myButton: UIButton!
}
class MyTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReuseIdentifier", for: indexPath) as! CustomCell
/// Add the indexpath or other data as a tag that we
/// might need later on.
cell.myButton.tag = indexPath.row
/// Add A Target so that we can call `changeIndex(sender:)` every time a user tapps on the
/// button inside a cell.
cell.myButton.addTarget(self,
action: #selector(MyTableViewController.changeIndex(sender:)),
for: .touchUpInside)
return cell
}
/// This will be called every time `myButton` is tapped on any tableViewCell. If you need
/// to know which cell was tapped, it was passed in via the tag property.
///
/// - Parameter sender: UIButton on a UITableViewCell subclass.
func changeIndex(sender: UIButton) {
/// now tag is the indexpath row if you need it.
let tag = sender.tag
self.tabBarController?.selectedIndex = 3
}
}

How to access index path of button in a custom TableViewCell?

I have created a custom TableViewCell and currently have a button placed in the cell. When the button is pressed, In the tableviewcell.swift file, IBAction func gets executed. I cannot figure out how to determine the index path of the cell that the button is in that is pressed. I was trying to use the following
#IBAction func employeeAtLunch(sender: AnyObject) {
let indexPath = (self.superview as! UITableView).indexPathForCell(self)
println("indexPath?.row")
}
but I get the following error on click:
Could not cast value of type 'UITableViewWrapperView' to 'UITableView'
Any help on how to access the index path of the cell?
You are just assuming that the cell's immediate superview is the table view - wrongly. There is no particular reason why that should be so (and indeed it is not). Work with fewer assumptions! You need to keep walking up the superview chain until you do reach the table, like this:
var v : UIView = self
do { v = v.superview! } while !(v is UITableView)
Now v is the table view, and you can proceed to work out what row this is.
What I would actually do, however, is work my up, not from the cell to the table, but from the button to the cell. The technique is exactly the same:
var v : UIView = sender as! UIView
do { v = v.superview! } while !(v is UITableViewCell)
Do that the button's action method, where sender is the button. If the target of the action method is the table view controller, it has access to the table, and the problem is solved.
You could subclass UIButton in your cell with a property for its row.
class MyButton: UIButton {
var row: Int?
}
Then when you set up your table view, in the cellForRowAtIndexPath method, you set the row property:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// ...
cell.button.row = indexPath.row
// ...
}
This way when the action fires, you can get the correct row:
#IBAction func employeeAtLunch(sender: MyButton) {
if let row = sender.row {
// access the row
}
}
In your situation, I will add a tag to your button to identify in which row it is. Whenever I configure cell in the call-back cellForRowAtIndexPath, I update this tag value.
When a button clicked, the handler specifies always the button pressed. With the tag defined to that pressed button, you can know the button of which row is pressed.
#IBAction func buttonPressed(sender: AnyObject) {
//convert to UIButton
if let btn = sender as? UIButton {
let rowId = btn.tag
//do your works
}
}
If your tableview has more than 1 sections, you will have to setup the value of tags the right way.
The second solution which is better: get the position of the button in your tableView, then get indexpath of that position in your tableview:
let position = sender.convertPoint(CGPointZero, toView: self.tblMain)
let indexPath = self.tblMain.indexPathForRowAtPoint(position)

Swift: Make button trigger segue to new scene

Alright, so I have a TableView scene and I'm going to put a button in each of the cells and I need each cell to, when clicked, segue to its own ViewController scene. In other words, I need to know how to connect a button to a scene (The only button I have right now is "milk")
I know how to create an IBAction linked to a button, but what would I put in the IBAction?
I'm a beginner so I need a step-by-step explanation here. I've included a picture of my storyboard. I haven't written any code yet.
If you want to have a button trigger the segue transition, the easiest thing to do is Control+Click from the button to the view controller and choose a Segue option (like push). This will wire it up in IB for you.
If you want to write the code to do this yourself manually, you can do so by naming the segue (there's an identifier option which you can set once you've created it - you still need to create the segue in IB before you can do it) and then you can trigger it with this code:
V2
#IBAction func about(sender: AnyObject) {
performSegueWithIdentifier("about", sender: sender)
}
V3
#IBAction func about(_ sender: Any) {
performSegue(withIdentifier: "about", sender: sender)
}
You can use the delegation pattern. Presuming that you have implemented a custom table cell, you can define a property in its class to hold whatever you think is helpful to identify the row - it can be its index, or (my preferred way) an instance of a class which represents the data displayed in the cell (I'm calling it MyCellData.
The idea is to let the cell notify the table view controller about a tap on that button, passing relevant data about (the data displayed in) the row. The table view controller then launches a segue, and in the overridden prepareForSegue method it stores the data passed by the cell to the destination controller. This way if you have to display details data about the row, you have all the relevant info, such as the details data itself, or an identifier the destination view controller can use to retrieve the data for example from a local database or a remote service.
Define a protocol:
protocol MyCellDelegate {
func didTapMilk(data: MyCellData)
}
then declare a property named delegate in the cell class, and call its didTapMilk method from the IBAction
class MyTableCell : UITableViewCell {
var delegate: MyCellDelegate?
var data: MyCellData!
#IBAction func didTapMilk() {
if let delegate = self.delegate {
delegate.didTapMilk(self.data)
}
}
}
Next, implement the protocol in your table view controller, along with an override of prepareForSegue
extension MyTableViewController : MyCellDelegate {
func didTapMilk(data: MyCellData) {
performSegueWithIdentifier("mySegueId", sender: data)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "mySegueId" {
let vc: MyDestinationViewController = segue.destinationViewController as MyDestinationViewController
vc.data = sender as? MyCellData
}
}
}
Of course you need a data property on your destination view controller for that to work. As mentioned above, if what it does is displaying details about the row, you can embed all required data into your MyCellData class - or at least what you need to retrieve the data from any source (such as a local DB, a remote service, etc.).
Last, in cellForRowAtIndexPath, store the data in the cell and set its delegate property to self:
extension MyTableViewController : UITableViewDataSource {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let data: MyCellData = retrieveDataForCell(indexPath.row) // Retrieve the data to pass to the cell
let cell = self.tableView.dequeueReusableCellWithIdentifier("myCellIdentifier") as MyTableCell
cell.data = data
cell.delegate = self
// ... other initializations
return cell
}
}
Use self.performSegueWithIdentifier("yourViewSegue", sender: sender) under your event for handling button's click:
#IBAction func redButtonClicked(sender: AnyObject) {
self.performSegueWithIdentifier("redView", sender: sender)
}
In the above code, redView is the segue identifier.

Resources