I am in the process of updating my app to iOS10 with Swift 2.3 and Xcode 8 Beta 1 and I have found that there is a UITableViewHeaderFooterContentView which is blocking touches to the UIButton on my subclass of UITableViewHeaderFooterView.
On the Xcode 8 Beta 1 simulator the UIButton works on iOS9.3 but not iOS10.
1) Is there any documentation for this?
2) How can I ensure my UI elements are on top of the new Content View in iOS10? (or allow touches through the UITableHeaderFooterContentView)
Thanks!
Table Header
import UIKit
class TableHeader: UITableViewHeaderFooterView {
#IBOutlet weak var dayLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var addNewEventButton: UIButton!
}
Code In View Controller
dateCell.addNewEventButton is the UIButton that is no longer receiving touches in iOS10
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let tintColor = TintManager().getTintColour()
let dateCell:TableHeader = tableView.dequeueReusableHeaderFooterViewWithIdentifier("TableHeader") as! TableHeader
//dateCell.bringSubviewToFront(dateCell.addNewEventButton)
dateCell.dayLabel.text = Dates.day.uppercaseString
dateCell.dateLabel.text = Dates.date
dateCell.backgroundView = UIView(frame: dateCell.frame)
dateCell.backgroundView!.backgroundColor = tintColor
dateCell.dayLabel.textColor = UIColor.whiteColor()
dateCell.dateLabel.textColor = UIColor.whiteColor()
dateCell.addNewEventButton.backgroundColor = tintColor
dateCell.addNewEventButton.tag = section
dateCell.addNewEventButton.layer.cornerRadius = 20.0
if (savedEventView.superview === self.view) {
dateCell.addNewEventButton.removeTarget(nil, action: nil, forControlEvents: .AllEvents)
dateCell.addNewEventButton.addTarget(self, action: #selector(ViewController.userPressedAddButtonToInsertSavedEvent(_:)), forControlEvents:.TouchUpInside)
} else {
dateCell.addNewEventButton.removeTarget(nil, action: nil, forControlEvents: .AllEvents)
dateCell.addNewEventButton.addTarget(self, action: #selector(ViewController.userPressedAddNewEventOnTableViewHeader(_:)), forControlEvents:.TouchUpInside)
}
return dateCell
}
The offending view is in fact the contentView of the UITableViewHeaderFooterView (see the Apple Docs). So you should be able just to use sendSubview(toBack:) in order to stop it interfering with touches.
However, it seems that under iOS9 the UITableViewHeaderFooterView fails to correctly initialise the contentView if the view is loaded from a NIB. Although the contentView property is not optional, it is in fact nil, and you get a BAD ACCESS error if you try to access it. Nor can you set a value for contentView (either in code or as an outlet in IB) because it's a read only property (*). So the only solution I can think of is to use #available to conditionally include code to move the contentView to the back, if you are running on iOS 10 or newer. I would put the relevant code into your subclass:
override func awakeFromNib() {
if #available(iOS 10, *) {
self.sendSubview(toBack: contentView)
}
}
(*) Indulging in wild speculation, my guess is that Apple based the UITableViewHeaderFooterView code heavily on UITableViewCell. Since IB has UITableViewCells in its object library (and notice these include the cell's contentView), it can ensure that the cell's contentView is correctly instantiated. But since there is no UITableViewHeaderFooterView in the object library, there's no way to get the contentView loaded correctly. Looks like they fixed it in iOS10 by instantiating an empty contentView. Pity they didn't also add UITableViewHeaderFooterView to the library.
Related
In my Xcode project I have a view controller class
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var refreshButton: UIBarButtonItem!
var refreshUiButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
refreshUiButton.setImage(#imageLiteral(resourceName: "refresh"), for: .normal)
refreshUiButton.imageView?.contentMode = UIViewContentMode.scaleAspectFit
refreshUiButton.addTarget(self, action: #selector(didRefreshClicked(_:)), for: .touchUpInside)
refreshButton.customView = refreshUiButton
}
and here what it looks like
The button covers segmented control in the middle of navigation panel.I have an extension for UIButton, because of it I want to use UIbutton as a custom view for UIBarButtonItem.
I want it to look like this
How can I organize that?
It’s seems that your button image has incorrect size. If I right you need sizes according Human Design. Or you can change tint color for system barButtonItem to black.
I’m new to swift dev and stack overflow, I’ll really appreciate if someone could shed some light over a search bar sizing issue I’m having. I’m not being able to correctly size a search bar in the screen. I created a simple version of my project to simplify the reproduction of the odd behavior. It’s a single ViewController with a tableView object to display results of a searchBar search. I don’t want the search bar to roll up along with the table cells, so I created a UIView as placeholder for the searchBar just above the tableView in the Main.storyboard and added necessary constraints to them, so they could be resized correctly with different screen sizes and orientations — indeed, auto-layout seems to be working fine.
Please see the image links as I still can't post images here. Main.storyboard image
I also don’t want to use a UISearchBar object in the storyboard because I want to have control over it in my code later. So I added the SearchController searchBar programmatically as a UIview subview with (apparently all) the needed constraints to be resized along with the UIview. Also, as you can see, tableView and the UIView widths are a little smaller than the screen width, it’s one difference to other posts I found, they were all fully stretched to screen edges — I don’t know if this is important or not. I'm using swift 4 on xcode 9.0.1 (9A1004).
The issue:
As you can see the searchBar right edge passes over the UIView frame right edge. When it's tapped the cancel button passes over the screen limit as well.
searchBar image 1
It's even more weird, when orientation is changed to landscape, searchBar width initially doesn’t stretch with the UIView. But when searchBar is tapped its width returns to pass over the screen limit. And when cancel is tapped searchBar width continues greater than the screen width. This behavior occurs no matter screen size and orientation I choose.
What I tried so far:
This project contains all the recommended properties and constraints I could find, with the exception of the property setTranslatesAutoresizingMaskIntoConstraints which I didn't manage to use. On swift 4, I only found a similar one translatesAutoresizingMaskIntoConstraints, but I couldn’t make it work, it returns “Cannot call value of non-function type 'Bool’”.
// searchController.searchBar.setTranslatesAutoresizingMaskIntoConstraints(true)
searchController.searchBar.translatesAutoresizingMaskIntoConstraints(true) (!) Cannot call value of non-function type 'Bool'
The post Display UISearchController's searchbar programmatically is a very good one, it’s the most similar to mine (except that it extends to the left, and my case it does do the right), but it didn’t work as well. I also realized that #kcstricks sets the searchBar width to self.view.frame.size.width instead of the UIview frame width.
// self.searchController.searchBar.frame.size.width = self.view.frame.size.width
self.searchController.searchBar.frame.size.width = searchBarPlaceholderView.frame.size.width
By changing it, solved the initial searchBar behavior. Now it's initially shown correctly:
searchBar image 2
But once it’s tapped the odd behavior returns and never gets back to normal again.
searchBar image 3
searchBar image 4
I also tried moving the main code to viewDidAppear method, but no results.
Where am I failing at?
Other researched stack overflow posts:
Fixed UISearchBar using UISearchController - Not using header view of UITableView
UISearchController's searchBar doesn't fill full width
UISearchBar width doesn't change when its embedded inside a UINavigationBar
Thank you very much in advance.
Complete code:
// ViewController.swift
// SearchBarTests
// Copyright © 2017 equilibrio. All rights reserved.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchResultsUpdating {
#IBOutlet var myTableView: UITableView!
#IBOutlet var searchBarPlaceholderView: UIView!
var searchController: UISearchController!
var selectedBeers = [Beer]()
struct Beer {
var type = String()
var examples = String()
}
var beers = [Beer(type: "American Lager", examples: "Budweiser, Coors, Pabst Blue Ribbon"),
Beer(type: "German Helles", examples: "Victory Helles Lager, Stoudt's Gold Lager"),
Beer(type: "German Pilsner", examples: "Tröegs Sunshine Pils, Bavaria, Sierra Nevada's Nooner Pilsner"),
Beer(type: "Belgian Gueuze", examples: "")]
// Delegates and Datasources
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectedBeers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: "cell")
cell.textLabel?.text = self.selectedBeers[indexPath.row].type
cell.detailTextLabel?.text = self.selectedBeers[indexPath.row].examples
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Row \(indexPath.row) selected")
}
func updateSearchResults(for searchController: UISearchController) {
if searchController.searchBar.text! == "" {
selectedBeers = beers
} else {
selectedBeers = beers.filter { $0.type.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
self.myTableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
selectedBeers = beers
self.searchController = UISearchController(searchResultsController: nil)
self.searchController.searchResultsUpdater = self
self.searchController.dimsBackgroundDuringPresentation = false
self.searchController.searchBar.delegate = self
definesPresentationContext = true
self.searchBarPlaceholderView.addSubview(self.searchController.searchBar)
self.searchController.searchBar.searchBarStyle = UISearchBarStyle.minimal
// self.searchController.searchBar.frame.size.width = self.view.frame.size.width
self.searchController.searchBar.placeholder = "Type desired beer style..."
self.searchController.searchBar.frame.size.width = searchBarPlaceholderView.frame.size.width
self.searchController.searchBar.sizeToFit()
// searchController.searchBar.setTranslatesAutoresizingMaskIntoConstraints(false)
// searchController.searchBar.translatesAutoresizingMaskIntoConstraints(true)
self.myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.myTableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I had the same issue. I added frame adjusting code to all over the place. For example when i choose an item from tableview suggestion list i call a function. Inside that function i wrote code below, so searchbar came back to width i wanted after animating. I hope somebody can give more professional solution. It doesnt look nice in my code but works.
func(youCallbyTapping) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
var searchBarFrame = self.fromSearchController.searchBar.frame
searchBarFrame.size.width = self.myFrom.frame.size.width - 15
self.fromSearchController.searchBar.frame = searchBarFrame
self.toSearchController.searchBar.frame = searchBarFrame
}
}
I have a UITableViewController with a list of custom UITableViewCells. The cell has text, which is sometimes long. I am truncating that at 2 lines in the table.
When a user touches the cell, I want to create a UIView as a popup with the exact same information as the UITableViewCell, with the addition of a header above the UITableViewCell content. So, essentially, I want something like the following:
I have built VersePopupView just as indicated in the picture, where the text "Custom Header" is actually a UILabel. I have instantiated the view from the XIB file and successfully set the UILabel text, but the custom UITableViewCell doesn't show, even though I have assigned that value as well. My IBOutlets are all hooked up to IB.
Here is my instantiation:
let popupView: VersePopupView = VersePopupView.instanceFromNib()
popupView.frame = CGRect(x: 10, y: 100, width: 300, height: 100)
popupView.assignVerseTableViewCell(cell: cell)
window.addSubview(popupView)
window.makeKeyAndVisible()
And here is my VersePopupView class:
class VersePopupView: UIView {
#IBOutlet weak var cell: VerseTableViewCell!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var headerView: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
class func instanceFromNib() -> VersePopupView {
return UINib(nibName: "VersePopupView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! VersePopupView
}
func assignVerseTableViewCell(cell: VerseTableViewCell)
{
// Assign it
self.cell = cell
// Get the verse details
if let verseDetails = cell.verse_details {
self.title.text = verseDetails.verseReference()
// Adjust the frame
// Adjust the text to be attributed text
// Set the title to the verse reference
// Anything else?
}
}
}
What am I doing wrong such that the UITableViewCell isn't showing up?
Is there a better pattern for this rather than embedding the UITableViewCell?
I don't want to duplicate code (which I have done in the past), because I want the user to interact with the popup just like they would in the table cell.
I'm not sure how to convince you of how easy this is to do without trying to misuse a table view cell, so here's a screen shot:
You just have to believe me when I say that that's a three-row table followed by a totally independent ordinary view plucked from the inside of the table view cell (by loading the cell nib).
They not only look identical, they act identical; the view is a custom UIView subclass with an action method from the switch, and when I click the switch, it triggers that action method, both within the table and in the view outside it.
The view was designed entirely in the nib, and absolutely no code was repeated.
That seems to be the sort of thing you want to do. So, I encourage you, don't misuse table view cells; do this with an ordinary view that can live happily inside a cell or outside it.
I can create simple custom viewForHeaderInSection in programmatically like below. But I want to do much more complex things maybe connection with a different class and reach their properties like a tableView cell. Simply, I want to see what I do.
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if(section == 0) {
let view = UIView() // The width will be the same as the cell, and the height should be set in tableView:heightForRowAtIndexPath:
let label = UILabel()
let button = UIButton(type: UIButtonType.System)
label.text="My Details"
button.setTitle("Test Title", forState: .Normal)
// button.addTarget(self, action: Selector("visibleRow:"), forControlEvents:.TouchUpInside)
view.addSubview(label)
view.addSubview(button)
label.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
let views = ["label": label, "button": button, "view": view]
let horizontallayoutContraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-10-[label]-60-[button]-10-|", options: .AlignAllCenterY, metrics: nil, views: views)
view.addConstraints(horizontallayoutContraints)
let verticalLayoutContraint = NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0)
view.addConstraint(verticalLayoutContraint)
return view
}
return nil
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
Is there anyone to explain how can I create a custom tableView header view using xib? I have encountered with old Obj-C topics but I'm new with Swift language. If someone explain as detailed, It would be great.
1.issue: Button #IBAction doesn't connect with my ViewController. (Fixed)
Solved with File's Owner, ViewController base class (clicked left outline menu.)
2.issue: Header height problem (Fixed)
Solved adding headerView.clipsToBounds = true in viewForHeaderInSection: method.
For constraint warnings this answer solved my problems:
When I added ImageView even same height constraint with this method in viewController, it flow over tableView rows look like picture.
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 120
}
If I use, automaticallyAdjustsScrollViewInsets in viewDidLoad, In this case image flows under navigationBar. -fixed-
self.automaticallyAdjustsScrollViewInsets = false
3.issue: If button under View (Fixed)
#IBAction func didTapButton(sender: AnyObject) {
print("tapped")
if let upView = sender.superview {
if let headerView = upView?.superview as? CustomHeader {
print("in section \(headerView.sectionNumber)")
}
}
}
The typical process for NIB based headers would be:
Create UITableViewHeaderFooterView subclass with, at the least, an outlet for your label. You might want to also give it some identifier by which you can reverse engineer to which section this header corresponds. Likewise, you may want to specify a protocol by which the header can inform the view controller of events (like the tapping of the button). Thus, in Swift 3 and later:
// if you want your header to be able to inform view controller of key events, create protocol
protocol CustomHeaderDelegate: class {
func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int)
}
// define CustomHeader class with necessary `delegate`, `#IBOutlet` and `#IBAction`:
class CustomHeader: UITableViewHeaderFooterView {
static let reuseIdentifier = "CustomHeader"
weak var delegate: CustomHeaderDelegate?
#IBOutlet weak var customLabel: UILabel!
var sectionNumber: Int! // you don't have to do this, but it can be useful to have reference back to the section number so that when you tap on a button, you know which section you came from; obviously this is problematic if you insert/delete sections after the table is loaded; always reload in that case
#IBAction func didTapButton(_ sender: AnyObject) {
delegate?.customHeader(self, didTapButtonInSection: section)
}
}
Create NIB. Personally, I give the NIB the same name as the base class to simplify management of my files in my project and avoid confusion. Anyway, the key steps include:
Create view NIB, or if you started with an empty NIB, add view to the NIB;
Set the base class of the view to be whatever your UITableViewHeaderFooterView subclass was (in my example, CustomHeader);
Add your controls and constraints in IB;
Hook up #IBOutlet references to outlets in your Swift code;
Hook up the button to the #IBAction; and
For the root view in the NIB, make sure to set the background color to "default" or else you'll get annoying warnings about changing background colors.
In the viewDidLoad in the view controller, register the NIB. In Swift 3 and later:
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
}
In viewForHeaderInSection, dequeue a reusable view using the same identifier you specified in the prior step. Having done that, you can now use your outlet, you don't have to do anything with programmatically created constraints, etc. The only think you need to do (for the protocol for the button to work) is to specify its delegate. For example, in Swift 3:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "CustomHeader") as! CustomHeader
headerView.customLabel.text = content[section].name // set this however is appropriate for your app's model
headerView.sectionNumber = section
headerView.delegate = self
return headerView
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44 // or whatever
}
Obviously, if you're going to specify the view controller as the delegate for the button in the header view, you have to conform to that protocol:
extension ViewController: CustomHeaderDelegate {
func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) {
print("did tap button", section)
}
}
This all sounds confusing when I list all the steps involved, but it's really quite simple once you've done it once or twice. I think it's simpler than building the header view programmatically.
In matt's answer, he protests:
The problem, quite simply, is that you cannot magically turn a UIView in a nib into a UITableViewHeaderFooterView merely by declaring it so in the Identity inspector.
This is simply not correct. If you use the above NIB-based approach, the class that is instantiated for the root view of this header view is a UITableViewHeaderFooterView subclass, not a UIView. It instantiates whatever class you specify for the base class for the NIBs root view.
What is correct, though, is that some of the properties for this class (notably the contentView) aren't used in this NIB based approach. It really should be optional property, just like textLabel and detailTextLabel are (or, better, they should add proper support for UITableViewHeaderFooterView in IB). I agree that this is poor design on Apple's part, but it strikes me as a sloppy, idiosyncratic detail, but a minor issue given all the problems in table views. E.g., it is extraordinary that after all these years, that we still can't do prototype header/footer views in storyboards at all and have to rely on these NIB and class registration techniques at all.
But, it is incorrect to conclude that one cannot use register(_:forHeaderFooterViewReuseIdentifier:), an API method that has actively been in use since iOS 6. Let’s not throw the baby out with the bath water.
See previous revision of this answer for Swift 2 renditions.
Rob's answer, though it sounds convincing and has withstood the test of time, is wrong and always was. It's difficult to stand alone against the overwhelming crowd "wisdom" of acceptance and numerous upvotes, but I'll try to summon the courage to tell the truth.
The problem, quite simply, is that you cannot magically turn a UIView in a nib into a UITableViewHeaderFooterView merely by declaring it so in the Identity inspector. A UITableViewHeaderFooterView has important features that are key to its correct operation, and a plain UIView, no matter how you may cast it, lacks them.
A UITableViewHeaderFooterView has a contentView, and all your custom subviews must be added to this, not to the UITableViewHeaderFooterView.
But a UIView mysteriously cast as a UITableViewHeaderFooterView lacks this contentView in the nib. Thus, when Rob says "Add your controls and constraints in IB", he is having you add subviews directly to the UITableViewHeaderFooterView, and not to its contentView. The header thus ends up incorrectly configured.
Another sign of the issue is that you are not permitted to give a UITableViewHeaderFooterView a background color. If you do, you'll get this message in the console:
Setting the background color on UITableViewHeaderFooterView has been deprecated. Please set a custom UIView with your desired background color to the backgroundView property instead.
But in the nib, you cannot help setting a background color on your UITableViewHeaderFooterView, and you do get that message in the console.
So what's the right answer to the question? There's no possible answer. Apple has made a huge goof here. They have provided a method that allows you to register a nib as the source of your UITableViewHeaderFooterView, but there is no UITableViewHeaderFooterView in the Object Library. Therefore this method is useless. It is impossible to design a UITableViewHeaderFooterView correctly in a nib.
This is a huge bug in Xcode. I filed a bug report on this matter in 2013 and it is still sitting there, open. I refile the bug year after year, and Apple keeps pushing back, saying "It has not been determined how or when the issue will be resolved." So they acknowledge the bug, but they do nothing about it.
What you can do, however, is design a normal UIView in the nib, and then, in code (in your implementation of viewForHeaderInSection), load the view manually from the nib and stuff it into the contentView of your header view.
For example, let's say we want to design our header in the nib, and we have a label in the header to which we want to connect an outlet lab. Then we need both a custom header class and a custom view class:
class MyHeaderView : UITableViewHeaderFooterView {
weak var content : MyHeaderViewContent!
}
class MyHeaderViewContent : UIView {
#IBOutlet weak var lab : UILabel!
}
We register our header view's class, not the nib:
self.tableView.register(MyHeaderView.self,
forHeaderFooterViewReuseIdentifier: self.headerID)
In the view xib file, we declare our view to be a MyHeaderViewContent — not a MyHeaderView.
In viewForHeaderInSection, we pluck the view out of the nib, stuff it into the contentView of the header, and configure the reference to it:
override func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
let h = tableView.dequeueReusableHeaderFooterView(
withIdentifier: self.headerID) as! MyHeaderView
if h.content == nil {
let v = UINib(nibName: "MyHeaderView", bundle: nil).instantiate
(withOwner: nil, options: nil)[0] as! MyHeaderViewContent
h.contentView.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
v.topAnchor.constraint(equalTo: h.contentView.topAnchor).isActive = true
v.bottomAnchor.constraint(equalTo: h.contentView.bottomAnchor).isActive = true
v.leadingAnchor.constraint(equalTo: h.contentView.leadingAnchor).isActive = true
v.trailingAnchor.constraint(equalTo: h.contentView.trailingAnchor).isActive = true
h.content = v
// other initializations for all headers go here
}
h.content.lab.text = // whatever
// other initializations for this header go here
return h
}
It's dreadful and annoying, but it is the best you can do.
Create a UITableViewHeaderFooterView and its corresponding xib file.
class BeerListSectionHeader: UITableViewHeaderFooterView {
#IBOutlet weak var sectionLabel: UILabel!
#IBOutlet weak var abvLabel: UILabel!
}
Register the nib similarly to how you register a table view cell. The nib name and reuse identifier should match your file names. (The xib doesn't have a reuse id.)
func registerHeader {
let nib = UINib(nibName: "BeerListSectionHeader", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "BeerListSectionHeader")
}
Dequeue and use similarly to a cell. The identifier is the file name.
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "BeerListSectionHeader") as! BeerListSectionHeader
let sectionTitle = allStyles[section].name
header.sectionLabel.text = sectionTitle
header.dismissButton?.addTarget(self, action: #selector(dismissView), for: .touchUpInside)
return header
}
Don't forget the header height.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return BeerListSectionHeader.height
}
I don't have enough reputation to add comment to Matt answer.
Anyway, the only thing missing here is to remove all subviews from UITableViewHeaderFooterView.contentView before adding new views. This will reset reused cell to initial state and avoid memory leak.
I know this issue is already been asked few times in SO. Despite trying those out, I am still unable to solve my problem.
I am using a UITableView inside a UIViewController. I have a custom UITableViewCell which has couple of buttons in it. However, I am not able to make the Button respond to Click event.
The development environment is iOS 9 and Swift 2
Snippets used :
BranchNearMeTableViewCell.swift contains
#IBOutlet weak var btnDetails: UIButton!
view controller class
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("branchNearTableCell") as! BranchNearMeTableViewCell
cell.btnDetails.tag = indexPath.row
cell.btnDetails.addTarget(self, action: "showDetails:", forControlEvents: .TouchUpInside)
}
func showDetails(sender: UIButton){
print("Button Pressed:")
}
Additional Info:
TableView and TableCellView has User interaction disabled in Interface builder since don't want the entire cell to be clickable.
UIButton inside TableViewCell has User Interaction enabled.
Being an iOS noob, I may be making a silly mistake which I might have overlooked.
Similar questions that I checked include:
SO1
SO2
SO3
I Deeply appreciate any help regarding this question.
I faced a similar issue. I was programmatically adding an UIButton to the UITableViewCell via addSubview. The button would not respond to touch events. Using Debug View Hierarchy, I finally discovered that any subviews added to the UITableViewCell was behind contentView, which was blocking user input from reaching the UIButton. The issue was resolved by adding the UIButton to contentView instead of the UITableViewCell.
I would have userInteractionEnabled set to true on the table view cell as well. I would prevent taps using the UITableView allowsSelection to false
Also remember to remove the target and action in tableView:cellForRowAtIndexPath: since the cells are recycled, the button might already have the target and action, it might add a second.
I found a simple solution:
Inherits UITableViewCell, and override init()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
//init subviews, eg. self.switch = UISwitch()
super.init(style: style, reuseIdentifier: reuseIdentifier)
// add this line magic code
contentView.isUserInteractionEnabled = true
//add subviews, e.g. self.addSubView(self.switch)
}
You only have to do (in ViewDidLoad):
mTableView.delaysContentTouches = false
For programmatically created views, the only thing to remember is to declare buttons using lazy var in UITableViewCell. And also add subviews to contentView instead of the cell itself For example:
class CounterCell: UITableViewCell {
lazy var incrementButton: UIButton = {
let button = UIButton()
button.setTitle("+", for: .normal)
button.addTarget(self, action: #selector(incrementAction), for: .touchUpInside)
return button
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(incrementButton)
// Your constrains here
}
#objc func incrementAction() {
}
}
When using programmatically views, there's no need to add .userInteractionEnabled flags.
Then to take the action out of the cell, just add a delegate and assign it from the UITableViewDataSource.
I came across this issue today, with a button inside a static UITableview cell, that was not responding to user events.
I realised the 'Content View' of the cell also has a 'User Interaction Enabled' tick box. Make sure you select the 'Content View' inside the UITableview cell in your Document Outline menu, then tick the box for 'User Interaction Enabled' in the Attributes Inspector - see attached photo for reference. 'User Interaction Enabled' also needs to be checked for the cell for this to work.
Hope this helps. XCcode screen shot
Also, make sure you are adding target actions to your buttons outside their setup. So instead of
let button: UIButton = {
//addTarget...
}()
you can have a function to set up your buttons after something happens:
func setButtonsUp() {
// myButton.addTarget
}
For anyone else struggling, here's my solution:
sendSubviewToBack(cell.contentView)
The thing that there's now an extra UITableViewCellContentView layer which blocks interaction with views behind it.
Related issue: An extra UITableViewCellContentView overlay appears in a TableView on iOS 14 preventing taps, but works fine on iOS 13
Ad a first sight nothing seems to be wrong with your code.
So I suggest you to add a background color to the superview of the button, why? because if the button is outside the frame of its superview it will never receive touches.
If you see that the button is not inside the background color probably you have an issue positioning the item, check constraints or whatever you are using.
Check also the frame of the button.
You can also do both by inspecting the view at runtime, here a tutorial.
I dont know what wrong in the code but i can suggest which i personally use and it works for me
In BranchNearMeTableViewCell.swift
#IBOutlet var btnDetails: UIButton!
#IBAction func btnDetailsClick(sender: AnyObject) {
tapButton?(self)
}
var tapButton: (UITableViewCell -> Void)?
In Table view controller
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("branchNearTableCell") as! BranchNearMeTableViewCell
cell.tapButton = {(user) in
//User will be tablecell here do whatever you want to do here
}
}
So if you click on button in table cell this cell.tapButton will be called you can do whatever you want to do here
The only things we need to do is in cellForRowAt just put:
cell.selectionStyle = .none
in this way, UITableview will bypass the touch of selecting cells and allow buttons inside our cells to be clickable.
set cell and cell content view isUserInteractionEnabled = true
Add Tapgesture to the button
Add a closure to handle gesture action
Add target for that button.
button.addTarget(self, action: #selector(connected(sender:)), for: .touchUpInside)
Set tag of that button since you are using it.
button.tag = indexPath.row
Achieve this by subclassing UITableViewCell. button on that cell, connect it via outlet.
Make sure button.isUserInteractionEnabled = true
To get the tag in the connected function:
#objc func connected(sender: UIButton){
let buttonTag = sender.tag
}
Make sure that ALL of tableView's superviews do have isUserInteractionEnabled set to true
User interaction was already enabled for my UIButton. The thing that worked for me is
switching the stackView distribution to "Fill".