Related
I'm a newbie, I have the following problem. There is a screen like this picture. I use UICollectionViewCell for header and I can't addTarget for the blue button in this.
I can’t manage. Could you help me?
class UserProfileVC: UICollectionViewController, UICollectionViewDelegateFlowLayout,UserProfileHeaderDelegate{
var currentUser: User?
var userToLoadFromSearchVC: User?
override func viewDidLoad() {
super.viewDidLoad()
// Register cell classes
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
// resgiter header class before use
self.collectionView!.register(UserProfileHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader , withReuseIdentifier: headerIdentifier)
// back ground color
self.collectionView?.backgroundColor = .white
//fetch user data
if userToLoadFromSearchVC == nil{
fetchCurrentUserData()
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 0
}
// config size for header
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 200)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
// Declare header
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as! UserProfileHeader
// set delegate
header.delegate = self
if let user = self.currentUser {
header.user = user
} else if let userToLoadFromSearchVC = self.userToLoadFromSearchVC {
header.user = userToLoadFromSearchVC
self.navigationItem.title = userToLoadFromSearchVC.username
}
// Return header
return header
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
This is my code in header file.
class UserProfileHeader: UICollectionViewCell {
var delegate: UserProfileHeaderDelegate?
var user: User? {
didSet {
configuredEditProfileFollowButton()
setUserStats(for: user)
let fullName = user?.name
nameLabel.text = fullName
guard let profileImageUrl = user?.profileImage else {return}
profileImageView.loadImage(with:profileImageUrl)
}
}
let profileImageView : UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.backgroundColor = .lightGray
return iv
}()
let nameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 12)
return label
}()
let postLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
let attributedText = NSMutableAttributedString(string:"5\n",attributes:[NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string:"posts",attributes: [NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14), NSAttributedString.Key.foregroundColor:UIColor.lightGray]))
label.attributedText = attributedText
return label
}()
let followersLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
let followingLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
let attributedText = NSMutableAttributedString(string:"5\n",attributes:[NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string:"following",attributes:[NSAttributedString.Key.font:UIFont.boldSystemFont(ofSize: 14), NSAttributedString.Key.foregroundColor:UIColor.lightGray]))
label.attributedText = attributedText
return label
}()
let editProfileFollowButton : UIButton = {
let button = UIButton(type: .system)
button.setTitle("Loading", for: .normal)
button.layer.cornerRadius = 5
button.layer.borderColor = UIColor.lightGray.cgColor
button.layer.borderWidth = 0.5
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(handleEditProfileFollow), for: .touchUpOutside)
return button
}()
let gridButton : UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named: "grid"), for: .normal)
return button
}()
let listButton : UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named:"list"), for: .normal)
button.tintColor = UIColor(white: 0, alpha: 0.2)
return button
}()
let bookmarkButton : UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(named:"ribbon"), for: .normal)
button.tintColor = UIColor(white: 0, alpha: 0.2)
button.addTarget(self, action: #selector(testFunc(_:)), for: UIControl.Event.touchUpInside)
return button
}()
#objc func testFunc(_ sender : UIButton){
print("Pressed ")
}
#objc func handleFollowersTapped() {
delegate?.handleFollowersTapped(for: self)
}
#objc func handleFollowingTapped() {
delegate?.handleFollowingTapped(for: self)
}
#objc func handleEditProfileFollow() {
guard let user = self.user else {return}
if editProfileFollowButton.titleLabel?.text == "Edit Profile"{
print("Handler edit profile ")
}
else{
if editProfileFollowButton.titleLabel?.text == "Follow"{
editProfileFollowButton.setTitle("Following", for: .normal)
user.follow()
}else{
editProfileFollowButton.setTitle("Follow", for: .normal)
user.unfollow()
}
}
}
This is because you are creating the button target before the UserProfileHeader initialized. So you need to create the button using lazy var (lazily).
private lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Button", for: .normal)
button.addTarget(self, action: #selector(handleButtonTapped), for: .touchUpInside)
return button
}()
#objc private func handleButtonTapped() {
print("Button tapped")
}
Try this, i think this will help you.
class customCell: UICollectionViewCell {
#IBOutlet weak var btn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: customCell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! customCell
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: #selector(self.btnpressed(sender:)), for: .touchUpInside)
return cell
}
#objc func btnpressed(sender: UIButton!) {
print(sender.tag)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: customCell, for: indexPath) as! customCell
view.pagePluginButtonAction = {
self.TapedBtn()
}
return view
}
func TapedBtn(){
print("click")
}
You need to add the target in 'viewForSupplementaryElementOfKind' where you set up your header. See example below, just after you set the header delegate.
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
// Declare header
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as! UserProfileHeader
// set delegate
header.delegate = self
// Add the target here
header.yourButton.addTarget(self, action: #selector(handleYourButton), for: .touchUpInside)
if let user = self.currentUser {
header.user = user
} else if let userToLoadFromSearchVC = self.userToLoadFromSearchVC {
header.user = userToLoadFromSearchVC
self.navigationItem.title = userToLoadFromSearchVC.username
}
// Return header
return header
}
my problem: I want to open some kind of Profil if a user pushes a Button in a Table-View Cell. The Cells Data is downloaded from Parse.
The idea is based on Instagram, if you click on the username-button on Insta the profile from the user who posted the image will open. I want to create the same code, but i can't create the code to get the user. Can you help me?
Heres some code:
import UIKit
import Parse
class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let reuseIdentifer = "FeedCell"
var delegate: HomeControllerDelegate?
var newCenterController: UIViewController!
let tableView = UITableView()
//Für Parse:
var users = [String: String]()
var comments = [String]()
var usernames = [String]()
var lastnames = [String]()
var imageFiles = [PFFileObject]()
var wischen: UISwipeGestureRecognizer!
var wischen2: UISwipeGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
getData()
configureNavigationBar()
configurateTableView()
wischen = UISwipeGestureRecognizer()
wischen.addTarget(self, action: #selector(handleMenuToggle))
wischen.direction = .right
wischen.numberOfTouchesRequired = 1
view.addGestureRecognizer(wischen)
wischen2 = UISwipeGestureRecognizer()
wischen2.addTarget(self, action: #selector(handleMenuToggle))
wischen2.direction = .left
wischen2.numberOfTouchesRequired = 1
view.addGestureRecognizer(wischen2)
}
#objc func handleMenuToggle() {
delegate?.handleMenuToggle(forMenuOption: nil)
}
#objc func showProfile() {
let vc: AProfileViewController!
vc = AProfileViewController()
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
func configureNavigationBar() {
navigationController?.navigationBar.barTintColor = .darkGray
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "Noteworthy", size: 22)!, NSAttributedString.Key.foregroundColor: UIColor.white]
//navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navigationItem.title = "Mobile Job Board"
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_menu_white_3x").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleMenuToggle))
navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_mail_outline_white_2x").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(showCreateNewArticle))
}
//MARK: Table View
//skiped table view configuration
}
// - MARK: Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer, for: indexPath) as! FeedCell
imageFiles[indexPath.row].getDataInBackground { (data, error) in
if let imageData = data {
if let imageToDisplay = UIImage(data: imageData) {
cell.postImage.image = imageToDisplay
}
}
}
cell.descriptionLabel.text = comments[indexPath.row]
cell.userButton.setTitle("\(usernames[indexPath.row]) \(lastnames[indexPath.row])", for: UIControl.State.normal)
cell.userButton.addTarget(self, action: #selector(showProfile), for: .touchUpInside)
return cell
}
//skiped
}
Thanks a lot!
Tom
The issue here is that your button works on a selector and it has no idea about the sender or where it was called from.
I would do this by creating a custom table view cell (e.g. FeedCell) which allows you to set a delegate (e.g. FeedCellDelegate). Set your class as the delegate for the cell and pass into the cell it's current indexPath. You can then return the indexPath in the delegate call.
Example: Note that code has been removed for simplicity and this code has not been tested. This is simply to guide you in the right direction.
View Controller
import UIKit
class HomeController: UIViewController {
// stripped additional information for example
func showProfile(_ username: String) {
let vc: AProfileViewController!
vc = AProfileViewController()
vc.username = username
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
}
extension HomeController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer, for: indexPath) as! FeedCell
cell.delegate = self
cell.descriptionLabel.text = comments[indexPath.row]
cell.userButton.setTitle("\(usernames[indexPath.row]) \(lastnames[indexPath.row])", for: UIControl.State.normal)
cell.setIndex(indexPath)
return cell
}
}
extension HomeController: FeedCellDelegate {
func didPressButton(_ indexPath: IndexPath) {
let userName = usernames[indexPath.row]
showProfile(username)
}
}
Feed Cell
import UIKit
protocol FeedCellDelegate {
didPressButton(_ indexPath: IndexPath)
}
class FeedCell: UICollectionViewCell {
var delegate: FeedCellDelegate?
var indexPath: IndexPath
#IBOutlet weak var userButton: UIButton
setIndex(_ indexPath: IndexPath) {
self.indexPath = indexPath
}
#IBAction userButtonPressed() {
if(delegate != nil) {
delegate?.didPressButton(indexPath)
}
}
}
You can generically and in a type safe way get the parent responder of any responder with:
extension UIResponder {
func firstParent<T: UIResponder>(ofType type: T.Type ) -> T? {
return next as? T ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
So:
Get the parent tableviewCell of your button in the target action function
Ask your tableview for the index path
Use the index path.row to index into your users array:
#objc func showProfile(_ sender: UIButton) {
guard let cell = firstParent(ofType: UITableViewCell.self),
let indexPath = tableView.indexPath(for: cell) else {
return
}
let user = users[indexPath.row]
... do other stuff here ...
}
I have table view cells like quiz. And in each cell I have a buttons And how can I identify in which cell button was pressed. Maybe by IndexPath???
This is how I connected button to
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionCell")!
variant1 = cell.contentView.viewWithTag(1) as! UIButton
variant2 = cell.contentView.viewWithTag(2) as! UIButton
variant3 = cell.contentView.viewWithTag(3) as! UIButton
variant4 = cell.contentView.viewWithTag(4) as! UIButton
variant1.addTarget(self, action: #selector(self.variant1ButtonPressed), for: .touchUpInside)
variant2.addTarget(self, action: #selector(self.variant2ButtonPressed), for: .touchUpInside)
variant3.addTarget(self, action: #selector(self.variant3ButtonPressed), for: .touchUpInside)
variant4.addTarget(self, action: #selector(self.variant4ButtonPressed), for: .touchUpInside)
return cell
}
func variant1ButtonPressed() {
print("Variant1")
variant1.backgroundColor = UIColor.green
}
func variant2ButtonPressed() {
print("Variant2")
variant2.backgroundColor = UIColor.green
}
func variant3ButtonPressed() {
print("Variant3")
variant3.backgroundColor = UIColor.green
}
func variant4ButtonPressed() {
print("Variant4")
variant4.backgroundColor = UIColor.green
}
This is how it looks like in Storyboard:
You should use delegate pattern, basic example:
protocol MyCellDelegate {
func didTapButtonInside(cell: MyCell)
}
class MyCell: UITableViewCell {
weak var delegate: MyCellDelegate?
func buttonTapAction() {
delegate?.didTapButtonInside(cell: self)
}
}
class ViewController: MyCellDelegate {
let tableView: UITableView
func didTapButtonInside(cell: MyCell) {
if let indexPath = tableView.indexPath(for: cell) {
print("User did tap cell with index: \(indexPath.row)")
}
}
}
Use this line to get indexPath, Where you have to pass UIButton on target selector
func buttonTapped(_ sender:AnyObject) {
let buttonPosition:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}
Since actions need to be inside the view controller, ctrl + drag from your button to the view controller - this will use the responder chain.
Basically you need to convert the view (button) to the coordinate system of the table view in order to tell what is the IndexPath and if you have the IndexPath you have the object that corresponds to the button inside the cell that was tapped:
#IBAction func buttonTapped(_ sender: Any) {
if let indexPath = indexPath(of: sender) {
// Your implementation...
}
}
private func indexPath(of element:Any) -> IndexPath? {
if let view = element as? UIView {
// Converting to table view coordinate system
let pos = view.convert(CGPoint.zero, to: self.tableView)
// Getting the index path according to the converted position
return tableView.indexPathForRow(at: pos) as? IndexPath
}
return nil
}
It is important to mention that there many solutions for your question. But you should know that in Apple's sample projects they also use this technic.
This is how you add tag to a UIButton inside UITableView, add below lines of code in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
cell.yourButton.tag = indexPath.row
cell.yourButton.addTarget(self, action:#selector(btnPressed(sender:)), for: .touchUpInside)
Add this function in your ViewController
func btnPressed(sender: UIButton)
{
print("Button tag \(sender.tag)")
}
Hope this helps...
Simple Subclass button just like JSIndexButton
class JSIndexButton : UIButton {
var indexPath : IndexPath!
}
Now at cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ItemCell
let itemCategory = dataList[button.indexPath.section];
let item = itemCategory.items[button.indexPath.row];
cell.imgView.setImageWithURL(item.photoUrl);
cell.btnBuy.indexPath = indexPath;
cell.btnBuy.addTarget(self, action: #selector(JSCollapsableTableView.btnBuyPressed(_:)), for: UIControlEvents.touchUpInside)
return cell;
}
Check Button Action
#IBAction func btnBuyPressed(_ button: JSIndexButton) {
let itemCategory = dataList[button.indexPath.section];
let item = itemCategory.items[button.indexPath.row];
}
#objc func ItemsDescription(_ sender: UIButton?,event: AnyObject?) {
let touches: Set<UITouch>
touches = (event?.allTouches!)!
let touch:UITouch = (touches.first)!
let touchPosition:CGPoint = touch.location(in: self.tableView)
let indexPath:NSIndexPath = self.tableView.indexPathForRow(at: touchPosition)! as NSIndexPath
}
adding target
cell.ItemsDescription.addTarget(self, action: #selector(ItemsDescription(_:event:)), for: UIControlEvents.touchUpInside)
I have a previously working delegate and protocol that since the conversion to Swift 3 is no longer being called.
protocol TaskCellDelegate {
func doneHit(_ cell : TaskCell)
}
class TaskCell : UITableViewCell {
var delegate : TaskCellDelegate?
#IBOutlet weak var label: UILabel!
#IBOutlet weak var detailLabel: UILabel!
#IBOutlet weak var _checkBox: M13Checkbox!
override func awakeFromNib() {
super.awakeFromNib()
let tap = UITapGestureRecognizer(target: self, action: #selector(TaskCell.buttonClicked(_:)))
tap.numberOfTapsRequired = 1
_checkBox.addGestureRecognizer(tap)
_checkBox.isUserInteractionEnabled = true
_checkBox.markType = .checkmark
_checkBox.boxType = .circle
_checkBox.stateChangeAnimation = .expand(.fill)
}
func buttonClicked(_ sender:UITapGestureRecognizer) {
delegate?.doneHit(self)
}
}
As you can see, when the _checkBox is tapped it should call the function doneHit in my class (not added because it doesn't seem necessary but I can) but I set a breakpoint and it's never called. I've set my delegate and conformed to the protocol in my class but nothing is happening. The doneHit function is supposed to update my backend but its not being called. If you need more info, I can provide.
Edit 1:
class TasksTVC: UITableViewController, TaskCellDelegate {
func doneHit(_ cell:TaskCell) {
if let indexPath = self.tableView.indexPath(for: cell) {
task = tasksInSectionArray[indexPath.section][indexPath.row]
if task.done == false {
cell._checkBox.setCheckState(.checked, animated: true)
task.done = true
task.completedBy = user
cell.detailLabel.text = "Completed By: \(task.completedBy)"
cell.label.textColor = UIColor.gray
print("cell checked")
}
else {
cell._checkBox.setCheckState(.unchecked, animated: true)
task.done = false
task.completedBy = ""
cell.detailLabel.text = ""
cell.label.textColor = UIColor.black
print("cell unchecked")
}
fb.updateTaskDoneBool(ref, taskID: task.id, taskDone: task.done)
fb.updateTaskCompletedBy(ref, taskID: task.id, taskCompletedBy: task.completedBy)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell
cell.selectionStyle = .none
task = tasksInSectionArray[indexPath.section][indexPath.row]
cell.label.text = task.title
if task.done == true {
cell._checkBox.setCheckState(.checked, animated: true)
cell.detailLabel.text = "Completed By: \(task.completedBy)"
cell.label.textColor = UIColor.gray
}
else {
cell._checkBox.setCheckState(.unchecked, animated: true)
cell.detailLabel.text = ""
cell.label.textColor = UIColor.black
}
doneHit(cell)
cell.delegate = self
return cell
}}
Looks like you didn't set correctly the delegate property in your TaskCell instance , I will make a very basic example hopefully it helps you to catch the issue:
Result (Edited)
Code
TableViewController
import UIKit
protocol TaskCellDelegate {
func doneHit(_ cell: TaskCell)
}
class TableViewController: UITableViewController, TaskCellDelegate {
func doneHit(_ cell: TaskCell) {
let alert = UIAlertController(
title: "Info",
message: "button touched in cell",
preferredStyle: .alert
)
present(alert, animated: true, completion: nil)
}
}
extension TableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TaskCell
cell.delegate = self // probably you forgot to set this part?
return cell
}
}
TaskCell (Edited)
Instead creating a new UITapGestureRecognizer to attach to the checkbox, you can use addTarget method to attach event handler for the UIControlEvents.valueChanged value.
import UIKit
import M13Checkbox
class TaskCell: UITableViewCell {
var delegate: TaskCellDelegate?
#IBOutlet weak var checkbox: M13Checkbox!
override func awakeFromNib() {
super.awakeFromNib()
checkbox.addTarget(self, action: #selector(buttonClicked), for: .valueChanged)
}
func buttonClicked() {
delegate?.doneHit(self)
}
}
There are following cases if delegate is not being called:
The action: buttonClicked is not being called.
The View Controller not Conforming to the protocol.
class ViewController: UIViewController, TaskCellDelegate {
The protocol method not implemented inside View Controller.
func doneHit(_ cell : TaskCell) {
print("delegate implementation called")
}
Delegate not assigned in cellForRowAtIndexPathMethod:
cell.delegate = self
I have a button (red color cross) in the UITableViewCell and on click of that button I want to get indexPath of the UITableViewCell.
Right now I am assigning tag to each of the button like this
cell.closeButton.tag = indexPath.section
and the on click of the button I get the indexPath.section value like this:
#IBAction func closeImageButtonPressed(sender: AnyObject) {
data.removeAtIndex(sender.tag)
tableView.reloadData()
}
Is this the right way of implementation or is there any other clean way to do this?
Use Delegates:
MyCell.swift:
import UIKit
//1. delegate method
protocol MyCellDelegate: AnyObject {
func btnCloseTapped(cell: MyCell)
}
class MyCell: UICollectionViewCell {
#IBOutlet var btnClose: UIButton!
//2. create delegate variable
weak var delegate: MyCellDelegate?
//3. assign this action to close button
#IBAction func btnCloseTapped(sender: AnyObject) {
//4. call delegate method
//check delegate is not nil with `?`
delegate?.btnCloseTapped(cell: self)
}
}
MyViewController.swift:
//5. Conform to delegate method
class MyViewController: UIViewController, MyCellDelegate, UITableViewDataSource,UITableViewDelegate {
//6. Implement Delegate Method
func btnCloseTapped(cell: MyCell) {
//Get the indexpath of cell where button was tapped
let indexPath = self.collectionView.indexPathForCell(cell)
print(indexPath!.row)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCell") as! MyCell
//7. delegate view controller instance to the cell
cell.delegate = self
return cell
}
}
How to get cell indexPath for tapping button in Swift 4 with button selector
#objc func buttonClicked(_sender:UIButton){
let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
let indexPath = self.tableView.indexPathForRow(at:buttonPosition)
let cell = self.tableView.cellForRow(at: indexPath) as! UITableViewCell
print(cell.itemLabel.text)//print or get item
}
Try with the best use of swift closures : Simple, Quick & Easy.
In cellForRowAtIndexPath method:
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCellIdentifier", for: indexPath) as! CustomCell
cell.btnTick.mk_addTapHandler { (btn) in
print("You can use here also directly : \(indexPath.row)")
self.btnTapped(btn: btn, indexPath: indexPath)
}
Selector Method for external use out of cellForRowAtIndexPath method:
func btnTapped(btn:UIButton, indexPath:IndexPath) {
print("IndexPath : \(indexPath.row)")
}
Extension for UIButton :
extension UIButton {
private class Action {
var action: (UIButton) -> Void
init(action: #escaping (UIButton) -> Void) {
self.action = action
}
}
private struct AssociatedKeys {
static var ActionTapped = "actionTapped"
}
private var tapAction: Action? {
set { objc_setAssociatedObject(self, &AssociatedKeys.ActionTapped, newValue, .OBJC_ASSOCIATION_RETAIN) }
get { return objc_getAssociatedObject(self, &AssociatedKeys.ActionTapped) as? Action }
}
#objc dynamic private func handleAction(_ recognizer: UIButton) {
tapAction?.action(recognizer)
}
func mk_addTapHandler(action: #escaping (UIButton) -> Void) {
self.addTarget(self, action: #selector(handleAction(_:)), for: .touchUpInside)
tapAction = Action(action: action)
}
}
In Swift 4 , just use this:
func buttonTapped(_ sender: UIButton) {
let buttonPostion = sender.convert(sender.bounds.origin, to: tableView)
if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
let rowIndex = indexPath.row
}
}
You can also get NSIndexPath from CGPoint this way:
#IBAction func closeImageButtonPressed(sender: AnyObject) {
var buttonPosition = sender.convertPoint(CGPointZero, to: self.tableView)
var indexPath = self.tableView.indexPathForRow(atPoint: buttonPosition)!
}
Create a custom class of UIButton and declare a stored property like this and use it to retrieve assigned indexPath from callFroRowAtIndexPath.
class VUIButton: UIButton {
var indexPath: NSIndexPath = NSIndexPath()
}
This is the full proof solution that your indexPath will never be wrong in any condition. Try once.
//
// ViewController.swift
// Table
//
// Created by Ngugi Nduung'u on 24/08/2017.
// Copyright © 2017 Ngugi Ndung'u. All rights reserved.
//
import UIKit
class ViewController: UITableViewController{
let identifier = "cellId"
var items = ["item1", "2", "3"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.title = "Table"
tableView.register(MyClass.self, forCellReuseIdentifier: "cellId")
}
//Return number of cells you need
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! MyClass
cell.controller = self
cell.label.text = items[indexPath.row]
return cell
}
// Delete a cell when delete button on cell is clicked
func delete(cell: UITableViewCell){
print("delete")
if let deletePath = tableView.indexPath(for: cell){
items.remove(at: deletePath.row)
tableView.deleteRows(at: [deletePath], with: .automatic)
}
}
}
class MyClass : UITableViewCell{
var controller : ViewController?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
let label : UILabel = {
let label = UILabel()
label.text = "My very first cell"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let btn : UIButton = {
let bt = UIButton(type: .system)
bt.translatesAutoresizingMaskIntoConstraints = false
bt.setTitle("Delete", for: .normal)
bt.setTitleColor(.red, for: .normal)
return bt
}()
func handleDelete(){
controller?.delete(cell: self)
}
func setUpViews(){
addSubview(label)
addSubview(btn)
btn.addTarget(self, action: #selector(MyClass.handleDelete), for: .touchUpInside)
btn.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
label.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 16).isActive = true
label.widthAnchor.constraint(equalTo: self.widthAnchor , multiplier: 0.8).isActive = true
label.rightAnchor.constraint(equalTo: btn.leftAnchor).isActive = true
}
}
Here is a full example that will answer your question.
In your cellForRow:
#import <objc/runtime.h>
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
setAssociatedObject(object: YOURBUTTON, key: KEYSTRING, value: indexPath)
}
#IBAction func closeImageButtonPressed(sender: AnyObject) {
let val = getAssociatedObject(object: sender, key: KEYSTROKING)
}
Here val is your indexPath object, your can pass any object like you can assign pass cell object and get it in button action.
try this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = (tableView.dequeueReusableCell(withIdentifier: "MainViewCell", forIndexPath: indexPath) as! MainTableViewCell)
cell.myButton().addTarget(self, action: Selector("myClickEvent:event:"), forControlEvents: .touchUpInside)
return cell
}
this function get the position of row click
#IBAction func myClickEvent(_ sender: Any, event: Any) {
var touches = event.allTouches()!
var touch = touches.first!
var currentTouchPosition = touch.location(inView: feedsList)
var indexPath = feedsList.indexPathForRow(atPoint: currentTouchPosition)!
print("position:\(indexPath.row)")
}
class MyCell: UICollectionViewCell {
#IBOutlet weak var btnPlus: UIButton!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
cell.btnPlus.addTarget(self, action: #selector(increment_Action(sender:)),
for: .touchUpInside)
cell.btnPlus.tag = indexPath.row
cell.btnPlus.superview?.tag = indexPath.section
}
#objc func increment_Action(sender: UIButton) {
let btn = sender as! UIButton
let section = btn.superview?.tag ?? 0
let row = sender.tag
}