Add a button in tableViewHeader like default iOS one - ios

I have a UITableViewController where I want to add a UIButton in the section header.
I saw in calendar app this button, and I wanted to know if this is part of a class I can reuse, or if I need to write my own class to add it.

You need to use your own custom class for this case.
Because UITableViewHeaderFooterView does not containing any kind of buttons itself.
So, better go with your customClass.

You have to create a xib file and create it class file.
Add your button and create its outlet.
Register your xib same in you viewDidLoad()
For example:
let headerNib = UINib(nibName: "yourNIBname", bundle: nil)
tableView.register(headerNib, forHeaderFooterViewReuseIdentifier: "reuseIdentifer")
Then add the following tableview delegate
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "reuseIdentifer") as! youNIBClassName
headerView.yourButton.addTarget(self, action: #selector(hideAllTapped(withSender:)), for: .touchUpInside)
return headerView
}
You will get the action event in your hideAllTapped() function
#objc func hideAllTapped(withSender sender: UIButton) {
// do your action
}

Related

indexPath is nil when passing cell from delegate to UITableViewController

I have a button called addSet at the end of each section of my tableView, it is used as a footerView and it is supposed to tell the UITableViewController of when it is pressed and in which section. My code for the custom table view cell is as follows
import UIKit
class FooterTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
var footerDelegate:FooterTableViewCellDelegate?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func addSetIsPressed(_ sender: AnyObject) {
print("Add Set is pressed")
footerDelegate?.didAddSetIsPressed(cell:self)
}
}
protocol FooterTableViewCellDelegate {
func didAddSetIsPressed(cell:FooterTableViewCell)
}
And in my TableViewController, I implement it like so
func didAddSetIsPressed(cell: FooterTableViewCell) {
let indexPath = tableView.indexPath(for: cell)
print("Index path is \(indexPath)")
}
I want to get the indexPath (the section specifically) when the user taps my button, however it always returns nil. What am I doing wrong?
To put things in context. I am using this cell as a footerView, so the cell is implemented like so
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "footerCell") as! FooterTableViewCell
cell.footerDelegate = self
return cell
}
so it isn't implemented in cellForRow at indexPath like it would normally be
Thanks in advance.
The thing is you put the cell FooterTableViewCell as a viewForFooterInSection,
so it's not used as a UITableViewCell in the UITableView, so the UITableView is not holding the indexPath of this UITableViewCell "Cause i said previously, the cell's view only is used as a footerView"
You need to add the button inside the cell that's being rendered on the UITableView. "The one that's being returned in the tableView(_:cellForRowAt:) method"
On a side note i noticed that you have a variable named footerDelegate in your cell, it needs to be weak to avoid memory leaks as you assign your TableViewController as this delegate,
so the UITableViewCell holds a strong reference of the TableViewController that leads to memory leak cause also in the view hierarchy the TableViewController contains the UITableView as a subView.
I found out how to do it, in order to detect the section in which the button was tapped. There must be an outlet reference in the FooterCell and in the tableViewController, in viewForFooter in Section, just add the following line
cell.addSetOutlet.tag = section

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
}
}

Swift 3 - button in .xib custom tableviewcell is not performing action

I have a tableviewcontroller, where i have created custom tableviewcell in a .xib files.
The button (myButton) is used on myCustomTableViewCell.xib has an outlet to myCustomTableViewCell.swift
I have set an action for the button in the TableViewController.swift file
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("myCustomTableViewCell", owner: self, options: nil)?.first as! myCustomTableViewCell
cell.myButton.setTitle(arrayOfMyData[indexPath.row].myText, for:.normal )
//other code to set some other cell.attribute values like above (all works)
cell.myButton.actions(forTarget: "myAction", forControlEvent: .touchUpInside)
}
and elsewhere in the TableViewController.swift I have the action:
func myAction(sender: UIButton){
print("pressed myButton")
}
but myAction is never called / "pressed myButton" is never printed. What am I missing?
You need to use addTarget(_:action:for:) method to add the action for your button not the actions(forTarget:forControlEvent:).
The function actions(forTarget:forControlEvent:) returns the actions that have been added to a control. It doesn't add a target/action.
cell.myButton.addTarget(self,action: #selector(myAction(sender:)), for: .touchUpInside)

unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard

My UITableViewController is causing a crash with the following error message:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
I understand that I need to register a nib or a class but I don't understand 'where or how?'.
import UIKit
class NotesListViewController: UITableViewController {
#IBOutlet weak var menuButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "preferredContentSizeChanged:",
name: UIContentSizeCategoryDidChangeNotification,
object: nil)
// Side Menu
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = "revealToggle:"
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// whenever this view controller appears, reload the table. This allows it to reflect any changes
// made whilst editing notes
tableView.reloadData()
}
func preferredContentSizeChanged(notification: NSNotification) {
tableView.reloadData()
}
// #pragma mark - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let note = notes[indexPath.row]
let font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
let textColor = UIColor(red: 0.175, green: 0.458, blue: 0.831, alpha: 1)
let attributes = [
NSForegroundColorAttributeName : textColor,
NSFontAttributeName : font,
NSTextEffectAttributeName : NSTextEffectLetterpressStyle
]
let attributedString = NSAttributedString(string: note.title, attributes: attributes)
cell.textLabel?.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.textLabel?.attributedText = attributedString
return cell
}
let label: UILabel = {
let temporaryLabel = UILabel(frame: CGRect(x: 0, y: 0, width: Int.max, height: Int.max))
temporaryLabel.text = "test"
return temporaryLabel
}()
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
label.sizeToFit()
return label.frame.height * 1.7
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
notes.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
// #pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if let editorVC = segue.destinationViewController as? NoteEditorViewController {
if "CellSelected" == segue.identifier {
if let path = tableView.indexPathForSelectedRow() {
editorVC.note = notes[path.row]
}
} else if "AddNewNote" == segue.identifier {
let note = Note(text: " ")
editorVC.note = note
notes.append(note)
}
}
}
}
You can register a class for your UITableViewCell like this:
With Swift 3+:
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
With Swift 2.2:
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
Make sure same identifier "cell" is also copied at your storyboard's UITableViewCell.
"self" is for getting the class use the class name followed by .self.
Have you set the Table Cell identifier to "Cell" in your storyboard?
Or have you set the class for the UITableViewController to your class in that scene?
This worked for me, May help you too :
Swift 4+ :
self.tableView.register(UITableViewCell.self, forCellWithReuseIdentifier: "cell")
Swift 3 :
self.tableView.register(UITableViewCell.classForKeyedArchiver(), forCellReuseIdentifier: "Cell")
Swift 2.2 :
self.tableView.registerClass(UITableViewCell.classForKeyedArchiver(), forCellReuseIdentifier: "Cell")
We have to Set Identifier property to Table View Cell as per below image,
I had this issue today which was solved by selecting Product -> Clean. I was so confused since my code was proper. The problem started from using command-Z too many times :)
y my case i solved this by named it in the "Identifier" property of Table View Cell:
Don't forgot: to declare in your Class: UITableViewDataSource
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
Just drag a cell (as you did for TableViewController) and add in to it just by releasing the cell on TableViewController. Click on the cell and.Go to its attributes inspector and set its identifier as "Cell".Hope it works.
Don't forget you want Identifier on the Attributes Inspector.
(NOT the "Restoration ID" on the "Identity Inspector" !)
Match the identifier name at both places
This error occurs when the identifier name of the Tablecell is different in the Swift file and in the Storyboard.
For example, the identifier is placecellIdentifier in my case.
1) The Swift File
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "placecellIdentifier", for: indexPath)
// Your code
return cell
}
2) The Storyboard
One more reason for this issue to happen is an earlier problem. When showing a new ViewController, instantiating the target ViewController directly will of course not load the prototype cells from the StoryBoard. The correct solution should always be to instantiate the view controller through the story board like this:
storyboard?.instantiateViewController(withIdentifier: "some_identifier")
In Swift 3.0, register a class for your UITableViewCell like this :
tableView.register(UINib(nibName: "YourCellXibName", bundle: nil), forCellReuseIdentifier: "Cell")
I had the same problem. This issue worked for me. In storyboard select your table view and change it from static cells into dynamic cells.
My problem was I was registering table view cell inside dispatch queue asynchronously. If you have registered table view source and delegate reference in storyboard then dispatch queue would delay the registration of cell as name suggests it will happen asynchronously and your table view is looking for the cells.
DispatchQueue.main.async {
self.tableView.register(CampaignTableViewCell.self, forCellReuseIdentifier: CampaignTableViewCell.identifier())
self.tableView.reloadData()
}
Either you shouldn't use dispatch queue for registration OR do this:
DispatchQueue.main.async {
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.register(CampaignTableViewCell.self, forCellReuseIdentifier: CampaignTableViewCell.identifier())
self.tableView.reloadData()
}
There is two way you can define cell. If your table cell is inside on your ViewControllern then get the cell this way:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
// write your code here
return cell
}
But if you define cell outside of your ViewController then call the sell this way:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("TableViewCell", owner: self, options: nil)?.first as! TableViewCell
// write your code here
return cell
}
And as everyone said don't forget to set your cell identifier:
Stupid mistake:
make sure you add register(TableViewCell.self, forCellReuseIdentifier: "Cell") instead of register(TableViewCell.self, forHeaderFooterViewReuseIdentifier: "Cell")
If you defined your cell through the Interface Builder, by placing a cell inside your UICollectionView, or UITableView :
Make sure you binded the cell with an actual class you created, and very important, that you checked "Inherit module from target"
It used to work on swift 3 and swift 4 but now its not working.
like
self.tableView.register(MyTestTableViewCell.self, forCellReuseIdentifier: "cell")
So I have tried the most of the solutions mentioned above in swift 5 but did not get any luck.
Finally I tried this solution and it worked for me.
override func viewDidLoad()
{
tableView.register(UINib.init(nibName: "MyTestTableViewCell", bundle: nil), forCellReuseIdentifier: "myTestTableViewCell")
}
I just met the same issue and see this post. For me it's because I forgot the set the identifier of cell, also as mentioned in other answers. What I want to say is that if you are using the storyboard to load custom cell we don't need to register the table view cell in code, which can cause other problems.
See this post for detail:
Custom table view cell: IBOutlet label is nil
Swift 5
you need to use UINib method to register cell in viewDidLoad
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
//register table view cell
tableView.register(UINib.init(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomTableViewCell")
}
I had the same issue where I registered my custom UITableViewCell classes within the viewDidLoad() which threw this error. To fix it what I did was registered the cells within the didSet property observer, as shown below
#IBOutlet tableview : UITableView! {
didSet {
tableview.register(CustomCell.self, forCellReuseIdentifier: "Cell")
}
}
Just for those new to iOS buddies (like me) who decided to have multiple cells and in a different xib file, the solution is not to have identifier but to do this:
let cell = Bundle.main.loadNibNamed("newsDetails", owner: self, options: nil)?.first as! newsDetailsTableViewCell
here newsDetails is xib file name.
I ran into this message when UITableView in the IB was moved into another subview with Cmd-C - Cmd-V.
All identifiers, delegate methods, links in the IB etc. stay intact, but exception is raised at the runtime.
The only solution is to clear all inks, related to tableview in the IB (outlet, datasource, delegate) and make them again.
If anyone is doing Unit Testing on a tableView and you're wondering why this error is appearing, just make sure that if you're using a text fixture, you must declare the system under test (SUT) in the setUp function correctly otherwise this error will keep coming up. It is also crucial you call loadViewIfNeeded() so the outlets between your code and storyboard are connected.
override func setUp() {
super.setUp()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
sutSearch = storyboard.instantiateViewController(identifier:String(describing: SearchTableViewController.self))
sutSearch.loadViewIfNeeded() // To make sure your outlets are connected.
}
In the “Subclass of” field, select UITableViewController.
The class title changes to xxxxTableViewController. Leave that as is.
Make sure the “Also create XIB file” option is selected.
Make sure you have the identifier in the attributes filled out with your cell identifier
I was also struggling with the same problem. I had actually deleted the class and rebuilt it. Someone, the storyboard had dropped the link between prototype cell and the identifier.
I deleted the identifier name and re-typed the identifier name again.
It worked.
If the classic solutions (register identifier for class in code or IB) do not work: try to relaunch Xcode, turns out my storyboard stopped saving edits I was made, including setting the reuse identifier.
My dynamic tableview was working properly, with cell identifier set on the Storyboard and in dequeueReusableCell(withIdentifier:.
I then switched the UITableView content from Dynamic Prototypes to Static Cells.
Running the app immediately caused the error, although the cell's identifier was still set to the same value on the Storyboard.
For a static table view, you must register the cell identifier outside the Storyboard:
tableView.register(EntryNutritionCell.self, forCellReuseIdentifier: "Cell")
or, comment out or remove cellForRowAtIndexPath: entirely. This function isn't really used by the Static table view, but is still called(?) and causes the crash:
// override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
// return cell
// }
'Table View Cell' identifier must match the class identifier.
ex: if your 'Table View Cell' identifier is named "myCellId", then your code should be:
let myCell = tableView.dequeueReusableCell(withIdentifier: "myCellId", for: indexPath).
Also, after hours of troubleshooting i realized that having a GestureRecognizer class in my didLoad() was not allowing me to click table cells. so removing all 'hide keyboard' functionality from didLoad() and other extra code solved it for me.
I was struggling with the same problem. i have already check my reusableCell Identifier it was same as in my code. I deleted line of my code
"let Cell = tableView.dequeueReusableCell(withIdentifier: "CELL", for: indexPath)"
clean build
and write it again!
It worked.

Get the IndexPath from inside a UITableViewCell subclass in Swift

I have a custom UITableViewCell subclass and its associated xib. I have a UILabel and a UIButton in this cell and I have wired the touch up inside action of the button to the subclass.
What I need is when that button in the cell is tapped, to get the indexpath of the cell which has that button. And maybe send it back to the view controller via a delegate or something.
Since I'm inside a UITableViewCell subclass, I can't use a solution like this because I don't have a reference to the tableview from inside the cell subclass. Upon further investigation I found another solution and I implemented it in Swift like this.
import UIKit
class ContactCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
selectionStyle = .None
}
#IBAction func callButtonPressed(sender: UIButton) {
let indexPath = (self.superview as UITableView).indexPathForCell(self)
println("indexPath?.row")
}
}
But when I tap on the button, it crashes with an error message saying Swift dynamic cast failed.
Any idea what's wrong with my code?
Or I'm open to any other suggestions which would allow me to achieve the desired result in any other way.
Thank you.
Sounds like you need a delegate:
Delegates in swift?
Then just pass the cell itself as a parameter to the delegate, and then you can easily do tableView.indexPathForCell(cellFromDelegateMethod)
Hey you can use "Tag" of the button also.Inside the cellForRowAt method of table delegate u can tag the button with Indexpath.row . here is the example what i m tried to say.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// get ur cell nib .As it has a button
cell.startOrConntinuBtn.addTarget(self, action: #selector(sumbitOrContinue), for: .touchUpInside)
cell.startOrConntinuBtn.tag = indexPath.row }
and in the touch method "sumbitOrContinue" -
func sumbitOrContinue(sender: UIButton!) {
let tag = sender.tag
// do what you want to do like this example
let detail = self.detailList[tag]
let vc = self.storyboard?.instantiateViewController(withIdentifier: "mockExamInt") as! MockWindowVc
vc.detailId = detail.id
self.navigationController?.pushViewController(vc, animated: true)}
UIButton.Type really does not have member superview, but sender have
var cell: UITableViewCell = sender.superview.superview as UITableViewCell

Resources