AutoLayout với UIViews ẩn?


164

Tôi cảm thấy như đó là một mô hình khá phổ biến để hiển thị / ẩn UIViews, thường xuyên nhất UILabels, tùy thuộc vào logic kinh doanh. Câu hỏi của tôi là, cách tốt nhất để sử dụng AutoLayout để trả lời các chế độ xem ẩn như thể khung của chúng là 0x0. Dưới đây là một ví dụ về danh sách động gồm 1-3 tính năng.

Danh sách tính năng động

Ngay bây giờ tôi có một không gian trên cùng 10px từ nút đến nhãn cuối cùng, điều này rõ ràng sẽ không trượt lên khi nhãn bị ẩn. Ngay bây giờ tôi đã tạo ra một lối thoát cho ràng buộc này và sửa đổi hằng số tùy thuộc vào số lượng nhãn tôi đang hiển thị. Đây rõ ràng là một chút hack vì tôi đang sử dụng các giá trị âm liên tục để đẩy nút lên trên các khung ẩn. Điều đó cũng tệ vì nó không bị hạn chế đối với các yếu tố bố cục thực tế, chỉ lén lút tính toán dựa trên độ cao / phần đệm của các yếu tố khác và rõ ràng là chiến đấu chống lại những gì AutoLayout được tạo ra.

Rõ ràng tôi chỉ có thể tạo ra các ràng buộc mới tùy thuộc vào nhãn động của mình, nhưng đó là rất nhiều vi mô và rất nhiều chi tiết cho việc cố gắng thu gọn một số khoảng trắng. Có cách tiếp cận tốt hơn? Thay đổi kích thước khung 0,0 và để AutoLayout thực hiện công việc của mình mà không cần thao tác với các ràng buộc? Xóa quan điểm hoàn toàn?

Thành thật mà nói, chỉ cần sửa đổi hằng số từ ngữ cảnh của chế độ xem ẩn yêu cầu một dòng mã với phép tính đơn giản. Tái tạo những hạn chế mới với constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:dường như rất nặng nề.

Chỉnh sửa tháng 2 năm 2018 : Xem câu trả lời của Ben với UIStackViews


Cảm ơn Ryan cho câu hỏi này. Tôi sẽ không biết phải làm gì cho các trường hợp như bạn đã hỏi. Mỗi khi tôi kiểm tra hướng dẫn cho autolayout, hầu hết trong số họ nói rằng hãy tham khảo trang web hướng dẫn raywenderlich mà tôi thấy hơi khó để làm cho nó ra.
Nassif

Câu trả lời:


82

UIStackViewcó lẽ là cách để đi cho iOS 9+. Nó không chỉ xử lý chế độ xem ẩn, nó cũng sẽ loại bỏ khoảng cách và lề bổ sung nếu được thiết lập chính xác.


8
Cảnh giác khi sử dụng UIStackView trong UITableViewCell. Đối với tôi, một khi chế độ xem trở nên rất phức tạp, việc cuộn bắt đầu trở nên rối mắt, nơi nó sẽ bị gián đoạn mỗi khi một ô được cuộn vào / ra. Dưới vỏ bọc, StackView đang thêm / xóa các ràng buộc và điều này sẽ không nhất thiết đủ hiệu quả để cuộn trơn tru.
Kento

7
Sẽ rất tuyệt nếu đưa ra một ví dụ nhỏ về cách stack view xử lý việc này.
lostintranslation

@Kento đây có còn là vấn đề trên iOS 10 không?
Crashalot

3
Năm năm sau ... tôi có nên cập nhật và đặt đây là câu trả lời được chấp nhận không? Nó đã có sẵn cho ba chu kỳ phát hành ngay bây giờ.
Ryan Romanchuk

1
@RyanRomanchuk Bạn nên, đây chắc chắn là câu trả lời tốt nhất bây giờ. Cảm ơn Ben!
Duncan Luk

234

Sở thích cá nhân của tôi để hiển thị / ẩn chế độ xem là tạo IBOutlet với giới hạn chiều rộng hoặc chiều cao phù hợp.

Sau đó tôi cập nhật constantgiá trị cần 0ẩn hoặc bất cứ giá trị nào cần hiển thị.

Ưu điểm lớn của kỹ thuật này là các ràng buộc tương đối sẽ được duy trì. Ví dụ: giả sử bạn có chế độ xem A và xem B với khoảng cách ngang là x . Khi chế độ xem A chiều rộng constantđược đặt thành 0.fthì chế độ xem B sẽ di chuyển sang trái để lấp đầy khoảng trống đó.

Không cần phải thêm hoặc loại bỏ các ràng buộc, đó là một hoạt động nặng. Chỉ cần cập nhật các ràng buộc constantsẽ thực hiện các mẹo.


1
chính xác. Miễn là - trong ví dụ này - bạn đặt chế độ xem B ở bên phải của chế độ xem A bằng cách sử dụng khoảng cách ngang. Tôi sử dụng kỹ thuật này mọi lúc và nó hoạt động rất tốt
Max MacLeod

9
@MaxMacLeod Chỉ cần đảm bảo: nếu khoảng cách giữa hai chế độ xem là x, để chế độ xem B bắt đầu từ chế độ xem A, chúng ta cũng phải thay đổi hằng số ràng buộc khoảng cách thành 0, phải không?
kernix

1
@kernix có nếu cần phải có một ràng buộc khoảng cách ngang giữa hai chế độ xem. hằng số 0 tất nhiên có nghĩa là không có khoảng cách. Vì vậy, ẩn chế độ xem A bằng cách đặt chiều rộng của nó thành 0 sẽ di chuyển chế độ xem B sang trái để làm phẳng với vùng chứa. Btw cảm ơn vì đã bỏ phiếu!
Max MacLeod

2
@MaxMacLeod Cảm ơn câu trả lời của bạn. Tôi đã thử thực hiện điều này với một UIView có chứa các chế độ xem khác, nhưng các cuộc phỏng vấn của chế độ xem đó không bị ẩn khi tôi đặt giới hạn chiều cao của nó thành 0. Giải pháp này có phải hoạt động với các chế độ xem có chứa các lượt xem không? Cảm ơn!
shaunlim

6
Cũng sẽ đánh giá cao một giải pháp hoạt động cho một UIView chứa các chế độ xem khác ...
Mark Gibaud

96

Giải pháp sử dụng hằng số 0khi ẩn và một hằng số khác nếu bạn hiển thị lại là chức năng, nhưng sẽ không thỏa mãn nếu nội dung của bạn có kích thước linh hoạt. Bạn cần đo nội dung linh hoạt của mình và đặt lại liên tục. Điều này cảm thấy sai và có vấn đề nếu nội dung thay đổi kích thước do sự kiện máy chủ hoặc giao diện người dùng.

Tôi có một giải pháp tốt hơn.

Ý tưởng là đặt quy tắc độ cao 0 có mức độ ưu tiên cao khi chúng ta ẩn phần tử để nó không chiếm không gian tự động thanh toán.

Đây là cách bạn làm điều đó:

1. thiết lập chiều rộng (hoặc chiều cao) bằng 0 trong trình tạo giao diện với mức độ ưu tiên thấp.

thiết lập quy tắc 0 chiều rộng

Interface Builder sẽ không hét lên về xung đột vì mức độ ưu tiên thấp. Kiểm tra hành vi chiều cao bằng cách tạm thời đặt mức ưu tiên thành 999 (1000 bị cấm đột biến theo chương trình, vì vậy chúng tôi sẽ không sử dụng nó). Trình xây dựng giao diện có thể sẽ hét lên về các ràng buộc xung đột. Bạn có thể khắc phục những điều này bằng cách đặt mức độ ưu tiên trên các đối tượng liên quan thành 900 hoặc hơn.

2. Thêm một ổ cắm để bạn có thể sửa đổi mức độ ưu tiên của ràng buộc độ rộng trong mã:

kết nối ổ cắm

3. Điều chỉnh mức độ ưu tiên khi bạn ẩn phần tử của mình:

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999

@SimplGy bạn có thể vui lòng cho tôi biết nơi này là gì không.closesSoon?
Niharika

Đó không phải là một phần của giải pháp chung, @Niharika, Đó chỉ là quy tắc kinh doanh trong trường hợp của tôi (hiển thị quan điểm nếu nhà hàng sắp / không đóng cửa sớm).
SimplGy

11

Trong trường hợp này, tôi ánh xạ chiều cao của nhãn Tác giả tới một IBOutlet thích hợp:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

và khi tôi đặt chiều cao của ràng buộc thành 0,0f, chúng tôi sẽ giữ "phần đệm", vì chiều cao của nút Play cho phép điều đó.

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show

nhập mô tả hình ảnh ở đây


9

Có rất nhiều giải pháp ở đây nhưng cách tiếp cận thông thường của tôi lại khác :)

Thiết lập hai bộ ràng buộc tương tự như câu trả lời của Jorge Arimany và TMin:

nhập mô tả hình ảnh ở đây

Tất cả ba ràng buộc được đánh dấu có cùng một giá trị cho Hằng. Các ràng buộc được đánh dấu A1 và A2 có Mức độ ưu tiên được đặt thành 500, trong khi các ràng buộc được đánh dấu B có Mức độ ưu tiên được đặt thành 250 (hoặc UILayoutProperty.defaultLowtheo mã).

Kết nối ràng buộc B với IBOutlet. Sau đó, khi bạn ẩn phần tử, bạn chỉ cần đặt mức ưu tiên ràng buộc thành cao (750):

constraintB.priority = .defaultHigh

Nhưng khi phần tử hiển thị, đặt mức ưu tiên trở về mức thấp (250):

constraintB.priority = .defaultLow

Ưu điểm (thừa nhận nhỏ) đối với phương pháp này so với việc chỉ thay đổi isActiveràng buộc B là bạn vẫn có một ràng buộc làm việc nếu yếu tố nhất thời bị loại bỏ khỏi chế độ xem bằng các phương tiện khác.


Điều này có lợi thế là không có các giá trị trùng lặp trong Storyboard và mã như chiều cao / chiều rộng của phần tử. Rất thanh lịch.
Robin Daugherty

Cảm ơn!! Nó đã giúp tôi
Parth Barot

Cách tiếp cận tuyệt vời, Cảm ơn bạn
Basil

5

Phân lớp xem và ghi đè func intrinsicContentSize() -> CGSize. Chỉ cần quay lại CGSizeZeronếu chế độ xem bị ẩn.


2
Điều đó thật tuyệt. Một số thành phần UI cần một kích hoạt invalidateIntrinsicContentSizemặc dù. Bạn có thể làm điều đó trong một ghi đè setHidden.
DrMickeyLauer

5

Tôi chỉ phát hiện ra rằng để có được một UILabel không chiếm dung lượng, bạn phải ẩn nó VÀ đặt văn bản của nó thành một chuỗi trống. (iOS 9)

Biết thực tế / lỗi này có thể giúp một số người đơn giản hóa bố cục của họ, thậm chí có thể là câu hỏi ban đầu, vì vậy tôi đoán rằng tôi đã đăng nó.


1
Tôi không nghĩ rằng bạn cần phải che giấu nó. Đặt văn bản của nó thành một chuỗi trống là đủ.
Rob VS

4

Tôi ngạc nhiên rằng không có một cách tiếp cận thanh lịch hơn được cung cấp bởi UIKit cho hành vi mong muốn này. Có vẻ như một điều rất phổ biến là muốn có thể làm được.

Vì việc kết nối các ràng buộc với IBOutletsvà đặt các hằng số của chúng thành 0cảm giác xui xẻo (và gây ra NSLayoutConstraintcảnh báo khi chế độ xem của bạn có các cuộc phỏng vấn), tôi đã quyết định tạo một tiện ích mở rộng mang lại cách tiếp cận đơn giản, rõ ràng để ẩn / hiển thị một UIViewràng buộc Bố cục tự động

Nó chỉ che giấu tầm nhìn và loại bỏ các ràng buộc bên ngoài. Khi bạn hiển thị lại chế độ xem, nó sẽ thêm các ràng buộc lại. Nhắc nhở duy nhất là bạn sẽ cần chỉ định các ràng buộc chuyển đổi dự phòng linh hoạt cho các chế độ xem xung quanh.

Chỉnh sửa Câu trả lời này được nhắm mục tiêu tại iOS 8.4 trở xuống. Trong iOS 9, chỉ cần sử dụng UIStackViewcách tiếp cận.


1
Đây là cách tiếp cận ưa thích của tôi quá. Đây là một cải tiến đáng kể cho các câu trả lời khác ở đây vì nó không làm giảm tầm nhìn về kích thước không. Cách tiếp cận bóng quần sẽ có vấn đề cho bất cứ điều gì ngoại trừ các khoản thanh toán khá nhỏ. Một ví dụ về điều này là khoảng trống lớn trong đó mục ẩn trong câu trả lời khác này . Và bạn có thể kết thúc với các vi phạm ràng buộc như đã đề cập.
Stewohn 10/11/2015

@DanielSchlaug Cảm ơn bạn đã chỉ ra điều đó. Tôi đã cập nhật giấy phép cho MIT. Tuy nhiên, tôi yêu cầu một tinh thần cao năm mỗi khi ai đó thực hiện giải pháp này.
Albert Bori

4

Cách thực hành tốt nhất là, một khi mọi thứ có các ràng buộc bố cục chính xác, hãy thêm chiều cao hoặc với ràng buộc, tùy thuộc vào cách bạn muốn các khung nhìn xung quanh di chuyển và kết nối ràng buộc vào một thuộc IBOutlettính.

Hãy chắc chắn rằng tài sản của bạn là strong

trong mã yo chỉ cần đặt hằng số thành 0 và kích hoạt nó, tho ẩn nội dung hoặc tắt nó để hiển thị nội dung. Điều này tốt hơn là làm rối tung giá trị không đổi một cách tiết kiệm - khôi phục nó. Đừng quên gọi layoutIfNeededsau đó.

Nếu nội dung cần ẩn được nhóm lại, cách tốt nhất là đặt tất cả vào một khung nhìn và thêm các ràng buộc cho khung nhìn đó

@property (strong, nonatomic) IBOutlet UIView *myContainer;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!!

-(void) showContainer
{
    self.myContainerHeight.active = NO;
    self.myContainer.hidden = NO;
    [self.view layoutIfNeeded];
}
-(void) hideContainer
{
    self.myContainerHeight.active = YES;
    self.myContainerHeight.constant = 0.0f;
    self.myContainer.hidden = YES;
    [self.view layoutIfNeeded];
}

Khi bạn đã thiết lập xong, bạn có thể kiểm tra nó trong IntefaceBuilder bằng cách đặt ràng buộc của bạn thành 0 và sau đó quay lại giá trị ban đầu. Đừng quên kiểm tra các ưu tiên ràng buộc khác để khi ẩn không có xung đột nào cả. cách khác để kiểm tra nó là đặt nó về 0 và đặt mức ưu tiên thành 0, nhưng, bạn không nên quên khôi phục lại mức ưu tiên cao nhất.


1
Đó là bởi vì ràng buộc và chế độ xem không phải lúc nào cũng được giữ lại bởi hệ thống phân cấp chế độ xem, vì vậy nếu bạn hủy kích hoạt ràng buộc và ẩn chế độ xem, bạn vẫn giữ chúng để hiển thị lại cho dù phân cấp chế độ xem của bạn có quyết định giữ lại hay không.
Jorge Arimany

3

Tôi xây dựng danh mục để cập nhật các ràng buộc dễ dàng:

[myView1 hideByHeight:YES];

Trả lời tại đây: Ẩn tự động xóa UIView: Cách nhận NSLayoutConstraint hiện có để cập nhật cái này

nhập mô tả hình ảnh ở đây


8
Thật không may, bạn có một khoảng cách gấp đôi ở đó, ngay nơi mũi tên màu vàng chỉ vào.
Zoltán

Cũng như các giải pháp khác ở trên, tôi nghĩ. Không lý tưởng chút nào.
Stewohn 10/11/2015

2

Phương pháp ưa thích của tôi rất giống với phương pháp được đề xuất bởi Jorge Arimany.

Tôi thích tạo ra nhiều ràng buộc. Đầu tiên tạo các ràng buộc của bạn khi nhãn thứ hai hiển thị. Tạo một lối thoát cho ràng buộc giữa nút và nhãn thứ 2 (nếu bạn đang sử dụng objc, hãy đảm bảo rằng nó mạnh). Ràng buộc này xác định chiều cao giữa nút và nhãn thứ hai khi nhìn thấy.

Sau đó tạo một ràng buộc khác chỉ định chiều cao giữa nút và nhãn trên cùng khi nút thứ hai bị ẩn. Tạo một ổ cắm cho ràng buộc thứ hai và đảm bảo ổ cắm này có một con trỏ mạnh. Sau đó bỏ chọninstalled hộp kiểm trong trình tạo giao diện và đảm bảo mức độ ưu tiên của ràng buộc thứ nhất thấp hơn mức ưu tiên ràng buộc thứ hai này.

Cuối cùng, khi bạn ẩn nhãn thứ hai, hãy chuyển đổi .isActive tính của các ràng buộc này và gọisetNeedsDisplay()

Và đó là nó, không có số Magic, không có toán học, nếu bạn có nhiều ràng buộc để bật và tắt, bạn thậm chí có thể sử dụng các bộ sưu tập ổ cắm để giữ cho chúng được tổ chức theo trạng thái. (AKA giữ tất cả các ràng buộc ẩn trong một OutletCollection và các ràng buộc không ẩn trong một cái khác và chỉ lặp lại qua mỗi bộ sưu tập để chuyển trạng thái .isActive của chúng).

Tôi biết Ryan Romanchuk nói rằng anh ấy không muốn sử dụng nhiều ràng buộc, nhưng tôi cảm thấy như đây không phải là micromanage-y, và đơn giản hơn là tự động tạo ra các quan điểm và các ràng buộc theo lập trình (đó là điều tôi nghĩ rằng anh ấy muốn tránh nếu tôi Tôi đang đọc câu hỏi đúng).

Tôi đã tạo một ví dụ đơn giản, tôi hy vọng nó hữu ích ...

import UIKit

class ViewController: UIViewController {

    @IBOutlet var ToBeHiddenLabel: UILabel!


    @IBOutlet var hiddenConstraint: NSLayoutConstraint!
    @IBOutlet var notHiddenConstraint: NSLayoutConstraint!


    @IBAction func HideMiddleButton(_ sender: Any) {

        ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
        notHiddenConstraint.isActive = !notHiddenConstraint.isActive
        hiddenConstraint.isActive = !hiddenConstraint.isActive

        self.view.setNeedsDisplay()
    }
}

nhập mô tả hình ảnh ở đây


0

Tôi cũng sẽ cung cấp giải pháp của mình, để cung cấp sự đa dạng.) Tôi nghĩ rằng việc tạo một lối thoát cho chiều rộng / chiều cao của mỗi mặt hàng cộng với khoảng cách là vô lý và làm nổ mã, các lỗi có thể và số lượng biến chứng.

Phương thức của tôi loại bỏ tất cả các khung nhìn (trong trường hợp UIImageView của tôi), chọn các khung nhìn cần được thêm lại và trong một vòng lặp, nó sẽ thêm lại các khung nhìn và tạo ra các ràng buộc mới. Nó thực sự rất đơn giản, xin vui lòng làm theo. Đây là mã nhanh và bẩn của tôi để làm điều đó:

// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];

// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];

// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
    [items addObject:self.twitterImageView];
}

// optionally add the location image
if (self.question.isLocal) {
    [items addObject:self.localQuestionImageView];
}

UIView *previousItem;
UIView *currentItem;

previousItem = items[0];
[self.contentView addSubview:previousItem];

// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
    previousItem = items[i - 1];
    currentItem = items[i];

    [self.contentView addSubview:currentItem];

    [currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(previousItem.mas_centerY);
        make.right.equalTo(previousItem.mas_left).offset(-5);
    }];
}


// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;

[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.right.equalTo(previousItem.mas_left);
    make.leading.equalTo(self.text.mas_leading);
    make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];

Tôi nhận được sạch sẽ, bố trí phù hợp và khoảng cách. Mã của tôi sử dụng Masonry, tôi thực sự khuyên bạn nên dùng nó: https://github.com/SnapKit/Masonry


0

Hãy thử BoxView , nó làm cho bố cục động ngắn gọn và dễ đọc.
Trong trường hợp của bạn, đó là:

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.