UICollectionViewCell has no member present [duplicate] - ios

calling this function from a UIViewController results in no problems, but calling it from a UICollectionViewCell raises a pre-compilation error
Function :
func didTapShare(sender: UIButton)
{
let textToShare = "Swift is awesome! Check out this website about it!"
if let myWebsite = NSURL(string: "http://www.google.com/")
{
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.excludedActivityTypes = [UIActivityTypeAirDrop, UIActivityTypeAddToReadingList]
activityVC.popoverPresentationController?.sourceView = sender
self.presentViewController(activityVC, animated: true, completion: nil)
}
}
Error :
your cell has no member presentViewController
what to do?

UITableViewCell should never handle any business logic. It should be implemented in a view controller. You should use a delegate:
UICollectionViewCell subclass:
protocol CustomCellDelegate: class {
func sharePressed(cell: MyCell)
}
class CustomCell: UITableViewCell {
var delegate: CustomCellDelegate?
func didTapShare(sender: UIButton) {
delegate?.sharePressed(cell: self)
}
}
ViewController:
class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
//...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CustomCell
cell.delegate = self
return cell
}
}
extension TableViewController: CustomCellDelegate {
func sharePressed(cell: CustomCell) {
guard let index = tableView.indexPath(for: cell)?.row else { return }
//fetch the dataSource object using index
}
}

That's because presentViewController is a UIViewController method, UITableViewCell does not has a method called presentViewController.
what to do?
You can use Delegation pattern for handling accessing the of the button's action (as #alexburtnik answer), or -for saving some extra work-
I suggest to handle the action of the cell's button in the viewController by recognizing it via tag for it.
Note: Swift 3 Code.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableViewCell
cell.myButton?.tag = indexPath.row
cell.myButton?.addTarget(self, action: #selector(), for: .touchUpInside)
return cell
}
func namesIsTapped(tappedButton: UIButton) {
// get the user (from users array for example) by using the tag, for example:
let currentUser = users[tappedButton.tag]
// do whatever you want with this user now...
}
Hope that helped.

Related

Protocol not passing to ViewController (Swift)

So I defined the ClickableCell protocol which is supposed to respond to tap gestures on an image. I want to redirect to ViewController2 when the user taps on the image, so my approach is to define a protocol, pass the info of tap to the CurrentViewController, and use the protocol in CurrentViewController to redirect to ViewController2. But I used print statements and it only printed "TAP TAP TAP" and "DONE" but not the portion from CurrentViewController. Can anyone help me figure out the problem? Thanks!
MessageCell.swift
protocol ClickableCell {
func onTapImage(indexPath: IndexPath)
}
class MessageCell: UITableViewCell {
#IBOutlet weak var leftImage: UIImageView!
var cellDelegate: ClickableCell?
var index: IndexPath?
override func awakeFromNib() {
super.awakeFromNib()
self.setupLabelTap()
}
#objc func imageTapped(tapGestureRecognizer: UITapGestureRecognizer){
print("TAP TAP TAP")
cellDelegate?.onTapImage(indexPath: index!)
print("DONE")
}
func setupLabelTap() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped(tapGestureRecognizer:)))
self.leftImage.isUserInteractionEnabled = true
self.leftImage.addGestureRecognizer(tapGestureRecognizer)
}
}
CurrentViewController
extension CurrentViewController: ClickableCell {
func onTapImage(indexPath: IndexPath) {
print("Doesn't Print This")
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "nextView") as! UIViewController
self.navigationController?.pushViewController(popOverVC, animated: true)
}
}
You need to assign that cellDelegate when you create cell
extension CurrentViewController: UITableViewDataSource {
// other your code here
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "your_message_cell_id",
for: indexPath) as! MessageCell
/// other setup code here
cell.cellDelegate = self // << here !!
return cell
}
}
You need to set the delegate. The core purpose of the delegate pattern is to allow an object to communicate back to its owner in a decoupled way.
So in cellForRowAt method:-
cell.delegate = self

index of button in custom cell

I create a custom cell that contains a button, I need to create segue from this button to other VC but first of all, I would like to push an object with that segue.
I already try to use cell.button.tag, but I did not succeed.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showMap" {
let mapVC = segue.destination as! MapViewController
//guard let indexPath = tableView.indexPathForSelectedRow else { return }
mapVC.place = places[] // <- "here I need index of button in cell"
}
}
Instead of using the segue, handle the navigation programatically through a closure in UITableViewCell.
class CustomCell: UITableViewCell {
var buttonTapHandler: (()->())?
#IBAction func onTapButton(_ sender: UIButton) {
self.buttonTapHandler?()
}
}
In the above code, I've create a buttonTapHandler - a closure, that will be called whenever the button inside the cell is tapped.
Now, in cellForRowAt method when you dequeue the cell, set the buttonTapHandler of CustomCell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.buttonTapHandler = {[weak self] in
if let mapVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MapViewController") as? MapViewController {
mapVC.place = places[indexPath.row]
self?.navigationController?.pushViewController(mapVC, animated: true)
}
}
return cell
}
In the above code, buttonTapHandler when called will push a new instance of MapViewController along with the relevant place based on the indexPath.
if you don't want to execute your code in didSelectRowAt method, another good approach in my opinion is to create a delegate of your custom cell. See the code below
// This is my custom cell class
class MyCustomCell: UITableViewCell {
// The button inside your cell
#IBOutlet weak var actionButton: UIButton!
var delegate: MyCustomCellDelegate?
#IBAction func myDelegateAction(_ sender: UIButton) {
delegate?.myCustomAction(sender: sender, index: sender.tag)
}
// Here you can set the tag value of the button to know
// which button was tapped
func configure(index: IndexPath){
actionButton.tag = index.row
}
}
protocol MyCustomCellDelegate {
func myDelegateAction(sender: UIButton, index: Int)
}
Delegate the ViewController where you use your custom cell.
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellIdentifier", for: indexPath) as! MyCustomCell
cell.configure(index: indexPath)
cell.delegate = self
return cell
}
}
And at the end customize your method extending your custom cell delegate
extension MyViewController: MyCustomCellDelegate {
func myDelegateAction(sender: UIButton, index: Int) {
// Do your staff here
}
}
I hope I was helpful.
In the custom cell:
import UIKit
protocol CustomCellDelegate: class {
func btnPressed(of cell: CustomCell?)
}
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
#IBAction func btnTapped(_ sender: UIButton) {
delegate?.btnPressed(of: self)
}
}
And in the view controller:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomCell = tableView.dequeueReusableCell(for: indexPath)
cell.delegate = self
return cell
}
extension ViewController: CustomCellDelegate {
func btnPressed(of cell: CustomCell?) {
if let cell = cell, let indexPath = tableView.indexPath(for: cell) {
// Your stuff here
}
}
}

iOS Swift: Pushing a View Onto the Stack From Within a Custom Tableview Cell

I have a tableview inside a VC that has a navigation controller and it contains custom table cells. I was wondering what the best practice is for pushing onto the parent VC's navigation stack if a button in the custom table cell is tapped. I am able to get this to work if i pass the parent VC's navigation controller to the cell; but is this the most effective/efficient practice? Please see my current implementation below:
UserAccountVC:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TextPostTableViewCell = Bundle.main.loadNibNamed("TextPostTableViewCell", owner: self, options: nil)?.first as! TextPostTableViewCell
cell.setupCell(navigationController: self.navigationController!)
cell.selectionStyle = .none
return cell
}
CustomTableCell:
import UIKit
class TextPostTableViewCell: UITableViewCell {
var aNavigationController: UINavigationController!
//MARK: Actions
#IBAction func profilePicButtonTapped() { //We want to present a users profile
let sb = UIStoryboard(name: "SuccessfulLogin", bundle: nil)
let cc = (sb.instantiateViewController(withIdentifier: "otherUserViewController")) as! OtherUserAccountViewController
self.aNavigationController.pushViewController(cc, animated: true)
}
func setupCell(navigationController: UINavigationController) -> Void {
aNavigationController = navigationController
}
}
Thank you in advance!
No, this is not best practice. You can setup an IBAction in interface builder for your UIButton or add your UIViewController as a target in cellForRowAt. With either method you may need some method of identifying the indexPath, since you are not using didSelectRow in your tableview delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TextPostTableViewCell = Bundle.main.loadNibNamed("TextPostTableViewCell", owner: self, options: nil)?.first as! TextPostTableViewCell
cell.button.tag = indexPath.row // Or use some other method of identifying your data in `myAction(_:)`
cell.button.addTarget(self, action:, #selector(myAction(_:)), for: .touchUpInside)
...
}
You can use delegate in this situation.
The code is a bit more here, but this is better way in iOS development IMO.
class ViewController: UIViewController {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TextPostTableViewCell = Bundle.main.loadNibNamed("TextPostTableViewCell", owner: self, options: nil)?.first as! TextPostTableViewCell
cell.delegate = self
cell.selectionStyle = .none
return cell
}
}
extension ViewController: TextPostTableViewCellDelegate {
func didTappedProfilePicButton() {
let sb = UIStoryboard(name: "SuccessfulLogin", bundle: nil)
let cc = (sb.instantiateViewController(withIdentifier: "otherUserViewController")) as! OtherUserAccountViewController
navigationController?.pushViewController(cc, animated: true)
}
}
protocol TextPostTableViewCellDelegate: class {
func didTappedProfilePicButton()
}
class TextPostTableViewCell: UITableViewCell {
weak var delegate: TextPostTableViewCellDelegate?
//MARK: Actions
#IBAction func profilePicButtonTapped() { //We want to present a users profile
delegate?.didTappedProfilePicButton()
}
}

how to call presentViewController from within a UICollectionViewCell

calling this function from a UIViewController results in no problems, but calling it from a UICollectionViewCell raises a pre-compilation error
Function :
func didTapShare(sender: UIButton)
{
let textToShare = "Swift is awesome! Check out this website about it!"
if let myWebsite = NSURL(string: "http://www.google.com/")
{
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.excludedActivityTypes = [UIActivityTypeAirDrop, UIActivityTypeAddToReadingList]
activityVC.popoverPresentationController?.sourceView = sender
self.presentViewController(activityVC, animated: true, completion: nil)
}
}
Error :
your cell has no member presentViewController
what to do?
UITableViewCell should never handle any business logic. It should be implemented in a view controller. You should use a delegate:
UICollectionViewCell subclass:
protocol CustomCellDelegate: class {
func sharePressed(cell: MyCell)
}
class CustomCell: UITableViewCell {
var delegate: CustomCellDelegate?
func didTapShare(sender: UIButton) {
delegate?.sharePressed(cell: self)
}
}
ViewController:
class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
//...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CustomCell
cell.delegate = self
return cell
}
}
extension TableViewController: CustomCellDelegate {
func sharePressed(cell: CustomCell) {
guard let index = tableView.indexPath(for: cell)?.row else { return }
//fetch the dataSource object using index
}
}
That's because presentViewController is a UIViewController method, UITableViewCell does not has a method called presentViewController.
what to do?
You can use Delegation pattern for handling accessing the of the button's action (as #alexburtnik answer), or -for saving some extra work-
I suggest to handle the action of the cell's button in the viewController by recognizing it via tag for it.
Note: Swift 3 Code.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableViewCell
cell.myButton?.tag = indexPath.row
cell.myButton?.addTarget(self, action: #selector(), for: .touchUpInside)
return cell
}
func namesIsTapped(tappedButton: UIButton) {
// get the user (from users array for example) by using the tag, for example:
let currentUser = users[tappedButton.tag]
// do whatever you want with this user now...
}
Hope that helped.

How to update DetailView

I have a swift app based on Master-Detail template. Every row in MasterView table is based on custom cell received from a nib. Every cell includes UIlabel and UIbutton. The logic of the app is following. If user taps on a row DetailView shows some details depending on selected row. The button on the row does not call tableView(_, didSelectRowAtIndexPath). If user taps on the button inside a row only an image belongs to DetailView should be changed (other elements on DetailView remain the same) but it isn't. If I select another row and than select previous row back, changed image is shown on the DetailView as it was foreseen. The question is how to redraw the image in the DetailView just by tapping on the button.
I've tried to do following but with no success:
class MasterViewCell: UITableViewCell {
weak var detailViewController: DetailViewController?
#IBAction func buttonTap(sender: AnyObject) {
//method to set new image
detailViewController!.setNewImage()
detailViewController!.view.setNeedsDisplay()
}
}
class MasterViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "itemCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "Cell")
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? MasterViewCell
cell?.detailView = self.detailViewController
return cell!
}
You need to use a handler
typealias ButtonHandler = (Cell) -> Void
class Cell: UITableViewCell {
var changeImage: ButtonHandler?
func configureButton(changeImage: ButtonHandler?) {
self.changeImage = changeImage
}
#IBAction func buttonTap(sender: UIButton) {
changeImage?(self)
}
}
And in your MasterView
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell
cell.configureButton(setNewImage())
return cell
}
private func setNewImage() -> ButtonHandler {
return { [unowned self] cell in
let row = self.tableView.indexPathForCell(cell)?.row //Get the row that was touched
//set the new Image
}
}
SOURCE: iOS Swift, Update UITableView custom cell label outside of tableview CellForRow using tag
I've found the solution. I've used protocol-delegate mechanism. Now the code is:
//protocol declaration:
protocol MasterViewCellDelegate: class {
func updateImage(sender: MasterViewCell, detVC: DetailViewController)
}
// cell class
class MasterViewCell: UITableViewCell {
weak var masterViewCellDelegate: MasterViewCellDelegate? // protocol property
weak var masterViewController: MasterViewController? {
didSet {
// set delegate
self.masterViewDelegate = masterViewController!.detailViewController
}
}
#IBAction func buttonTap(sender: AnyObject) {
var detVC: DetailViewController?
if let split = masterViewController!.splitViewController {
let controllers = split.viewControllers
detVC = (controllers[controllers.count - 1] as! UINavigationController).topViewController as? DetailViewController
}
// call delegate
masterViewCellDelegate?.updateImage(self, detVC: detVC)
}
class MasterViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "itemCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "Cell")
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? MasterViewCell
cell?.masterViewController = self
return cell!
}
// declare detailviewcontroller as delegate
class DetailViewController: UIViewController, MasterViewCellDelegate {
func updateImage(sender: MasterViewCell, detVC: DetailViewController){
detVC.setNewImage()
}
}
It may well be that this solution is excessively complex, but it works and easy could be adapted for various purposes.

Resources