Home > Software design >  How should I set constraints to the subviews of my tableHeaderView?
How should I set constraints to the subviews of my tableHeaderView?

Time:08-08

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...

enter image description here

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.

CodePudding user response:

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

CodePudding user response:

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)
        }
    }
}
  • Related