I want to customise the header section in my application but it's in static cell. I tried to make one cell to be my header by including identifier and add a new file to control that cell but that doesn't work. I tried to drag an object into new file but it's can't be done. So how to customise section header? Is my way of approach is good?
There are a couple of ways to customize the header section in a UITableView. For instance, if all you want to do is change the text, you can do so in the attributes inspector while making sure your TableViewSection is selected:
However, if you want the ability to do customizations such as text size, tont, capitalizations - any customizations inherent to UILabels, you'll need to override this method from the UITableViewController:
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header = view as! UITableViewHeaderFooterView
header.textLabel.textColor = UIColor(red: 243/255, green: 153/255, blue: 193/255, alpha: 1.0)
header.textLabel.font = UIFont(name: "Helvetica Neue", size: 18)
header.textLabel.text = "About Us"
header.textLabel.frame = header.frame
header.textLabel.textAlignment = NSTextAlignment.Left
}
For example, in the code above, I took the header that was passed as a parameter and configured the textColor, font, text, alignment - really anything you can do on the UILabel can be done here.
Before customization
After customization
Also you can customise header section use Nib. Simple example below.
HeaderView.xib (Screenshot)
HeaderView.swift
import UIKit
class HeaderView: UITableViewHeaderFooterView {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var titleBackgroundView: UIView!
static var nib: UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
override func awakeFromNib() {
titleBackgroundView.layer.shadowColor = UIColor.gray.cgColor
titleBackgroundView.layer.shadowOpacity = 0.5
titleBackgroundView.layer.shadowOffset = CGSize(width: 0, height: 5)
titleBackgroundView.layer.shadowRadius = 5
}
}
ProfileTableViewController.swift
import UIKit
class ProfileTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(HeaderView.nib, forHeaderFooterViewReuseIdentifier: HeaderView.identifier)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: HeaderView.identifier) as! HeaderView
switch section {
case 0:
headerView.titleLabel.text = "Profile"
return headerView
case 1:
headerView.titleLabel.text = "Social"
return headerView
default:
return UIView()
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
}
Simulator (Screenshot)
This is the easy way to do it without set a new string, just leave text in header that used in UITableview with static cell as it and add this override for header in your code
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section{
if ([view isKindOfClass:[UITableViewHeaderFooterView class]]) {
((UITableViewHeaderFooterView*)view).textLabel.text = [((UITableViewHeaderFooterView*)view).textLabel.text capitalizedString];
}
}
Related
Here's my DisruptionsViewController which has the tableView. the function setUp() is used in another ViewController class to set up the DisruptionsViewController.
public class DisruptionsInfoViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
public override func viewDidLoad() {
super.viewDidLoad()
self.setUp()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: "DisruptionInfoTableViewCell", bundle: nil), forCellReuseIdentifier: "disruptionsInfoTableViewCell")
}
private func loadFromNib() -> UIView? {
let nibName = String(describing: DisruptionsInfoViewController.self)
let nib = UINib(nibName: nibName, bundle: Bundle(for: type(of: self)))
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
public func setUp() {
guard let disruptionsInfoViewController = self.loadFromNib() else { return }
disruptionsInfoViewController.frame = self.view.bounds
}
}
extension DisruptionsInfoViewController: UITableViewDataSource, UITableViewDelegate {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "disruptionsInfoTableViewCell", for: indexPath) as? DisruptonInfoTableViewCell else { return UITableViewCell() }
return cell
}
}
Here's the tableViewCell class.
import UIKit
class DisruptonInfoTableViewCell: UITableViewCell {
#IBOutlet weak var testLabel: UILabel!
}
I can see the tableView in the view debugger, but unable to see it in the view as the tableViewCell is not registered for some reason.
Here's how I am using it in another controller's delegate method
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let viewModel = presenter.headerViewModel(for: section) else { return nil }
let dateSummaryView = DateSummaryView(frame: .zero)
dateSummaryView.setup(with: viewModel)
let disruptionsViewController = DisruptionsInfoViewController()
return disruptionsViewController.view
}
Does anyone know where the problem could be?
I tried following tutorials from YouTube and other articles, they use the same approach but for some reason it doesn't work for me.
First, as mentioned in the comments, nothing in your setUp() is doing anything, so it can be removed.
The reason you are not seeing your table view rows in your other table's section header views is because here (I'll ignore the first three lines since they have nothing to do with this):
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//guard let viewModel = presenter.headerViewModel(for: section) else { return nil }
//let dateSummaryView = DateSummaryView(frame: .zero)
//dateSummaryView.setup(with: viewModel)
let disruptionsViewController = DisruptionsInfoViewController()
return disruptionsViewController.view
// as soon as we return, disruptionsViewController is released
// and no code it contains will be executed
}
You created an instance of DisruptionsInfoViewController, pulled out its view, and then tossed away the controller code.
If you want to use this approach (rather odd, but we have to assume you have a logical reason to do this), you need to keep a reference to DisruptionsInfoViewController so its code can be used:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let disruptionsViewController = DisruptionsInfoViewController()
// add disruptionsViewController as a child view controller
// this will "hold on to it" so its code can execute
addChild(disruptionsViewController)
disruptionsViewController.didMove(toParent: self)
return disruptionsViewController.view
}
Now, this is technically incorrect, as Apple's docs state:
Call the addChildViewController: method of your container view controller.
Add the child’s root view to your container’s view hierarchy.
Add any constraints for managing the size and position of the child’s root view.
Call the didMoveToParentViewController: method of the child view controller.
But, because we are returning the view for use as a section header view, we cannot perform 2. before calling didMove(toParent: self).
You didn't include in your post (or mention in your comments) how you're setting the Height of the section header, so, using:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 150.0
}
We get this with your original code - section header view background color is blue, the "table view in header view" background color is green:
and we get this when using addChild():
Here is a complete example project: https://github.com/DonMag/Disrupt
While this will work, if you really want to embed a table view in another table view's section header(s), there are better ways to do it.
With Swift 3, I am using a subclass of UITableViewHeaderFooterView (called HeaderView) for the header sections on my TableView.
After dequeueing HeaderView, I customise it by
(1) setting the textLabel.textColor = UIColor.red, and (2) adding a subview to it.
When the application first loads, the table view loads up the headers but they have (what I assume is) the 'default' view (with textLabel.textColor being grey, and without my added subview). When I start scrolling and it starts dequeueing more HeaderViews, then the HeaderViews start coming up correctly, until there are eventually no more 'default' formatted HeaderViews.
Subsequent loads of the app no longer shows the 'default' view.
Alternatives considered
I know that this could be done by making my HeaderView a subclass of
UITableViewCell and customising it from the Storyboard, but that
seems like more of a workaround to use a prototype cell when there is
a UITableViewHeaderFooterView class that was designated for headers
Similarly it could be done with a XIB file, but even in Xcode 8 when
creating a subclass of UITableViewHeaderFooterView it doesn't allow
you to create an XIB file (so there must be some reason..)
Any comments/answers explaining why this is happening and how to resolve it are really appreciated!
UPDATE
As requested I've added in the code to show what I've done- you can recreate the problem with the code below and the usual setting up a TableViewController in the Storyboard (Swift 3, Xcode 8.2, Simulating on iOS 10.2 for iPhone 7)
ListTableViewController.swift
import UIKit
class ListTableViewController: UITableViewController {
// List of titles for each header
var titles: [String] {
var titles = [String]()
for i in 1...100 {
titles.append("List \(i)")
}
return titles as [String]
}
// Register view for header in here
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(ListHeaderView.self, forHeaderFooterViewReuseIdentifier: "Header")
}
// Table view data source
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let dequeuedCell = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header")
if let cell = dequeuedCell as? ListHeaderView {
cell.title = titles[section]
}
return dequeuedCell
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
override func numberOfSections(in tableView: UITableView) -> Int {
return titles.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
}
ListHeaderView.swift
import UIKit
class ListHeaderView: UITableViewHeaderFooterView {
var title: String? {
didSet {
updateUI()
}
}
private func updateUI() {
textLabel?.textColor = UIColor.red
textLabel?.text = title!
let separatorFrame = CGRect(x: 0, y: frame.height-1, width: frame.width, height: 0.25)
let separator = UIView(frame: separatorFrame)
separator.backgroundColor = UIColor.red
contentView.addSubview(separator)
}
}
Here is a screen shot of when the grey headers (screen is full of them upon initial load) and the customised red headers which start to appear upon scrolling.
For anyone interested, seems like this is a bug for which the best solution at this stage is to configure properties such as textColor on the header view in the tableView delegate method willDisplayHeaderView. Doing so 'last minute' just before the view appears allows you to override whatever configurations the system tries to force on the font etc.
Credit to answer found here
Troubles with changing the font size in UITableViewHeaderFooterView
Use this below code
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let dequeuedCell : ListHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header") as? ListHeaderView
cell.title = titles[section]
cell.tittle.textcolor = uicolor.red
return dequeuedCell
}
I'm trying to apply a radius to the corner of the images showed in a table to make them look like a circle. The problem is that the images are located via autolayout, so the final size is not calculated till the view has layout all its subviews, and I need to code the circle mask after that.
The solution that came to me was to write the code inside the method tableView(... willDisplayCell ...), or inside the methods didMoveToSuperview() and layoutSubviews() on the custom UITableViewCell subclass. I have seen this solutions on some questions of this forum, but no one of them are working.
Here is the code with one of those methods commented:
import UIKit
struct Contact {
var name: String
var image: UIImage
}
class ActiveChatsController: UITableViewController {
let contacts = [
Contact(name: "Alex", image: UIImage(named: "Alex")!),
Contact(name: "Puto Albert", image: UIImage(named: "Albert")!),
Contact(name: "Golfo-man", image: UIImage(named: "Pablo")!)
]
override func viewDidLoad() {
self.navigationController!.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: AppDesign.color(withIntensity: 6)]
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("OpenedChatCell", owner: self, options: nil)?.first as! OpenedChatCell
cell.contactImage.image = contacts[indexPath.row].image
cell.contactName.text = contacts[indexPath.row].name
return cell
}
}
And the custom cell subclass is got from a XIB file:
import UIKit
class OpenedChatCell: UITableViewCell {
#IBOutlet weak var contactImage: UIImageView!
#IBOutlet weak var contactName: UILabel!
/*
override func didMoveToSuperview() {
self.contactImage.layer.cornerRadius = self.contactImage.frame.width / 2
self.contactImage.clipsToBounds = true
}
*/
}
If I run these codes on the simulator, I get this:
But if I delete the comments on the didMoveToSuperview() method and let it change the radius I get this:
After this, I wrote inside didMoveToSuperview():
print(self.contactImage.frame.height)
And it shows a height of 177.0, so I don't know where the error may be.
The problem might be that the table view itself is being created early, before the final setting of the frame of the image view. Thus, all your attempts to set the rounded corners are also happening too early, because they all depend on the initial formation of the table view.
The solution, in that case, would be as follows:
var didLayout = false
override func viewDidLayoutSubviews() {
if !self.didLayout {
self.didLayout = true // only need to do this once
self.tableView.reloadData()
}
}
If you have moved your code into this delegate method:
func tableView(UITableView, willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
...that method will now run again for all the cells of the table, and the image view frame will be correct and the rounded corners will come out correctly.
Try to ovveride layoutSubviews instead of didMoveToSuperview:
override func layoutSubviews() {
super.layoutSubviews()
self.contactImage.layer.cornerRadius = self.contactImage.frame.width / 2
self.contactImage.clipsToBounds = true
}
Try this out. It may work
import UIKit
class OpenedChatCell: UITableViewCell {
#IBOutlet weak var contactImage: UIImageView!
#IBOutlet weak var contactName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setRoundedView(roundedView: customImageView)
}
func setRoundedView (roundedView:UIView) {
let saveCenter = roundedView.center
let newFrame:CGRect = CGRect(origin: CGPoint(x: roundedView.frame.origin.x,y :roundedView.frame.origin.y), size: CGSize(width: roundedView.frame.size.width, height: roundedView.frame.size.height))
roundedView.layer.cornerRadius = roundedView.frame.height/2
roundedView.frame = newFrame;
roundedView.center = saveCenter
roundedView.clipsToBounds = true
}
}
If you already know the size of your image and don't need to determine it on the fly (oftentimes this is the case with images in a table view), you can just try it like this:
let imageHeight = CGFloat(100)
imageView.layer.masksToBounds = true
imageView.layer.cornerRadious = imageHeight / 2
I have a custom cell with some simple labels and a UIImage. Everything appears to be set correctly, and stepping through the debugger shows that everything is getting a value and even using the print in the debugger shows that the labels have text. However my table view is still empty when executed. I have been looking at this for too long and cannot figure out the problem.
Here is the cell code
class CurrentFileCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var statusImage: UIImageView!
#IBOutlet weak var dateLabel: UILabel!
var currentContent: AircraftContent! {
didSet{
setStyles(Constants.appStyleSetting)
self.nameLabel.text = currentContent.contentName
self.dateLabel.text = currentContent.contentStatus
self.statusImage.image = UIImage(named: "color_label_circle_green")
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
private func setStyles(settings: StyleSettings) {
let tableFont = UIFont(name: settings.bodyFont, size: CGFloat(settings.tableFontSize))
nameLabel.font = tableFont
dateLabel.font = tableFont
// Text color
let tableFontColor = settings.tableFontColor
nameLabel.textColor = tableFontColor
dateLabel.textColor = tableFontColor
}
Here is the ViewController code with a tableview inside.
class CurrentFilesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var content: AircraftContent?
#IBOutlet weak var currentFiles: UILabel!
#IBOutlet weak var downloadingLabel: UILabel!
#IBOutlet weak var readyLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.content = loadContent()
setStyles(Constants.appStyleSetting)
//self.tableView.reloadData()
// Do any additional setup after loading the view.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CurrentFileCell", forIndexPath: indexPath) as? CurrentFileCell
cell?.currentContent = content
return cell!
}
func loadContent() -> AircraftContent {
return (NSKeyedUnarchiver.unarchiveObjectWithFile(AircraftContent.ArchiveURL.path!) as? AircraftContent)!
}
private func setStyles(settings: StyleSettings) {
let titleFont = UIFont(name: settings.bodyFont, size: CGFloat(settings.titleFontSize))
let key = UIFont(name: settings.bodyFont, size: CGFloat(settings.tableFontSize))
currentFiles.font = titleFont
downloadingLabel.font = key
readyLabel.font = key
// Text color
let titleFontColor = settings.titleFontColor
currentFiles.textColor = titleFontColor
downloadingLabel.textColor = titleFontColor
readyLabel.textColor = titleFontColor
}
Here are some images showing the debug location where the cell is not empty, and also printing out the label which has a value, but isn't being shown during simulation.
http://imgur.com/a/dBkpe
This is an image showing the prototype cell. The cell has the correct class set as well as the identifier.
http://imgur.com/PKtFTeQ
Lastly another image showing that the prototype cell is linked to the labels within the CurrentFileCell.
http://imgur.com/nW0QUjM
Any help at all with this would be appreciated. I have tried recreating everything but continue to be stumped as it seems like everything is how it should be.
You have to implement the 'heightForRowAtIndexPath' method for the table view
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height:CGFloat = 75
return height
}
You may consider registering the custom class as it does not appear that you did. You can do that by using the following code in the viewDidLoad of your View Controller.
tableView.registerClass(CurrentFileCell.self, forCellReuseIdentifier: "cell")
If you are using an external nib you will want to use registerNib instead like so:
tableView.registerNib(UINib(name:"ReplaceWithYourNibName", bundle: nil), forCellReuseIdentifier: "ReuseIdentifier")
Of course, replace ReplaceWithYourNibName and ReuseIdentifier with the appropriate values. In addition, if your nib is in a different bundle specify that instead of nil (nil defaults to the main bundle).
However, do not use both registerClass and registerNib as whichever one you call last will be used and they were designed to be mutually exclusive. Whenever you make a custom UITableViewCell you must use either of the two for it to work unless you have set it explicitly in the storyboard.
Also, you could instead, use prototype cells to define your custom cell, which would, I believe, automatically register the cell. But only if you did not use prototype cells make sure to use registerClass or registerNib.
Good luck! Hope this helps!
If your cell is a static cell, then you need to comment out these methods in UITableViewDataSource:
/* override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
} */
I had the same issue.
Data has values and cell rows are showing empty.
I figured it by adding
contentView.addSubview(titleLabel)
where cell values are being set.
With VoiceOver switched-on, when focus comes on a UIButton/UITableViewCell/UICollectionViewCell, VoiceOver reads it's accessibility label once.
Then as soon as user double taps to select that UIButton/UITableViewCell/UICollectionViewCell, VoiceOver reads the same accessibility label again besides performing action (navigation etc) on UIButton/UITableViewCell/UICollectionViewCell selection.
I've searched a lot but not able to find a way to stop/disable VoiceOver reading accessibility label on UIButton/UITableViewCell/UICollectionViewCell selection.
Any help would be highly appreciated.
Let's see how to stop the VoiceOver accessibility reading for the UIButton and the UITableViewCell elements.
UIBUTTON : just create your own button class and override the accessibilityActivate method.
class BoutonLabelDoubleTap: UIButton {
override func accessibilityActivate() -> Bool {
accessibilityLabel = ""
return true
}
}
UITABLEVIEWCELL : two steps to be followed.
Create a custom UIAccessibilityElement overriding the accessibilityActivate method.
class TableViewCellLabelDoubleTap: UIAccessibilityElement {
override init(accessibilityContainer container: Any) {
super.init(accessibilityContainer: container)
}
override var accessibilityTraits: UIAccessibilityTraits {
get { return UIAccessibilityTraitNone }
set { }
}
override func accessibilityActivate() -> Bool {
accessibilityLabel = ""
return true
}
}
Use the previous created class to implement your table view cells in the view controller.
class TestButtonTableViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var bottomButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
myTableView.delegate = self as UITableViewDelegate
myTableView.dataSource = self as UITableViewDataSource
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let zeCell = tableView.dequeueReusableCell(withIdentifier: "myPersoCell",
for: indexPath)
zeCell.accessibilityElements = nil
var elements = [UIAccessibilityElement]()
let a11yEltCell = TableViewCellLabelDoubleTap(accessibilityContainer: zeCell)
a11yEltCell.accessibilityLabel = "cell number " + String(indexPath.row)
a11yEltCell.accessibilityFrameInContainerSpace = CGRect(x: 0,
y: 0,
width: zeCell.contentView.frame.size.width,
height: zeCell.contentView.frame.size.height)
elements.append(a11yEltCell)
zeCell.accessibilityElements = elements
return zeCell
}
}
I haven't tried for a UICollectionViewCell but it should be the same rationale as the UITableViewCell's.
Following these code snippets, you're now able to decide if VoiceOver should stop reading out the desired elements labels when selected.
Swift 5
What worked for me was setting myElementIWantSilent.accessibilityTraits = .none
EDIT: I should note that these are also present:
viewContainingSilentElement.isAccessibilityElement = true
viewContainingSilentElement.accessibilityTraits = .image
viewContainingSilentElement.accessibilityLabel = "some text i want read aloud"
iPhone 8
iOS 14.5.1
XCode 12.5