【问题标题】:Setting tableHeaderView height dynamically动态设置 tableHeaderView 高度
【发布时间】:2016-04-12 05:39:12
【问题描述】:

我的应用程序创建了一个 UITableViewController,其中包含一个自定义的 tableHeaderView,它可能具有任意高度。我一直在努力寻找一种动态设置此标头的方法,因为似乎建议的方法已经缩短了此标头。 我的 UITableViewController 的相关代码:

import UIKit
import SafariServices

class RedditPostViewController: UITableViewController, NetworkCommunication, SubViewLaunchLinkManager {

    //MARK: UITableViewDataSource
    var post: PostData?
    var tree: CommentTree?
    weak var session: Session! = Session.sharedInstance

    override func viewDidLoad() {
        super.viewDidLoad()

        // Get post info from api
        guard let postData = post else { return }

        //Configure comment table
        self.tableView.registerClass(RedditPostCommentTableViewCell.self, forCellReuseIdentifier: "CommentCell")

       let tableHeader = PostView(withPost: postData, inViewController: self)
       let size = tableHeader.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize)
       let height = size.height
       let width = size.width
       tableHeader.frame = CGRectMake(0, 0, width, height)
       self.tableView.tableHeaderView = tableHeader


       session.getRedditPost(postData) { (post) in
           self.post = post?.post
           self.tree = post?.comments
           self.tableView.reloadData()
       }
    }
}

这会导致以下不正确的布局:

如果我将行:tableHeader.frame = CGRectMake(0, 0, width, height) 更改为 tableHeader.frame = CGRectMake(0, 0, width, 1000),tableHeaderView 将正确布局:

我不确定我在这里做错了什么。此外,自定义 UIView 类,如果这有帮助:

import UIKit
import Foundation

protocol SubViewLaunchLinkManager: class {
    func launchLink(sender: UIButton)
}

class PostView: UIView {

    var body: UILabel?
    var post: PostData?
    var domain: UILabel?
    var author: UILabel?
    var selfText: UILabel?
    var numComments: UILabel?

    required init?(coder aDecoder: NSCoder) {
        fatalError("Not implemented yet")
    }

    init(withPost post: PostData, inViewController viewController: SubViewLaunchLinkManager) {
        super.init(frame: CGRectZero)

        self.post = post
        self.backgroundColor = UIColor.lightGrayColor()

        let launchLink = UIButton()
        launchLink.setImage(UIImage(named: "circle-user-7"), forState: .Normal)
        launchLink.addTarget(viewController, action: "launchLink:", forControlEvents: .TouchUpInside)
        self.addSubview(launchLink)

        selfText = UILabel()
        selfText?.backgroundColor = UIColor.whiteColor()
        selfText?.numberOfLines = 0
        selfText?.lineBreakMode = .ByWordWrapping
        selfText!.text = post.selfText
        self.addSubview(selfText!)
        selfText?.sizeToFit()

        //let attributedString = NSAttributedString(string: "Test"/*post.selfTextHtml*/, attributes: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType])
        //selfText.attributedText = attributedString

        body = UILabel()
        body!.text = post.title
        body!.numberOfLines = 0
        body!.lineBreakMode = .ByWordWrapping
        body!.textAlignment = .Justified
        self.addSubview(body!)

        domain = UILabel()
        domain!.text = post.domain
        self.addSubview(domain!)

        author = UILabel()
        author!.text = post.author
        self.addSubview(author!)

        numComments = UILabel()
        numComments!.text = "\(post.numComments)"
        self.addSubview(numComments!)

        body!.translatesAutoresizingMaskIntoConstraints = false
        domain!.translatesAutoresizingMaskIntoConstraints = false
        author!.translatesAutoresizingMaskIntoConstraints = false
        selfText!.translatesAutoresizingMaskIntoConstraints = false
        launchLink.translatesAutoresizingMaskIntoConstraints = false
        numComments!.translatesAutoresizingMaskIntoConstraints = false

        let views: [String: UIView] = ["body": body!, "domain": domain!, "author": author!, "numComments": numComments!, "launchLink": launchLink, "selfText": selfText!]
        //let selfTextSize = selfText?.sizeThatFits((selfText?.frame.size)!)
        //print(selfTextSize)
        //let metrics = ["selfTextHeight": selfTextSize!.height]

                   self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[domain]-|", options: [], metrics: nil, views: views))
       self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[author]-|", options: [], metrics: nil, views: views))
    self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[numComments]-|", options: [], metrics: nil, views: views))
    self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[launchLink]-[numComments]-|", options: [], metrics: nil, views: views))
    self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[body][launchLink]|", options: [], metrics: nil, views: views))
    self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[selfText][launchLink]|", options: [], metrics: nil, views: views))
    self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[domain][author][numComments][launchLink]|", options: [], metrics: nil, views: views))
}

override func layoutSubviews() {
    super.layoutSubviews()
    body?.preferredMaxLayoutWidth = body!.bounds.width
}
}

【问题讨论】:

    标签: ios swift autolayout


    【解决方案1】:

    复制自this post。 (如果您正在寻找更多详细信息,请确保看到它)

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        if let headerView = tableView.tableHeaderView {
    
            let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
            var headerFrame = headerView.frame
    
            //Comparison necessary to avoid infinite loop
            if height != headerFrame.size.height {
                headerFrame.size.height = height
                headerView.frame = headerFrame
                tableView.tableHeaderView = headerView
            }
        }
    }
    

    【讨论】:

    • 没有最后一行 tableView.tableHeaderView = headerView 不起作用。 @TravMatth,你能解释一下为什么我们必须重新分配 tableView.tableHeaderView 吗?
    • 如果您已经尝试了有关此主题的许多帖子中的所有代码示例,但似乎没有一个有效,请检查您的限制!在我的情况下,作为我的标题的 UIView 的尾随空格丢失了。无论您选择哪种代码示例,确保标题视图中包含的控件具有从超级视图的顶部到底部的连续约束链是必不可少的。
    • let height 对我来说永远是0
    • 我尝试使用您的解决方案,它创建了一个带有多行标签的空白区域。 stackoverflow.com/questions/63174149/…
    • 如果有人遇到与@frank61003 相同的问题,可以尝试我的解决方法,我在这篇文章下留下了答案:stackoverflow.com/a/69591753/9182804
    【解决方案2】:

    使用

    确定标头的帧大小
    header.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
    

    当我的标题视图由单个多行标签组成时,上面的答案对我不起作用。将标签的换行模式设置为换行,文本就会被截断:

    相反,对我有用的是使用表格视图的宽度和 0 的高度作为目标大小:

    header.systemLayoutSizeFitting(CGSize(width: tableView.bounds.width, height: 0))
    

    把它们放在一起(我更喜欢使用扩展):

    extension UITableView {
        func updateHeaderViewHeight() {
            if let header = self.tableHeaderView {
                let newSize = header.systemLayoutSizeFitting(CGSize(width: self.bounds.width, height: 0))
                header.frame.size.height = newSize.height
            }
        }
    }
    

    然后这样称呼它:

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        tableView.updateHeaderViewHeight()
    }
    

    【讨论】:

    【解决方案3】:

    OP 答案的更精简版本,具有允许布局自然发生的好处(注意此解决方案使用视图WillLayoutSubviews):

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
    
        if let header = tableView.tableHeaderView {
            let newSize = header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            header.frame.size.height = newSize.height
        }
    }
    

    感谢 TravMatth 的原始答案。

    【讨论】:

    • 这是因为tableView.tableHeaderView.frame.size.height 是只读的。您必须设置框架tableView.tableHeaderView.frame = ...
    • 我也不确定为什么这对你不起作用@Harris。 CGSize 高度/宽度属性不再是只读的,所以这不应该是原因。如果有人提供有关此解决方案的问题的见解,我很乐意帮助诊断,因为我仍然支持它。
    • 工作得很好,比公认的答案更干净
    • 我尝试使用您的解决方案,它创建了一个带有多行标签的空白区域。 stackoverflow.com/questions/63174149/…
    • @frank61003 对于表头视图,您的布局似乎异常复杂,尤其是当您在堆栈视图中使用任何约束时。我建议首先将您的最根堆栈视图(表头视图)从表视图中拉出,并查看自动布局是否可以解决您期望的约束。如果没有,你的问题是你的限制。如果是这样,那么我的解决方案对于您的布局来说太笼统了,您可能需要进行一些编程布局操作。
    【解决方案4】:

    如果上述代码示例的布局仍然存在问题,您可能会在自定义标题视图中禁用 translatesAutoresizingMaskIntoConstraints。在这种情况下,您需要在设置标题的框架后将translatesAutoresizingMaskIntoConstraints 设置回true

    这是我正在使用的代码示例,并且可以在 iOS 11 上正常运行。

    public override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        guard let headerView = tableView.tableHeaderView else { return }
    
        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var headerFrame = headerView.frame
    
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
    
            if #available(iOS 9.0, *) {
                tableView.layoutIfNeeded()
            }
        }
    
        headerView.translatesAutoresizingMaskIntoConstraints = true
    }
    

    【讨论】:

    • 这是一个非常有价值的注释,尤其是如果您在代码中构建 ui 并几乎一直重置该标志。如果它解释了为什么即使translatesAutoresizingMaskIntoConstraints=false UITableView 无法为其页眉/页脚视图构建所需的约束,答案将是非常宝贵的
    【解决方案5】:

    基于@TravMatth 和@NSExceptional's 答案:

    Dynamic TableView Header,多行文字(有无)

    我的解决办法是:

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        if let footView = tableView.tableFooterView {
            let newSize = footView.systemLayoutSizeFitting(CGSize(width: self.view.bounds.width, height: 0))
            if newSize.height != footView.frame.size.height {
                footView.frame.size.height = newSize.height
                tableView.tableFooterView = footView
            }
        }
    }
    

    tableView.tableFooterView = footView 以确保您的 tableview Header 或 Footer 已更新。 而if newSize.height != footView.frame.size.height帮助你不要被多次调用这个方法

    【讨论】:

    • 不知道为什么这对我有用,但 UIView.layoutFittingCompressedSize 没有。谢谢。
    【解决方案6】:

    我长期使用公认的答案,它一直对我有用,直到今天,当我在复杂的表格标题视图中使用多行标签时,我遇到了 @frank61003 遇到的同样问题:

    它创建一个带有多行标签的空白区域。

    所以在我的例子中,我的标签周围有很大的垂直边距。如果标签文本只有 1 行,那么一切都很好。 此问题仅在标签包含多行文本时发生。

    我不知道导致此问题的确切原因,但我挖掘了一段时间并找到了解决此问题的解决方法,因此我想在此处留下参考,以防有人遇到同样的问题。

    可选的第一步,确保您的多行标签在表格标题视图中具有最低的Content Hugging Priority,以便它可以自动增加以适合其文本。

    然后,将此计算标签高度方法添加到您的视图控制器

    private func calculateHeightForString(_ string: String) -> CGFloat {
            let yourLabelWidth = UIScreen.main.bounds.width - 20
            let constraintRect = CGSize(width: yourLabelWidth, height: CGFloat.greatestFiniteMagnitude)
            let rect = string.boundingRect(with: constraintRect,
                                           options: .usesLineFragmentOrigin,
                                           // use your label's font
                                           attributes: [.font: descriptionLabel.font!],
                                           context: nil)
            
            return rect.height + 6  // give a little extra arbitrary space (6), remove it if you don't need
        }
    

    并使用上面的方法在viewDidLoad中配置你的多行标签

    let description = "Long long long ... text"
    descriptionLabel.text = description
    // manually calculate multiple lines label height and add a constraint to avoid extra space bug
    descriptionLabel.heightAnchor.constraint(equalToConstant: calculateHeightForString(description)).isActive = true
    

    这解决了我的问题,希望它也对你有用。

    【讨论】:

      【解决方案7】:
      override func viewDidLayoutSubviews() {
              super.viewDidLayoutSubviews()
              if let headerView = self.tableView.tableHeaderView {
                  let headerViewFrame = headerView.frame
                  let height = headerView.systemLayoutSizeFitting(headerViewFrame.size, withHorizontalFittingPriority: UILayoutPriority.defaultHigh, verticalFittingPriority: UILayoutPriority.defaultLow).height
                  var headerFrame = headerView.frame
                  if height != headerFrame.size.height {
                      headerFrame.size.height = height
                      headerView.frame = headerFrame
                      self.tableView.tableHeaderView = headerView
                  }
              }
          }
      

      使用水平或垂直拟合时计算标签大小的问题

      【讨论】:

        【解决方案8】:
        **My Working Solution is:
        Add this function in viewcontroller**
        public override func viewDidLayoutSubviews() {
                super.viewDidLayoutSubviews()
                guard let headerView = myTableView.tableHeaderView else { return }
                let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
                var headerFrame = headerView.frame
        
                if height != headerFrame.size.height {
                    headerFrame.size.height = height
                    headerView.frame = headerFrame
                    myTableView.tableHeaderView = headerView
        
                    if #available(iOS 9.0, *) {
                        myTableView.layoutIfNeeded()
                    }
                }
        
                headerView.translatesAutoresizingMaskIntoConstraints = true
        } 
        
        
         **Add one line in header's view class.**
        override func layoutSubviews() {
                super.layoutSubviews()
                bookingLabel.preferredMaxLayoutWidth = bookingLabel.bounds.width
        
            }
        

        【讨论】:

          【解决方案9】:

          仅实现这两个 UITableView 委托方法对我有用:

          -(CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
          {
              return 100;
          }
          
          -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
          {
              return UITableViewAutomaticDimension;
          }
          

          【讨论】:

          • Tableview Header 和 headerForSection 不同。阅读问题
          猜你喜欢
          • 2014-01-25
          • 2011-06-04
          • 2018-06-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-06-15
          • 2013-10-07
          相关资源
          最近更新 更多