1

I have a UITableView with custom section headers, made via the storyboard using a custom prototype cell with a Identifier of "headerCell", along with a Cocoa Touch Class called "HeaderViewCell" subclassing UITableViewCell.

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let headerCell = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderViewCell

    headerCell.sectionTitle.text = viewModel.items[section].sectionTitle
    headerCell.section = section
    headerCell.delegate = self

    return headerCell
}

The button in the cell fires a delegate func passing in the section that was assigned to it.

Everything works great- setting the title, tapping the button I needed, etc... EXCEPT that when you tap the blank space between the section title (on the left) and button (on the right), the section header highlights as if it's a cell in the section, and then performs the segue for the first row in the section.

Selection is set to "None" in the attributes inspector. If I toggle User Interaction Enabled, then the button does not work.

I've found lots of posts where people are trying to register taps on the section headers (answer: with tap gestures), but exhausted myself in search of how to block them. In the didSelectRow at delegate method, I see the same IndexPath I would as if I clicked on the row and not the header, so I can't block it from there.

Being that using a custom prototype cell is the most widely suggested response to a custom section header, I would have expected this to have been an issue for someone else as well. ?

Andy Stagg
  • 267
  • 3
  • 14
  • do you want to stop the headerCell from highlighting or stop it from performing the segue? – Lance Samaria Apr 29 '18 at 00:22
  • try: https://stackoverflow.com/a/6305493/4833705 – Lance Samaria Apr 29 '18 at 00:30
  • Lance, the section header both highlights and performs the segue- section headers should not do either of those things. tapping the header results in this output from didSelectRow print("section: \(indexPath.section) | row: \(indexPath.row)") section: 1 | row: 0 it's not differencating between the section header and first row [0] – Andy Stagg Apr 29 '18 at 00:46
  • Matt’s answer should work. – Lance Samaria Apr 29 '18 at 00:48
  • Matt's answer does not work. The only way it stops the selection and segue is if it's set not to have user interaction. – Andy Stagg Apr 29 '18 at 00:52
  • Try this first answer and see what happens. I don't know if your using a performSegue or navigationPush but this should prevent the HeaderCell from activating a segue or pushing – Lance Samaria Apr 29 '18 at 01:22

2 Answers2

1

"HeaderViewCell" subclassing UITableViewCell.

Stop right there. That's totally wrong. Your section header should not be a UITableViewCell. It should be a UITableViewHeaderFooterView (or a subclass thereof).

As soon as you make that change (along with any needed corresponding changes to registration of the header view type), your problem will go away.

matt
  • 447,615
  • 74
  • 748
  • 977
  • I get "Cast from 'UITableViewCell?' to unrelated type 'ListsHeaderViewCell' always fails" viewForHeaderInSection returns a UIView – Andy Stagg Apr 29 '18 at 00:55
  • I can't guide you through all the changes you will need to make because of doing this wrong previously. Please note that you cannot make a prototype header in the storyboard; if you thought you were doing that correctly before, you were wrong – matt Apr 29 '18 at 01:38
  • If you need an example of how to create a custom header in code, see https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch08p416sections/ch21p718sections/RootViewController.swift – matt Apr 29 '18 at 01:42
  • Using Matt's answer you need to change the UITableViewCell to a UITableViewHeaderFooterView – Lance Samaria Apr 29 '18 at 02:07
  • thanks again for your help.. but just as a "I'm not totally insane" note: this is an widely accepted practice throughout the web, see https://stackoverflow.com/questions/9219234/how-to-implement-custom-table-view-section-headers-and-footers-with-storyboard?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa – Andy Stagg Apr 29 '18 at 02:47
  • @user1778608 And it was always wrong, and as you can see _from that very answer_, it causes unwanted selection clicks on the header! [Not to mention that that was 6 years ago, perhaps before UITableViewHeaderFooterView even existed.] – matt Apr 29 '18 at 02:49
  • matt writes 1200 page books on iOS, this is one time you should listen to him and not the answer you referred to on SO https://www.amazon.com/Programming-iOS-11-Controllers-Frameworks/dp/1491999225 – Lance Samaria Apr 29 '18 at 02:57
  • @LanceSamaria https://stackoverflow.com/a/36931047/4833705 is wrong too. I've posted a corrective answer. The amount of wrong information about how to configure header views is simply staggering. – matt Apr 29 '18 at 13:32
  • @matt the problem here is it seems the op didn’t understand what you said when you told him to use the HeaderFooterView. I say that because of the cast problem he had. I suggested he use your answer but he said it didn’t work. I sent that link based on his answer that he was still using (tableCell instead of HFV). If he used the HFV then your right about the link be incorrect but he still chooses to use a tableCell so... As for everyone else who uses a tableCell the problem is they follow most popular answer as long as it works. For most It working is more important then being correct ‍♂️ – Lance Samaria Apr 29 '18 at 13:40
  • @LanceSamaria The problem is that Apple has made this really hard to do correctly! They don't give you a way to make a UITableViewHeaderFooterView in the nib, and they don't give you a way to design a header or footer prototype in the storyboard. Thus, it's really easy to slide into an incorrect way of doing it. – matt Apr 29 '18 at 13:58
0

Matt's answer should would work.

Create a Subclass of type UITableViewHeaderFooterView and name it CustomHeaderView

class CustomHeaderView: UITableViewHeaderFooterView {
   // programmatically add the sectionTitle and whatever else inside here. Matt said there isn’t a storyboard or nib for a HeaderFooterView so do it programmatically
}

Then inside inside viewForHeaderInSection use tableView.dequeueReusableHeaderFooterView and cast it as the CustomHeaderView

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    // don't forget to rename the identifier 
    let customHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "customHeaderView") as! CustomHeaderView

    customHeaderView.sectionTitle.text = viewModel.items[section].sectionTitle
    customHeaderView.section = section
    customHeaderView.delegate = self

    return customHeaderView
}

If not try this.

If you don't want the cell to highlight first set the selection style to .none:

Either set .selectionStyle = .none inside the HeaderCell itself

or

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let headerCell = tableView.dequeueReusableCell(withIdentifier: "headerCell") as! HeaderViewCell

    headerCell.sectionTitle.text = viewModel.items[section].sectionTitle
    headerCell.section = section
    headerCell.delegate = self
    headerCell.selectionStyle = .none // set it here

    return headerCell
}

Then in didSelectRowAtIndexPath find out the type of cell that is being selected. If it's a HeaderCell then just return and the cell shouldn't push. If it's any of the other type of cells (eg PushCell) then those cells should perform the segue:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    // if it's a HeaderCell then do nothing but print
    if let _ = tableView.cellForRowAtIndexPath(indexPath) as? HeaderCell {
        tableView.deselectRow(at: indexPath, animated: true)
        print("+++++HeaderCell was tapped")
        return // nothing should happen
    }

    // if it's a PushCell then push
    if let _ = tableView.cellForRowAtIndexPath(indexPath) as? PushCell {
        print("-----PushCell was tapped")
        performSegue(withIdentifier...
        // or if your using navigationController?.pushViewController(...
    }
}
Lance Samaria
  • 11,429
  • 8
  • 67
  • 159
  • thanks for taking the time to think about this... however setting the .selectionStyle from code does not alter the behavior. if casting it to a HeaderCell also doesn't catch, it still returns a "normal" row cell for that section, because the index path is that of the first row in the section. – Andy Stagg Apr 29 '18 at 01:42
  • let's work on 1 problem at a time. Does the HeaderCell no longer push or is it still pushing? – Lance Samaria Apr 29 '18 at 01:43
  • look at what I just added inside didSelectRowAtIndexPath inside the if let for the HeaderCell >>>tableView.deselectRow(at: indexPath, animated: true) – Lance Samaria Apr 29 '18 at 02:04