You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击按钮后无法更新TableViewHeader及内部UILabel高度的问题

问题:设置长状态文本后,Label和TableViewHeader高度未更新

点击按钮设置新的长状态文本时,profileStatusLabel的高度以及TableViewHeader的高度均未发生变化。

状态文本被截断的界面

ProfileHeaderView: UIView

import UIKit

...
        
    private lazy var profileStatusLabel: UILabel = {
        let profileStatusLabel = UILabel()
        profileStatusLabel.numberOfLines = 0
        profileStatusLabel.text = "Looking for a big, young, good looking, able to cook female gorilla"
        profileStatusLabel.textColor = .gray
        profileStatusLabel.font = profileNameLabel.font.withSize(14)
        profileStatusLabel.textAlignment = .left
        profileStatusLabel.sizeToFit()
        profileStatusLabel.translatesAutoresizingMaskIntoConstraints = false
        return profileStatusLabel
    }()
    
    private lazy var setStatusButton: UIButton = {
        let setStatusButton = UIButton()
        setStatusButton.backgroundColor = .systemBlue
        setStatusButton.layer.cornerRadius = 4
        setStatusButton.layer.shadowOffset = CGSize(width: 4, height: 4)
        setStatusButton.layer.shadowOpacity = 0.7
        setStatusButton.layer.shadowRadius = 4
        setStatusButton.layer.shadowColor = UIColor.black.cgColor
        setStatusButton.setTitle("Set status", for: .normal)
        setStatusButton.setTitleColor(.white, for: .normal)
        setStatusButton.titleLabel?.font = setStatusButton.titleLabel?.font.withSize(14)
        setStatusButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        setStatusButton.translatesAutoresizingMaskIntoConstraints = false
        return setStatusButton
    }()
        
    private lazy var statusText: String = {
        return statusText
    }()
        
    private func setupView() {
        addSubview(profileImageView)
        addSubview(profileNameLabel)
        addSubview(profileStatusLabel)
        addSubview(statusTextField)
        addSubview(setStatusButton)
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            
            profileNameLabel.topAnchor.constraint(equalTo: topAnchor, constant: 27),
            profileNameLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
            profileNameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            profileNameLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 30),
            
            profileStatusLabel.topAnchor.constraint(equalTo: profileNameLabel.bottomAnchor, constant: 10),
            profileStatusLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
            profileStatusLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            profileStatusLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 49),
            
            statusTextField.topAnchor.constraint(equalTo: profileStatusLabel.bottomAnchor, constant: 10),
            statusTextField.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 20),
            statusTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            statusTextField.heightAnchor.constraint(equalToConstant: 40),
            
            setStatusButton.topAnchor.constraint(equalTo: statusTextField.bottomAnchor, constant: 16),
            setStatusButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            setStatusButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            setStatusButton.heightAnchor.constraint(equalToConstant: 50),
            bottomAnchor.constraint(equalTo: setStatusButton.bottomAnchor, constant: 16),
        ])
    }
        
    @objc private func statusTextChanged(_ textField: UITextField) {
        statusText = statusTextField.text!
    }
    
    @objc private func buttonAction() {
        guard statusTextField.text != nil else {
            print("Text the status before press the button")
            return
        }
        profileStatusLabel.text = statusText
        profileStatusLabel.updateConstraintsIfNeeded()
        self.setNeedsUpdateConstraints()
        self.layoutIfNeeded()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .systemGray4
        setupView()
        setupConstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

MyCustomHeader: UITableViewHeaderFooterView

import UIKit

class MyCustomHeader: UITableViewHeaderFooterView {

    var profileHeaderView = ProfileHeaderView()
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        configureContents()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func configureContents() {
        contentView.addSubview(profileHeaderView)
        profileHeaderView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: profileHeaderView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: profileHeaderView.trailingAnchor),
            contentView.widthAnchor.constraint(equalTo: profileHeaderView.widthAnchor),
            contentView.heightAnchor.constraint(equalTo: profileHeaderView.heightAnchor),
            contentView.topAnchor.constraint(equalTo: profileHeaderView.topAnchor),
        ])
    }
}

ProfileViewController

import UIKit

class ProfileViewController: UIViewController, UIGestureRecognizerDelegate {
    
    let postList = [robberyPost, eatingPost, elephantPost, camelPost]

    var profileTableView = UITableView()
    let myCustomHeader = MyCustomHeader()
    
    let headerID = "headerId"
    let headerID2 = "headerId2"
    let cellID = "cellId"
    let collectionCellID = "collectionCellId"
    
...
        
    func setupTableView() {
        profileTableView.contentInsetAdjustmentBehavior = .never
        profileTableView.register(PhotosTableViewCell.self, forCellReuseIdentifier: PhotosTableViewCell.cellID)
        profileTableView.register(PostTableViewCell.self, forCellReuseIdentifier: PostTableViewCell.cellID)
        profileTableView.delegate = self
        profileTableView.dataSource = self
        profileTableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(profileTableView)
    }
    
    func setupMyCustomHeader() {
        profileTableView.register(MyCustomHeader.self, forHeaderFooterViewReuseIdentifier: headerID)
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            profileTableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            profileTableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            profileTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            profileTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        ])
    }
        
    ...

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemGray6
        setupTableView()
        setupMyCustomHeader()
        setupConstraints()
    }
}

extension ProfileViewController: UITableViewDelegate, UITableViewDataSource {
    
    // MARK: - Table view data source
    
...

    // MARK: - Table view delegate
    
...

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        if #available(iOS 15, *) {
            tableView.sectionHeaderTopPadding = 0
        }

        if section == 0 {
            return UITableView.automaticDimension
        } else {
            return 50
        }
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        if section == 0 {

            let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerID) as! MyCustomHeader
            let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(profileImageViewClicked(_ :)))
            tapRecognizer.numberOfTapsRequired = 1
            tapRecognizer.numberOfTouchesRequired = 1
            tapRecognizer.delegate = self
            header.profileHeaderView.profileImageView.isUserInteractionEnabled = true
            header.profileHeaderView.profileImageView.addGestureRecognizer(tapRecognizer)
            return header
        } else {
            let header2 = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerID2) as! OneMoreCustomHeader
            return header2
        }
    }
...
    
}

尝试过的无效代码

在buttonAction中添加了以下代码,但未生效:

profileStatusLabel.updateConstraintsIfNeeded()
self.setNeedsUpdateConstraints()
self.layoutIfNeeded()

解决方案

1. 移除profileStatusLabel的高度约束

删除setupConstraints中这一行约束:

// profileStatusLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 49),

固定的高度约束会限制Label自动扩展,即使设置了numberOfLines = 0也无法正常换行。

2. 修改buttonAction方法

更新文本后,需要通知TableView重新计算Header高度:

@objc private func buttonAction() {
    guard let newStatus = statusTextField.text, !newStatus.isEmpty else {
        print("请先输入状态文本")
        return
    }
    profileStatusLabel.text = newStatus
    // 让Label重新计算自身尺寸
    profileStatusLabel.sizeToFit()
    // 强制更新当前视图布局
    self.layoutIfNeeded()
    
    // 找到所属TableView,触发高度重新计算
    if let tableView = self.superview?.superview as? UITableView {
        tableView.beginUpdates()
        tableView.endUpdates()
    }
}

beginUpdates()和endUpdates()会让TableView重新计算所有自动尺寸的元素,包括Header。

3. 修复statusText初始化问题

原代码的lazy初始化会导致无限递归,修改为:

private var statusText: String = ""

内容的提问来源于stack exchange,提问作者Алан Хайманов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 21:05:19