Câu hỏi Sử dụng bố cục tự động trong UITableView cho bố cục ô động và chiều cao hàng biến


Cách bạn sử dụng Bố cục tự động trong UITableViewCells trong một cái nhìn bảng để cho phép nội dung của mỗi tế bào và subviews xác định chiều cao hàng (chính nó / tự động), trong khi duy trì hiệu suất di chuyển trơn tru?


1350
2017-09-11 16:48


gốc


Đây là mã mẫu trong nhanh 2.3 github.com/dpakthakur/DynamicCellHeight - Deepak Thakur


Các câu trả lời:


TL; DR: Bạn 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

Hai bước đầu tiên bên dưới được áp dụng bất kể 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 của bạn UITableViewCell phân lớp, thêm ràng buộc để các bản xem trước của ô có cạnh của chúng được ghim vào các cạnh của ô contentView (quan trọng nhất là các cạnh trên và dưới). LƯU Ý: không ghim các bản xem trước vào chính ô đó; chỉ đến của tế bào contentView! Hãy để kích thước nội dung nội tại của các bản xem trước này thúc đẩy chiều cao của chế độ xem nội dung của ô xem bảng bằng cách đảm bảo kháng nội dung nén và nội dung ôm các ràng buộc trong 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 vào. (Huh? Bấm vào đây.)

Hãy nhớ rằng, ý tưởng là có các bản xem trước của ô được kết nối theo chiều dọc đến chế độ xem nội dung của ô để chúng có thể "gây áp lực" và làm cho khung nhìn nội dung mở rộng để phù hợp với chúng. Sử dụng ô mẫu với một vài bản xem phụ, đây là minh họa trực quan về một số  (không phải tất cả!) các ràng buộc của bạn sẽ cần phải trông giống như:

Example illustration of constraints on a table view cell.

Bạn có thể tưởng tượng rằng khi có thêm văn bản được thêm vào nhãn thân nhiều dòng trong ô mẫu ở trên, nó sẽ cần phải tăng theo chiều dọc để vừa với văn bản, điều này sẽ buộc tế bào phát triển chiều cao một cách hiệu quả. (Tất nhiên, bạn cần phải có được các ràng buộc đúng để cho việc này hoạt động chính xác!)

Nhận được những ràng buộc của bạn đúng là chắc chắn phần khó nhất và quan trọng nhất nhận được chiều cao của ô động đang hoạt động với Bố cục tự động. Nếu bạn phạm sai lầm ở đây, nó có thể ngăn chặ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 bạn trong mã vì bạn biết chính xác những ràng buộc nào đang được thêm vào ở đâu và dễ dàng hơn để gỡ lỗi khi mọi thứ xảy ra sai. 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 xây dựng giao diện bằng cách sử dụng các neo định vị, 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 làm điều này một lần từ bên 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 bao bọc mã bổ sung ràng buộc của bạn trong updateConstraints trong một kiểm tra cho một tài sản boolean như didSetupConstraints (mà bạn đặt thành CÓ sau khi bạn chạy mã ràng buộc thê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 constanttài sản trên một số ràng buộc), đặt nó vào updateConstraints nhưng ngoài kiểm tra didSetupConstraints vì vậy 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 số nhận dạng 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 bố cục duy nhất, mỗi bố cục duy nhất sẽ nhận được số nhận dạng sử dụng lại của riêng nó. (Một gợi ý tốt 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ể ô của bạn có số lượng các bản xem trước khác nhau hoặc các bản xem trước được sắp xếp theo một cách khác biệt.)

Ví dụ: nếu bạn đang hiển thị thông báo email trong mỗi ô, bạn có thể có 4 bố cục duy nhất: thư chỉ với chủ đề, thư có chủ đề và nội dung, thư có chủ đề và tệp đính kèm ảnh và thư có chủ đề, nội dung và tệp đí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 khi ô được khởi tạo và các ràng buộc được thêm vào cho một trong các kiểu ô này, ô sẽ nhận được một mã định danh sử dụng lại cụ thể cho loại ô đó. Điều này có nghĩa là khi bạn dequeue một tế bào để tái sử dụng, các ràng buộc đã được thêm vào và đã sẵn sàng để đi cho loại tế bào đó.

Lưu ý rằng do sự khác biệt về kích thước nội tại nội tại, các ô có cùng ràng buộc (loại) có thể vẫn có các độ cao khác nhau! Đừng nhầm lẫn giữa các bố cục khác nhau về cơ bản (các ràng buộc khác nhau) với các khung nhìn được tính toán khác nhau (được giải quyết từ các ràng buộc giống nhau) do các kích thước nội dung khác nhau.

  • Không thêm các ô có các ràng buộc hoàn toàn khác nhau vào cùng một nhóm sử dụng lại (tức 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 khử. Công cụ Tự động sắp xếp nội bộ 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 - Ô tự định kích thước

3. Kích hoạt ước tính chiều cao hàng

Để bật các ô xem bảng tự định kích thước, bạn phải đặt chế độ xem bảng   rowHeight bất động sản để UITableViewAutomaticDimension. Bạn cũng phải   gán một giá trị cho thuộc tính EstimatedRowHeight. Ngay sau khi cả hai   các thuộc tính này được thiết lập, hệ thống sử dụng Bố cục Tự động để tính toán   chiều cao thực tế của hàng

Táo: Làm việc với các ô xem bảng tự định kích thước

Với iOS 8, Apple đã thực hiện rất nhiều công việc mà trước đó bạn phải thực hiện trước iOS 8. Để cho phép cơ chế cell tự định cỡ hoạt động, trước tiên bạn phải thiết lập rowHeight thuộc tính trên khung nhìn bảng tới hằng số UITableViewAutomaticDimension. Sau đó, bạn chỉ cần bật ước tính chiều cao hàng bằng cách đặt chế độ xem bảng estimatedRowHeight đối với một 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 có nghĩa là cung cấp chế độ xem bảng với ước tính tạm thời / trình giữ chỗ cho chiều cao hàng của các ô chưa được đặt trên màn hình. Sau đó, khi các ô này sắp di chuyể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 tự động hỏi từng ô chiều cao của nó contentViewcần phải 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 bảng xem, trừ 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à xem phụ của ô . Khi chiều cao tế bào thực tế đượ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à bất kỳ điều chỉnh nào cho contentSize / contentOffset 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 phải rất chính xác - chỉ được sử dụng để kích thướ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 công 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 estimatedRowHeight thuộc tính trên chế độ xem bảng (trong viewDidLoad hoặc tương tự) với 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 đổi cực cao (ví dụ: khác nhau theo thứ tự độ 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 việc triển khai tableView:estimatedHeightForRowAtIndexPath: để thực hiện phép tính tối thiểu cần thiết để trả lại ước tính chính xác hơn cho mỗi hàng.

Đối với hỗ trợ iOS 7 (triển khai kích thước ô tự động của chính bạn)

3. Thực hiện một giao diện Pass & Nhận chiều cao tế bào

Đầu tiên, khởi tạo một trường hợp tắt màn hình của một ô xem bảng, một ví dụ cho mỗi số nhận dạng sử dụng lại, đượ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 thuộc tính / ivar trên bộ điều khiển xem và không bao giờ quay trở lại từ tableView:cellForRowAtIndexPath: để xem bảng thực sự hiển thị trên màn hình.) Tiếp theo, ô phải được định cấu hình với nội dung chính xác (ví dụ: văn bản, hình ảnh, v.v.) mà 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 systemLayoutSizeFittingSize: phương pháp trên UITableViewCell'S contentView để tìm hiểu chiều cao yêu cầu của ô là bao nhiêu. Sử dụng UILayoutFittingCompressedSize để có kích thước nhỏ nhất cần thiết để phù hợp với tất cả nội dung của ô. Chiều cao có thể được trả về từ tableView:heightForRowAtIndexPath: phương thức ủy nhiệm.

4. Sử dụng ước tính hàng 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 bog xuống luồng chính khi lần đầu tiên tải chế độ xem bảng, như tableView:heightForRowAtIndexPath: được gọi trên mỗi hàng mỗi lần tải đầ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 estimatedRowHeight thuộc tính trên màn hình bảng. Điều này có nghĩa là cung cấp chế độ xem bảng với ước tính tạm thời / trình giữ chỗ cho chiều cao hàng của các ô chưa được đặt trên màn hình. Sau đó, khi các ô này sắp di chuyển trên màn hình, chiều cao hàng thực tế sẽ được tính toán (bằng cách gọi tableView:heightForRowAtIndexPath:) và chiều cao ước tính được cập nhật với độ cao thực tế.

Nói chung, ước tính bạn cung cấp không phải rất chính xác - chỉ được sử dụng để kích thướ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 công 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 estimatedRowHeight thuộc tính trên chế độ xem bảng (trong viewDidLoad hoặc tương tự) với 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 đổi cực cao (ví dụ: khác nhau theo thứ tự độ 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 việc triển khai tableView:estimatedHeightForRowAtIndexPath: để thực hiện phép tính tối thiểu cần thiết để trả lại ước tính chính xác hơn cho mỗi hàng.

5. (Nếu cần) Thêm hàng cao Caching

Nếu bạn đã thực hiện tất cả những điều trên và vẫn thấy rằng hiệu suất là không thể chấp nhận chậm khi thực hiện giải quyết ràng buộc trong tableView:heightForRowAtIndexPath:, bạn sẽ không may cần phải thực hiện một số bộ nhớ đệm cho chiều cao của ô. Ý tưởng chung là để cho công cụ Auto Layout giải quyết các ràng buộc lần đầu tiên, sau đó cache chiều cao tính toán cho ô đó và sử dụng giá trị được lưu trữ cho tất cả các yêu cầu trong tương lai cho chiều cao của ô đó. Bí quyết của khóa học là đảm bảo bạn xóa chiều cao được lưu trữ cho ô khi xảy ra bất kỳ điều gì có thể làm cho chiều cao của ô thay đổi - chủ yếu, điều này sẽ xảy ra 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 Loại động).

Mã mẫu chung của iOS 7 (với nhiều nhận xét ngon ngọt)

- (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 multi-line 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à ví dụ làm việc đầy đủ về các chế độ xem bảng với chiều cao hàng biến 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 đặt lại với nhau bởi @KentBoogaart.


2268
2018-06-19 08:21



Nếu tôi là 95% con đường ở đó, 5% còn lại sẽ đưa tôi nửa tuần để tìm ra! Tôi thực sự đánh giá cao bạn ném vào và cho vay một bàn tay quá nhanh. Mã làm việc là trên GitHub như vậy và phần còn lại của cộng đồng có thể được hưởng lợi. Smileyborg thật tuyệt vời! - caoimghgin
@ Alex311 Rất thú vị, cảm ơn vì đã cung cấp ví dụ này. Tôi đã làm một thử nghiệm nhỏ vào cuối của tôi và đã viết lên một số ý kiến ​​ở đây: github.com/Alex311/TableCellWithAutoLayout/commit/… - smileyborg
Tôi sẽ mạnh mẽ khuyên bạn nên lưu vào bộ nhớ cache ô cho từng loại số nhận dạng sử dụng lại của ô. Mỗi khi bạn dequeue cho chiều cao bạn đang lấy một tế bào ra khỏi hàng đợi mà không được thêm trở lại. Caching có thể làm giảm đáng kể số lần khởi tạo cho các ô bảng của bạn. Vì một số lý do, khi tôi không lưu vào bộ nhớ đệm, lượng bộ nhớ đang được sử dụng cứ tăng lên khi tôi cuộn. - Alex311
nghiêm túc .... Apple không thể chỉ đơn giản cho phép cell.rowHeight = DYNAMIC_HEIGHT; ô trả về; ????? NHỮNG GÌ ĐÓ LÀ NGH TH? - user1504605
@NathanBuggia, Ohh Gosh !! Kính gửi Apple, thật khó để chỉ cần đặt wrap_contentbất cứ nơi nào có huh? - guness


Đối với IOS8 nó thực sự đơn giản:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

HOẶC LÀ

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

Nhưng đối với IOS7, điều quan trọng là tính toán chiều cao sau khi autolayout,

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height
}

Quan trọng 

  • Nếu nhiều dòng nhãn, đừng quên đặt numberOfLines đến 0.

  • Đừng quên label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

Mã ví dụ đầy đủ là đây.


122
2018-04-05 09:23



Tôi nghĩ chúng ta không cần triển khai hàm heightForRowAtIndexPath trong trường hợp OS8 - jpulikkottil
Như @ đám cưới ghi chú trên một câu trả lời khác, điều quan trọng là các ràng buộc được sử dụng để khóa trong chiều cao hàng không bao gồm margin. - Richard
1 cho "Nếu nhiều nhãn dòng, đừng quên đặt numberOfLines thành 0" điều này đã khiến cho các ô của tôi không có kích thước động. - Ian-Fogelman
Hãy gọi cho tôi một điều khiển quái dị nhưng ngay cả trong những ngày của iOS 11 tôi vẫn không thích sử dụng UITableViewAutomaticDimension. Đã có một số kinh nghiệm xấu với nó trong quá khứ. Vì vậy, tôi thường sử dụng các giải pháp iOS7 được liệt kê ở đây. Lưu ý của William không quên label.preferredMaxLayoutWidth ở đây đã cứu tôi. - Brian Sachetta


Ví dụ Swift về chiều cao biến UITableViewCell

Đã cập nhật cho Swift 3

Câu trả lời Swift của William Hu là tốt, nhưng nó giúp tôi có một số bước đơn giản nhưng chi tiết khi học cách làm điều gì đó lần đầu tiên. Ví dụ bên dưới là dự án thử nghiệm của tôi trong khi học cách tạo UITableView với chiều cao ô biến đổi. Tôi dựa trên nó ví dụ UITableView cơ bản này cho Swift.

Dự án đã hoàn thành sẽ trông như sau:

enter image description here

Tạo một dự án mới

Nó có thể chỉ là một ứng dụng xem đơn.

Thêm mã

Thêm một tệp Swift mới vào dự án của bạn. Đặt tên là MyCustomCell. Lớp này sẽ giữ các cửa hàng cho các khung nhìn mà bạn thêm vào ô của bạn trong bảng phân cảnh. Trong ví dụ cơ bản này, chúng ta sẽ chỉ có một nhãn trong mỗi ô.

import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UILabel!
}

Chúng tôi sẽ kết nối ổ cắm này sau.

Mở ViewController.swift và đảm bảo bạn có nội dung sau:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

Lưu ý quan trọng:

  • Đây là hai dòng mã sau (cùng với bố cục tự động) làm cho chiều cao ô biến có thể:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Thiết lập bảng phân cảnh

Thêm một Bảng Xem vào bộ điều khiển xem của bạn và sử dụng bố trí tự động để ghim nó vào bốn bên. Sau đó kéo một ô xem bảng vào dạng xem bảng. Và vào ô Prototype, hãy kéo nhãn. Sử dụng bố cục tự động để ghim nhãn vào bốn cạnh của chế độ xem nội dung của Ô xem bảng.

enter image description here

Lưu ý quan trọng:

  • Bố cục tự động hoạt động cùng với hai dòng mã quan trọng mà tôi đã đề cập ở trên. Nếu bạn không sử dụng bố cục tự động, nó sẽ không hoạt động.

Các cài đặt IB khác

Tên lớp và Số nhận dạng tùy chỉnh

Chọn ô xem bảng và đặt lớp tùy chỉnh thành MyCustomCell (tên của lớp trong tệp Swift chúng tôi đã thêm). Đồng thời đặt Mã định danh thành cell(cùng một chuỗi mà chúng tôi đã sử dụng cho cellReuseIdentifier trong đoạn mã trên.

enter image description here

Dòng không cho nhãn

Đặt số dòng thành 0 trong Nhãn của bạn. Điều này có nghĩa là đa dòng và cho phép nhãn tự thay đổi kích thước dựa trên nội dung của nó.

enter image description here 

Treo lên các cửa hàng

  • Điều khiển kéo từ Chế độ xem bảng trong bảng phân cảnh tới tableView biến trong ViewController mã.
  • Làm tương tự cho Nhãn trong ô Prototype của bạn đến myCellLabel biến trong MyCustomCell lớp học.

Đã kết thúc

Bạn sẽ có thể chạy dự án của bạn ngay bây giờ và nhận các ô có chiều cao biến đổi.

Ghi chú

  • Ví dụ này chỉ hoạt động cho iOS 8 và sau đó. Nếu bạn vẫn cần hỗ trợ iOS 7 thì điều này sẽ không hiệu quả với bạn.
  • Các ô tùy chỉnh của riêng bạn trong các dự án tương lai của bạn có thể sẽ có nhiều hơn một nhãn. Hãy chắc chắn rằng bạn có được tất cả mọi thứ pinned đúng để bố trí tự động có thể xác định chiều cao chính xác để sử dụng. Bạn cũng có thể phải sử dụng sức đề kháng nén dọc và ôm. Xem bài viết này để biết thêm về điều đó.
  • Nếu bạn không ghim cạnh đầu và cuối (trái và phải), bạn cũng có thể cần đặt nhãn preferredMaxLayoutWidth để nó biết khi nào xếp hàng. Ví dụ: nếu bạn đã thêm một giới hạn theo chiều ngang của Trung tâm vào nhãn trong dự án ở trên thay vì ghim các cạnh đầu và cuối, thì bạn sẽ cần phải thêm dòng này vào tableView:cellForRowAtIndexPath phương pháp:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Xem thêm


71
2018-06-11 15:18





Tôi đã gói giải pháp iOS7 của @ smileyborg vào một danh mục

Tôi quyết định kết hợp giải pháp thông minh này bởi @smileyborg UICollectionViewCell+AutoLayoutDynamicHeightCalculation thể loại.

Danh mục cũng khắc phục các vấn đề được nêu trong câu trả lời của @ wildmonkey (tải một ô từ một ngòi và systemLayoutSizeFittingSize: trở về CGRectZero)

Nó không đưa vào tài khoản bất kỳ bộ nhớ đệm nhưng phù hợp với nhu cầu của tôi ngay bây giờ. Hãy sao chép, dán và hack vào nó.

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
 */
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

/**
 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *
 *  @param name Name of the nib file.
 *
 *  @return collection view cell for using to calculate content based height
 */
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

/**
 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *
 *  @param block Render the model data to your UI elements in this block
 *
 *  @return Calculated constraint derived height
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

/**
 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;

@end

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

Ví dụ sử dụng:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

Rất may, chúng tôi sẽ không phải làm nhạc jazz này trong iOS8, nhưng bây giờ đã có!


60
2018-02-10 19:41



Bạn chỉ có thể sử dụng một cách đơn giản: [YourCell new] và sử dụng nó như là giả. Miễn là mã xây dựng mã ràng buộc được kích hoạt trong cá thể của bạn, và bạn kích hoạt một đường dẫn bố trí lập trình, bạn nên sử dụng tốt. - Adam Waite
Cảm ơn! Những công việc này. Danh mục của bạn thật tuyệt vời. Đó là điều khiến tôi nhận ra rằng kỹ thuật này hoạt động với UICollectionViews cũng. - Ricardo Sanchez-Saez
Làm thế nào bạn sẽ làm điều này bằng cách sử dụng một tế bào nguyên mẫu được định nghĩa trong một bảng phân cảnh? - David Potter


Đây là giải pháp của tôi. Bạn cần phải nói cho TableView ước tính trước khi nó tải khung nhìn. Nếu không nó sẽ không thể cư xử như mong đợi.

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}

43
2017-11-11 16:52



Thiết lập autolayout chính xác, cùng với mã này đang được thêm vào trong viewDidLoad đã làm các trick. - Bruce
nhưng nếu như estimatedRowHeight đang thay đổi từng hàng? tôi có nên vượt quá ước tính hay không? sử dụng tối thiểu hoặc tối đa chiều cao mà tôi sử dụng tableView? - János
@ János đây là điểm của rowHeight. Để thực hiện điều này như mong đợi, bạn cần sử dụng các ràng buộc không có lề và căn chỉnh đối với các đối tượng TableViewCell. và tôi giả sử bạn đang sử dụng một UITextView vì vậy bạn vẫn cần phải loại bỏ autoscroll = false nếu không nó sẽ duy trì chiều cao và chiều cao tương đối sẽ không hành động như mong đợi. - eddwinpaz
Đó là giải pháp mạnh mẽ nhất. Lo lắng không về estimatedRowHeight, nó chủ yếu ảnh hưởng đến kích thước của thanh cuộn, không bao giờ là chiều cao của các ô thực tế. Được in đậm ở độ cao bạn chọn: nó sẽ ảnh hưởng đến hoạt ảnh chèn / xóa. - SwiftArchitect
Đây là mã mẫu trong nhanh 2.3 github.com/dpakthakur/DynamicCellHeight - Deepak Thakur


Giải pháp được đề xuất bởi @smileyborg gần như hoàn hảo. Nếu bạn có một ô tùy chỉnh và bạn muốn một hoặc nhiều UILabel với chiều cao năng động thì systemLayoutSizeFittingSize phương pháp kết hợp với tính năng AutoLayout được bật trả về CGSizeZero trừ khi bạn di chuyển tất cả các ràng buộc của ô từ ô sang contentView của nó (như được đề xuất bởi @TomSwift tại đây Làm thế nào để thay đổi kích thước superview để phù hợp với tất cả các subviews với autolayout?).

Để làm như vậy, bạn cần phải chèn đoạn mã sau vào triển khai UITableViewCell tùy chỉnh của bạn (nhờ @Adrian).

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

Trộn @smileyborg câu trả lời với điều này sẽ hoạt động.


42
2017-12-03 02:23



systemLayoutSizeFittingSize cần được gọi trên contentView, không phải là ô - onmyway133


Một gotcha đủ quan trọng tôi vừa chạy vào để đăng bài như một câu trả lời.

@ smileyborg của câu trả lời là chủ yếu là chính xác. Tuy nhiên, nếu bạn có bất kỳ mã nào trong layoutSubviews phương pháp của lớp ô tùy chỉnh của bạn, ví dụ: thiết lập preferredMaxLayoutWidth, sau đó nó sẽ không được chạy với mã này:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

Nó làm tôi bối rối một lúc. Sau đó, tôi nhận ra đó là bởi vì những người đó chỉ kích hoạt layoutSubviews trên contentView, không phải chính tế bào.

Mã làm việc của tôi trông như thế này:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

Lưu ý rằng nếu bạn đang tạo một ô mới, tôi chắc chắn bạn không cần phải gọi setNeedsLayout vì nó đã được thiết lập rồi. Trong trường hợp bạn lưu một tham chiếu đến một ô, có lẽ bạn nên gọi nó. Dù bằng cách nào nó cũng không làm tổn thương gì cả.

Một mẹo khác nếu bạn đang sử dụng các lớp con của ô nơi bạn đang thiết lập những thứ như preferredMaxLayoutWidth. Như @smileyborg đề cập, "ô xem bảng của bạn chưa có chiều rộng cố định cho chiều rộng của bảng xem". Điều này là đúng, và rắc rối nếu bạn đang làm công việc của bạn trong phân lớp của bạn và không phải trong bộ điều khiển xem. Tuy nhiên, bạn có thể chỉ cần thiết lập khung tế bào tại thời điểm này bằng cách sử dụng chiều rộng bảng:

Ví dụ trong tính toán chiều cao:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(Tôi tình cờ nhớ cache ô này để sử dụng lại, nhưng điều đó không liên quan).


22
2018-03-05 15:53





Trong trường hợp mọi người vẫn gặp sự cố với điều này. Tôi đã viết một bài đăng blog nhanh về việc sử dụng Autolayout với UITableViews Tận dụng Autolayout cho Dynamic Cell Heights cũng như một thành phần nguồn mở để giúp làm cho điều này trừu tượng hơn và dễ thực hiện hơn. https://github.com/Raizlabs/RZCellSizeManager


19
2018-06-27 22:49





(đối với Xcode 8.x / Xcode 9.x đọc ở dưới cùng)

Hãy coi chừng vấn đề sau trong Xcode 7.x, đây có thể là một nguồn gây nhầm lẫn:

Trình tạo giao diện không xử lý thiết lập ô tự động định cỡ đúng cách. Ngay cả khi các ràng buộc của bạn là hoàn toàn hợp lệ, IB vẫn sẽ phàn nàn và cung cấp cho bạn những gợi ý và lỗi lầm lẫn lộn. Lý do là IB không muốn thay đổi chiều cao của hàng khi các ràng buộc của bạn ra lệnh (để ô phù hợp với nội dung của bạn). Thay vào đó, nó giữ chiều cao của hàng cố định và bắt đầu gợi ý bạn thay đổi các ràng buộc của bạn, bạn nên bỏ qua.

Ví dụ, hãy tưởng tượng bạn đã thiết lập mọi thứ tốt đẹp, không cảnh báo, không có lỗi, tất cả đều hoạt động.

enter image description here

Bây giờ nếu bạn thay đổi kích thước phông chữ (trong ví dụ này tôi đang thay đổi kích thước phông chữ nhãn mô tả từ 17.0 đến 18.0).

enter image description here

Bởi vì kích thước phông chữ tăng lên, nhãn bây giờ muốn chiếm 3 hàng (trước khi nó chiếm 2 hàng).

Nếu Interface Builder làm việc như mong đợi, nó sẽ thay đổi kích thước chiều cao của ô để phù hợp với chiều cao nhãn mới. Tuy nhiên điều thực sự xảy ra là IB hiển thị biểu tượng lỗi bố cục tự động màu đỏ và đề nghị bạn sửa đổi ưu tiên nén / nén.

enter image description here

Bạn nên bỏ qua những cảnh báo này. Thay vào đó, những gì bạn có thể làm là thay đổi chiều cao của hàng bằng tay (chọn Ô> Bộ kiểm tra kích thước> Chiều cao hàng).

enter image description here

Tôi đã thay đổi chiều cao này một cú nhấp chuột tại một thời điểm (bằng cách sử dụng lên / xuống stepper) cho đến khi các lỗi mũi tên màu đỏ biến mất! (bạn sẽ thực sự nhận được cảnh báo màu vàng, tại thời điểm đó chỉ cần đi trước và làm 'cập nhật khung hình', tất cả nên hoạt động).

* Lưu ý rằng bạn không thực sự phải giải quyết các lỗi màu đỏ này hoặc cảnh báo màu vàng trong Trình tạo giao diện - khi chạy, mọi thứ sẽ hoạt động chính xác (ngay cả khi IB hiển thị lỗi / cảnh báo). Chỉ cần đảm bảo rằng khi chạy trong nhật ký bảng điều khiển, bạn sẽ không nhận được bất kỳ lỗi AutoLayout nào.

Trong thực tế, cố gắng để luôn luôn cập nhật chiều cao hàng trong IB là siêu gây phiền nhiễu và đôi khi gần như không thể (vì các giá trị phân đoạn).

Để ngăn chặn các cảnh báo / lỗi IB gây phiền nhiễu, bạn có thể chọn các khung nhìn có liên quan và trong Size Inspector cho tài sản Ambiguity chọn Verify Position Only

enter image description here


Xcode 8.x / Xcode 9.x dường như (đôi khi) làm những việc khác với Xcode 7.x, nhưng vẫn không chính xác. Ví dụ ngay cả khi compression resistance priority / hugging priority được đặt theo yêu cầu (1000), Trình tạo giao diện có thể kéo dài hoặc cắt nhãn để vừa với ô (thay vì thay đổi kích thước chiều cao ô để vừa với nhãn). Và trong trường hợp này, nó thậm chí không thể hiển thị bất kỳ cảnh báo hoặc lỗi AutoLayout nào. Hoặc đôi khi nó thực hiện chính xác những gì Xcode 7.x đã làm, mô tả ở trên.


18
2018-04-29 18:16



Ồ, cảm ơn bạn!! Tôi không nghĩ rằng Apple sẽ là ngu ngốc này sau khi 2 phiên bản hệ điều hành với các tế bào kích thước tự động !! Nhưng hóa ra chúng ta cần phải đợi ios15 để làm việc này một cách chính xác. Dù sao bạn đã cứu tôi gặp khó khăn từ hack các thuộc tính ôm của tế bào và quan điểm khác, vv - EralpB
hi là nó có thể cung cấp cho chiều cao năng động cho tế bào, có tableview với tế bào với nội dung tế bào năng động.? - Jignesh B


Miễn là bố cục của bạn trong ô của bạn là tốt.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

Cập nhật: Bạn nên sử dụng thay đổi kích thước động được giới thiệu trong iOS 8.


16
2017-07-08 01:34



Tính năng này đang hoạt động với tôi trên iOS7, bây giờ có thể gọi điện cho tôi không tableView:cellForRowAtIndexPath: trong tableView:heightForRowAtIndexPath: hiện nay? - Ants
Ok vì vậy điều này không hoạt động, nhưng khi tôi gọi systemLayoutSizeFittingSize: trong tableView:cellForRowAtIndexPath: và lưu vào bộ nhớ cache kết quả rồi sau đó sử dụng tableView:heightForRowAtIndexPath: nó hoạt động tốt miễn là các ràng buộc được thiết lập một cách chính xác tất nhiên! - Ants
Trong iOS 7 nó hoạt động. Làm thế nào để làm cho nó hoạt động trong iOS 8? - Winston
Điều này chỉ hoạt động nếu bạn sử dụng dequeueReusableCellWithIdentifier: thay vì dequeueReusableCellWithIdentifier: forIndexPath: - Antoine
Tôi thực sự không nghĩ rằng gọi tableView: cellForRowAtIndexPath: trực tiếp là một cách tốt. - Itachi


Như @ Bob-Spryn Tôi chạy vào một gotcha đủ quan trọng mà tôi đăng bài này như là một câu trả lời.

Tôi đã vật lộn với @ smileyborg's trả lời một lúc. Các gotcha mà tôi chạy vào là nếu bạn đã xác định tế bào nguyên mẫu của bạn trong IB với các yếu tố bổ sung (UILabels, UIButtons, vv) trong IB khi bạn khởi tạo ô bằng [[YourTableViewCellClass alloc] init] nó sẽ không khởi tạo tất cả các phần tử khác trong ô đó trừ khi bạn đã viết mã để làm điều đó. (Tôi đã có một trải nghiệm tương tự với initWithStyle.)

Để bảng phân cảnh diễn tả tất cả các yếu tố bổ sung có được ô của bạn [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (Không phải [tableView dequeueReusableCellWithIdentifier:forIndexPath:] vì điều này sẽ gây ra các vấn đề thú vị.) Khi bạn thực hiện điều này, tất cả các phần tử bạn đã định nghĩa trong IB sẽ được khởi tạo.


14
2017-08-22 10:14