Установка tableHeaderView высоты динамически
Мое приложение создает 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
}
}
12 ответов
Скопировано из этого поста. (Убедитесь, что вы видите это, если вы ищете более подробную информацию)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).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
}
}
}
Определение размера кадра заголовка с помощью
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()
}
Более сжатая версия ответа OP, с преимуществом, позволяющим разметку происходить естественным образом (обратите внимание, что это решение использует представлениеWill LayoutSubviews):
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let header = tableView.tableHeaderView {
let newSize = header.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
header.frame.size.height = newSize.height
}
}
Как видно из вышесказанного, у Apple нет оправдания не поддерживать автоматическую высоту верхнего / нижнего колонтитула табличного представления, как это теперь происходит с ячейками. Разочарование.
Спасибо TravMatth за оригинальный ответ.
Если у вас все еще есть проблемы с компоновкой с приведенным выше примером кода, есть небольшая вероятность, что вы отключили 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
}
На основе TravMatth @ TravMatth и @ NSExceptional:
Для заголовка Dynamic TableView с несколькими строками текста (независимо от того, есть они или нет)
Мое решение:
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 обновлен. А также
if newSize.height != footView.frame.size.height
помогает не называть этот метод много раз
Я использую принятый ответ в течение долгого времени, и он всегда работал у меня, до сегодняшнего дня, когда я использовал метку из нескольких строк в сложном представлении заголовка таблицы, я столкнулся с той же проблемой, что и @frank61003:
он создает пустую область с меткой из нескольких строк.
В моем случае вокруг лейбла были большие вертикальные поля. Если текст метки занимает всего 1 строку, то все в порядке. Эта проблема возникает только в том случае, если на этикетке есть несколько строк текста.
Я не знаю точной причины, вызывающей это, но я некоторое время копал и нашел обходной путь для решения проблемы, поэтому я хочу оставить здесь ссылку на случай, если кто-то столкнется с той же проблемой.
Необязательный первый шаг: убедитесь, что ваш многострочный ярлык имеет самый низкий приоритет обработки содержимого в представлении заголовка таблицы, чтобы он мог автоматически увеличиваться, чтобы соответствовать его тексту.
Затем добавьте этот метод вычисления высоты метки в свой контроллер представления.
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
Это решило мою проблему, надеюсь, это сработает и для вас.
Просто реализация этих двух методов делегата UITableView работала для меня:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
{
return 100;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return UITableViewAutomaticDimension;
}
Я создал подкласс, чтобы хранить макет внутри самого представления.
/// The view gets automatically resized based on it's constraints to fit into parent `UITableView`. Assign to `tableHeaderView`, do not set constraints between `DynamicHeaderView` and the `UITableView`.
class DynamicTableHeaderView: UIView {
override func layoutSubviews() {
if let tableView = superview as? UITableView {
let targetSize = CGSize(width: tableView.contentSize.width, height: UIView.layoutFittingCompressedSize.height)
let fittingSize = self.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
if frame.size != fittingSize {
self.frame.size = fittingSize
tableView.tableHeaderView = self // Forces the tableView to redraw
}
}
super.layoutSubviews()
}
}
Вот так «просто» в 2023 году.
Обратите внимание на критический совет от @MrKew.
Перетащите простой UIView в положение заголовка таблицы. Не создайте подкласс и не ссылайтесь на него.
Внутри этого представления добавьте все, что хотите.
Очевидно, соедините вертикальные ограничения сверху вниз. (Если вы не совсем знакомы с этим процессом, обратитесь к базовым руководствам.)
Обратите внимание: вам не нужно и не следует уменьшать/увеличивать какой-либо из трех верхних/хвостовых приоритетов. Обратите внимание, что вертикальное представление стека здесь распространено и работает отлично.
Волшебный код от @MrKew:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let thv = table.tableHeaderView {
var f = thv.frame
let targetSize = CGSize(
width: table.contentSize.width,
height: UIView.layoutFittingCompressedSize.height)
let fittingSize = thv.systemLayoutSizeFitting(
targetSize,
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel)
f.size = fittingSize
thv.frame = f
table.tableHeaderView = thv
}
}
Это конец.
Другого способа сделать это нет.
Обратите внимание, что ответ @MrKew дает ключевой элемент, необходимый в последних версиях UIKit.
(Обратите внимание, что вам действительно нужно указать ширину (т. е. просто ширину таблицы), а не одну из
Если все ограничения добавлены, это будет работать:
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
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
}
}
}
Проблема с расчетом размера этикетки при использовании горизонтальной или вертикальной установки
**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
}