Có vẻ như những gì bạn đang yêu cầu là một cách sử dụng UICollectionView để tạo ra một bố cục giống như UITableView. Nếu đó thực sự là những gì bạn muốn, cách phù hợp để làm điều này là với một lớp con UICollectionViewLayout tùy chỉnh (có thể giống như SBTableLayout ).
Mặt khác, nếu bạn thực sự hỏi liệu có cách nào hợp lý để làm điều này với UICollectionViewFlowLayout mặc định, thì tôi tin rằng không có cách nào. Ngay cả với các ô tự định cỡ của iOS8, nó không đơn giản. Vấn đề cơ bản, như bạn nói, là bộ máy của bố cục luồng không có cách nào để sửa một thứ nguyên và để thứ nguyên khác phản hồi. (Ngoài ra, ngay cả khi bạn có thể, sẽ có thêm sự phức tạp xung quanh việc cần hai lần chuyển bố cục để định kích thước các nhãn nhiều dòng. Điều này có thể không phù hợp với cách các ô tự định kích thước muốn tính toán tất cả kích thước thông qua một lệnh gọi tới systemLayoutSizeFittingSize.)
Tuy nhiên, nếu bạn vẫn muốn tạo một bố cục giống như chế độ xem bảng với bố cục dòng, với các ô xác định kích thước riêng của chúng và phản hồi tự nhiên với chiều rộng của chế độ xem bộ sưu tập, tất nhiên là có thể. Vẫn còn đường lộn xộn. Tôi đã làm điều đó với một "ô định cỡ", tức là, một UICollectionViewCell không hiển thị mà bộ điều khiển chỉ giữ lại để tính toán kích thước ô.
Có hai phần trong cách tiếp cận này. Phần đầu tiên dành cho đại biểu chế độ xem bộ sưu tập để tính toán kích thước ô chính xác, bằng cách lấy chiều rộng của chế độ xem bộ sưu tập và sử dụng ô định cỡ để tính toán chiều cao của ô.
Trong UICollectionViewDelegateFlowLayout của bạn, bạn triển khai một phương pháp như sau:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
Điều này sẽ làm cho chế độ xem bộ sưu tập đặt chiều rộng của ô dựa trên chế độ xem bộ sưu tập bao quanh, nhưng sau đó yêu cầu ô định cỡ tính toán chiều cao.
Phần thứ hai là để ô định cỡ sử dụng các ràng buộc AL của chính nó để tính chiều cao. Điều này có thể khó hơn mức cần thiết, vì cách thức hiệu quả của UILabel nhiều dòng đòi hỏi một quy trình bố trí hai giai đoạn. Công việc được thực hiện theo phương thức preferredLayoutSizeFittingSize
như sau:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(Mã này được điều chỉnh từ mã mẫu của Apple từ mã mẫu của phiên WWDC2014 trên "Chế độ xem bộ sưu tập nâng cao".)
Một vài điểm cần lưu ý. Nó sử dụng layoutIfNeeded () để buộc bố trí toàn bộ ô, nhằm tính toán và thiết lập chiều rộng của nhãn. Nhưng điều đó vẫn chưa đủ. Tôi tin rằng bạn cũng cần thiết lập preferredMaxLayoutWidth
để nhãn sẽ sử dụng chiều rộng đó với Bố cục Tự động. Và chỉ khi đó, bạn mới có thể sử dụng systemLayoutSizeFittingSize
để yêu cầu ô tính toán chiều cao của nó trong khi tính đến nhãn.
Tôi có thích cách tiếp cận này không? Không!! Nó cảm thấy quá phức tạp và nó phải bố trí hai lần. Nhưng miễn là hiệu suất không trở thành vấn đề, tôi thà thực hiện bố cục hai lần trong thời gian chạy hơn là phải xác định nó hai lần trong mã, đây dường như là giải pháp thay thế duy nhất.
Hy vọng của tôi là cuối cùng các tế bào tự định cỡ sẽ hoạt động khác đi và điều này sẽ trở nên đơn giản hơn rất nhiều.
Dự án ví dụ hiển thị nó tại nơi làm việc.
Nhưng tại sao không chỉ sử dụng các tế bào tự định cỡ?
Về lý thuyết, các cơ sở mới của iOS8 cho "tế bào tự định cỡ" sẽ khiến điều này trở nên không cần thiết. Nếu bạn đã xác định một ô bằng Bố cục Tự động (AL), thì chế độ xem bộ sưu tập phải đủ thông minh để cho phép nó tự kích thước và sắp xếp chính xác. Trong thực tế, tôi chưa thấy bất kỳ ví dụ nào làm cho điều này hoạt động với các nhãn nhiều dòng. Tôi nghĩ điều này một phần là do cơ chế tế bào tự định cỡ vẫn còn nhiều lỗi.
Nhưng tôi dám cá rằng đó chủ yếu là do sự phức tạp thông thường của Bố cục Tự động và nhãn, đó là Nhãn dán yêu cầu một quy trình bố trí cơ bản gồm hai bước. Tôi không rõ làm thế nào bạn có thể thực hiện cả hai bước với các ô tự định cỡ.
Và như tôi đã nói, đây thực sự là một công việc cho một bố cục khác. Nó là một phần bản chất của bố cục luồng là nó định vị những thứ có kích thước, thay vì cố định chiều rộng và cho phép chúng chọn chiều cao của chúng.
Và những gì về ưu tiênLayoutAttributesFittingAttributes:?
Các preferredLayoutAttributesFittingAttributes:
phương pháp là một cá trích đỏ, tôi nghĩ. Điều đó chỉ được sử dụng với cơ chế tế bào tự định cỡ mới. Vì vậy, đây không phải là câu trả lời miễn là cơ chế đó không đáng tin cậy.
Và có gì với systemlayoutSizeFittingSize:?
Bạn nói đúng, tài liệu khó hiểu.
Các tài liệu trên systemLayoutSizeFittingSize:
và systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
cả hai đều gợi ý rằng bạn chỉ nên vượt qua UILayoutFittingCompressedSize
và UILayoutFittingExpandedSize
như targetSize
. Tuy nhiên, bản thân chữ ký phương thức, chú thích tiêu đề và hành vi của các hàm cho thấy rằng chúng đang phản hồi với giá trị chính xác của targetSize
tham số.
Trên thực tế, nếu bạn đặt UICollectionViewFlowLayoutDelegate.estimatedItemSize
, để kích hoạt cơ chế ô tự định cỡ mới, giá trị đó dường như được chuyển vào dưới dạng targetSize. Và UILabel.systemLayoutSizeFittingSize
dường như trả về các giá trị giống hệt như UILabel.sizeThatFits
. Điều này thật đáng ngờ, vì đối số tới systemLayoutSizeFittingSize
được cho là một mục tiêu thô và đối số tới sizeThatFits:
được cho là kích thước vòng tròn tối đa.
Nhiêu tai nguyên hơn
Mặc dù thật đáng buồn khi nghĩ rằng một yêu cầu thường xuyên như vậy cần phải có "tài nguyên nghiên cứu", tôi nghĩ nó đúng. Các ví dụ và thảo luận hay là: