숨겨진 UIView가있는 자동 레이아웃?


164

비즈니스 로직에 따라 UIViews가장 자주 표시 / 숨기는 것이 일반적인 패러다임 인 것 같습니다 UILabels. 내 질문은 AutoLayout을 사용하여 프레임이 0x0 인 것처럼 숨겨진 뷰에 응답하는 가장 좋은 방법은 무엇입니까? 다음은 1-3 개의 기능으로 구성된 동적 목록의 예입니다.

동적 기능 목록

지금은 버튼에서 마지막 레이블까지 10px의 상단 공간이 있으며 레이블이 숨겨져 있으면 분명히 미끄러지지 않습니다. 지금 까지이 제약 조건에 대한 콘센트를 만들고 표시하는 레이블 수에 따라 상수를 수정했습니다. 음수 상수 값을 사용하여 숨겨진 프레임 위로 버튼을 밀어 올리므로 분명히 해킹입니다. 실제 레이아웃 요소에 구속되지 않고 다른 요소의 알려진 높이 / 패딩을 기반으로 몰래 정적 계산을 수행하고 AutoLayout이 만들어진 것과 분명히 싸우기 때문에 나쁘다.

동적 레이블에 따라 새로운 제약 조건을 만들 수는 있지만 공백을 축소하려고 시도하는 것에 대한 많은 미세 관리 및 자세한 표시입니다. 더 나은 접근 방법이 있습니까? 프레임 크기 0,0을 변경하고 AutoLayout이 제약 조건을 조작하지 않고 그 일을 수행하게합니까? 뷰를 완전히 제거 하시겠습니까?

솔직히, 숨겨진보기의 컨텍스트에서 상수를 수정하면 간단한 계산으로 한 줄의 코드가 필요합니다. 새로운 제약 조건을 재현하는 constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:것은 너무 무거워 보입니다.

2018 년 2 월 편집 : UIStackViews로 Ben의 답변보기


이 질문에 대해 Ryan에게 감사합니다. 당신이 요청한대로 사건에 대해 어떻게해야할지 고민했습니다. autolayout에 대한 자습서를 확인할 때마다 대부분이 raywenderlich 자습서 사이트를 참조한다고 말합니다.
Nassif

답변:


82

UIStackView아마도 iOS 9 이상으로가는 길일 것입니다. 숨겨진보기를 처리 할뿐만 아니라 올바르게 설정된 경우 추가 간격과 여백도 제거합니다.


8
UITableViewCell에서 UIStackView를 사용할 때주의하십시오. 나에게보기가 매우 복잡해지면 셀이 스크롤 될 때마다 스크롤링이 끊기면서 스크롤이 고르지 않게되었습니다. 표지 아래에서 StackView는 제약 조건을 추가 / 제거하고 있으며 부드러운 스크롤에 충분할 필요는 없습니다.
Kento

7
스택보기가 이것을 처리하는 방법에 대한 작은 예를 제시하면 좋을 것입니다.
lostintranslation

@ Kento는 여전히 iOS 10에서 문제입니까?
Crashalot

3
5 년 후 ... 이것을 업데이트 된 답변으로 설정해야합니까? 지금은 세 가지 릴리스 주기로 사용할 수 있습니다.
Ryan Romanchuk

1
@RyanRomanchuk 당신은 지금, 이것은 확실히 최고의 답변입니다. 고마워 벤!
Duncan Luk

234

뷰를 표시하거나 숨기는 데 개인적으로 선호하는 것은 적절한 너비 또는 높이 제약 조건으로 IBOutlet을 만드는 것입니다.

그런 다음 constant값을 업데이트하여 0숨기거나 값을 표시해야합니다.

이 기술의 가장 큰 장점은 상대 구속 조건이 유지된다는 것입니다. 예를 들어 가로 간격이 x 인 뷰 A와 뷰 B가 있다고 가정 해 봅시다 . 뷰 A 너비 constant가로 설정 되면 뷰 0.fB가 왼쪽으로 이동하여 해당 공간을 채 웁니다.

제약 조건을 추가하거나 제거 할 필요가 없습니다. 이는 무거운 작업입니다. 제약 조건을 간단히 업데이트하면 constant트릭을 수행합니다.


1
바로 그거죠. 이 예에서는 가로 간격을 사용하여보기 B를보기 A의 오른쪽에 배치합니다. 이 기술을 항상 사용하고 잘 작동합니다
Max MacLeod

9
@MaxMacLeod 확인하기 위해 : 두보기 사이의 간격이 x 인 경우,보기 B 가보기 A 위치에서 시작하려면 간격 제약 상수도 0으로 변경해야합니까?
kernix

1
@kernix 예, 두 뷰 사이에 수평 간격 제약이 필요한 경우. 물론 상수 0은 간격이 없음을 의미합니다. 따라서 너비를 0으로 설정하여보기 A를 숨기면보기 B가 왼쪽으로 이동하여 컨테이너와 플러시됩니다. 투표에 감사드립니다!
Max MacLeod

2
@MaxMacLeod 답변 주셔서 감사합니다. 다른보기가 포함 된 UIView 로이 작업을 시도했지만 높이 제약 조건을 0으로 설정하면 해당보기의 하위보기가 숨겨지지 않습니다.이 솔루션은 하위보기가 포함 된보기에서 작동합니까? 감사!
shaunlim

6
다른 뷰를 포함하는 UIView에서 작동하는 솔루션에 대해서도 감사하겠습니다.
Mark Gibaud

96

0숨겨 졌을 때 상수를 사용하고 다시 표시하면 다른 상수 를 사용하는 솔루션 은 작동하지만 내용의 크기가 가변적이면 만족스럽지 않습니다. 유연한 콘텐츠를 측정하고 일정하게 되돌려 야합니다. 서버 또는 UI 이벤트로 인해 컨텐츠 크기가 변경되면 문제가 발생합니다.

더 나은 해결책이 있습니다.

자동 레이아웃 공간을 차지하지 않도록 요소를 숨길 때 높이 규칙을 0 우선 순위로 설정하는 것이 좋습니다.

그렇게하는 방법은 다음과 같습니다.

1. 우선 순위가 낮은 인터페이스 빌더에서 너비 (또는 높이)를 0으로 설정하십시오.

너비 0 규칙 설정

우선 순위가 낮기 때문에 Interface Builder는 충돌에 대해 소리 치지 않습니다. 우선 순위를 일시적으로 999로 설정하여 높이 동작을 테스트하십시오 (1000은 프로그래밍 방식으로 변경하는 것이 금지되어 있으므로 사용하지 않습니다). 인터페이스 빌더는 이제 충돌하는 제약 조건에 대해 소리 칠 것입니다. 관련 개체의 우선 순위를 900 정도로 설정하여이 문제를 해결할 수 있습니다.

2. 코드에서 너비 제약 조건의 우선 순위를 수정할 수 있도록 콘센트를 추가하십시오.

콘센트 연결

3. 요소를 숨길 때 우선 순위를 조정하십시오.

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

@SimplGy이 장소가 무엇인지 말해 주시겠습니까?
Niharika

그것은 @Niharika의 일반적인 해결책의 일부가 아닙니다. 제 경우에는 비즈니스 규칙이였습니다 (레스토랑이 곧 닫히지 않으면 견해를 보여주십시오).
SimplGy

11

이 경우 Author 레이블의 높이를 적절한 IBOutlet에 매핑합니다.

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

구속 조건의 높이를 0.0f로 설정하면 재생 버튼의 높이가 허용하기 때문에 "패딩"을 유지합니다.

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

여기에 이미지 설명을 입력하십시오


9

여기에는 많은 솔루션이 있지만 일반적인 접근 방식은 다시 다릅니다. :)

Jorge Arimany와 TMin의 대답과 비슷한 두 가지 제약 조건을 설정하십시오.

여기에 이미지 설명을 입력하십시오

표시된 세 제약 조건 모두 상수 값이 동일합니다. A1 및 A2로 표시된 제약 조건의 우선 순위는 500으로 설정되어 있고 B로 표시된 제약 조건의 우선 순위는 250 (또는 UILayoutProperty.defaultLow코드)으로 설정되어 있습니다.

구속 조건 B를 IBOutlet에 연결하십시오. 그런 다음 요소를 숨길 때 구속 조건 우선 순위를 높음 (750)으로 설정하면됩니다.

constraintB.priority = .defaultHigh

그러나 요소가 표시되면 우선 순위를 다시 낮음 (250)으로 설정하십시오.

constraintB.priority = .defaultLow

isActive구속 조건 B를 변경 하는 것보다이 방법의 장점은 (사소하게도) 다른 방법으로 과도 요소가 뷰에서 제거되는 경우에도 여전히 구속 조건이 있다는 것입니다.


스토리 보드와 요소 높이 / 너비와 같은 코드에 중복 값이없는 장점이 있습니다. 매우 우아합니다.
Robin Daugherty

감사!! 그것은 나를 도왔다
Parth Barot

위대한 접근, 감사합니다
바질

5

뷰를 서브 클래 싱하고 재정의 func intrinsicContentSize() -> CGSize합니다. CGSizeZero보기가 숨겨져 있으면 돌아 오십시오 .


2
대단하다. 일부 UI 요소에는 트리거가 필요합니다 invalidateIntrinsicContentSize. 재정의 된 작업을 수행 할 수 있습니다 setHidden.
DrMickeyLauer

5

방금 UILabel이 공간을 차지하지 않도록하려면 숨기고 텍스트를 빈 문자열로 설정해야합니다. (iOS 9)

이 사실 / 버그를 알면 일부 사람들이 레이아웃을 단순화하는 데 도움이 될 수 있습니다. 아마도 원래 질문의 레이아웃조차도 게시 할 것이라고 생각했습니다.


1
숨길 필요는 없다고 생각합니다. 텍스트를 빈 문자열로 설정하면 충분합니다.
Rob VS

4

UIKit이 원하는 행동 에 대해 더 우아한 접근 방식이 제공되지 않는다는 것에 놀랐습니다 . 할 수 있기를 원하는 것은 매우 일반적인 것 같습니다.

제약 조건을 연결 IBOutlets하고 상수를 0유쾌 하게 느끼 도록 설정하고 ( NSLayoutConstraint뷰에 하위 뷰가있을 때 경고가 발생 함 ) 자동 레이아웃 제약 조건이 있는 것을 숨기거나 표시하는 간단한 상태 기반 접근 방식을 제공하는 확장을 만들기로 결정했습니다.UIView

단지 뷰를 숨기고 외부 구속 조건을 제거합니다. 뷰를 다시 표시하면 구속 조건이 다시 추가됩니다. 유일한 경고는 주변 뷰에 대해 유연한 장애 조치 제약 조건을 지정해야한다는 것입니다.

편집 이 답변은 iOS 8.4 이하를 대상으로합니다. iOS 9에서는 UIStackView접근 방식 만 사용하십시오 .


1
이것은 내가 선호하는 접근 방식입니다. 뷰를 제로 크기로 스쿼시하지 않기 때문에 다른 답변에서 크게 개선되었습니다. 스쿼시 접근 방식은 사소한 지불금 이외의 문제에 대해 노크 할 것입니다. 이것의 예는 숨겨진 항목 이이 다른 대답 에있는 큰 간격 입니다. 언급 한대로 제약 조건 위반으로 끝날 수 있습니다.
Benjohn

@DanielSchlaug 지적 해 주셔서 감사합니다. 라이센스를 MIT로 업데이트했습니다. 그러나 누군가 가이 솔루션을 구현할 때마다 정신적으로 5가 필요합니다.
Albert Bori

4

가장 좋은 방법은 모든 것이 올바른 배치 구속 조건을 가지고 있으면 주변 뷰를 이동하고 구속 조건을 IBOutlet특성에 연결하는 방법에 따라 높이 또는 구속 조건을 추가하는 것 입니다.

귀하의 속성이 strong

코드에서 yo는 상수를 0으로 설정하고 활성화 하여 내용을 숨기거나 비활성화 하여 내용을 표시해야합니다. 이것은 일정한 값으로 저장 복원하는 것보다 엉망입니다. layoutIfNeeded나중에 전화하는 것을 잊지 마십시오 .

숨길 내용을 그룹화하는 경우 가장 좋은 방법은 모두를 뷰에 넣고 제약 조건을 해당 뷰에 추가하는 것입니다.

@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];
}

설정이 완료되면 제약 조건을 0으로 설정 한 다음 원래 값으로 다시 설정하여 IntefaceBuilder에서 테스트 할 수 있습니다. 다른 제약 우선 순위를 확인하는 것을 잊지 마십시오. 숨겨져있을 때 전혀 충돌이 없습니다. 테스트하는 다른 방법은 0으로 설정하고 우선 순위를 0으로 설정하는 것이지만 다시 높은 우선 순위로 복원하는 것을 잊지 마십시오.


1
제약 조건과 뷰가 항상 뷰 계층 구조에 의해 유지 될 수 없었기 때문에 제약 조건을 비활성화하고 뷰를 숨기면 뷰 계층 구조가 유지하기로 결정한 것에 관계없이 뷰를 다시 표시하기 위해 계속 유지합니다.
Jorge Arimany


2

내가 선호하는 방법은 Jorge Arimany가 제안한 방법과 매우 유사합니다.

여러 제약 조건을 만드는 것을 선호합니다. 먼저 두 번째 레이블이 표시 될 때 제한 조건을 작성하십시오. 버튼과 두 번째 레이블 사이의 구속을위한 배출구를 만듭니다 (objc를 사용하는 경우 강점을 확인하십시오). 이 구속 조건은 버튼과 보이는 두 번째 레이블 사이의 높이를 결정합니다.

그런 다음 두 번째 버튼이 숨겨져있을 때 버튼과 상단 레이블 사이의 높이를 지정하는 다른 구속 조건을 작성하십시오. 두 번째 구속 조건에 대한 콘센트를 작성하고이 콘센트에 강력한 포인터가 있는지 확인하십시오. 그런 다음 installed인터페이스 빌더 에서 확인란을 선택 취소하고 첫 번째 제한 우선 순위가 두 번째 제한 우선 순위보다 낮은 지 확인하십시오.

마지막으로 두 번째 레이블을 숨길 때 .isActive이러한 제약 조건 의 속성을 토글하고setNeedsDisplay()

그리고 그것은 마법의 숫자, 수학이 아닙니다. 여러 제약 조건을 켜고 끄는 경우 콘센트 모음을 사용하여 상태별로 구성 할 수도 있습니다. (AKA는 모든 숨겨진 제약 조건을 하나의 OutletCollection과 숨겨지지 않은 제약 조건을 다른 OutletCollection에 유지하고 각 컬렉션을 반복하여 .isActive 상태로 전환합니다).

Ryan Romanchuk은 여러 제약 조건을 사용하고 싶지 않다고 말했지만, 이것이 미세 관리가 아니라고 생각하고 프로그래밍 방식으로 뷰와 제약 조건을 동적으로 만드는 것이 더 간단합니다 (그가 내가 피할 경우 피하고 싶었던 생각입니다) 질문을 올바르게 읽고 있습니다).

나는 간단한 예를 만들었습니다.

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()
    }
}

여기에 이미지 설명을 입력하십시오


0

나는 다양한 솔루션을 제공하기 위해 솔루션을 제공 할 것입니다.) 각 항목의 너비 / 높이에 간격을 더한 콘센트를 만드는 것은 말도 안되며 코드, 가능한 오류 및 합병증의 수를 증가시킵니다.

내 메소드는 모든보기 (내 경우에는 UIImageView 인스턴스)를 제거하고 다시 추가해야 할 뷰를 선택하고 루프에서 다시 추가하고 새로운 제약 조건을 만듭니다. 실제로는 간단합니다. 따라 해주세요. 빠르고 더러운 코드는 다음과 같습니다.

// 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);;
}];

깨끗하고 일관된 레이아웃과 간격을 얻습니다. 내 코드는 Masonry를 사용하므로 강력히 권장합니다 : https://github.com/SnapKit/Masonry


0

BoxView를 사용해보십시오 . 동적 레이아웃이 간결하고 읽기 쉽습니다 .
귀하의 경우 :

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.