CLOSE SEARCH

Auto Layout #3. Auto Layout with Code

Auto Layout을 적용할 때, 대부분의 경우 Interface Builder에서 제공하는 기능만으로 다양한 조건을 만족시키는 제약을 추가할 수 있습니다. 제약을 WYSIWYG 방식으로 쉽게 추가할 수 있고, 제약에 문제가 있을 경우 즉각적으로 시각적인 피드백을 받을 수 있다는 점은 큰 장점입니다. 하지만 좀 더 복잡하고 동적인 제약을 구성하기 위해서는 코드를 통해 제약을 사용하는 방식에 익숙해져야 합니다.

당연한 예기겠지만 두번째 글에서 인터페이스 필더를 통해 구현한 UI는 코드를 통해서도 구현할 수 있습니다. 하드 코딩 방식에 비해 작성해야 하는 코드의 양이 많고 난이도가 조금 높은 것은 사실이지만 얻게 되는 이점이 매우 큰 것 또한 사실입니다.

NSLayoutConstraint

코드를 통해 제약을 추가할 때 사용하는 클래스는 NSLayoutConstraint 입니다. 첫번째 글에서 이 클래스가 제공하는 속성에 대해 간략하게 설명했습니다. 이 글에서는 이 클래스가 제공하는 메소드를 통해 제약을 추가하는 방법을 설명합니다.

코드를 통해 제약을 생성할 때는 “제약 공식을 활용하는 방법”과 “Visual Format Language를 활용하는 방법” 중 하나를 사용합니다. 선택한 방법에 따라 NSLayoutConstraint에서 제공하는 두 개의 메소드 중 하나를 사용하게 됩니다.

Creating Constraints with Fomular

제약 공식을 활용할 때는 다음과 같은 메소드를 사용합니다.

메소드의 파라미터는 다음과 같이 제약 공식(targetView.attribute = multiplier * referenceView.attribute + constant)의 요소와 1:1로 연결됩니다.

targetView -> view1
(targetView).attribute -> attr1
=, >=, <= -> relation
multiplier -> multiplier
referenceView -> view2
(referenceView.)attribute -> attr2
constant -> constant

view1 파라미터는 제약을 추가한 대상 뷰입니다. 이 파라미터에는 반드시 nil이 아닌 유효한 뷰를 전달해야 합니다. view2 파라미터는 제약을 설정할 때 참조하게 되는 뷰로, 경우에 따라서 nil이 될 수 있습니다. 예를 들어 고정된 크기 제약을 추가하는 경우에는 다른 뷰를 참조할 필요가 없기 때문에 이 파라미터로 nil을 전달합니다.

attr1, attr2 파라미터는 제약의 종류를 지정합니다. NSLayoutAttribute 열거형 값을 적용할 수 있으며, view2 파라미터가 nil인 경우 attr2 파라미터에는 NSLayoutAttributeNotAnAttribute(ObjC), NSLayoutAttribute.NotAnAttribute(Swift) 를 전달합니다.

relation은 제약 공식에서 두 변의 관계를 나타내는 값으로 코드에서는 NSLayoutRelation 열거형으로 표현됩니다.

나머지 multiplier와 constant는 제약 공식에서 설명했던 것과 동일한 의미를 가지며 모두 CGFloat 자료형으로 표현됩니다.

이제 이 메소드를 통해 제약을 만들어 보겠습니다. 추가할 제약은 두번째 글에서 만들었던 예제 중 화면 중앙에 위치하는 레이블에 추가되는 제약입니다. 이 레이블에는 다음과 같이 두개의 제약이 추가되어야 합니다. 앞서 설명한 메소드는 한번에 하나의 제약을 만들수 있기 때문에 총 두번의 호출이 필요합니다.

  • 상위 뷰의 centerX와 레이블의 centerX를 = 관계로 연결하는 제약
  • 상위 뷰의 centerY와 레이블의 centerY를 = 관계로 연결하는 제약
1. Xcode에서 새로운 Single View Application 프로젝트를 생성합니다.
2. ViewController.m(ObjC) 파일 또는 ViewController.swift(Swift) 파일을 선택합니다.
3. 다음과 같이 viewDidLoad에 새로운 레이블과 제약을 추가하는 코드를 구현합니다.

[6~9]
새로운 레이블을 생성합니다. 하드 코딩 방식과 달리 생성시점에 frame이나 bound 값을 지정하는 것은 아무런 의미가 없습니다. 그래서 일반적으로 파라미터가 없는 기본 생성자를 사용하거나(Swift), init 또는 new 메소드를 사용합니다(ObjC). 추가로 레이블의 위치와 크기를 쉽게 확인할 수 있도록 임의의 문자열과 배경색을 지정합니다.

두번째 글에서 뷰에 제약을 하나도 추가하지 않으면 Automatic Constraint가 자동으로 추가된다고 설명했습니다. UIView는 iOS 6 이후의 버전부터 내부적으로 Autoresizing Mask를 Auto Layout 제약으로 자동 변환하는데 사용되는 플래그 값을 가지고 있습니다. 이 플래그의 기본값이 YES이기 때문에 Automatic Constraint가 자동으로 추가되는 것입니다. 그러나 인터페이스 빌더에서 제약을 추가할 때와 달리 제약의 추가여부에 관계없이 Automatic Constraint가 항상 추가됩니다. 이로인해 하나의 뷰에 동일한 제약이 함께 추가되고 “제약 충돌” 오류의 원인이 됩니다. 이와 관련된 내용은 조금 후에 살펴보도록 하겠습니다. 코드를 통해서 제약을 추가할 때는 반드시 9번 라인과 같이 Automatic Constraint가 추가되지 않도록 해야 한다는 것을 기억하시기 바랍니다.

[11]

레이블을 가로 방향 중앙에 두는 제약을 추가합니다. 메소드로 전달된 파라미터의 의미는 설명이 없더라도 쉽게 이해하실 수 있을 것입니다. 참고로 Swift 코드에서 열거형을 사용할 때는 NSLayoutAttribute.CenterX 대신 .CenterX와 같이 축약된 형태를 사용할 수도 있습니다.

[19]

레이블을 세로 방향 중앙에 두는 제약을 추가합니다. 이 코드에서 주목해서 볼 것은 11번 라인과 달리 view1과 view2 파라미터가 서로 바뀌어 있다는 것입니다. 제약은 첫번째 글에서 설명했던 “누적되는 특성”과 더불어 순환되는 특성을 가지고 있습니다. 그래서 레이아웃 시스템이 UI를 구성하는데 모호함이 없다면 제약이 추가되는 대상이 바뀌어도 문제가 없습니다.

[27~28]

제약을 추가할 때는 UIView의 UIConstraintBasedLayoutInstallingConstraints 카테고리에서 제공하는 메소드를 사용합니다. 여기에서는 addConstraint: 메소드(ObjC) 또는 addConstraint() 메소드(Swift)를 사용해서 제약을 하나씩 추가하고 있습니다. 여러 제약을 동시에 추가할 때는 addConstraints: 메소드(ObjC) 또는 addConstraints() 메소드(Swift)를 사용할 수도 있습니다.

코드를 통해 제약을 추가할 때 특히 주의해야 하는 것은 제약이 추가되는 위치입니다. 고정된 크기 제약을 추가할 때는 다른 뷰를 참조할 필요가 없기 때문에 뷰 자체에 추가됩니다. 그러나 참조 뷰를 가지고 있는, 즉, view2 파라미터가 nil이 아닌 경우에는 두 뷰의 공통 부모 뷰에 제약을 추가해야 합니다.

view_hierarchy

예를 들어 뷰 D를 대상으로 하는 제약이 뷰 E를 참조하고 있다면 이 제약은 뷰 B에 추가해야 하고, 뷰 C 또는 F를 참조하고 있다면 가장 인접한 공통 조상인 뷰 A에 추가해야 합니다. 그리고 예제와 같이 동일한 뷰 계층구조에 있는 부모와 자식(또는 자손) 관계일 때는 부모 뷰에 제약을 추가합니다.

[30]

centerLabel를 뷰에 추가합니다.

4. 실행 결과를 확인합니다.

스크린샷 2015-01-28 오후 5.34.28

안타깝게도 한눈에 파악하기 힘든 로그를 출력하고 프로그램 실행이 중지될 것입니다. 인터페이스 빌더에서 제약을 추가할 때는 제약과 관련된 오류를 즉각적으로 발견할 수 있지만, 코드를 통해서 추가할 때는 컴파일 타임에 오류를 발견하기가 어렵습니다. 그래서 코드를 능숙하게 다루기 전까지 다양한 런타임 오류를 마주하게 됩니다. 다행스럽게도 Xcode는 제약의 문제점을 추적할 수 있는 다양한 디버깅 도구를 제공합니다. 이와 관련된 내용은 이어지는 글에서 자세히 다룰 예정입니다.

이 예제가 정상적으로 실행되지 않는 이유는 3번 단계에서 설명한 내용에서 힌트를 얻을 수 있습니다. 참조 뷰가 존재하지 않는 제약을 제외한 나머지 제약들은 부모 뷰 또는 인접한 공통 부모 뷰에 추가해야 한다고 설명했습니다. 레이아웃 시스템은 제약이 추가되는 시점에 대상 뷰와 참조되는 뷰가 위치한 뷰의 계층구조를 파악할 수 있어야 합니다. 하지만 [27~28] 라인에서 제약을 추가할 때, centerLabel은 어떠한 뷰에도 속하지 않은 상태입니다. 레이아웃 시스템이 추가된 제약에 따라 UI를 업데이트 해야 하는데 centerLabel의 계층구조 상 위치를 파악할 수 없어서 런타임 오류를 발생시키는 것입니다. 예제를 정상적으로 실행하려면 제약을 추가하기 전에 cunterLabel을 상위 뷰에 추가해야 합니다.

5. 30번 라인의 코드를 27번 라인 이전으로 이동시킵니다.

6. 실행 결과를 확인합니다.

스크린샷 2015-01-28 오후 5.52.45

스크린샷 2015-01-28 오후 5.52.49

Creating Constraints with Visual Format Language

이번에는 Visual Format Language(이하 VFL)를 사용해서 제약을 추가하는 방법에 대해 알아보겠습니다. VFL은 뷰의 특성이나 뷰 사이의 관계를 간결하게 표현하기 위해서 Apple에서 새롭게 개발한 언어입니다. 표현의 “풍부함”보다는 “간결함”에 중점을 두고 있어서 일부 복잡한 제약을 표현하는데는 무리가 있습니다. 개인적인 경험으로는 첫번째 방법을 통해 추가할 수 있는 제약의 90%이상을 VFL로 표현할 수 있었습니다.

VFL을 통해서 제약을 추가할 때는 아래와 같은 메소드를 사용합니다. 제약 공식을 사용하는 메소드와 달리 VFL로 표현된 모든 제약을 포함하고 있는 배열을 리턴합니다.

format 파라미터에는 VFL 문자열을 전달합니다.

opts 파라미터는 VFL 내에서 참조하고 있는 뷰의 정렬방식을 지정하는데 사용됩니다. metrics 파라미터는 VFL 내에서 사용하는 상수의 값을 담고 있는 딕셔너리 입니다. 두 파라미터에 대한 사용법은 이어지는 예제에서 설명할 예정입니다. 두 파라미터를 사용하지 않을 때는 각각 0, nil을 전달합니다.

views 파라미터는 VFL 내에서 참조하고 있는 뷰의 목록을 담고 있는 딕셔너리 입니다. VFL 내부의 상수와 뷰를 연결하는 매우 중요한 역할을 합니다.

Syntax & View Binding

VFL을 사용하기 위해서는 몇가지 새로운 개념에 익숙해져야 합니다.

가장 중요한 것은 VFL 문법과 VFL 내에서 뷰를 표현하고 바인딩하는 방법입니다. 우선 VFL에서 뷰를 표현할 때는 다음과 같이 [ ] 안에 바인딩 이름을 적어줍니다.

[viewBindingName]

viewBindingName은 앞서 설명한 메소드의 views 파라미터에 포함되어 있어야 합니다. 뷰를 바인딩하는 방법은 조금 후 예제를 통해 살펴보겠습니다.

두 뷰를 나란히 위치시킬 때는 다음과 같이 별도의 문자없이 연결합니다.

[view1][view2]

VFL은 한번에 하나의 방향에 대해서만 표현할 수 있습니다. 기본적으로 위의 VFL은 view1과 view2의 가로방향 위치를 지정합니다. 명시적으로 가로방향을 지정할 때는 VFL 앞에 H:  접두어를 추가합니다.

H:[view1][view2]

세로방향을 지정할 때는 V: 접두어를 추가합니다.

V:[view1][view2]

이 VFL에서 view2는 view1의 오른쪽이 아닌 아래쪽에 위치하게 됩니다.

두 뷰 사이의 공백을 추가할 때는 – (하이픈) 문자를 사용합니다. 다음과 같이 뷰 사이에 – 을 하나 추가하면 두 뷰 사이에 기본 여백이 추가됩니다.

[view1]-[view2]

여백의 크기를 지정할 때는 뷰 사이에 – 을 두개 적고, – 사이에 여백의 크기를 적어줍니다.

[view1]-10-[view2]

상위 뷰를 나타낼 때는 | 문자를 사용합니다. 예를 들어 view1이 화면 상단에서 10pt 아래쪽에  위치해야 한다면 다음과 같은 VFL을 사용합니다.

V:|-10-[view1]

뷰의 크기를 지정할 때는 뷰 이름 옆에 괄호를 적고 고정된 값을 적어줍니다.

[view1(100)]

이 VFL은 view1의 너비를 100pt로 설정합니다. 높이를 지정할 때는 다음과 같이 V: 접두어를 추가합니다.

V:[view1(100)]

제약의 우선순위를 지정할 때는 다음과 같이 값 뒤에 @를 적고 1~1000 사이의 우선순위를 적습니다. 우선순위를 생략하면 기본적으로 1000이 할당됩니다.

[view1(100)]-(10@750)-[view2(200@500)]

이 VFL에서 view1의 너비는 100pt이고 1000의 우선순위를 가집니다. view1과 view2 사이의 여백은 10pt이고 우선순위는 750입니다. view2의 너비는 200이고 우선순위는 500입니다. @ 양쪽에 위치하는 값의 성격을 혼동할 수 있으므로 주의해야 합니다.

두 뷰의 크기를 동일하게 설정할 때는 다음과 같이 ==을 사용하여 표현할 수 있습니다.

[view1(==view2)]

V:[view1(==view2)]

여기에서 알 수 있듯이 괄호 사이에는 설정된 값과의 관계 또는 다른 뷰와의 관계를 지정할 수 있습니다. VFL에서는 이것을 특별히 Predicate라고 합니다. Predicate는 ==, >=, <=와 같이 세가지 관계를 설정할 수 있습니다.

예를 들어 상위뷰의 시작점에서 10pt 떨어진 위치에 있는 100pt 너비의 뷰는

|-(10)-[view(100)]

와 같이 표현할 수 있습니다. 만약 10pt 이상 떨어진 위치에 있고, 크기가 100pt보다 작아질 수 있다면 다음과 같이 표현할 수 있습니다.

|-(>=10)-[view(<=100)]

좀 더 복잡한 조건을 추가하여 view의 최소 너비가 30pt 이상이어야 한다면

|-(>=10)-[view(>=30, <=100)]

와 같이 표현할 수 있습니다. 이처럼 괄호 사이에는 두개 이상의 조건을 추가할 수 있습니다.

지금까지 설명한 VFL 문법을 사용해서 하단 중앙에 위치하고 (100, 50)의 고정된 크기를 가지는 버튼을 위한 제약을 추가해보겠습니다.

7. 새로운 버튼을 생성한 후 뷰에 추가합니다.

버튼의 배경색과 제목을 지정한 후 Autoresizing Mask가 Auto Layou 제약으로 전환되는 것을 금지합니다.

8. VFL 내부에서 사용할 뷰에 대한 바인딩 딕셔너리를 생성합니다.

바인딩 딕셔니리를 생성할 때는 VFL 내부에서 사용할 이름을 키로 지정하고, 이 키와 연결할 뷰를 값으로 지정합니다.

Swift는 자료형을 엄격하게 제한하는 언어이기 때문에 views 딕셔너리의 자료형을 [NSObject: AnyObject]로 선언해 주어야 합니다. 그렇지 않을 경우에는 자료형과 관련된 오류가 발생합니다. views 자료형을 선언하는 코드를 제거하고 코드를 컴파일하면 정확한 이유를 파악하실 수 있을 겁니다.

매크로를 지원하지 않는 Swift와 달리 Objective-C에서는 NSDictionaryOfVariableBindings 매크로를 사용해서 바인딩 딕셔너리를 쉽게 구성할 수 있습니다. 물론 이 매크로를 사용하지 않고 직접 딕셔너리를 생성해도 됩니다.

9. 버튼에 추가할 제약을 생성합니다.

[1~7]

제약 공식을 사용하는 첫번째 방법을 통해 가로 방향 중앙에 위치하는 제약을 추가합니다. VFL은 표현의 “풍부함”보다는 “간결함”에 중점을 두고 있어서 일부 제약은 표현하기 힘들다고 설명했습니다. centerX와 같은 제약이 그 중 하나입니다. 물론 꼼수를 쓸 수도 있지만 제약 오류가 발생하기 쉽습니다. 그래서 제약 공식을 사용하는 방식과 VFL을 사용하는 방식을 함께 사용하는 것이 좋습니다. 뷰에 제약을 추가할 때 어느 한가지 방법으로 통일해야 하는 것은 아닙니다. 그러니 마음대로 섞어 씁시다!!

[9~12]

update 버튼의 너비 제약을 추가합니다. ObjC에서 options가 metrics 파라미터를 사용하지 않을때는 각각 0, nil을 전달합니다. 하지만 Swift에서는 NSLayoutFormatOptions.allZeros와 같이 options 파라미터의 자료형과 일치하는 열거형 값을 전달해야 합니다.

8번 단계에서 생성한 바인딩 딕셔너리를 마지막 파라미터로 전달합니다. 이를 통해 updateButton과 VFL 내부에서 사용되는 updateButton이 연결됩니다.

[13~16]

버튼의 수직 방향 제약을 추가합니다. V: 접두어가 추가된 것과 상위 뷰를 나타내는 |문자가 오른쪽 마지막 부분에만 추가되어 있을 것을 염두에 두고 해석해 보면 쉽게 이해되는 VFL 문자열입니다. 이 문자열은 버튼의 하단 여백과 높이를 나타내는 두개의 제약을 표현하고 있고, 메소드를 통해 리턴되는 배열에는 이 두개의 제약이 포함되어 있습니다.

10. 생성한 제약을 적절한 위치에 추가합니다.

horzConstraints는 다른 뷰와 연관되지 않은 제약이므로 updateButton에 추가합니다. 나머지 제약들은 updateButton의 상위 뷰와 연관된 제약이기 때문에 상위 뷰에 추가합니다.

11. 실행 결과를 확인합니다.

스크린샷 2015-01-28 오후 10.29.57

스크린샷 2015-01-28 오후 10.30.01

Metric Binding

마지막으로 레이블 아래쪽에 4pt의 라인 뷰를 추가하는 예제를 통해 Metric Binding에 대해 알아보겠습니다.

VFL 문자열에는 다양한 숫자값이 포함됩니다. 이 값이 특정 조건에 따라 달라져야 한다면 다음과 같이 형식화 문자열을 활용할 수 있습니다.

하지만 Auto Layout은 Metric Binding을 통한 좀 더 세련된 방법을 제공합니다. Metric Binding은 View Binding과 동일한 개념을 사용하여 VFL 문자열에서 사용할 식별자와 실제 값을 연결합니다.

12. 라인 뷰를 추가한 후 상위 뷰에 추가합니다.

13. VFL 문자열에서 사용할 View Binding 딕셔너리를 생성합니다.

14. lineView와 centerLabel의 너비를 일치시키는 제약을 추가합니다.

15. lineView의 높이와 centerLabel과의 여백을 지정하는 제약을 추가합니다.

metrics 딕셔너리는 VFL 문자열에서 사용할 식별자와 값을 포함하고 있습니다. 딕셔너리의 키는 반드시 문자열이어야 하고, 값은 반드시 NSNumber 형이어야 합니다. 이 딕셔너리에 포함된 margin 식별자는 2번 라인과 같이 centerLabel과 lineView 사이의 여백을 지정하는 데 사용할 수 있습니다. 이처럼 VFL 문자열에 포함된 식별자가 metrics 딕셔너리에 존재한다면 해당 값으로 교체됩니다.

이전에는 메소드를 통해 centerX 제약을 명시적으로 추가했습니다. 이번에는 정렬 옵션을 통해서 동일한 효과를 구현합니다. 정렬 옵션은 VFL 문자열에 포함된 뷰의 정렬 방식을 지정하는데 사용됩니다. 위의 코드와 같이 NSLayoutFormatAlignAllCenterX를 전달하면 centerLabel과 lineView를 가운데 정렬할 수 있습니다. centerLabel에는 가운데 정렬을 위한 제약이 이미 추가되어 있기 때문에 이 플래그를 통해 두 뷰를 가운데 정렬하면 lineView를 명시적으로 가운데 정렬한 것과 동일한 효과를 얻을 수 있습니다.

16. 생성된 제약을 뷰에 추가한 후 실행 결과를 확인합니다.

10번 단계에서 horzConstraints를 updateButton에 추가했던 것과 달리, 이번에는 lineView가 아닌 상위 뷰에 추가합니다. 그 이유는 lineView의 너비가 centerLabel의 너비와 연관되어 있고, 연관값이 있는 제약은 공통 부모 뷰에 추가해야 하기 때문입니다.

실행 결과를 보면 모든 제약이 정상적으로 적용되어 있는 것을 확인할 수 있습니다.

스크린샷 2015-01-29 오전 12.43.56

스크린샷 2015-01-29 오전 12.44.00

 

Enumerating Constraints

뷰에 포함되어 있는 제약을 열거할 때는 for in 반복문과 constraints 메소드의 조합을 활용합니다.

Remove Constraints

제약을 삭제할 때는 UIConstraintBasedLayoutInstallingConstraints 카테고리에서 제공하는 remove… 메소드를 사용합니다. 각 언어에서 제공하는 삭제 메소드는 다음과 같습니다.

 

And More…

지금까지 코드를 통해서 제약을 추가하고, 열거하고, 삭제하는 방법에 대해서 살펴보았습니다. 여기에서 설명했던 방법은 iOS 8뿐만 아니라 iOS 7 이하의 버전에서도 동일하게 사용할 수 있습니다. 이어지는 글에서는 iOS 8부터 도입된 Size Class와 제약을 추가하는 또 다른 방법에 대해 설명할 예정입니다. 그리고 Xcode에서 제공하는 다양한 도구를 통해 제약을 디버깅 하는 방법에 대해 설명할 예정입니다.

전체 소스는 아래의 링크에서 받으실 수 있습니다.

 

Filed under: iOS, Swift

  1. IPDev
    good! very helpful
  2. salt doll
    최곱니다. 감사 댓글을 안달 수가 없습니다. 수고 하셨습니다.
  3. 호링턴
    감사합니다. 정말 많은 도움이 되었습니다.
  4. 가렌
    감사합니다. 가뭄의 단비같은 글입니다.
  5. 가렌
    감사합니다. 가뭄의 단비같은 글입니다.
  6. Jason
    좋은 글 정말 감사합니다.
  7. AquaMacker
    좋은 글이네요 ^^; 많은 도움 됐습니다.
  8. dongkang
    좋은 설명과 글 잘 읽었습니다.
  9. hc
    자세한 내용 감사합니다. 덕분에 오토레이아웃에 대해서 자세히 알고가요~
  10. 많은 도움 되었습니다. 지금까지 저는 반쪽짜리 AutoLayout 밖에 사용을 못했었네요. 다음 프로젝트부터 적극적으로 사용해 볼 생각입니다.
  11. 알려JHO
    좋은글 잘 읽었습니다! 그런데 글을 읽는 중간에 궁금증이 생겨서 질문드립니다. 만약 화면에 뷰에 A, B라는 물체가 그려지는데 A.frame = CGRectMake(0, 0, 100, 100); B.frame = CGRectMake(A.frame.origin.x + A.frame.size.width, 0, self.view.bounds.size.width - (A.frame.origin.x + A.frame.size.width), 100); 같은 형태로 배치가 된다면 Auto Layout을 이용하는 방식으론 어떤식으로 코딩해야하나요?
    • Kei
      UIView* viewA = [UIView new];
      viewA.backgroundColor = [UIColor redColor];
      viewA.translatesAutoresizingMaskIntoConstraints = NO;
      [self.view addSubview:viewA];
      
      UIView* viewB = [UIView new];
      viewB.backgroundColor = [UIColor blueColor];
      viewB.translatesAutoresizingMaskIntoConstraints = NO;
      [self.view addSubview:viewB];
      
      NSDictionary* views = NSDictionaryOfVariableBindings(viewA, viewB);
      
      [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[viewA(100)][viewB]|" options:0 metrics:0 views:views]];
      [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[viewA(100)]" options:0 metrics:0 views:views]];
      [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[viewB(==viewA)]" options:0 metrics:0 views:views]];
      
      이렇게 하시면 됩니다!
  12. […] Auto Layout #3. Auto Layout with Code […]
    • 정말 잘봤습니다. 감사합니다. metrix을 NSString stringWithFormat]로 대체하여 사용할 수 있다고 봐도될까요?