Avoid sticky header view when tableview scrolls with Auto Layout - ios

Currently when table content is scrolling, the headerLabel follows scroll and sticks to the top. How can I have avoid this behaviour with Auto Layout?
var tableView: UITableView!
let headerLabel: UILabel = {
let label = UILabel(frame: .zero)
label.font = UIFont.boldSystemFont(ofSize: 34.0)
label.textColor = .black
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
tableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: view.frame.width, height: view.frame.height - barHeight))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
view.addSubview(headerLabel)
view.addSubview(tableView)
headerLabel.snp.makeConstraints { (make) in
make.top.equalTo(view).offset(35)
make.width.equalToSuperview()
}
tableView.snp.makeConstraints { (make) in
make.top.equalTo(headerLabel.snp.bottom)
make.left.bottom.right.equalToSuperview()
}
}
The headerLabel should scroll with tableView and should not look like sticky header.

Change the Tableview Style from Plain to Grouped. Your header will move with the table cell scroll.

Currently your table view and your label are siblings inside your UIViewController's view, which means your label is not part of the table view so it won't scroll with it. You can add the label to a UIView, set it's constraints and then set the tableHeaderView property of the table view. Here's a sample code with some hardcoded values:
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "some text"
label.sizeToFit()
let headerView = UIView()
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.addSubview(label)
tableView.tableHeaderView = headerView
headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 80).isActive = true
headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true
label.leftAnchor.constraint(equalTo: headerView.leftAnchor, constant: 50).isActive = true
tableView.tableHeaderView?.layoutIfNeeded()

Related

How should I set constraints to the subviews of my tableHeaderView?

I have this viewController:
class CreateSkillGroupViewController: UIViewController {
lazy var headerStack: UIStackView = {
let stack = UIStackView(frame: CGRect(x: 0, y: 0, width: 20, height: 400))
stack.axis = .vertical
let titleField = UITextView(frame: CGRect(x: 0, y: 0, width: 300, height: 88))
titleField.backgroundColor = .green
titleField.snp.makeConstraints{ (make) in
make.height.equalTo(50)
}
let descriptionField = UITextView(frame: CGRect(x: 0, y: 0, width: 300, height: 120))
descriptionField.snp.makeConstraints{ (make) in
make.height.equalTo(100)
}
let headerImage = UIImageView(image: UIImage(named: "AppIcon-bw"))
headerImage.snp.makeConstraints{ (make) in
make.height.equalTo(300)
make.width.equalTo(200)
}
stack.addArrangedSubview(headerImage)
stack.addArrangedSubview(titleField)
stack.addArrangedSubview(descriptionField)
stack.backgroundColor = .blue
return stack
}()
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationItem()
skillsTableView = UITableView(frame: .zero, style: .insetGrouped)
skillsTableView.register(SkillSummaryCell.self)
skillsTableView.tableHeaderView = headerStack
view.addSubview(skillsTableView)
skillsTableView.tableHeaderView?.snp.makeConstraints{ (make) in
make.top.equalToSuperview()
make.left.equalToSuperview()
make.right.equalToSuperview()
make.width.equalToSuperview()
make.height.equalTo(400)
}
skillsTableView.snp.makeConstraints{ (make) in
make.edges.equalToSuperview()
}
...
This is what it creates...
As you can see I use the lazy var headerStack to setup the tableHeaderView which is a stackView. As you can see all of the constraints in that stack view are explicit number sizes. Then in the viewDidLoad, I add the constraints for the tableView itself.
I want to know how I would for instance, center the headerImage in the viewController, or in the tableView for that matter or make its width half of the tableView's width. I cannot set equalToSuperView because the view hasn't been laid out yet. And once its laid out, I cannot access the stack view subviews to retroactively add constraints to them.
First of all, I wouldn't use a stackView as a tableHeaderView because you need your tableHeaderView to be the same width as the tableView. Embed your stackView in a view and use that view as the header. Ensure that header remains the width of the tableView regardless of the stackView content.
Also, it looks like you are trying to mix autolayout with frame-based layout and that's gonna get you into trouble. I'm not sure why you were setting frames on some of your subviews.
Pay attention to how you define stackView.alignment and stackView.distribution. I'm not sure what your goal is so it's hard to give you much advice there. Bit I assume you want your subviews centered and to have their own unique width.
You defined a lot of your subviews in your stackView builder and that got you into trouble. Ensure that you have one builder for each subview. It helps keep your code clean.
Lastly, you can use autolayout to define the width equal to the width of the tableView. There are a lot of solutions on the web that make you compute the frames for your header manually and that's just a pain.
I changed some names around added some colors but I think this will help you:
extension UIColor {
static let headerImage = UIColor.systemPurple
static let header = UIColor.systemPink
static let titleField = UIColor.white
static let descriptionField = UIColor.systemYellow
static let headerStack = UIColor.systemOrange
static let tableView = UIColor.systemMint
}
class ViewController: UIViewController {
lazy var headerImage: UIImageView = {
let headerImage = UIImageView(image: UIImage(systemName: "checkmark"))
headerImage.translatesAutoresizingMaskIntoConstraints = false
headerImage.backgroundColor = .headerImage
return headerImage
}()
lazy var headerView: UIView = {
let header = UIView()
header.backgroundColor = .header
header.translatesAutoresizingMaskIntoConstraints = false
return header
}()
lazy var titleField: UITextView = {
let titleField = UITextView(frame: .zero)
titleField.translatesAutoresizingMaskIntoConstraints = false
titleField.backgroundColor = .titleField
return titleField
}()
lazy var descriptionField: UITextView = {
let descriptionField = UITextView(frame: .zero)
descriptionField.translatesAutoresizingMaskIntoConstraints = false
descriptionField.backgroundColor = .descriptionField
return descriptionField
}()
lazy var headerStack: UIStackView = {
let stack = UIStackView(frame: .zero)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.distribution = .fillProportionally
stack.alignment = .center
stack.spacing = 10
stack.backgroundColor = .headerStack
return stack
}()
lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .insetGrouped)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(SkillSummaryCell.self, forCellReuseIdentifier: "SkillSummaryCell")
tableView.backgroundColor = .tableView
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
addViews()
arrangeViews()
tableView.layoutIfNeeded()
}
func addViews() {
view.addSubview(tableView)
headerStack.addArrangedSubview(headerImage)
headerStack.addArrangedSubview(titleField)
headerStack.addArrangedSubview(descriptionField)
headerView.addSubview(headerStack)
tableView.tableHeaderView = headerView
}
func arrangeViews() {
tableView.snp.makeConstraints{ (make) in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
descriptionField.snp.makeConstraints{ (make) in
make.height.equalTo(100)
make.width.equalTo(300)
}
titleField.snp.makeConstraints{ (make) in
make.height.equalTo(100)
make.width.equalTo(300)
}
headerStack.snp.makeConstraints { make in
make.top.equalToSuperview()
make.bottom.equalToSuperview()
make.centerX.equalToSuperview()
}
headerView.snp.makeConstraints { make in
make.width.equalTo(tableView)
}
headerImage.snp.makeConstraints{ (make) in
make.width.equalTo(tableView).dividedBy(2)
make.height.equalTo(headerImage.snp.width)
}
}
}
use it:
viewForHeaderInSection
as
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let tableBounds = tableView.bounds // <- table size
let sectionIndex = section // <- Section index
}
In this method, you can customize the header for a specific section, and take into account the size of your table
ALSO:
You can use UIScreen.main.bounds - get the screen size of your phone at any time, this can be very useful, especially considering that tables are often equal in width to the width of the screen

UIScrollView for ViewController horizontally and vertically

My code tutorial works for scrolling horizontally with ScrollView.
I would like to make it with ViewControllers (instead of views) from my Storyboard.
And I would like to add a vertical ScrollView (To move from center to right or left = ScrollView horizontal, and to move from center to top or bottom (vertically).
Is that possible? Could someone give me a hint ?
Thanks
For information, this is my code :
import UIKit
class ViewController: UIViewController {
lazy var view0: UIView = {
let view = UIView()
view.backgroundColor = .systemTeal
let label = UILabel()
label.text = "Page 0"
label.textAlignment = .center
view.addSubview(label)
label.edgeTo(view: view)
return view
}()
lazy var view1: UIView = {
let view = UIView()
view.backgroundColor = .systemPink
let label = UILabel()
label.text = "Page 1"
label.textAlignment = .center
view.addSubview(label)
label.edgeTo(view: view)
return view
}()
lazy var view2: UIView = {
let view = UIView()
view.backgroundColor = .systemYellow
let label = UILabel()
label.text = "Page 2"
label.textAlignment = .center
view.addSubview(label)
label.edgeTo(view: view)
return view
}()
lazy var views = [view0, view1, view2]
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.isPagingEnabled = true
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(views.count), height: view.frame.height)
for i in 0..<views.count {
scrollView.addSubview(views[i])
views[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
}
scrollView.delegate = self
return scrollView
}()
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.numberOfPages = views.count
pageControl.currentPage = 0
pageControl.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .touchUpInside)
return pageControl
}()
#objc
func pageControlTapHandler(sender: UIPageControl) {
scrollView.scrollTo(horizontalPage: sender.currentPage, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(scrollView)
scrollView.edgeTo(view: view)
view.addSubview(pageControl)
pageControl.pinTo(view)
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x / view.frame.width)
pageControl.currentPage = Int(pageIndex)
}

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:

UICollectionview in UIStackView in UITableViewCell

I have a tableview and would want to add views dynamically to it, based on the data. For every other view it is easy. Works like charm but collectionview, I don't even see the view on stackview.
The UICollectionView's cellForItem delegate is not called, the numberOfItems delegate is called but not cellForItem.
Not sure if I am missing anything.
func testView() -> UIView {
let containerView = UIView(frame: .zero)
containerView.backgroundColor = .red
let layout = KTCenterFlowLayout()
layout.minimumInteritemSpacing = 10.0
layout.minimumLineSpacing = 10.0
layout.estimatedItemSize = CGSize(width: 80, height: 100)
layout.sectionInset = UIEdgeInsetsMake(20, 30, 20, 30)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout).then {
$0.dataSource = self
$0.isScrollEnabled = false
$0.setCollectionViewLayout(layout, animated: false)
$0.register(NPSCollectionViewCell.self, forCellWithReuseIdentifier: "npsCell")
$0.contentSize = CGSize(width: view.frame.size.width-100, height: 500.0)
containerView.addSubview($0)
}
collectionView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
make.height.equalTo(500).priority(500)
}
return containerView
}
I am adding this above view in CellForRowAtIndexPath in
cell?.stackView.addArrangedSubview(testView())
Any pointers?
Edit: here is the screenshot
I just see a blank space
Here is my StackTableViewCell
lazy var stackView : UIStackView! = {
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fillProportionally
stack.alignment = .center
stack.spacing = 10.0
stack.translatesAutoresizingMaskIntoConstraints = false
stack.setContentCompressionResistancePriority(UILayoutPriority.required, for: .vertical)
return stack
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.top.equalToSuperview().offset(10)
make.left.equalToSuperview().offset(20)
make.right.equalToSuperview().offset(-20)
make.bottom.equalToSuperview().offset(-10)
}
I had a problem that the cell wasn't displaying and I just gave it a size manually using a property of the collectionView try this approach

Put UIView above UITableView with section

I want to put a UIView above a UITableView with Sections.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(ActivityCell.self, forCellReuseIdentifier: "cellId")
//tableView?.contentInset = UIEdgeInsetsMake(70, 0, 0, 0)
objectArray = [Objects(sectionName: "sec 1", sectionObjects: ["sdqdsq", "sdsqdqsd", "dsqdsqd", "dsqqsdds"]),Objects(sectionName: "sec 2", sectionObjects: ["sdqdsq", "sdsqdqsd", "dsqdsqd", "dsqqsdds"]),Objects(sectionName: "sec 3", sectionObjects: ["sdqdsq", "sdsqdqsd", "dsqdsqd", "dsqqsdds"])]
setupProfilBar()
}
let profilBar: profilMenu = {
let pb = profilMenu()
pb.translatesAutoresizingMaskIntoConstraints = false
return pb
}()
func setupProfilBar() {
view.addSubview(profilBar)
profilBar.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
profilBar.heightAnchor.constraint(equalToConstant: 64).isActive = true
profilBar.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
}
How can i fix this ? thanks
There is an even easier method than mentioned in the comments:
You can set the tableHeaderView-property of your UITableView. This is a header above the whole table. (not a section header!).
Add this code in your viewDidLoad():
let v = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 200))
v.backgroundColor = UIColor.red
tableView.tableHeaderView = v
This would be the result:
You'll want to set your custom view as the table view's tableHeaderView property. There's no need to use a UIViewController instead of a UITableViewController as others have mentioned. Here's the relevant Apple Documentation.

Resources