簡體   English   中英

iOS - 自定義視圖:在 layoutSubviews() 中更新后忽略固有內容大小

[英]iOS - Custom view: Intrinsic content size ignored after updated in layoutSubviews()

我有一個表格視圖單元格,其中包含其他視圖中的自定義視圖,並使用了自動布局。

我的自定義視圖的目的是在行中布局其子視圖,如果當前子視圖不適合當前行,則拆分為新行。 它有點像多線 label 但有視圖。 我通過精確定位而不是自動布局來實現這一點。

由於我只知道layoutSubviews()中視圖的寬度,因此我需要計算那里的確切位置和行數。 效果很好,但是由於缺少intrinsicContentSize ,我的視圖的框架(零)不匹配。

因此,如果自上次布局通過后我的高度發生了變化,我在計算的末尾添加了一個檢查。 如果是這樣,我會更新在我的intrinsicContentSize屬性中使用的 height 屬性並調用invalidateIntrinsicContentSize()

我觀察到最初layoutSubviews()被調用了兩次。 第一遍效果很好,即使單元格的寬度小於應有的寬度,也會考慮到intrinsicContentSize 第二遍使用實際寬度並更新intrinsicContentSize 但是父級(tableview 單元格中的 contentView)忽略了這個新的intrinsicContentSize

所以基本上結果是子視圖的布局和繪制正確,但自定義視圖的框架沒有在父級中更新/使用。

問題:有沒有辦法通知父級有關固有大小的變化或指定的地方來更新layoutSubviews()中計算的大小,以便在父級中使用新的大小?

編輯:

這是我的自定義視圖中的代碼。

僅供參考:8 只是兩個子視圖之間的垂直和水平空間

class WrapView : UIView {

    var height = CGFloat.zero

    override var intrinsicContentSize: CGSize {
        CGSize(width: UIView.noIntrinsicMetric, height: height)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        guard frame.size.width != .zero else { return }

        // Make subviews calc their size
        subviews.forEach { $0.sizeToFit() }

        // Check if there is enough space in a row to fit at least one view
        guard subviews.map({ $0.frame.size.width }).max() ?? .zero <= frame.size.width else { return }

        let width = frame.size.width
        var row = [UIView]()

        // rem is the remaining space in the current row
        var rem = width
        var y: CGFloat = .zero
        var i = 0

        while i < subviews.count {
            let view = subviews[i]
            let sizeNeeded = view.frame.size.width + (row.isEmpty ? 0 : 8)
            let last = i == subviews.count - 1
            let fit = rem >= sizeNeeded

            if fit {
                row.append(view)
                rem -= sizeNeeded
                i += 1
                guard last else { continue }
            }

            let rowWidth = row.map { $0.frame.size.width + 8 }.reduce(-8, +)
            var x = (width - rowWidth) * 0.5

            for vw in row {
                vw.frame.origin = CGPoint(x: x, y: y)
                x += vw.frame.width + 8
            }

            y += row.map { $0.frame.size.height }.max()! + 8
            rem = width
            row = []
        }

        if height != y - 8 {
            height = y - 8
            invalidateIntrinsicContentSize()
        }
    }
}

經過大量的嘗試和研究,我終於解決了這個錯誤。

正如@DonMag 在評論中提到的那樣,直到新的布局通過后才能識別單元格的新大小。 這可以通過在屏幕外滾動顯示正確布局的單元格並返回來驗證。 不幸的是,觸發新通道比預期的要難,因為.beginUpdates() + .endUpdates()沒有完成這項工作。

無論如何,我沒有找到觸發它的方法,但我按照此答案中描述的說明進行操作。 尤其是帶有用於高度計算的原型單元格的部分提供了一個可以在tableview(heightForRowAt:)中返回的值。

Swift 5:

這是用於計算的代碼:

let fitSize = CGSize(width: view.frame.size.width, height: .zero)
/* At this point populate the cell with the exact same data as the actual cell in the tableview */
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
cell.bounds = CGRect(x: .zero, y: .zero, width: view.frame.size.width, height: cell.bounds.height)
cell.setNeedsLayout()
cell.layoutIfNeeded()
height = headerCell.contentView.systemLayoutSizeFitting(fitSize).height + 1

該值僅計算一次,並且在我的情況下緩存的大小不再改變。

然后可以在委托中返回該值:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    indexPath.row == 0 ? height : UITableView.automaticDimension
}

我只用於第一個單元,因為它是我的 header 單元,並且只有一個部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM