Advanced Auto Layout

Panther·2021년 6월 2일
0

Auto Layout Guide

목록 보기
4/5

Programmatically Creating Constraints

가능하다면 인터페이스 빌더를 사용해 제약들을 설정하는 것이 좋습니다. 인터페이스 빌더는 시각화, 편집, 관리, 제약 디버그에 이르는 도구들을 제공합니다. 제약들을 분석하는 것을 통해 디자인 타임에서 많은 오류들을 파악할 수 있습니다. 이는 앱 실행 전에 문제들을 발견하고 수정할 수 있도록 해줍니다.

인터페이스는 지속적으로 증가하는 작업들을 관리할 수 있습니다. 인터페이스에서 모든 타입의 제약을 빌드할 수 있습니다(Working with Constraints in Interface Builder를 살펴보시기 바랍니다). 또한, size-class-specific 제약들을 구체화할 수 있고(Debugging Auto Layout를 살펴보시기 바랍니다), 스택뷰와 같은 새로운 도구를 사용해 런타임 동안 뷰를 동적으로 추가하거나 제거할 수 있습니다(Dynamic Stack View를 살펴보시기 바랍니다). 그러나 뷰 계층구조에서 몇 가지 동적인 변화들은 오직 코드에서만 다룰 수 있습니다.

Working with Constraints in Interface Builder는 이 시리즈의 첫 번째 글에서 볼 수 있으며, Debugging Auto Layout과 Dynamic Stack View는 각각 세 번째, 두 번째 글에서 볼 수 있습니다.

제약을 코드로 할 때 세 가지를 선택할 수 있습니다. layout anchor를 사용할 수 있습니다. 그리고 NSLayoutConstraint 클래스를 사용할 수도 있습니다. 또한, Visual Format 언어도 사용할 수 있습니다.

Layout Anchors

NSLayoutAnchor 클래스는 제약을 생성하기 위한 풍부한 인터페이스를 제공합니다. 이 API를 사용하려면 원하는 제약을 설정할 수 있는 아이템에서 anchor 속성에 접근해야 합니다. 예를 들어 뷰 컨트롤러의 top과 bottom 레이아웃 가이드는 topAnchor, bottomAnchor, heightAnchor 속성을 갖고 있습니다. 반면에 뷰는 갖고 있는 edge, center, size, baseline에 대한 anchor를 노출합니다.

NOTE
iOS에서 뷰는 layoutMarginsGuidereadableContentGuide 속성을 갖습니다. 이 속성은 뷰의 마진과 readable content guides 각각을 표현하는 UILayoutGuide 객체를 노출합니다.
마진 혹은 readable content guides에 대한 제약을 코드로 생성할 때 이 가이드를 사용해야 합니다.

레이아웃 anchor는 읽기 쉬운, 간결한 형식으로 제약을 생성할 수 있게 해줍니다. 아래에 보이는 것처럼 다른 타입의 제약을 생성하기 위한 몇 가지 메소드를 노출합니다.

Listing 13-1Creating layout anchors

// 슈퍼뷰의 레이아웃에 접근
let margins = view.layoutMarginsGuide

// 마진의 leading edge에 myView의 leading edge를 고정
myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true

// 마진의 trailing edge에 myView의 trailing edge를 고정
myView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true

// myView에 1:2 aspect ratio 적용
myView.heightAnchor.constraint(equalTo: myView.widthAnchor, multiplier: 2.0).isActive = true

Anatomy of a Constraint에 설명한 것처럼 제약은 간단한 선형 수식입니다.

Anatomy of a Constraint는 첫 번째 글에 있습니다.

레이아웃 anchor는 제약 생성을 위한 몇 가지 다른 메소드를 갖고 있습니다. 각각의 메소드는 결과에 영향을 미치는 수식의 요소만을 위한 파라미터를 포함합니다. 아래와 같은 코드에 있습니다.

myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true

심볼들은 아래에 나오는 수식의 부분들에 상응합니다.

EquationSymbol
Item 1myView
Attribute 1leadingAnchor
RelationshipconstraintEqualToAnchor
MultiplierNone (defaults to 1.0)
Item 2margins
Attribute 2leadingAnchor
ConstantNone (defaults to 0.0)

레이아웃 anchor는 추가적인 타입 세이프티를 제공합니다. NSLayoutAnchor 클래스는 타입 정보와 제약 생성을 위한 하위 클래스의 구체적 메소드를 추가하는 몇 가지 하위 클래스를 갖습니다. 이 클래스는 유효하지 않은 레이아웃 생성을 막아줍니다. 예를 들어 다른 수평 anchor에만 수평 anchor(leadingAnchor 혹은 trailingAnchor) 제약을 두도록 합니다. 유사하게 크기 제약에만 멀티플라이어를 적용할 수 있도록 합니다.

NOTE
이 규칙들은 NSLayoutConstraint API에 의해 강제되지는 않습니다. 대신 유효한 제약을 생성하면, 그 제약은 런타임에 예외를 반환합니다. 그러므로 레이아웃 anchor는 런타임 에러를 컴파일 타임 에러로 바꿔줍니다.

더 많은 정보는 NSLayoutAnchor Class Reference에 있습니다.

https://developer.apple.com/documentation/appkit/nslayoutanchor

NSLayoutConstraint Class

또한, NSLayoutConstraint 클래스의 constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: 편의 메소드를 사용해 직접 제약을 생성할 수 있습니다. 이 메소드는 제약 수식을 코드로 바꿔줍니다. 각 파라미터는 수식의 한 부분에 상응합니다(The constraint equation를 보시기 바랍니다).

The constraint equation는 이 시리즈의 첫 번째 글에 있습니다.

레이아웃 anchor API로 접근하는 방식과 다르게 각각의 파라미터에 값을 구체화해야만 하고, 레이아웃에 영향을 미치지 않는다고 하더라도 구체화해야 합니다. 최종 결과는 읽기 어려운 상당한 양의 boilerplace 코드일 것입니다. 예를 들어 아래에 있는 Listing 13-2 코드는 앞서 살펴본 Listing 13-1에 있는 코드와 동일한 의미를 갖습니다.

Listing 13-2Directly Instantiating Constraints

NSLayoutConstraint(item: myView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
 
NSLayoutConstraint(item: myView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0).isActive = true
 
NSLayoutConstraint(item: myView, attribute: .height, relatedBy: .equal, toItem: myView, attribute:.width, multiplier: 2.0, constant:0.0).isActive = true

NOTE
iOS에서 NSLayoutAttribute 열거형은 뷰의 마진에 대한 값들을 포함합니다. 이는 layoutMarginsGuide 속성을 거치지 않고 마진에 대한 제약을 생성할 수 있음을 의미합니다. 그러나 the readable content guides에 대한 제약들을 위해 readableContentGuide 속성을 사용할 필요가 있습니다.

레이아웃 anchor API와 달리 편의 메소드는 특정 제약의 중요한 기능에 강조표시를 하지 않습니다. 결과적으로 코드를 볼 때 중요 세부사항을 놓치기 쉽습니다. 추가적으로 컴파일러는 제약의 모든 정적 분석을 수행하지 않습니다. 유효하지 않은 제약들을 생성할 수 있습니다. 이 제약들은 런타임에 예외를 반환합니다. 그러므로 iOS 8 혹은 OS X v10.10 이전을 지원할 필요가 없는 한 새로운 레이아웃 anchor API에 코드를 마이그레이션하는 것을 고려해야 합니다.

더 자세한 사항은 NSLayoutConstraint Class Reference를 참고하시기 바랍니다.

NSLayoutConstraint Class Reference
https://developer.apple.com/documentation/appkit/nslayoutanchor

Visual Format Language

Visual Format 언어는 제약을 정의하기 위해 스트링과 유사한 ASCII-art를 사용할 수 있도록 해줍니다. 이 언어는 제약의 시각적으로 설명하는 표현을 제공합니다. Visual Formatting 언어는 아래와 같은 장단점이 있습니다.

  • 오토 레이아웃은 Visual Format 언어를 사용하면서 콘솔에 제약들을 출력합니다. 이런 이유로 디버깅 메시지는 제약을 생성하기 위해 사용된 코드와 매우 유사합니다.

  • Visual Format 언어는 매우 간결한 표현을 사용해 한 번에 여러 제약들을 생성할 수 있도록 해줍니다.

  • Visual Format 언어는 오직 유효한 제약만을 생성하도록 해줍니다.

  • 표기법은 완전함보다 좋은 시각화를 강조합니다. 그러므로 몇 가지 제약(예를 들어 aspect ratio)들은 Visual Format 언어를 사용해서 생성될 수 없습니다.

  • 컴파일러는 어떤 방식이더라도 스트링을 유효성 검사하지 않습니다. 런타임 테스팅에서만 실수를 발견할 수 있습니다.

Listing 13-1 예제를 Visual Format 언어를 사용해 다시 썼습니다.

Listing 13-3Creating constraints with the Visual Format Language

let views = ["myView" : myView]
let formatString = "|-[myView]-|"
 
let constraints = NSLayoutConstraint.constraints(withVisualFormat: formatString, options: .alignAllTop, metrics: nil, views: views)
 
NSLayoutConstraint.activate(constraints)

이 예제는 leading과 trailing 제약 모두를 생성하고 활성화합니다. Visual Format 언어는 항상 기본값 여백을 사용할 때 슈퍼뷰의 마진에 대한 제약을 0 포인트 제약으로 생성합니다. 그렇기 때문에 이 제약들은 앞선 예쩨와 동일합니다. 그러나 Listing 13-3은 aspect ratio 제약을 생성할 수 없습니다.

한 줄로 여러 아이템을 통해 더 복잡한 뷰를 생성하려고 한다면, Visual Format 언어는 수직 정렬과 수평 여백을 구체화합니다. 쓰여진 것처럼 “Align All Top” 옵션은 레이아웃에 영향을 미치지 않습니다. 왜냐하면 예제는 오직 하나의 뷰(슈퍼뷰는 포함하지 않고)만 있기 때문입니다.

Visual Format 언어를 사용해 제약을 생성하려면 아래 내용이 필요합니다.

  1. 뷰 딕셔너리를 생성합니다. 이 딕셔너리는 키를 위한 스트링과 뷰 객체를 포함해야 합니다(혹은 레이아웃 가이드처럼 오토 레이아웃에 의해 제약을 줄 수 있는 다른 아이템들). 포맷 스트링에서 뷰를 확인할 수 있도록 키를 사용해야 합니다.

NOTE
Objective-C를 사용할 때 뷰 딕셔너리를 생성하려면 NSDictionaryOfVariableBindings 매크로를 사용하시기 바랍니다. 스위프트에서는 딕셔너리를 직접 생성해야 합니다.

  1. (선택사항) 메트릭 딕셔너리를 생성해야 합니다. 이 딕셔너리는 키를 위한 스트링을 갖고 있어야 하고, 값을 위한 NSNumber 객체를 갖고 있어야 합니다. 포맷 스트링에서 상수값을 표현하기 위해 키를 사용해야 합니다.

  2. 아이템들의 단일 행 혹은 열을 배치하는 것을 통해 포맷 스트링을 생성합니다.

  3. NSLayoutConstraint 클래스의 constraintsWithVisualFormat:options:metrics:views: 메소드를 호출합니다. 이 메소드는 모든 제약을 포함하는 배열을 반환합니다.

  4. NSLayoutConstraint 클래스의 activateConstraints: 메소드 호출을 통해 제약들을 활성화합니다.

더 많은 정보는 Visual Format Language appendix를 살펴보시기 바랍니다.

Visual Format Language appendix는 이 시리즈의 마지막 글에 나옵니다.

Size-Class-Specific Layout

기본값의 인터페이스 빌더에 있는 스토리보드는 사이즈 클래스를 사용합니다. 사이즈 클래스는 씬 혹은 뷰와 같은 UI 요소들에 할당된 특성입니다. 이들은 요소 크기의 대략적인 표시를 제공합니다. 인터페이스 빌더는 현재 사이즈 클래스를 기준으로 많은 레이아웃의 기능들을 커스터마이징 할 수 있도록 해줍니다. 그러면 레이아웃은 사이즈 클래스에 변화가 있을 때 자동으로 적응합니다. 구체적으로 per-size-class basis에 따라 아래 기능들을 설정할 수 있습니다.

  • 뷰 혹은 컨트롤을 인스톨하거나 언인스톨합니다.

  • 제약을 인스톨하거나 언인스톨합니다.

  • 선택한 특성의 값(예를 들어 폰트와 레이아웃 마진 세팅)을 설정합니다.

시스템이 씬을 로드할 때, 시스템은 모든 뷰, 컨트롤, 제약들을 인스턴스화하고, 뷰 컨트롤러에서 이 아이템들에 적절한 아웃렛을 할당합니다. 씬의 현재 사이즈 클래스에 상관없이 아웃렛을 통해 모든 아이템에 접근할 수 있습니다. 그러나 시스템은 오직 현재 사이즈 클래스에서 인스톨될 때에만 시스템이 이 아이템들을 뷰 계층구조에 추가합니다.

뷰의 사이즈 클래스가 변화할 때, 시스템은 뷰 계층구조로부터 자동으로 아이템들을 추가하거나 제거합니다. 시스템은 또한 뷰의 레이아웃에 모든 변화를 애니메이션합니다.

NOTE
시스템은 언인스톨된 아이템들의 참조를 유지합니다. 그렇기 때문에 아이템들은 뷰 계층구조로부터 제거될 때 할당이 해제되지 않습니다.

Final and Base Size Classes

인터페이스 빌더는 아홉 가지 다른 사이즈 클래스를 인식합니다.

이들의 네 가지는 파이널 사이즈 클래스입니다. 네 가지는 Compact-Compact, Compact-Regular, Regular-Compact, Regular-Regular입니다. 파이널 클래스는 기기에 표시되는 실제 사이즈 클래스를 나타냅니다.

나머지 다섯 가지는 베이스 사이즈 클래스입니다. 다섯 가지는 Compact-Any, Regular-Any, Any-Compact, Any-Regular, Any-Any입니다. 이들은 둘 혹은 이상의 파이널 사이즈 클래스를 나타내는 추상 사이즈 클래스입니다. 예를 들어 Compact-Any 사이즈 클래스에서 인스톨된 아이템들은 Compact-Compact, Compact-Regular 사이즈 뷰 모두에서 나타납니다.

더 구체적인 사이즈 클래스에서 모든 설정은 더 일반적인 사이즈 클래스를 오버라이드합니다. 추가적으로 아홉 가지 사이즈 클래스와 베이스 사이즈 클래스에서 모호하지 않고 조건을 충족시킬 수 있는 레이아웃을 제공해야 합니다. 앱에서 기본값 레이아웃을 선택하고, Any-Any 사이즈 클래스에서 이 레이아웃을 디자인해야 합니다. 그러면 필요한 경우 다른 베이스 혹은 파이널 사이즈 클래스를 수정합니다.

Using the Size Class Tool

인터페이스 빌더의 사이즈 클래스 도구를 사용해 현재 편집하고 있는 사이즈 클래스를 선택합니다. 이 도구는 에디터 윈도우의 bottom 중앙에 표시됩니다. 기본값으로 인터페이스 빌더는 Any-Any 사이즈 클래스가 선택된 채로 시작합니다.

새로운 사이즈 클래스로 전환하려면 사이즈 클래스 도구를 클릭합니다. 인터페이스 빌더는 사이즈 클래스의 3 x 3 그리드를 포함하는 팝오버 뷰를 나타나게 합니다. 사이즈 클래스를 바꾸기 위해 마우스를 그리드 위로 올립니다. 그리드는 상단에 선택된 사이즈 클래스의 이름을 보여주고, 하단에 사이즈 클래스의 설명(기기와 오리엔티이션이 영향을 미치는 것도 포함해)을 보여줍니다. 또한, 현재 사이즈 클래스에 의해 영향을 받는 각가의 사이즈 클래스를 초록색 점으로 표시합니다.

캔버스에 추가된 모든 뷰와 제약은 현재 사이즈 클래스에서만 인스톨됩니다. 아이템을 제거할 때 어디서 어떻게 아이템이 제거되어야 하는지에 따라 움직임이 달라집니다.

  • 캔버스 혹은 document outline으로부터 아이템을 제거하는 것은 프로젝트 전체적으로 아이템을 제거합니다.

  • 아이템을 캔버스 혹은 document outline으로부터 Command-Deleting 하는 것은 현재 사이즈 클래스로부터만 아이템을 제거합니다.

  • 씬이 하나 이상의 사이즈를 갖고 있을 때, 캔버스 혹은 document outline이 아닌 모든 곳으로부터 아이템을 삭제하는 것은 오직 현재 사이즈 클래스로부터만 아이템을 언인스톨합니다.

  • 만약 오직 Any-Any 클래스만 편집한다면, 아이템을 삭제하는 것은 항상 프로젝트로부터 제거합니다.

만약 Any-Any 사이즈 클래스가 아닌 다른 사이즈 클래스를 편집하고 있다면, 인터페이스 빌더는 에디터 윈도우의 하단에서 툴바를 파란색으로 강조합니다. 이를 통해 더 구체적인 사이즈 클래스에서 작업할 때 더 명확하도록 만들어줍니다.

Using the Inspectors

또한, size-class-specific 세팅을 인스펙터에서 수정할 수 있습니다. size-class-specific 세팅을 지원하는 모든 것은 작은 플러스 아이콘 옆에 인스펙터에서 나타납니다.

기본값으로 인스펙터는 Any-Any 사이즈 클래스에 대한 값을 설정합니다. 더 구체적인 사이즈 클래스에 대한 다른 값을 설정하려면, 새로운 사이즈 클래스를 추가하기 위해 플러스 아이콘을 클릭해야 합니다. 추가하고자 하는 사이즈 클래스에서 넓이, 높이를 선택합니다.

인스펙터는 각각의 사이즈 클래스가 갖는 선을 보여줍니다. Any-Any 세팅은 top line이고, 더 구체적인 사이즈 클래스는 아래 방향에 목록으로 보여집니다. 각각에 대해 선의 값을 편집할 수 있습니다.

커스텀 사이즈 클래스를 제거하려면 선의 시작 부분에 x 아이콘을 클릭해야 합니다.

인터페이스 빌더에서 사이즈 클래스와 함께 작업하는 것에 대한 자세한 정보는 Size Classes Design Help에서 볼 수 있습니다.

Working with Scroll Views

스크롤뷰로 작업할 때, 슈퍼뷰 내부에서 스크롤뷰의 크기와 위치 모두를 정의해야 할 필요가 있고, 스크롤뷰가 갖는 컨텐트 영역의 크기를 정의해야 합니다. 이와 같은 모든 것은 오토 레이아웃을 사용해 설정할 수 있습니다.

스크롤뷰를 지원하기 위해 시스템은 제약이 어느 곳에 위치하는가에 따라 제약들을 다르게 해석합니다.

  • 다른 뷰처럼 스크롤뷰와 스크롤뷰 밖의 객체들이 갖는 모든 제약들은 스크롤뷰의 프레임에 붙습니다.

  • 스크롤뷰와 스크롤뷰의 컨텐트 사이에 있는 제약들에 대해서 동작은 제약 특성에 따라 다르게 움직입니다.

    • 스크롤뷰의 edge 혹은 마진과 스크롤뷰의 컨텐트 사이에 제약들은 스크롤뷰의 컨텐트 영역에 붙습니다.

    • 높이 넓이 혹은 중심 사이 제약들은 스크롤뷰의 프레임에 붙습니다.

  • 스크롤뷰 위에 컨텐트가 떠있는 것처럼 나타나도록, 스크롤뷰의 컨텐트에서 고정된 포지션을 제공하기 위해 스크롤뷰의 컨텐트와 스크롤뷰 밖에 있는 객체들에 대한 제약을 둘 수도 있습니다.

대부분의 일반적인 레이아웃 작업에서 스크롤뷰의 컨텐트를 포함시키기 위해 더미뷰 혹은 레이아웃 그룹을 사용하면 로직은 더 쉬워집니다. 인터페이스에서 작업할 때, 일반적인 접근법은 아래와 같습니다.

  1. 씬에 스크롤뷰를 추가합니다.

  2. 스크롤뷰의 크기와 위치를 정의하기 위해 제약들을 그립니다.

  3. 스크롤뷰에 뷰를 추가합니다. 컨텐트뷰에 뷰의 Xcode 구체화 레이블을 설정합니다.

  4. 스크롤뷰의 edge에 상응하도록 컨텐트뷰의 top, bottom, leading, trailing edge를 고정시킵니다.

REMEMBER
이 시점에 컨텐트뷰는 고정된 크기를 갖지 않습니다. 컨텐트뷰는 내부에 들어가는 모든 뷰와 컨트롤이 맞춰질 수 있도록 확장되고 늘어날 수 있습니다.

  1. (선택사항) 수평 스크롤링을 활성화하지 않기 위해서 스크롤뷰의 넓이에 컨텐트뷰의 넓이가 같도록 설정합니다. 컨텐트뷰는 스크롤뷰를 수평으로 채우게 됩니다.

  2. (선택사항) 수직 스크롤링을 활성화하지 않기 위해서 스크롤뷰의 높이에 컨텐트뷰의 높이가 같도록 설정합니다. 컨텐트뷰는 스크롤뷰를 수평으로 채웁니다.

  3. 컨텐트뷰의 내부에 스크롤뷰의 컨텐트를 놓습니다. 컨텐트뷰 내부에 컨텐트의 위치를 잡아주기 위해 제약들을 사용합니다.

IMPORTANT
레이아웃은 컨텐트뷰의 크기를 완전하게 정의해야 합니다(5, 6 단계에서 정의된 곳을 제외하고). 컨텐트의 내재된 크기를 기준으로 높이를 설정하려면, 끊김없는 제약들과 컨텐트뷰의 top edge에서 bottom edge 사이를 뻗어나갈 수 있는 뷰를 가져야 합니다. 유사하게 넓이를 설정하기 위해 컨텐트뷰의 leading edge로부터 trailing edge까지 끊김없는 제약과 뷰를 가지고 있어야 합니다.
만약 컨텐트가 내재된 컨텐트 크기를 갖지 않는다면, 컨텐트뷰 혹은 컨텐트에 적절한 크기 제약을 추가해야만 합니다.
컨텐트뷰가 스크롤뷰다 클 때, 스크롤뷰는 수직 스크롤링이 가능해집니다. 컨텐트뷰가 스크롤뷰보다 더 넓을 때, 스크롤뷰는 수평 스크롤링이 가능해집니다. 스크롤링은 기본값이 스크롤되지 않음입니다.

Working with Self-Sizing Table View Cells

iOS에서 테이블뷰 셀의 높이를 정의하기 위해 오토 레이아웃을 사용할 수 있습니다. 그러나 이 기능은 기본값으로 활성화되어 있지는 않습니다. self-sizing 테이블뷰 셀을 활성화시키려면 UITableViewAutomaticDimension에 테이블뷰의 rowHeight 속성을 설정해야 합니다. 또한, estimatedRowHeight 속성에 값을 할당해야 합니다. 이 속성들이 설정되자마자 시스템은 행의 실제 높이를 계산하기 위해 오토 레이아웃을 사용합니다.

tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension

다음으로 셀의 컨텐트뷰 내부에 테이블뷰 셀의 컨텐트를 놓습니다. 셀의 높이를 정의하려면 컨텐트뷰의 top edge와 bottom edge 사이 영역을 채우기 위해 끊김없는 제약들과 뷰(높이가 정의된)가 필요합니다. 뷰가 내재된 컨텐트 높이를 갖는다면, 시스템은 그 값을 사용합니다. 그렇지 않다면 뷰와 컨텐트뷰 자체에 높이 제약을 추가해줘야 합니다.

추가적으로 가능한 정확하게 측정된 행 높이를 만들어야 합니다. 시스템은 이 측정값을 기준으로 스크롤바 높이와 같은 아이템들을 계산합니다. 측정이 더 정확할 수록 사용자의 경험이 더 원활해집니다.

NOTE
테이블뷰 셀을 작업할 때, 사전에 정의된 컨텐트의 레이아웃(예를 들어 textLabel, detailTextLabel, imageView 속성)을 변경시킬 수 없습니다.
아래 제약들이 지원됩니다.

  • 셀의 컨텐트뷰에 상대적인 서브뷰를 위치시키는 제약들입니다.
  • 셀의 bounds에 상대적인 서브뷰를 위치시키는 제약들입니다.
  • 사전에 정의된 컨텐트에 상대적인 서브뷰를 위치시키는 제약들입니다.

Changing Constraints

제약 변경은 제약의 기본적은 수학적 표현을 바꾸는 것 모두입니다(Figure 17-1를 살펴보시기 바랍니다). Anatomy of a Constraint에서 제약 수식에 대해 학습할 수 있습니다.

Anatomy of a Constraint는 이 시리즈의 첫 번째 글에 나옵니다.

Figure 17-1The constraint equation

아래 보이는 것들은 하나 혹은 이상의 제약들을 변경시킵니다.

  • 제약을 활성화하거나 비활성화 하는 것입니다.

  • 제약의 상수값을 변경시키는 것입니다.

  • 제약의 우선순위를 변경시키는 것입니다.

  • 뷰 계층구조로부터 뷰를 제거하는 것입니다.

컨트롤의 속성을 설정하는 것 혹은 뷰 계층구조를 수정하는 것과 같은 다른 변경들 역시 제약을 변화시킬 수 있습니다. 변경사항이 발생하면 시스템은 지연된 레이아웃 패스를 스케줄합니다(The Deferred Layout Pass를 살펴보시기 바랍니다).

The Deferred Layout Pass는 아래에 나옵니다.

일반적으로 언제든지 이와 같은 변경사항을 만들 수 있습니다. 대부분의 제약은 인터페이스 빌더에서 설정되어야 하거나 컨트롤러가 초기 셋업(예를 들어 viewDidLoad)을 하는 동안 뷰 컨트롤러에 의해 코드로 생성되어야 합니다. 런타임에 동적인 제약 변경이 필요하다면, 앱의 상태 변화가 생길 때 제약을 변경하도록 하는 것이 최우선입니다. 예를 들어 버튼 탭의 반응으로 제약을 변경시키길 원한다면, 버튼의 액션 메소드에서 직접 해당 변경사항을 만들기 바랍니다.

때때로 성능상의 이유로 변경사항의 집합을 일괄적으로 처리해야 할 수도 있습니다. 더 많은 정보는 Batching Changes를 살펴보시기 바랍니다.

Batching Changes는 이 글의 아래 부분에 나옵니다.

The Deferred Layout Pass

영향을 받는 뷰의 프레임을 즉시 업데이트하는 것 대신 오토 레이아웃은 가까운 미래에 레이아웃 패스를 스케줄합니다. 이와 같은 지연된 패스는 레이아웃의 제약들을 업데이트하고, 뷰 계층구조에 있는 모든 뷰에 대한 프레임을 계산합니다.

setNeedsLayout 메소드 혹은 setNeedsUpdateConstraints 메소드를 호출하는 것을 통해 직접 지연된 레이아웃 패스를 스케줄할 수도 있습니다.

지연된 레이아웃 패스는 실제로 뷰 계층구조를 통해 두 가지 패스에 관여합니다.

  1. 업데이트 패스는 필요한 경우 제약들을 업데이트 합니다.

  2. 레이아웃 패스는 필요한 경우 뷰의 프레임을 재배치합니다.

Update Pass

시스템은 뷰 계층구조를 탐색하고 모든 뷰 컨트롤러에 대해 updateViewConstraints 메소드와 updateConstraints 메소드를 호출합니다. 제약의 변경사항을 최적화하기 위해 이 메소드들을 오버라이드 할 수도 있습니다. 더 많은 정보는 Batching Changes를 살펴보시기 바랍니다.

Batching Changes는 이 글의 아래 부분에 나옵니다.

Layout Pass

시스템은 다시 뷰 계층구조를 탐색하고 모든 뷰 컨트롤러에 대해 viewWillLayoutSubviews를 호출하고, 모든 뷰에 대해 layoutSubviews(OS X에서의 레이아웃)를 호출합니다. 기본값으로 layoutSubviews 메소드는 오토 레이아웃 엔진에 의해 계산되는 사각형으로 각각의 서브뷰가 갖는 프레임을 업데이트합니다. 레이아웃을 수정하기 위해 이 메소드들을 오버라이드 할 수 있습니다(Custom Layouts를 살펴보시기 바랍니다).

Custom Layouts는 이 글의 아래 부분에 나옵니다.

Batching Changes

영향을 미치는 변경사항이 발생한 후 즉시 제약을 업데이트하는 것이 대부분 항상 명확하고 쉽습니다. 변경사항을 지연시키는 것을 이후 메소드에서 담으려면 코드는 더 복잡하고 이해하기 어려워집니다.

그러나 성능상의 이유로 변경들을 일괄처리하려고 할 때가 있습니다. 이 경우 제약을 변경시키는 것이 너무 느릴 때 혹은 뷰가 몇 가지 중복적인 변경사항을 만들 때에만 마무리가 됩니다.

변경사항을 일괄처리하려면 변경사하을 직접 만드는 것 대신 제약을 갖고 있는 뷰에 setNeedsUpdateConstraints 메소드를 호출해야 합니다. 이후 영향을 받는 제약들을 수정하기 위해 뷰의 updateConstraints 메소드를 오버라이드 합니다.

NOTE
updateConstraints 구현은 가능한 효율적이어야 합니다. 모든 제약들을 비활성화시키지 말고, 필요한 한 가지를 재활성화해야 합니다. 대신 앱은 제약을 추적하는 몇 가지 방법을 갖고 있어야 하고, 각각의 업데이트 패스가 발생하는 동안 유효한지 검증해야 합니다. 변경이 필요한 아이템들만 변경해야 합니다. 각각의 업데이트 패스가 발생하는 동안 앱의 현재 상태에 따라 적절한 제약들을 갖도록 해야 합니다.

항상 updateConstraints 메소드의 구현 마지막 부분에서 슈퍼클래스 구현을 호출해야 합니다.

updateConstraints 메소드 내부에 setNeedsUpdateConstraints를 호출하지 않아야 합니다. setNeedsUpdateConstraints를 호출하는 것은 피드백 루프를 생성하면서 다른 업데이트 패스를 스케줄합니다.

Custom Layouts

레이아웃 엔지에 의해 반환되는 결과를 수정하려면 viewWillLayoutSubviews 혹은 layoutSubviews 메소드를 오버라이드해야 합니다.

IMPORTANT
만약 간으하다면 모든 레이아웃을 정의하기 위해 제약들을 사용하시기 바랍나다. 레이아웃 결과는 더 로버스트하고 디버그하기 쉬워집니다. 제약들 홀로 표현될 수 없는 레이아웃 생성이 필요할 때 오직 viewWillLayoutSubviews 혹은 layoutSubviews 메소드만을 오버라이드해야 합니다.

이 메소드들을 오버라이드할 때 레이아웃은 일관되지 않은 상태입니다. 몇 가지 뷰는 이미 놓여져 있습니다. 다른 뷰들은 그렇지 않습니다. 뷰 계층구조를 어떻게 수정할지에 대해 매우 신중할 필요가 있습니다. 혹은 피드백 루프를 생성할 수 있습니다. 아래 규칙들은 피드백 루프를 피할 수 있도록 도와줘야만 합니다.

  • 메소드 어딘가에 슈퍼클래스의 구현을 호출해야 합니다.

  • 하위 트리에서 뷰의 레이아웃을 안전하게 무효화할 수 있습니다. 그러나 슈퍼클래스의 구현 호출 전에 해줘야 합니다.

  • 하위 트리 밖에 있는 모든 뷰의 레이아웃에 대해서 무효화시키지 않아야 합니다. 이는 피드백 루프를 생성할 수 있습니다.

  • setNeedsUpdateConstraints를 호출하지 않아야 합니다. 업데이트 패스를 완료했습니다. 이 메소드르 호출하면 피드백 루프를 생성합니다.

  • setNeedsLayout를 호출하지 않아야 합니다. 이 메소드는 피드백 루프를 생성합니다.

  • 제약들을 변경시키는 것에 대해 신중해야 합니다. 하위 트리 밖의 모든 뷰에 대한 레이아웃을 실수로 무효화하는 것을 원하지 않을 것입니다.

0개의 댓글