Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/119.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 如何覆盖具有灵活高度和固定宽度的视图的intrinsictContentSize?_Ios_Uitableview_Autolayout - Fatal编程技术网

Ios 如何覆盖具有灵活高度和固定宽度的视图的intrinsictContentSize?

Ios 如何覆盖具有灵活高度和固定宽度的视图的intrinsictContentSize?,ios,uitableview,autolayout,Ios,Uitableview,Autolayout,我试图在tableView中显示自定义视图,并让iOS使用UITableViewAutomaticDimension计算每行的高度。不幸的是,我的手机尺寸不合适(高度不好) 我的自定义视图定义如下(出于许多原因,我不在此零件上使用AutoLayout,我不想使用它): 现在,我将此自定义视图包装在UITableViewCell中,以便在UITableView中使用它。这里我使用的是自动布局: class MyCell : UITableViewCell { var myView: MyV

我试图在tableView中显示自定义视图,并让iOS使用
UITableViewAutomaticDimension
计算每行的高度。不幸的是,我的手机尺寸不合适(高度不好)

我的自定义视图定义如下(出于许多原因,我不在此零件上使用AutoLayout,我不想使用它):

现在,我将此自定义视图包装在
UITableViewCell
中,以便在
UITableView
中使用它。这里我使用的是自动布局:

class MyCell : UITableViewCell {
    var myView: MyView!

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupCell()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupCell()
    }

    func setupCell() {
        myView = MyView()
        contentView.addSubview(myView)
        myView.snp.makeConstraints { make in
            make.edges.equalTo(contentView)
        }
    }
}
现在,使用以下viewController,我显示一个带有大文本的单元格,以查看其高度是否自动计算:

class ViewController: UIViewController {
    var tableView: UITableView!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        tableView = UITableView()
        view.addSubview(tableView)
        tableView.snp.makeConstraints { make in
            make.edges.equalTo(view)
        }
        tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 30
        tableView.dataSource = self
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyCell
        cell.myView.setText(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur")
        return cell
    }
}
不幸的是,单元格的高度等于21。即使最后一次调用
MyView
时返回的高度是143,也是如此。以下是结果的屏幕截图:

如果回收,单元格大小正确(我将单元格滚动到屏幕外):

我知道
MyView
intrinsictContentSize
使用了它的框架,它不应该这样做,但我不知道如何设计一个UIView子类,它在给定固定宽度的情况下具有灵活的高度。考虑到我在MyView的
layoutSubviews()
中调用了
invalidateIntrinsicContentSize()
,我认为它会起作用


如何更改此代码,使其从一开始就具有良好的单元格高度?

这里发生了一些不同的事情,使您具有这种行为

您会注意到,如果在intrinsicContentSize内部打印height值,则首先会以零宽度调用它,因为视图还没有大小。然后在LayoutSubView中强制使用标签的框架。此时,单元格已布局,即使您使用的是invalidateIntrinsicContentSize,也不会立即重新绘制!当您将单元格从视图中滚出并重新滚入时,它将在该点重新绘制

强力解决方案是计算LayoutSubView内部标签的正确高度。或者,在设置稍后应用的文本字符串时,计算、保存并存储该字符串。我仍然倾向于首先尝试基于约束的解决方案,这对我来说已经很好了,即使有多个可变高度标签。一个小技巧是根据superview的宽度确定宽度-它已经有了一个宽度:

override var intrinsicContentSize: CGSize {
    guard
        let text = label.text,
        let parent = superview
    else { return .zero }
    let width = parent.bounds.width
    let height = text.heightWithConstrainedWidth(width: width, font: label.font)
    return CGSize(width: width, height: height)
}

此外,您正在使用灵活的高度,并将MyView的所有边约束到内容视图。通常,您会将顶部/前导/尾随从MyView约束到内容视图,然后将内容视图的底部约束到MyView。这样,内容视图的高度将始终调整为MyView的高度。

文档表明,覆盖
intrinsicContentSize
可能不是实现您想要实现的目标的最佳方式。正如您所观察到的,从类参考中:

设置此属性允许自定义视图根据其内容与布局系统通信它希望的大小。这种固有的大小必须独立于内容框架,因为例如,无法根据更改的高度将更改的宽度动态传达给布局系统

MyView的
边界在其
intrinsicContentSize
被请求之后才会设置,并且在自动布局上下文中,不保证在调用
layoutSubviews()
之前的任何时间都已设置。有一种方法可以解决这个问题,并将宽度传递给自定义视图,以便在
intrinsicContentSize
中使用,但我已将其作为第二个选项包含在本答案中

选项1:通过覆盖
sizeThatFits(:)

首先,在没有自动布局的情况下,将MyView及其标签子视图配置为适当调整大小:

//  MyView.swift

func setupView() {
    label = UILabel()
    label.numberOfLines = 0
    label.autoresizingMask = [.flexibleWidth] // New
    addSubview(label)
}

override func layoutSubviews() {
    super.layoutSubviews()
    label.sizeToFit() // New
}

override func sizeThatFits(_ size: CGSize) -> CGSize {
    return label.sizeThatFits(size) // New
}
MyView现在将根据其标签子视图的大小调整自身大小,标签的大小将根据其superview的宽度(因为其
.flexibleWidth
自动调整大小掩码)及其文本内容(因为
numberOfLines
设置为0)调整。如果MyView有其他子视图,则需要计算并返回总大小,单位为
sizehattfits(u:)

其次,我们希望UITableView能够根据上面的手动布局计算其单元格和自定义子视图的高度。自调整单元格大小时,UITableView会调用每个单元格,而每个单元格又会调用该单元格。(见附件。)

传递给这些方法的目标尺寸或管件尺寸是高度为零的表视图的宽度。水平配件的优先级为所需的
UILayoutPriorityRequired
。通过覆盖自定义单元子类中的其中一个方法(前者用于自动布局,后者用于手动布局),并使用传入的大小宽度计算并返回单元高度,可以控制自调整单元大小的过程

在这种情况下,单元格的大小为
myView

//  MyCell.swift

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
    return myView.sizeThatFits(targetSize)
}
你完成了

选项2:通过覆盖
intrinsicContentSize

由于MyView的大小取决于其标签的大小,因此第一步是确保标签的位置和大小正确。您已经这样做了:

//  MyView.swift

override func layoutSubviews() {
    super.layoutSubviews()
    label.frame = bounds
}
第二步是定义MyView的固有内容大小。在这种情况下,没有固有宽度(因为该尺寸将完全由单元或其他superview确定),固有高度是标签的固有高度:

//  MyView.swift

override var intrinsicContentSize: CGSize {
    return CGSize(width: UIViewNoIntrinsicMetric, height: label.intrinsicContentSize.height)
}
由于标签是多行标签,因此在没有最大宽度的情况下无法确定其固有高度。默认情况下,如果没有最大宽度,UILabel将返回适合单行标签的内部内容大小,这不是我们想要的

为com提供多行标签最大宽度的唯一有文档记录的方法
//  MyView.swift

override var intrinsicContentSize: CGSize {
    return CGSize(width: UIViewNoIntrinsicMetric, height: label.intrinsicContentSize.height)
}
//  MyCell.swift

override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
    myView.label.preferredMaxLayoutWidth = targetSize.width
    myView.invalidateIntrinsicContentSize()

    return super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
}
DispatchQueue.main.async { 
    self.tableView.reloadData()
}
func setText(text: String) {
    label.text = text
    invalidateIntrinsicContentSize() // invalidate before layout
    setNeedsLayout()
}
override var intrinsicContentSize: CGSize {
    return CGSize(width: bounds.width, height: label.sizeThatFits(CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)))
}