Search code examples
iosswiftuitableviewuikitautolayout

Couldn't layout Subviews in UITableViewHeaderFooterView


I'm trying to make a custom header for 0 section of my UITableView. Here's the code I'm using to create this header:

class ProfileHeaderView: UITableViewHeaderFooterView {
    
    // MARK: - Subviews
    
    private var statusText: String = ""
    
    private lazy var userImage: UIImageView = {
        let imageView = UIImageView(image: UIImage(named: me.login))
                
        imageView.layer.cornerRadius = 48
        imageView.clipsToBounds = true
        
        imageView.translatesAutoresizingMaskIntoConstraints = false
        
        return imageView
    }()
    
    ...
    
    // MARK: - Lifecycle
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        setupUI()
        addSuviews()
        setupConstraints()
    }
     // MARK: - Private

    private func setupUI() {
        backgroundColor = .lightGray
    }
    
    private func addSuviews() {
        addSubview(userImage)
    }
    
    private func setupConstraints() {
        let layoutMarginGuide = contentView.layoutMarginsGuide
            
        NSLayoutConstraint.activate([
            userImage.leadingAnchor.constraint(equalTo: layoutMarginGuide.leadingAnchor, constant: 10),
            userImage.topAnchor.constraint(equalTo: layoutMarginGuide.topAnchor, constant: 16),
            userImage.bottomAnchor.constraint(equalTo: layoutMarginGuide.bottomAnchor, constant: -16),
            userImage.widthAnchor.constraint(equalToConstant: 90),
            userImage.heightAnchor.constraint(equalToConstant: 90),
        ])
    }
}

Here's how I add this header in my ViewController:

    private func setupConstraints() {
        feedView.frame = view.bounds
        
        feedView.delegate = self
        feedView.dataSource = self
        feedView.register(PostViewCell.self, forCellReuseIdentifier: "cell")
        feedView.register(ProfileHeaderView.self, forHeaderFooterViewReuseIdentifier: "ProfileHeaderView")
        //feedView.register(ProfileHeaderView.self, forHeaderFooterViewReuseIdentifier: "profileView")
    }

Here's the result I'm getting: Screenshot

As you see, image is ignoring safeArea on the screen. How to fix it?


Solution

  • Well, you left out much of the needed code (such as your cell class and your controller class).

    And, you haven't shown your code for viewForHeaderInSection.

    But, we can look at a quick example...

    your ProfileHeaderView with minor modifications

    class ProfileHeaderView: UITableViewHeaderFooterView {
        
        // MARK: - Subviews
        
        private var statusText: String = ""
        
        private lazy var userImage: UIImageView = {
            //let imageView = UIImageView(image: UIImage(named: me.login))
            let imageView = UIImageView(image: UIImage(named: "face"))
    
            imageView.layer.cornerRadius = 48
            imageView.clipsToBounds = true
            
            imageView.translatesAutoresizingMaskIntoConstraints = false
            
            return imageView
        }()
        
        //...
        
        // MARK: - Lifecycle
        
        override init(reuseIdentifier: String?) {
            super.init(reuseIdentifier: reuseIdentifier)
            setupUI()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            setupUI()
        }
        // MARK: - Private
        
        private func setupUI() {
            contentView.backgroundColor = .lightGray
            addSuviews()
            setupConstraints()
        }
        
        private func addSuviews() {
            contentView.addSubview(userImage)
        }
        
        private func setupConstraints() {
            let g = contentView.layoutMarginsGuide
            
            // this will avoid auto-layout complaints
            let bottomC = userImage.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -16.0)
            bottomC.priority = .required - 1
            
            NSLayoutConstraint.activate([
                userImage.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10),
                userImage.topAnchor.constraint(equalTo: g.topAnchor, constant: 16),
                
                //userImage.bottomAnchor.constraint(equalTo: layoutMarginGuide.bottomAnchor, constant: -16),
                bottomC,
                
                userImage.widthAnchor.constraint(equalToConstant: 90),
                userImage.heightAnchor.constraint(equalToConstant: 90),
            ])
        }
    }
    

    a simple single-label cell class

    class PostViewCell: UITableViewCell {
        let theLabel = UILabel()
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            theLabel.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(theLabel)
            let g = contentView.layoutMarginsGuide
            NSLayoutConstraint.activate([
                theLabel.topAnchor.constraint(equalTo: g.topAnchor),
                theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            ])
        }
    }
    

    a simple view controller class with table view - number of rows set to 30 so we can see the section header stays in place...

    class FeedViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
        
        let feedView = UITableView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            self.title = "Log In"
            view.backgroundColor = .systemBackground
            
            feedView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(feedView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                feedView.topAnchor.constraint(equalTo: g.topAnchor),
                feedView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                feedView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                feedView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            ])
            
            feedView.delegate = self
            feedView.dataSource = self
            feedView.register(PostViewCell.self, forCellReuseIdentifier: "cell")
            feedView.register(ProfileHeaderView.self, forHeaderFooterViewReuseIdentifier: "ProfileHeaderView")
            
            // remove default padding
            feedView.sectionHeaderTopPadding = 0.0
    
        }
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 30
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PostViewCell
            cell.theLabel.text = "\(indexPath)"
            return cell
        }
        func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
            if section == 0 {
                let v = tableView.dequeueReusableHeaderFooterView(withIdentifier: "ProfileHeaderView")
                return v
            }
            // return some other header view for subsequent sections?
            return nil
        }
    }
    

    Looks like this when run:

    enter image description here