I have a table view where the first cell has an imageView occupying the entire cell. I want the status bar and navigation bar to be transparent and show the image. So far, I have the navigation bar clear and looking as needed. My question, is how can I get the status bar to behave the same way. I will include the code for my navigation bar, but the navigation bar is looking good.
Inside where I define and return table view
table.contentInsetAdjustmentBehavior = .never
Inside view did load
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = true
Here's a pic of how it currently looks:
If possible, please send programmatic suggestions, as I am not using any storyboards for this project.
Thank you!!!
You are on the right track here. Just make sure your table view is covering whole super view. If you are using constraints, make sure table view top constraint is attached to super view's top anchor.
Here is an working example of transparent status bar & navigation bar with table view cells under status bar.
class ViewController: UIViewController, UITableViewDataSource {
private var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setupNavbar()
setupTableView()
}
private func setupNavbar() {
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = true
}
private func setupTableView() {
let tableView = UITableView()
tableView.contentInsetAdjustmentBehavior = .never
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.contentInsetAdjustmentBehavior = .never
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
tableView.dataSource = self
self.tableView = tableView
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "Cell")
cell.backgroundColor = [UIColor.red, UIColor.gray, UIColor.green, UIColor.blue].randomElement()
return cell
}
}
After your
table.contentInsetAdjustmentBehavior = .never
make sure to also add:
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
Related
I am to implement a table with a list of items which includes one item that should always be onscreen. So, for example:
you have 50 items in the list
your "sticky" list item is 25th
you have 10 items that may be displayed onscreen at a time
despite of you position in the list, "sticky" list should always remain visible
if your item is lower than your position in the list it is displayed on the bottom of the list
if your item is between previous items it should be displayed on the top of the list
as soon as you reach you item's real position in the list, it should move together with the scroll of the list
Here are the illustrations to better understand the implementation requirements:
Will be glad for any possible ideas, suggestions or recommendations on how can this possibly implemented. Unfortunately, I failed to find any useful libraries or solutions that solve this problem. UICollectionView and UITableView are both acceptable for this case.
Sticky header or footer, as per my understanding do not work in this case as they cover only half of the functionality that I need.
Thank you in advance for your comments and answers!!!
I'm pretty sure you can't actually have the same actual cell be sticky like that. You can create the illusion of stickiness through auto layout trickery though. Basically, my suggestion is that you can have views that are the same as your cells that you want to be "sticky" and constrain them on top of your sticky cells while your sticky cells are visible. The best I could pull off on this doesn't look quite perfect if you scroll slowly. (The sticky cell goes mostly off screen before snapping to the top or bottom position. It isn't noticeable in my opinion at fairly normal scrolling speeds. Your mileage may vary.)
The key is setting up a table view delegate so you can get notified about when the cell will or will not be on the screen.
I've included an example view controller. I'm sure there are areas where my example code won't work. (For example, I didn't handle stacking multiple "sticky" cells, or dynamic row heights. Also, I made my sticky cell blue so it would be easier to see the stickiness.)
In order to run the example code, you should just be able to paste it into a default project Xcode generates if you create a new UIKit app. Just replace the view controller they gave you with this one to see it in action.
import UIKit
struct StickyView {
let view: UIView
let constraint: NSLayoutConstraint
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var stickyViewConstraints = [Int: StickyView]()
lazy var tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
table.rowHeight = 40
table.dataSource = self
table.delegate = self
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
addTable()
setupStickyViews()
}
private func addTable() {
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
private func setupStickyViews() {
let cell25 = UITableViewCell()
cell25.translatesAutoresizingMaskIntoConstraints = false
cell25.backgroundColor = .blue
cell25.textLabel?.text = "25"
view.addSubview(cell25)
cell25.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
cell25.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
cell25.heightAnchor.constraint(equalToConstant: tableView.rowHeight).isActive = true
let bottom = cell25.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
bottom.isActive = true
stickyViewConstraints[25] = StickyView(view: cell25, constraint: bottom)
}
// MARK: - Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 50 : 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
// MARK: - Delegate
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let stickyView = stickyViewConstraints[indexPath.row] else { return }
stickyView.constraint.isActive = false
var verticalConstraint: NSLayoutConstraint
if shouldPlaceStickyViewAtTop(stickyRow: indexPath.row) {
verticalConstraint = stickyView.view.topAnchor.constraint(equalTo: view.topAnchor)
} else {
verticalConstraint = stickyView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
}
verticalConstraint.isActive = true
stickyViewConstraints[indexPath.row] = StickyView(view: stickyView.view, constraint: verticalConstraint)
}
private func shouldPlaceStickyViewAtTop(stickyRow: Int) -> Bool {
let visibleRows = tableView.indexPathsForVisibleRows?.map(\.row)
guard let min = visibleRows?.min() else { return false }
return min > stickyRow
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let stickyView = stickyViewConstraints[indexPath.row] {
stickyView.constraint.isActive = false
let bottom = stickyView.view.bottomAnchor.constraint(equalTo: cell.bottomAnchor)
bottom.isActive = true
stickyViewConstraints[indexPath.row] = StickyView(view: stickyView.view, constraint: bottom)
}
}
}
hey guys hope everyone is fine and safe
I'm actually implementing a table view for a new app and I want the title to always stay large, I don't want it to collapse when the user scrolls down and I'm fighting since this morning with my code, and nothing work. The last SO solutions I found about it were like from 3 years ago and don't work.
So I got a navigation controller and then my root view controller and then this table view controller
Here are the different presets used in storyboard
nav bar storyboard presets
root vc nav bar storyboard presets
and there's the code of the table view controller
class PositionVC: UITableViewController {
let positions = ["QB", "WR", "RB", "TE", "OL", "DT", "DE", "EDGE", "LB", "CB", "S"]
override func viewDidLoad() {
super.viewDidLoad()
title = "Positions"
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.largeTitleDisplayMode = .always
self.tableView.backgroundColor = .primaryBlue
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return positions.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = positions[indexPath.row]
cell.textLabel?.textAlignment = .center
cell.textLabel?.font = UIFont(name: "AlfaSlabOne-Regular", size: 25)
return cell
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.contentView.layer.masksToBounds = true
cell.backgroundColor = .primaryBlue
}
tell me if you need to see anything else
thank you guys
I have been working on a project which also required large title not to collapse to a small one and it took me pretty long time to know that there is no other way to do so rather than hiding navigationController and setting UILabel with large font you need on a place of NavigationControllers title.
Here is the settings for UILabel I used in that process:
navigationController?.navigationBar.tintColor = UIColor.clear
navigationController?.navigationBar.isHidden = true
let lbl: UILabel = {
let label = UILabel()
label.backgroundColor = UIColor.clear
label.textColor = UIColor.black
label.textAlignment = .left
label.text = "Running Map"
label.font = UIFont.boldSystemFont(ofSize: 35.0)
return label
}()
In order to place it right don't forget to use constraints
lbl.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 100).isActive = true
lbl.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
lbl.heightAnchor.constraint(equalToConstant: 50).isActive = true
lbl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 33).isActive = true
lbl.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -(view.frame.height * 0.844)).isActive = true
I’m coding a mock messaging application in Swift Playgrounds for iPad. So far, I have a UITableViewController with a custom cell, whose background color changes every other cell. Now, I’d like to add a sticky footer of some kind to the bottom of the screen that will have a text field and a send button so that the user can send messages. I’m not quite sure how to approach this. Any ideas? Here’s what I have so far:
import UIKit
import PlaygroundSupport
class ViewController: UITableViewController {
let textMessages = [
"Here's my very first message",
"I'm going to message another long message that will word wrap",
"I'm going to message another long message that will word wrap, I'm going to message another long message that will word wrap, I'm going to message another long message that will word wrap",
"Somejfjfidiskkejejsjsjsjdjjdj blah blah blah"
]
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Messages"
navigationController?.navigationBar.prefersLargeTitles = true
tableView.register(ChatMessageCell.self, forCellReuseIdentifier: "cell_1")
tableView.separatorStyle = .none
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textMessages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_1", for: indexPath) as! ChatMessageCell
cell.selectionStyle = .none
if indexPath.row % 2 == 1{
cell.messageLabel.text = textMessages[indexPath.row]
//cell.setupConstraints(side: 1)
cell.bubbleBackgroundView.backgroundColor = UIColor(white: 0.9, alpha: 1)
return cell
}else{
cell.messageLabel.text = textMessages[indexPath.row]
//cell.setupConstraints(side: 0)
cell.bubbleBackgroundView.backgroundColor = .blue
return cell
}
}
//let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ChatMessageCell
// cell.textLabel?.text = "We want to provide a longer string that is actually going to wrap onto the next line and maybe even a third line."
// cell.textLabel?.numberOfLines = 0
}
class ChatMessageCell: UITableViewCell {
let messageLabel = UILabel()
let bubbleBackgroundView = UIView()
var leadingAnchorConstant = CGFloat()
func setupConstraints(side: Int){
if side == 1{
leadingAnchorConstant = frame.size.width - 176
}else{
leadingAnchorConstant = 32
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
bubbleBackgroundView.backgroundColor = .yellow
let gradient = CAGradientLayer()
bubbleBackgroundView.layer.shadowOpacity = 0.35
bubbleBackgroundView.layer.shadowRadius = 6
bubbleBackgroundView.layer.shadowOffset = CGSize(width: 0, height: 0)
bubbleBackgroundView.layer.shadowColor = UIColor.black.cgColor
bubbleBackgroundView.layer.cornerRadius = 25
bubbleBackgroundView.translatesAutoresizingMaskIntoConstraints = false
addSubview(bubbleBackgroundView)
addSubview(messageLabel)
// messageLabel.backgroundColor = .green
messageLabel.text = "We want to provide a longer string that is actually going to wrap onto the next line and maybe even a third line."
messageLabel.numberOfLines = 0
messageLabel.translatesAutoresizingMaskIntoConstraints = false
// lets set up some constraints for our label
let constraints = [messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 32),
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32),
messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -32),
messageLabel.widthAnchor.constraint(equalToConstant: 250),
bubbleBackgroundView.topAnchor.constraint(equalTo: messageLabel.topAnchor, constant: -16),
bubbleBackgroundView.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor, constant: -16),
bubbleBackgroundView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
bubbleBackgroundView.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor, constant: 16),
]
NSLayoutConstraint.activate(constraints)
// messageLabel.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
PlaygroundPage.current.liveView = ViewController()
I think you are looking for these tableview delegate methods to set up a custom footer:
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
//let cell = tableView.dequeueReusableCell(withIdentifier: "footer_cell") as! FooterCell
// Set up cell
let cell = UITableViewCell()
cell.textLabel?.text = "Footer"
cell.backgroundColor = .white
return cell
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 40.0 // Set height of footer
}
You can use custom tableview cells the same way you use them in cellForRow - I just set it up with simple default cells to produce a working sample - you can also create UIView() to return to this method
By default it will stick to bottom of screen above tableview - if this isn't the case or you want it to stick to bottom of table you can change this setting in attributes inspector if using storyboard:
Style - Plain -> Sticks to bottom of view on top of tableview
Style - Grouped -> Sticks to bottom of tableview no matter how tall
Although another option probably even better without the use of a footer is to use a UITableView on UIViewController - this will give you the space to add your TextView, Buttons and whatever else you need directly on the View Controller under the UITableView
Left is UITableViewController - Right is UIViewController with UITableView
Up to you how you want to play it but I'd recommend the second option to provide the most flexibility on a ViewController
Hope this helps!
I am using a tableview where the first cell contains two buttons. When the first button is activated, the second should be disabled (you should not be able to press it). It all works fine until I start scrolling down the tableview. If I scroll down as far as possible while still being able to see the buttons in first cell, and I activate the first button, I am still able to press the other one. Other things also stops working, but I guess that it is cause by the same thing. Do you have any idea on what happens? See the gif below to see what is going on
I have uploaded the source code on this link, if you need it
https://github.com/Rawchris/scroll-down
I hope you can help :)
Table view cells are reused - which means a couple things:
in general (particularly for your case) you should only add UI elements when you initialize the cell. Otherwise, new ones get added over and over and over.
you need to maintain "row" information, usually in your data source. In this example, you want at least an array of Bool values indicating whether the button in the row should be enabled or not when the cell is reused.
Change your View Controller class to this:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
// this would be replaced with your real data
// test with 20 rows
// start with all rows having button enabled
var buttonStatus: [Bool] = Array(repeating: true, count: 20)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Configure the button
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(200)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return buttonStatus.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
cell.selectionStyle = UITableViewCell.SelectionStyle.none
cell.setButtonEnabled(buttonStatus[indexPath.row], with: "Row \(indexPath.row)")
cell.callback = { b in
// update data source with enabled state of button
self.buttonStatus[indexPath.row] = b
}
return cell
}
}
and change your cell class to this:
class TableViewCell: UITableViewCell {
var callback: ((Bool) -> ())?
var button = DropDownBtn()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
button = DropDownBtn.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
button.setTitle("Button1", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
//Add Button to the View Controller
self.addSubview(button)
//button Constraints
button.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: 30).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.heightAnchor.constraint(equalToConstant: 40).isActive = true
//Set the drop down menu's options
button.dropView.dropDownOptions = ["Option1", "Option2", "Option3", "Option4"]
self.clipsToBounds = false
self.contentView.clipsToBounds=false
}
func setButtonEnabled(_ b: Bool, with title: String) {
button.isUserInteractionEnabled = b
// update the UI - green enabled, red disabled
button.backgroundColor = b ? UIColor(red: 0.0, green: 0.6, blue: 0.0, alpha: 1.0) : .red
// update the title so we can see what row we're on
button.setTitle(title, for: [])
}
#IBAction func deactivate(_ sender: Any) {
// toggle the enabled property of "button"
button.isUserInteractionEnabled = !button.isUserInteractionEnabled
// tell the controller the status changed
callback?(button.isUserInteractionEnabled)
// update the UI - green enabled, red disabled
button.backgroundColor = button.isUserInteractionEnabled ? UIColor(red: 0.0, green: 0.6, blue: 0.0, alpha: 1.0) : .red
}
}
This will demonstrate using an array to track the Bool enabled state for the dropDown button in each row. It also changes the button's background color to Green when enabled, Red when disabled. And, it sets the Title of the dropDown button to make it easy to see which rows you're looking at when you scroll up and down.
I have a "plain" style UITableView. I am setting a view as the tableViewHeader for the table view. The table also shows the section index down the right side.
My issue is figuring out how to inset the left and right edge of the header view to take into account safe area insets if run on an iPhone X (in landscape) and the table view's section index (if there is one).
I created a simple test app that adds a few dummy rows, a section header, and the section index.
Here is my code for creating a simple header view using a UILabel. My real app won't be using a label but a custom view.
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 30)
label.backgroundColor = .green
label.text = "This is a really long Table Header label to make sure it is working properly."
label.sizeToFit()
tableView.tableHeaderView = label
Without any special attempts to fix the left and right edges, the result in the iPhone X simulator is as follows:
Portait:
Landscape:
Note that without any extra effort, the cells and section header get the desired insets but the header view does not.
I'd like the left edge of the header view to line up with the left edge of the section header and the cells.
I'd like the right edge of the header view to stop where it meets the left edge of the section index. Note that the portrait picture seems like it is already do that but if you look close you can tell the header view goes all the way to the right edge of the table view. You can't see the third . of the ellipses and you can barely see the green behind the section title view.
One attempt I've made was to add the following to my table view controller:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let header = tableView.tableHeaderView {
var insets = header.layoutMargins
insets.left = tableView.layoutMargins.left
insets.right = tableView.layoutMargins.right
header.layoutMargins = insets
}
}
That code has no effect.
What properties do I set to ensure the header view's left and right edges are indented as needed? Are there constraints that should be applied?
Please note that I'm doing everything in code. So please don't post any solutions that require storyboards or xib files. Answers in either Swift or Objective-C are welcome.
For anyone that wants to replicate this, create a new single view project. Adjust the main storyboard to use a UITableViewController instead of a plan UIViewController and use the following for ViewController:
import UIKit
class ViewController: UITableViewController {
// MARK: - UITableViewController methods
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Row \(indexPath.row)"
cell.accessoryType = .disclosureIndicator
return cell
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section Header"
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
let coll = UILocalizedIndexedCollation.current()
return coll.sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return index
}
// MARK: - UIViewController methods
override func viewDidLoad() {
super.viewDidLoad()
tableView.sectionIndexMinimumDisplayRowCount = 1
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 30)
label.backgroundColor = .green
label.text = "This is a really long Table Header label to make sure it is working properly."
label.sizeToFit()
tableView.tableHeaderView = label
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let header = tableView.tableHeaderView {
var insets = header.layoutMargins
insets.left = tableView.layoutMargins.left
insets.right = tableView.layoutMargins.right
header.layoutMargins = insets
}
}
}
For UITableViews without section index:
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
For UITableViews with section index:
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
label.trailingAnchor.constraint(equalTo: tableView.layoutMarginsGuide.trailingAnchor)
])
You need to add some Auto Layout constraints to the label after you add it to the tableview:
…
tableView.tableHeaderView = label
//Add this
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: (label.superview?.safeAreaLayoutGuide.leadingAnchor)!).isActive = true
label.trailingAnchor.constraint(equalTo: (label.superview?.safeAreaLayoutGuide.trailingAnchor)!).isActive = true
Also, if you want to see all the text in the label use label.numberOfLines = 0.
You can get rid of the code you added to viewDidLayoutSubviews.
Update:
For fun I did some experimenting in a playground and found that using the layoutMarginsGuide didn't push the trailing edge of the header label far enough over (I'm thinking it comes out right on iPhone X but maybe not on all devices, or the playground behaves a bit differently). I did find though that for table views with at least one cell already in place I could use aCell.contentView.bounds.width, subtract the table view's width and divide the result by two and the header label would sit very nicely next to the section index. As a result I wrote a helper function for setting up constraints. The table view is optional so the function can be used with any view that has a superview and needs to keep inside the safe area. If a table view is passed in it can have a section index or not but it does need to have at least one cell at row 0 section 0:
func constrain(view: UIView, inTableView aTableView: UITableView?) {
guard let container = view.superview else {
print("Constrain error! View must have a superview to be constrained!!")
return
}
view.translatesAutoresizingMaskIntoConstraints = false
view.leadingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.leadingAnchor).isActive = true
if let table = aTableView, let aCell = table.cellForRow(at: IndexPath(row: 0, section: 0)) {
let tableWidth = table.bounds.width
let cellWidth = aCell.contentView.bounds.width
view.trailingAnchor.constraint(equalTo: table.safeAreaLayoutGuide.trailingAnchor, constant: cellWidth - tableWidth).isActive = true
} else {
view.trailingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.trailingAnchor).isActive = true
}
}
I did find one issue when using this. When using a label set to 0 lines with your text it covers the first section header and the first cell of that section. It takes a bit of scrolling to get them out from under the header too. Clipping the label to one line works out quite well though.