I am trying to control my UITableView based on the selectedSegmentIndex of a UISegmentedControl inside my UITableView header. Essentially, I want this UISegmentedControl to work like Twitter's 'Me' tab. I have the UISegmentedControl inside a UITableView header and it is dequeued using this method:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "sectionDetailsHeaderView") as! SectionDetailsHeaderTableViewCell
return cell
}
I have an outlet for the UISegmentedControl hooked up to SectionDetailsHeaderTableViewCell, but I can't figure out how to detect a change in the UISegmentedControl. I want to set a variable, var segmentedControlValue = Int() to the selectedSegmentIndex every time the value changes and call a function, func chooseDataToDisplay() {} when the value changes. How do I go about doing this?
With the help of #Schomes answer and this post, I was able to figure it out!
1) Add the UISegmentedControl into its own UITableViewCell. I would recommend adding a UIView with a white background behind the UISegmentedControl that covers the entire UITableViewCell so the TableView cells flow behind the UISegmentedControl.
2) Add your custom cell class and hook it up to the UITableViewCell
3) Add an outlet, such as yourSegmentedControl to your custom UITableViewCell class. DO NOT add an action into the custom UITableViewCell class. This is done programmatically in step 4.
4) In the UIViewController or UITableViewController class, add the code below.
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "yourCellIdentifier") as! yourCustomCellClass
header.yourSegmentedControl.selectedSegmentIndex = self.segmentedControlValue
header.yourSegmentedControl.addTarget(self, action: #selector(self.getSegmentValue(sender:)), for: .valueChanged)
return header
}
self.segmentedControlValue should be declared as var segmentedControlValue = Int() at the top of your ViewController class. self.getSegmentValue(sender:) should be declared as:
func getSegmentValue(sender: UISegmentedControl) {
self.segmentedControlValue = sender.selectedSegmentIndex
}
5) You also need to add:
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
This is the height of the header. In my case, the size is 45
6) You can now access self.segmentedControlValue anywhere in your ViewController. It will update when the user taps on a different segment.
segmentedControl.addTarget(self, action: "chooseDataToDisplay:", forControlEvents: .ValueChanged)
Where segmentedControl is your UISegmentedControl. This will call func chooseDataToDisplay(segment: UISegmentedControl) {} every time the value changes.
References:
https://developer.apple.com/reference/uikit/uisegmentedcontrol
The section Behavior of Segmented Controls
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UISegmentedControl.html#//apple_ref/doc/uid/TP40012857-UISegmentedControl
Related
So I have 2 Views that are shown inside a UIView, based on what is selected on the SegmentViewController. I create dummy data, returning 20 row of a custom cell. This works great.
Everything is fine, till I interact with the TableView.
Bellow is my code:
GoalsViewController.swift
import UIKit
class GoalsViewController: UIViewController {
#IBOutlet weak var goalsTableView: UITableView!
let goalCellIdentifier = "goalCell"
override func viewDidLoad() {
super.viewDidLoad()
goalsTableView.delegate = self
goalsTableView.dataSource = self
goalsTableView.register(UINib(nibName: "GoalsViewCell", bundle: nil), forCellReuseIdentifier: goalCellIdentifier)
goalsTableView.reloadData()
}
}
extension GoalsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = goalsTableView.dequeueReusableCell(withIdentifier: goalCellIdentifier, for: indexPath) as! GoalsViewCell
cell.goalTitle.text = "aaaaa"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected \(indexPath.row)")
}
}
After any of the empty rows is selected, the didSelectRowAt is not called, so the cells are not there at all. I tried to find a solution, but I was only to find issues about empty lists, before being populated.
What could be the reason for the empty tableview?
I might be wrong here but one thing that I've noticed is that you are not implementing a function which sets the height of each cell.
// Specify the height of your cells
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100 // Or your given cell height.
}
So here is my theory: If you are using constraints something is missing and your cell's height can't be identified by constraints alone or you are not using constraints at all thus you must use heightForRowAt function to specify each cell's height.
I will explain what was the issue for people who probably did not know (like me).
So my UITableView is inside a UIView that changes based on what the user is selecting. In total I had 2 different Views that where switching. The reason that it was emptying it was because my parent ViewController, could not access the delegate for UITableView. To fix that, after adding a subview to the UIView, you need also to move the ViewController to the parent controller. In code it goes like this.
// Empty array of UIViewControllers
var views: [UIViewController]!
// Add the UIViewControllers to the array
views = [UIViewController()]
views.append(EventsViewController())
views.append(GoalsViewController())
for view in views {
// Needed to adjust the size of Subview to size of View
view.view.frame = containerView.bounds
// Add the subviews
containerView.addSubview(view.view)
}
// Bring the view in position 1 to the front of the UIView
containerView.bringSubviewToFront(views[1].view)
// Add Views[1] UIViewController as a child to the parent controller
self.addChild(views[1])
views[1].didMove(toParent: self)
// After done with everything with the UIViewController remove it
views[1].removeFromParent()
addChild Apple.com
didMove Apple.com
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. ?
"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'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(...
}
}
I'm using a custom cell as a section header in my UITableView. In that cell there are three buttons. If any of the buttons are clicked in that section's cell, it should reload that custom section cell only, not any rows. Is this possible?
I was using the following code to reload that cell:
tableViewHome.reloadSections([1], with: UITableViewRowAnimation.none)
It's hiding my section cell and distorting my entire table.
UPDATE
I'm using UITableView and following code I'm using:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableCell(withIdentifier: "header") as! HeaderTableViewCell
cellHeader.filter1btn.addTarget(self, action: #selector(filterBtnAction(_:)), for: .touchUpInside)
cellHeader.filter2Btn.addTarget(self, action: #selector(filterBtnAction(_:)), for: .touchUpInside)
cellHeader.filter3btn.addTarget(self, action: #selector(filterBtnAction(_:)), for: .touchUpInside)
return cellHeader
}
#IBAction func filterBtnAction(_ sender: UIButton) {
print(sender.tag)
tableViewHome.reloadSections([1], with: UITableViewRowAnimation.none)
}
I'm a little unclear as to what's going on here, but it sounds like there is a UITableView concepts worth explaining here:
UITableView has its own concept of a cell, implemented as UITableViewCell, and its own concept of a header/footer, implemented as UITableViewHeaderFooterView.
Depending on which of these two you meant, there are a few things you can do to get the intended effect:
The UITableViewCell Approach:
If you're using a UITableViewCell as the first row of a section to act like a "header," and you just want to reload that row to the exclusion of the rest of the section, you can call yourTableViewInstance.reloadRows(at:with:) (Apple Documentation) This method takes an array of IndexPaths, and an animation style. You can pass in the indexPath of the one you want to reload.
The UITableViewHeaderFooterView Approach:
If you're using a proper UITableViewHeaderFooterView then you need to make sure that you're providing the appropriate view when reloading the section. Zack Shapiro outlines the steps you need to take in this answer:
Create a class that's a subclass of UITableViewHeaderFooterView.
Register it with your UITableView instance.
Then in viewForHeaderInSection, you do let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! YourHeaderViewSubclass
The last thing he points out is this:
The deceptive thing is the function calls for a return of UIView? when it really needs a dequeuedReusableHeaderFooterView or reloadData will cause it to disappear.
It depends on which of these two implementation paths you're taking, but this should be enough information to point you in the right direction.
Edit:
Based on the code you added, it looks like you're calling yourTableViewInstance.dequeueReusableCell(withIdentifier:for:) instead of yourTableViewInstance.dequeueReusableHeaderFooterView(withIdentifier:) inside of viewForHeaderInSection.
You need to have a subclass of UITableViewHeaderFooterView and then call it correctly. Create that new subclass, then change this:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableCell(withIdentifier: "header") as! HeaderTableViewCell
// ...
to this:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableHeaderFooterView(withIdentifier: "header") as! HeaderTableView
// ...
You need to follow two steps here:
Create a new class, subclassing UITableViewHeaderFooterView instead of UITableViewCell.
Then use the appropriate class as outlined above.
Yes, It is.
Let's say that this is implementation of your method:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let customCell = .... as! YourCustomCell
customCell.someLabel.text = "Some Data"
//... filling your curstom cell
return customCell
}
You can change it in this way
func updateHeaderView(headerView:YourCustomCell, section: Int) {
customCell.someLabel.text = "Some Data"
//... filling your curstom cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let customCell = .... as! YourCustomCell
self.updateHeaderView(customCell, section)
return customCell
}
And call again self.updateHeaderView(customCell, section) whenever you want, e.g.
func buttonClicked() {
let customCell = self.tableView.headerView(forSection: 0) as! YourCustomCell
self.updateHeaderView(customCell, 0)
}
I think your header view class is extending UITableViewHeaderFooterView class. Add a function in the extension
extension UITableViewHeaderFooterView{
func reloadHeaderCell(){
preconditionFailure("This method must be overridden")
}
}
Now override this in your Header class as below
class HeaderView: UITableViewHeaderFooterView {
override func reloadHeaderCell() {
////// add your logic to reload view
}
}
Now you can simply call below line to refresh views
self.tableView?.headerView(forSection:0)?.reloadHeaderCell()
What I did and working very correctly, Please follow the given answer:
SWIFT 3.1
Step 1:
Firstly I took a view xib, designed that according to my requirement and did register in my required class.
Secondly, did sub class class HeaderView: UITableViewHeaderFooterView of UITableViewHeaderFooterView
Like following image:
In my required class(here homeclass) I did register my xib file for my tableview.
override func viewDidLoad() {
super.viewDidLoad()
tableViewHome.register(UINib(nibName: "HeaderView", bundle: nil), forHeaderFooterViewReuseIdentifier: "HeaderView")
}
Step 2:
Then in my required class i did following:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cellHeader = tableViewHome.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! HeaderView
cellHeader.filterAction(cellHeader.filter1Btn)
return cellHeader
}
And it started working as per my requirement, later i added custom delegate for more actions in my class, but by subviewing, its now working.
I know the tableview header and footer but I want to add UIView between the row of tableview in objective-c. and uiview have three button also.
but how can it possible. Anybody can help me..
You can not add UIView in between two row or cell of UITableView. You need to customise your cells. And while filling data to your datasource of UITableView you need some kind of indications which you need to match in your cellForRowAtIndexPath method of UITableView and based on condition you need to show your data cell or custom UIView which is already in your custom cell.
Every UITableViewCell is a child of UIView class.
So in effect all your cells are UIViews which you can customize in any way you want.
So what you want to do is use custom cell. Create a model class for it, customize it as you want.
Suppose I have a UIView and I am creating its object in Controller class in which I have TableView:
var obj_ProfileView : UserProfileView!
In ViewDidLoad Method:
self.obj_ProfileView = Bundle.main.loadNibNamed("UserProfileView", owner: self, options: nil)![0] as! UserProfileView
Then In TableViewFooter Method:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
{
if section == 0
{
//Do as per your requirements
return 184.0
}
else
{
//Do as per your requirements
return 45.0
}
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?
{
if section == 0
{
return obj_ProfileView
}
else{
return nil
}
}
I have a tableView set up so that when a cell is touched, it expands in height to reveal more information. The tableView has 5 sections.
I have a bug: when a cell expands, all headersCells below that cell go invisible. The console outputs the following: "[31233:564745] no index path for table cell being reused"
In my storyboard I have 2 custom cells : "myCell" for the data bearing cells, and "headerCell" for the headers.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let thisGoal : GoalModel = goalArray[indexPath.section][indexPath.row]
if self.currentPath != nil && self.currentPath == indexPath {
self.currentPath = nil
} else {
self.currentPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
If I enter tableView.reloadData() in between the begin/end updates, it functions properly, although the header background turns black, and loses animation.
I have all of the stuff for headers declared in: func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
what am I missing? I'd really like the tableView to still have animations, and keep the background clearColor().
Thanks in advance. I did read through the objective C answers, but couldn't get them to work for me. I'd really appreciate some SWIFT help.
I think the problem is the no index path for table cell being reused.
I found an answer in the console output. Use this code in the header function:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
Do not return your headerCell, or your reusable identifier. Return the reuseIdentifier.contentView. For me it's: return headerCell!.contentView.
Just to add, I was baffled for WAY longer than I should have been as to why I couldn't refer to the contentView of my cell, when I could quite clearly see it was there. My custom class (using UITableViewCell rather than UITableViewHeaderFooterView) would return a fatal error each time. Therefore make sure any custom styling is setup under UITableViewHeaderFooterView class like:
class CustomHeaderCell: UITableViewHeaderFooterView {
You will also need to register the resuableIdentifer like this:
tableView.registerNib(UINib(nibName: "HeaderCell", bundle: nil), forHeaderFooterViewReuseIdentifier: "CellHeader")
Then this bad boy:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("CellHeader") as! CustomHeaderCell!
return headerCell!.contentView
}
Since I'm not at 50 reputation yet, I can't comment on the previous answer, so I apologize for listing this as another answer.
Returning the ContentView will make the function work but will remove all formatting done to the reuseIdentifier (headerCell)
headerCell.backgroundColor = UIColor.cyanColor()
This will NOT provide a Cyan color to your headerCell
To fix this, just add the ".contentView" to your formatting lines
headerCell.contentView.backgroundColor = UIColor.cyanColor()
Table view headers in 2 tables disappeared when I converted my app to IOS 10 - I found the reason in Apple developer API documentation on table headers. When I added the following, the missing headers reappeared!
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 44 // where 44 is the header cell view height in my storyboard
}
You could wrap the TableViewCell inside an UIView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 50)) // 50 = Header height
guard let headerCell = tableView.dequeueReusableCell(withIdentifier: "MyHeaderView") as? MyHeaderView else { fatalError(" Failed to load MyHeaderView") }
headerCell.frame = containerView.bounds
containerView.addSubview(headerCell)
return containerView
}
I had the same bug because I was returning a cell using dequeue method instead of a UITableViewHeaderFooterView.
Solution:
Add a view outside of the view hierarchy
Set the type to UITableViewHeaderFooterView
Customize
Link to an #IBOutlet
In func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? return the outlet
Common pitfalls:
Don't forget to set the header sizes
Don't forget to set the outlet as strong.