TL; DR: Không thích đọc sách? Chuyển thẳng đến các dự án mẫu trên GitHub:
Mô tả khái niệm
2 bước đầu tiên bên dưới được áp dụng cho dù bạn đang phát triển phiên bản iOS nào.
1. Thiết lập và thêm các ràng buộc
Trong UITableViewCell
lớp con của bạn , hãy thêm các ràng buộc để các khung nhìn của ô có các cạnh được ghim vào các cạnh của contentView của ô (quan trọng nhất là các cạnh trên cùng VÀ dưới cùng). LƯU Ý: không ghim các cuộc phỏng vấn vào chính tế bào; Chỉ đến tế bào contentView
! Hãy để kích thước nội dung nội tại của các lần xem trước này điều khiển chiều cao của chế độ xem nội dung của ô xem bảng bằng cách đảm bảo rằng các ràng buộc nén nội dung và các ràng buộc ôm nội dung theo chiều dọc cho mỗi lần xem phụ không bị ghi đè bởi các ràng buộc ưu tiên cao hơn mà bạn đã thêm. ( Hả? Bấm vào đây. )
Hãy nhớ rằng, ý tưởng là để các cuộc phỏng vấn của ô được kết nối theo chiều dọc với chế độ xem nội dung của ô để chúng có thể "gây áp lực" và làm cho chế độ xem nội dung mở rộng để phù hợp với chúng. Sử dụng một ô mẫu với một vài lần xem xét, đây là một minh họa trực quan về những gì một số (không phải tất cả!) Các ràng buộc của bạn sẽ cần trông giống như:
Bạn có thể tưởng tượng rằng khi thêm nhiều văn bản vào nhãn cơ thể nhiều dòng trong ô ví dụ ở trên, nó sẽ cần phát triển theo chiều dọc để phù hợp với văn bản, điều này sẽ buộc tế bào tăng trưởng chiều cao một cách hiệu quả. (Tất nhiên, bạn cần phải có các ràng buộc ngay để điều này hoạt động chính xác!)
Làm cho các ràng buộc của bạn đúng chắc chắn là phần khó nhất và quan trọng nhất để có được độ cao của ô động làm việc với Bố cục tự động. Nếu bạn mắc lỗi ở đây, nó có thể ngăn mọi thứ khác hoạt động - vì vậy hãy dành thời gian của bạn! Tôi khuyên bạn nên thiết lập các ràng buộc của mình trong mã bởi vì bạn biết chính xác các ràng buộc nào đang được thêm vào ở đâu và việc gỡ lỗi sẽ dễ dàng hơn rất nhiều khi gặp sự cố. Việc thêm các ràng buộc trong mã có thể dễ dàng và mạnh hơn đáng kể so với Trình tạo giao diện bằng cách sử dụng các neo bố cục hoặc một trong các API nguồn mở tuyệt vời có sẵn trên GitHub.
- Nếu bạn đang thêm các ràng buộc trong mã, bạn nên thực hiện việc này một lần từ trong
updateConstraints
phương thức của lớp con UITableViewCell của bạn. Lưu ý rằng updateConstraints
có thể được gọi nhiều lần, do đó, để tránh thêm các ràng buộc giống nhau nhiều lần, hãy đảm bảo bọc mã bổ sung ràng buộc của bạn updateConstraints
trong một kiểm tra cho một thuộc tính boolean như didSetupConstraints
(mà bạn đặt thành CÓ sau khi bạn chạy ràng buộc của mình -đánh dấu mã một lần). Mặt khác, nếu bạn có mã cập nhật các ràng buộc hiện có (chẳng hạn như điều chỉnh thuộc constant
tính trên một số ràng buộc), hãy đặt mã này vào updateConstraints
nhưng bên ngoài kiểm tra didSetupConstraints
để nó có thể chạy mỗi khi phương thức được gọi.
2. Xác định số nhận dạng tái sử dụng ô xem bảng duy nhất
Đối với mỗi bộ ràng buộc duy nhất trong ô, hãy sử dụng mã định danh tái sử dụng ô duy nhất. Nói cách khác, nếu các ô của bạn có nhiều hơn một bố cục duy nhất, mỗi bố cục duy nhất sẽ nhận được mã định danh tái sử dụng riêng. (Một gợi ý hay mà bạn cần sử dụng một mã định danh tái sử dụng mới là khi biến thể di động của bạn có số lần xem khác nhau hoặc các cuộc phỏng vấn được sắp xếp theo một kiểu riêng biệt.)
Ví dụ: nếu bạn đang hiển thị một thông báo email trong mỗi ô, bạn có thể có 4 bố cục duy nhất: tin nhắn chỉ có chủ đề, tin nhắn có chủ đề và nội dung, tin nhắn có chủ đề và tệp đính kèm ảnh và tin nhắn có chủ đề, cơ thể, và đính kèm hình ảnh. Mỗi bố cục có các ràng buộc hoàn toàn khác nhau cần thiết để đạt được nó, vì vậy một khi ô được khởi tạo và các ràng buộc được thêm vào một trong các loại ô này, ô sẽ nhận được một định danh tái sử dụng duy nhất cụ thể cho loại ô đó. Điều này có nghĩa là khi bạn hủy bỏ một ô để sử dụng lại, các ràng buộc đã được thêm vào và sẵn sàng cho loại ô đó.
Lưu ý rằng do sự khác biệt về kích thước nội dung bên trong, các ô có cùng ràng buộc (loại) vẫn có thể có độ cao khác nhau! Đừng nhầm lẫn giữa các bố cục khác nhau (các ràng buộc khác nhau) với các khung xem được tính toán khác nhau (được giải quyết từ các ràng buộc giống hệt nhau) do các kích thước nội dung khác nhau.
- Không thêm các ô có các nhóm ràng buộc hoàn toàn khác nhau vào cùng một nhóm sử dụng lại (nghĩa là sử dụng cùng một mã định danh tái sử dụng) và sau đó cố gắng loại bỏ các ràng buộc cũ và thiết lập các ràng buộc mới từ đầu sau mỗi lần xử lý. Công cụ Bố cục tự động bên trong không được thiết kế để xử lý các thay đổi quy mô lớn trong các ràng buộc và bạn sẽ thấy các vấn đề hiệu suất lớn.
Dành cho iOS 8 - Các ô tự kích thước
3. Kích hoạt ước tính chiều cao hàng
Để bật các ô xem kích thước bảng tự kích thước, bạn phải đặt thuộc tính rowHeight của chế độ xem bảng thành UITableViewAutomaticDimension. Bạn cũng phải gán một giá trị cho thuộc tính ước tính. Ngay sau khi cả hai thuộc tính này được đặt, hệ thống sẽ sử dụng Bố cục tự động để tính chiều cao thực tế của hàng
Apple: Làm việc với các ô xem bảng tự kích thước
Với iOS 8, Apple đã nội bộ hóa nhiều công việc mà trước đây bạn phải triển khai trước iOS 8. Để cho phép cơ chế tế bào tự kích hoạt hoạt động, trước tiên bạn phải đặt thuộc rowHeight
tính trên chế độ xem bảng thành hằng số UITableViewAutomaticDimension
. Sau đó, bạn chỉ cần kích hoạt ước tính chiều cao hàng bằng cách đặt thuộc tính của chế độ xem bảng estimatedRowHeight
thành giá trị khác không, ví dụ:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
Điều này không cung cấp cho chế độ xem bảng với ước tính tạm thời / giữ chỗ cho chiều cao hàng của các ô chưa được hiển thị trên màn hình. Sau đó, khi các ô này sắp cuộn trên màn hình, chiều cao hàng thực tế sẽ được tính. Để xác định chiều cao thực tế cho mỗi hàng, chế độ xem bảng sẽ tự động hỏi mỗi ô về chiều cao contentView
cần dựa trên chiều rộng cố định đã biết của chế độ xem nội dung (dựa trên chiều rộng của chế độ xem bảng, trừ đi mọi thứ bổ sung như chỉ mục phần hoặc chế độ xem phụ kiện) và các ràng buộc bố cục tự động mà bạn đã thêm vào chế độ xem nội dung và các nội dung của ô. Khi chiều cao ô thực tế này đã được xác định, chiều cao ước tính cũ cho hàng được cập nhật với chiều cao thực tế mới (và mọi điều chỉnh đối với contentSize / contentPackset của chế độ xem bảng được thực hiện khi cần thiết cho bạn).
Nói chung, ước tính bạn cung cấp không nhất thiết phải chính xác - nó chỉ được sử dụng để định cỡ chính xác chỉ báo cuộn trong chế độ xem bảng và chế độ xem bảng thực hiện tốt việc điều chỉnh chỉ báo cuộn cho các ước tính không chính xác như bạn di chuyển các ô trên màn hình. Bạn nên đặt thuộc estimatedRowHeight
tính trên chế độ xem bảng (bằng viewDidLoad
hoặc tương tự) thành giá trị không đổi là chiều cao hàng "trung bình". Chỉ khi chiều cao hàng của bạn có độ biến thiên cực lớn (ví dụ: khác nhau theo độ lớn) và bạn nhận thấy chỉ báo cuộn "nhảy" khi bạn cuộn, bạn nên thực hiện tableView:estimatedHeightForRowAtIndexPath:
phép tính tối thiểu cần thiết để trả về ước tính chính xác hơn cho mỗi hàng.
Để được hỗ trợ iOS 7 (tự thực hiện kích thước ô tự động)
3. Thực hiện Giao diện và lấy chiều cao di động
Đầu tiên, khởi tạo một thể hiện ngoài màn hình của một ô xem bảng, một thể hiện cho mỗi mã định danh tái sử dụng , được sử dụng nghiêm ngặt để tính toán chiều cao. (Offscreen có nghĩa là tham chiếu ô được lưu trữ trong một thuộc tính / ivar trên bộ điều khiển chế độ xem và không bao giờ được quay lại từ tableView:cellForRowAtIndexPath:
chế độ xem bảng để thực sự hiển thị trên màn hình.) Tiếp theo, ô phải được cấu hình với nội dung chính xác (ví dụ: văn bản, hình ảnh, v.v.) rằng nó sẽ giữ nếu nó được hiển thị trong chế độ xem bảng.
Sau đó, buộc các tế bào ngay lập tức bố trí subviews của nó, và sau đó sử dụng các systemLayoutSizeFittingSize:
phương pháp trên UITableViewCell
's contentView
để tìm ra những chiều cao cần thiết của tế bào. Sử dụng UILayoutFittingCompressedSize
để có được kích thước nhỏ nhất cần thiết để phù hợp với tất cả các nội dung của ô. Chiều cao sau đó có thể được trả về từ tableView:heightForRowAtIndexPath:
phương thức ủy nhiệm.
4. Sử dụng ước tính Row Heights
Nếu chế độ xem bảng của bạn có nhiều hơn một vài hàng trong đó, bạn sẽ thấy rằng việc thực hiện giải quyết ràng buộc Bố cục tự động có thể nhanh chóng làm hỏng luồng chính khi tải lần đầu tiên vào chế độ xem bảng, như tableView:heightForRowAtIndexPath:
được gọi trên mỗi và mỗi hàng khi tải lần đầu tiên ( để tính kích thước của chỉ báo cuộn).
Kể từ iOS 7, bạn có thể (và hoàn toàn nên) sử dụng thuộc estimatedRowHeight
tính trên chế độ xem bảng. Điều này không cung cấp cho chế độ xem bảng với ước tính tạm thời / giữ chỗ cho chiều cao hàng của các ô chưa được hiển thị trên màn hình. Sau đó, khi các ô này sắp cuộn trên màn hình, chiều cao hàng thực tế sẽ được tính (bằng cách gọi tableView:heightForRowAtIndexPath:
) và chiều cao ước tính được cập nhật với chiều cao thực tế.
Nói chung, ước tính bạn cung cấp không nhất thiết phải chính xác - nó chỉ được sử dụng để định cỡ chính xác chỉ báo cuộn trong chế độ xem bảng và chế độ xem bảng thực hiện tốt việc điều chỉnh chỉ báo cuộn cho các ước tính không chính xác như bạn di chuyển các ô trên màn hình. Bạn nên đặt thuộc estimatedRowHeight
tính trên chế độ xem bảng (bằng viewDidLoad
hoặc tương tự) thành giá trị không đổi là chiều cao hàng "trung bình". Chỉ khi chiều cao hàng của bạn có độ biến thiên cực lớn (ví dụ: khác nhau theo độ lớn) và bạn nhận thấy chỉ báo cuộn "nhảy" khi bạn cuộn, bạn nên thực hiện tableView:estimatedHeightForRowAtIndexPath:
phép tính tối thiểu cần thiết để trả về ước tính chính xác hơn cho mỗi hàng.
5. (Nếu cần) Thêm bộ đệm chiều cao hàng
Nếu bạn đã thực hiện tất cả các cách trên và vẫn thấy rằng hiệu suất chậm đến mức không thể chấp nhận được khi thực hiện giải quyết ràng buộc tableView:heightForRowAtIndexPath:
, bạn sẽ không may phải thực hiện một số bộ đệm cho chiều cao của ô. (Đây là cách tiếp cận được đề xuất bởi các kỹ sư của Apple.) Ý tưởng chung là để công cụ Autolayout giải quyết các ràng buộc lần đầu tiên, sau đó lưu bộ đệm chiều cao tính toán cho ô đó và sử dụng giá trị được lưu trong bộ đệm cho tất cả các yêu cầu trong tương lai cho chiều cao của ô đó. Thủ thuật tất nhiên là đảm bảo bạn xóa chiều cao được lưu trong bộ nhớ cache khi có bất cứ điều gì xảy ra có thể khiến chiều cao của ô thay đổi - chủ yếu, đó là khi nội dung của ô đó thay đổi hoặc khi các sự kiện quan trọng khác xảy ra (như người dùng điều chỉnh thanh trượt kích thước văn bản Kiểu động).
Mã mẫu chung của iOS 7 (có nhiều bình luận thú vị)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline UILabels inside the
// -[layoutSubviews] method of the UITableViewCell subclass, or do it
// manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
Dự án mẫu
Các dự án này là các ví dụ hoạt động đầy đủ về chế độ xem bảng với độ cao hàng thay đổi do các ô xem bảng có chứa nội dung động trong UILabels.
Xamarin (C # /. NET)
Nếu bạn đang sử dụng Xamarin, hãy xem dự án mẫu này được kết hợp bởi @KentBoogaart .