How to set left and right margin for UITableView in Swift - ios

I am using UITableView inside my controller that displays cells with some informations (title, subtitle and image). I would like to add some padding to the tableView, but I can't find the right solution. This is my code for table view:
private let tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.allowsSelection = false
tableView.showsVerticalScrollIndicator = false
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
return tableView
}()
Inside viewDidLoad() I setup constraints for that tableView
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
Right now my view looks like this:
Now when I try to configure contentInset property inside my tableView closure by adding this:
tableView.contentInset = .init(top: 0, left: 23.5, bottom: 0, right: -23.5)
As you can see it only added space on the left side, but looking at the debug view hierarchy
I can say that it moved that contentView to the right side (It's still have the same width as its superview)
Info from view debugger.
TableView:
<UITableView: 0x7fba82016000; frame = (0 0; 390 844); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x60000023de90>; layer = <CALayer: 0x600000c4b9e0>; contentOffset: {-23.333333333333332, -47}; contentSize: {390, 635.66666666666663}; adjustedContentInset: {47, 23.5, 34, -23.5}; dataSource: <Myapp.WelcomeViewController: 0x7fba81c08f60>>
and First cell:
<UITableViewCellContentView: 0x7fba84021250; frame = (0 0; 390 87); gestureRecognizers = <NSArray: 0x6000002cf900>; layer = <CALayer: 0x600000c2b900>>
TableView has the same width as cell (390 vs 390). How can I add my paddings to the left and right to the tableView, without making constraints on that tableView (I want that area to also be scrollable)

You can do this either by setting the .layoutMargins on the cell's contentView (either in the cell class itself or in cellForRowAt:
cell.contentView.layoutMargins = .init(top: 0.0, left: 23.5, bottom: 0.0, right: 23.5)
or on the table view itself:
override func viewDidLoad() {
super.viewDidLoad()
tableView.layoutMargins = .init(top: 0.0, left: 23.5, bottom: 0.0, right: 23.5)
// if you want the separator lines to follow the content width
tableView.separatorInset = tableView.layoutMargins
}

You may try this:
Add the constant while adding the constraints
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 20),
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])

Try this
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.x += 10
frame.size.width -= 2 * 10
super.frame = frame
}
}

Related

Multi-line UILabel in TableView header [duplicate]

I am currently using a UIViewController and adding a UITableView to the view. With this tableView I am adding a UIView called containerView to its tableHeaderView. I set the height of the container view and then adding a second UIView to its subview, that is pinned to the bottom of the containerView.
When I add it to the header view the cells are being overlapped. What's odd though is that if I don't add the subview to the container view the headerView is not being overlapped by the cells, it is only occurring when I am adding the second view as a subview to the container view.
class ViewController: UIViewController {
private var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.alpha = 0.7
view.backgroundColor = .red
return view
}()
private var bottomView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .blue
return view
}()
private(set) lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
containerView.addSubview(bottomView)
tableView.tableHeaderView = containerView
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
containerView.topAnchor.constraint(equalTo: tableView.topAnchor),
containerView.heightAnchor.constraint(equalToConstant: 214),
containerView.widthAnchor.constraint(equalToConstant: view.frame.size.width),
bottomView.topAnchor.constraint(equalTo: containerView.bottomAnchor),
bottomView.heightAnchor.constraint(equalToConstant: 114),
bottomView.widthAnchor.constraint(equalToConstant: view.frame.size.width),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.contentInset = UIEdgeInsets(top: -view.safeAreaInsets.top, left: 0, bottom: 0, right: 0)
tableView.tableHeaderView?.autoresizingMask = []
tableView.tableHeaderView?.layoutIfNeeded()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
}
The reason your "blue view" is overlapping the cells is because you are constraining its Top to the red view's Bottom, but you're not updating the header view size.
One good approach is to create a UIView subclass to use as your header view. Setup all of its content with proper auto-layout constraints.
Then, in the controller's viewDidLayoutSubviews(), we use .systemLayoutSizeFitting(...) to determine the header view's height and update its frame:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update table header size
guard let headerView = tableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow).height
var frame = headerView.frame
// avoids infinite loop!
if height != frame.height {
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
}
}
Here is a complete example...
First, our custom view class:
class SampleHeaderView: UIView {
let redView: UIView = {
let v = UIView()
v.backgroundColor = .systemRed
return v
}()
let blueView: UIView = {
let v = UIView()
v.backgroundColor = .systemBlue
return v
}()
let redTopLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .yellow
v.numberOfLines = 0
return v
}()
let redBottomLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .green
v.numberOfLines = 0
return v
}()
let multiLineLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .cyan
v.numberOfLines = 0
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// all views will use auto-layout
[redView, blueView, redTopLabel, redBottomLabel, multiLineLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
// prevent label vertical compression
[redTopLabel, redBottomLabel, multiLineLabel].forEach { v in
v.setContentCompressionResistancePriority(.required, for: .vertical)
}
// add top and bottom labels to red view
redView.addSubview(redTopLabel)
redView.addSubview(redBottomLabel)
// add multi-line label to blue view
blueView.addSubview(multiLineLabel)
// add red and blue views to self
addSubview(redView)
addSubview(blueView)
// the following constraints need to have less-than required to avoid
// auto-layout warnings
// blue view bottom to self
let c1 = blueView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0)
// labels trailing contraints
let c2 = redTopLabel.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -8.0)
let c3 = redBottomLabel.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -8.0)
let c4 = multiLineLabel.trailingAnchor.constraint(equalTo: blueView.trailingAnchor, constant: -8.0)
[c1, c2, c3, c4].forEach { c in
c.priority = .required - 1
}
NSLayoutConstraint.activate([
// red view top to self
redView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
// leading / trailing to self
redView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
redView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// blue view top to red view bottom
blueView.topAnchor.constraint(equalTo: redView.bottomAnchor, constant: 0.0),
// leading / trailing to self
blueView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
blueView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// top and bottom labels, constrained in red view
// with a little "padding"
redTopLabel.topAnchor.constraint(equalTo: redView.topAnchor, constant: 8.0),
redTopLabel.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 8.0),
redBottomLabel.topAnchor.constraint(equalTo: redTopLabel.bottomAnchor, constant: 8.0),
redBottomLabel.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 8.0),
redBottomLabel.bottomAnchor.constraint(equalTo: redView.bottomAnchor, constant: -8.0),
// multi-line label, constrained in blue view
// with a little "padding"
multiLineLabel.topAnchor.constraint(equalTo: blueView.topAnchor, constant: 8.0),
multiLineLabel.leadingAnchor.constraint(equalTo: blueView.leadingAnchor, constant: 8.0),
multiLineLabel.bottomAnchor.constraint(equalTo: blueView.bottomAnchor, constant: -8.0),
// the less-than-required priority constraints
c1, c2, c3, c4,
])
}
}
and a sample controller:
class TableHeaderViewController: UIViewController {
var sampleHeaderView = SampleHeaderView()
private(set) lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
sampleHeaderView.redTopLabel.text = "The Red Top Label"
sampleHeaderView.redBottomLabel.text = "The Red Bottom Label, with enough text that is should wrap."
sampleHeaderView.multiLineLabel.text = "This text is for the Label in the Blue View. It is also long enough that it will require word-wrapping. Note that the header updates itself when the frame changes, such as on device rotation."
tableView.tableHeaderView = sampleHeaderView
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update table header size
guard let headerView = tableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow).height
var frame = headerView.frame
// avoids infinite loop!
if height != frame.height {
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
}
}
}
extension TableHeaderViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
}
Output:
and rotated:

tableHeaderView is overlapping cells when adding custom view to subview of container view

I am currently using a UIViewController and adding a UITableView to the view. With this tableView I am adding a UIView called containerView to its tableHeaderView. I set the height of the container view and then adding a second UIView to its subview, that is pinned to the bottom of the containerView.
When I add it to the header view the cells are being overlapped. What's odd though is that if I don't add the subview to the container view the headerView is not being overlapped by the cells, it is only occurring when I am adding the second view as a subview to the container view.
class ViewController: UIViewController {
private var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.alpha = 0.7
view.backgroundColor = .red
return view
}()
private var bottomView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .blue
return view
}()
private(set) lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
containerView.addSubview(bottomView)
tableView.tableHeaderView = containerView
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
containerView.topAnchor.constraint(equalTo: tableView.topAnchor),
containerView.heightAnchor.constraint(equalToConstant: 214),
containerView.widthAnchor.constraint(equalToConstant: view.frame.size.width),
bottomView.topAnchor.constraint(equalTo: containerView.bottomAnchor),
bottomView.heightAnchor.constraint(equalToConstant: 114),
bottomView.widthAnchor.constraint(equalToConstant: view.frame.size.width),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.contentInset = UIEdgeInsets(top: -view.safeAreaInsets.top, left: 0, bottom: 0, right: 0)
tableView.tableHeaderView?.autoresizingMask = []
tableView.tableHeaderView?.layoutIfNeeded()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
}
The reason your "blue view" is overlapping the cells is because you are constraining its Top to the red view's Bottom, but you're not updating the header view size.
One good approach is to create a UIView subclass to use as your header view. Setup all of its content with proper auto-layout constraints.
Then, in the controller's viewDidLayoutSubviews(), we use .systemLayoutSizeFitting(...) to determine the header view's height and update its frame:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update table header size
guard let headerView = tableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow).height
var frame = headerView.frame
// avoids infinite loop!
if height != frame.height {
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
}
}
Here is a complete example...
First, our custom view class:
class SampleHeaderView: UIView {
let redView: UIView = {
let v = UIView()
v.backgroundColor = .systemRed
return v
}()
let blueView: UIView = {
let v = UIView()
v.backgroundColor = .systemBlue
return v
}()
let redTopLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .yellow
v.numberOfLines = 0
return v
}()
let redBottomLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .green
v.numberOfLines = 0
return v
}()
let multiLineLabel: UILabel = {
let v = UILabel()
v.backgroundColor = .cyan
v.numberOfLines = 0
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// all views will use auto-layout
[redView, blueView, redTopLabel, redBottomLabel, multiLineLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
// prevent label vertical compression
[redTopLabel, redBottomLabel, multiLineLabel].forEach { v in
v.setContentCompressionResistancePriority(.required, for: .vertical)
}
// add top and bottom labels to red view
redView.addSubview(redTopLabel)
redView.addSubview(redBottomLabel)
// add multi-line label to blue view
blueView.addSubview(multiLineLabel)
// add red and blue views to self
addSubview(redView)
addSubview(blueView)
// the following constraints need to have less-than required to avoid
// auto-layout warnings
// blue view bottom to self
let c1 = blueView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0)
// labels trailing contraints
let c2 = redTopLabel.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -8.0)
let c3 = redBottomLabel.trailingAnchor.constraint(equalTo: redView.trailingAnchor, constant: -8.0)
let c4 = multiLineLabel.trailingAnchor.constraint(equalTo: blueView.trailingAnchor, constant: -8.0)
[c1, c2, c3, c4].forEach { c in
c.priority = .required - 1
}
NSLayoutConstraint.activate([
// red view top to self
redView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
// leading / trailing to self
redView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
redView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// blue view top to red view bottom
blueView.topAnchor.constraint(equalTo: redView.bottomAnchor, constant: 0.0),
// leading / trailing to self
blueView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
blueView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// top and bottom labels, constrained in red view
// with a little "padding"
redTopLabel.topAnchor.constraint(equalTo: redView.topAnchor, constant: 8.0),
redTopLabel.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 8.0),
redBottomLabel.topAnchor.constraint(equalTo: redTopLabel.bottomAnchor, constant: 8.0),
redBottomLabel.leadingAnchor.constraint(equalTo: redView.leadingAnchor, constant: 8.0),
redBottomLabel.bottomAnchor.constraint(equalTo: redView.bottomAnchor, constant: -8.0),
// multi-line label, constrained in blue view
// with a little "padding"
multiLineLabel.topAnchor.constraint(equalTo: blueView.topAnchor, constant: 8.0),
multiLineLabel.leadingAnchor.constraint(equalTo: blueView.leadingAnchor, constant: 8.0),
multiLineLabel.bottomAnchor.constraint(equalTo: blueView.bottomAnchor, constant: -8.0),
// the less-than-required priority constraints
c1, c2, c3, c4,
])
}
}
and a sample controller:
class TableHeaderViewController: UIViewController {
var sampleHeaderView = SampleHeaderView()
private(set) lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
sampleHeaderView.redTopLabel.text = "The Red Top Label"
sampleHeaderView.redBottomLabel.text = "The Red Bottom Label, with enough text that is should wrap."
sampleHeaderView.multiLineLabel.text = "This text is for the Label in the Blue View. It is also long enough that it will require word-wrapping. Note that the header updates itself when the frame changes, such as on device rotation."
tableView.tableHeaderView = sampleHeaderView
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update table header size
guard let headerView = tableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(CGSize(width: tableView.frame.width, height: .greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow).height
var frame = headerView.frame
// avoids infinite loop!
if height != frame.height {
frame.size.height = height
headerView.frame = frame
tableView.tableHeaderView = headerView
}
}
}
extension TableHeaderViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
}
Output:
and rotated:

Troubleshooting an error: The behavior of the UICollectionViewFlowLayout is not defined because the item height must be less than the height

I have a ViewController that at the top, holds a UIView with a UITextView. Constrained to the bottom of the UIView is a UICollectionView that scrolls horizontally.
The UITextView resizes based on user typing in additional lines of text but I am getting an error because of some sizing issue between the collectionView and the collectionViewFlowLayout but I can't for the life of me work it out.
The code is as follows:
import UIKit
class ViewController: UIViewController {
let containerView = UIView()
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardObservers()
setupTopTextView()
setupCollectionView()
}
func setupTopTextView() {
view.backgroundColor = .lightGray
view.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
])
let textView = UITextView()
containerView.addSubview(textView)
textView.backgroundColor = .systemBackground
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = false
textView.becomeFirstResponder()
textView.textContainer.maximumNumberOfLines = 0
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 50),
textView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20),
textView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20),
containerView.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: 0)
])
textView.text = "This is a test"
}
func setupCollectionView() {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
flowLayout.minimumInteritemSpacing = 0
collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView.backgroundColor = .systemYellow
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
])
collectionView.isPagingEnabled = true
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(BasicCollectionViewCell.self, forCellWithReuseIdentifier: BasicCollectionViewCell.reuseIdentifier)
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width, height: collectionView.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BasicCollectionViewCell.reuseIdentifier, for: indexPath) as! BasicCollectionViewCell
let backgroundColorOptions = [UIColor.systemGreen, .systemPurple, .brown, .systemRed]
cell.backgroundColor = backgroundColorOptions[indexPath.row]
return cell
}
}
The BasicCollectionViewCell literally has nothing yet as I am just trying to get these elements working and resizing correctly:
class BasicCollectionViewCell: UICollectionViewCell {
static let reuseIdentifier = "BASIC_COLLECTIONVIEW_CELL"
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The error I get happens when the UITextView increases or decreases in height:
2020-07-02 16:34:06.922059+1000 collectionViewResizingTest[97611:1371485] The behavior of the UICollectionViewFlowLayout is not defined because:
2020-07-02 16:34:06.922175+1000 collectionViewResizingTest[97611:1371485] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
2020-07-02 16:34:06.922554+1000 collectionViewResizingTest[97611:1371485] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7ffbfc016350>, and it is attached to <UICollectionView: 0x7ffbf9844600; frame = (0 94; 414 802); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x6000017c9200>; layer = <CALayer: 0x6000019d2ba0>; contentOffset: {0, 0}; contentSize: {1656, 816}; adjustedContentInset: {0, 0, 34, 0}; layout: <UICollectionViewFlowLayout: 0x7ffbfc016350>; dataSource: <collectionViewResizingTest.ViewController: 0x7ffbf9411ad0>>.
2020-07-02 16:34:06.922642+1000 collectionViewResizingTest[97611:1371485] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
2020-07-02 16:34:08.131628+1000 collectionViewResizingTest[97611:1371485] The behavior of the UICollectionViewFlowLayout is not defined because:
2020-07-02 16:34:08.131766+1000 collectionViewResizingTest[97611:1371485] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
2020-07-02 16:34:08.131940+1000 collectionViewResizingTest[97611:1371485] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7ffbfc016350>, and it is attached to <UICollectionView: 0x7ffbf9844600; frame = (0 107.5; 414 788.5); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x6000017c9200>; layer = <CALayer: 0x6000019d2ba0>; contentOffset: {0, 0}; contentSize: {1656, 768}; adjustedContentInset: {0, 0, 0, 0}; layout: <UICollectionViewFlowLayout: 0x7ffbfc016350>; dataSource: <collectionViewResizingTest.ViewController: 0x7ffbf9411ad0>>.
2020-07-02 16:34:08.132058+1000 collectionViewResizingTest[97611:1371485] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
Any help would be greatly appreciated. I think it has something to do with the safe area at the bottom (this happens on devices in the simulator that have FaceID).
Fixed by setting the collectionView.contentInsetAdjustmentBehavior to .never

UITableView automatic row height (based upon content) - still the same error

I want the rows in my tableview to scale dynamicly based upon the content (2 labels, total of 3 lines of text). I keep getting this error (and my cells are set to standard size):
[Warning] Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a table view cell's content view. We're considering the collapse unintentional and using standard height instead. Cell: <ProjectSammy.AnnotationCell: 0x106061d80; baseClass = UITableViewCell; frame = (0 647.5; 414 70); autoresize = W; layer = <CALayer: 0x282d09ee0>>
I think I've cohered to all of the requirements (as mentioned in this post:https://stackoverflow.com/questions/25902288/detected-a-case-where-constraints-ambiguously-suggest-a-height-of-zero):
I fully constrain (top and bottom anchors) the labels in my custom cell:
private func configureContents() {
backgroundColor = .clear
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
contentView.addSubviews(titleLabel, detailsLabel)
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 5),
titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
titleLabel.bottomAnchor.constraint(equalTo: detailsLabel.topAnchor),
detailsLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
detailsLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
detailsLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
detailsLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
I set the rows on the TableView to automic dimension:
private func configureTableView() {
tableView = UITableView(frame: view.bounds, style: .grouped)
view.addSubview(tableView)
tableView.delegate = self
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tableView.backgroundColor = .clear
tableView.register(AnnotationCell.self, forCellReuseIdentifier: AnnotationCell.reuseIdentifier)
tableView.register(AnnotationHeaderCell.self, forHeaderFooterViewReuseIdentifier: AnnotationHeaderCell.reuseIdentifier)
tableView.sectionHeaderHeight = 40
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 70
}
And I even supply an estimated row heigth in the delegate method: -NOT NEEDED1- can be done on tableview directly!
Where do I go wrong ?
The estimated row height can not be set to automatic dimension. You have to set it to a specific number try setting it to 44 which is by default the row height.

line breaks not working on UILabel in tableFooterView

I habe a tableView with a footerView. It should display a simple label.
In my ViewController, in viewDidLoad, I assign the tableFooterView like so:
let footerView = MyFooterView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 0))
tableView.tableFooterView = footerView
MyFooterView is a UIView with a single label. The label setup looks like so:
label.font = someFont
label.adjustsFontForContentSizeCategory = true
label.textColor = .black
label.numberOfLines = 0
label.text = "my super looooooong label that should break some lines but it doesn't."
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 40),
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -40),
label.topAnchor.constraint(equalTo: self.topAnchor, constant: 20),
label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20)
])
In order to get AutoLayout to work with MyFooterView, I call this method inside UIViewControllers viewDidLayoutSubviews:
func sizeFooterToFit() {
if let footerView = self.tableFooterView {
footerView.setNeedsLayout()
footerView.layoutIfNeeded()
let height = footerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var frame = footerView.frame
frame.size.height = height
footerView.frame = frame
self.tableFooterView = footerView
}
}
Problem: The lines in the label do not break. I get the following result:
What can I do so that the label has multiple lines? AutoLayout is working thanks to the method sizeFooterToFit. The only thing is that the labels height is only as high as a single line.
HERE is the way how you can achieve it for tableHeaderView and with your case you just need to add below code in your UIViewController class
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tbl.updateHeaderViewHeight()
}
And Helper extension
extension UITableView {
func updateHeaderViewHeight() {
if let header = self.tableFooterView {
let newSize = header.systemLayoutSizeFitting(CGSize(width: self.bounds.width, height: 0))
header.frame.size.height = newSize.height
}
}
}
And remove
func sizeFooterToFit() {
if let footerView = self.tableFooterView {
footerView.setNeedsLayout()
footerView.layoutIfNeeded()
let height = footerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var frame = footerView.frame
frame.size.height = height
footerView.frame = frame
self.tableFooterView = footerView
}
}
Above code.
And result will be:

Resources