CLOSE SEARCH

Auto Layout #1. Overview

Auto Layout은 제약(Constraints) 기반의 서술형 레이아웃 시스템입니다. WWDC 2011에서 OS X Lion과 함께 Cocoa Auto Layout이름으로 처음 발표되었습니다. 첫번째 버전에서는 OS X에서만 사용가능한 기술이었지만  WWDC 2012에서 iOS 6 이상의 버전에 대한 지원을 추가하고 명칭도 플랫폼에 제한적이지 않은 Auto Layout으로 변경하였습니다. Auto Layout에서 사용하는 개념과  API들은 두 플랫폼에서 거의 동일하게 사용할 수 있어서 하나의 플랫폼에 대해 익혀두면 다른 플랫폼에서도 Auto Layout을 문제없이 사용할 수 있습니다. 이 글에서는 iOS 플랫폼을 기준으로 설명하고 있습니다.

아직도 많은 개발자들이 Auto Layout 보다 하드 코딩 방식을 선호하고 있습니다. Auto Layout을 사용하지 않는 이유를 물어봤을때 대부분 배우기 어렵고 하드 코딩이 편하다고 답했습니다. 물론 iPhone 6 시리즈가 출시되기 전에 화면 해상도가 2가지인 상황에서는 UI 구성에 사용되는 화면 너비가 동일하고 높이만 고려하면 문제가 없었습니다. 그래서 Auto Layout을 배움으로써 얻는 이점이 하드 코딩 방식의 익숙함을 뛰어 넘지 못했다고 생각됩니다. 하지만! iPhone 6 시리즈가 두 가지 크기로 출시된 현 시점에서 Auto Layout은 더 이상 선택사항이 아닌 반드시 익숙해져야 하는 필수사항이 되었습니다.

Auto Layout의 가장 큰 매력은 하드 코딩 방식에 비해 월등히 적은 양의 코드(또는 코드 없이)로 다양한 화면 크기와 방향에 대응할 수 있는 UI를 쉽게 개발할 수 있다는 것입니다. 다만, 처음에 익숙해져야 할 개념들이 조금 많을 뿐입니다.

Auto Layout을 사용하면서 항상 염두에 두고 있어야하는 가장 중요한 개념은 “frame, bound, center 속성을 통해 뷰의 크기와 위치를 조절하는 주체는 개발자가 아닌 레이아웃 시스템이다.” 라는 것입니다. 개발자가 의도에 적합한 제약들을 구성해 두면 레이아웃 시스템이 화면 크기나 방향에 따라 좌표를 “자동”으로 계산하고 적용합니다. Auto 라는 단어가 들어가 있어서 레이아웃을 자동으로 만들어 준다는 것으로 생각할 수 있지만, 안타깝게도 그런 인공지능은 아직 존재하지 않습니다. 개발자는 이제 화면 좌표가 아닌 제약을 고려해야 합니다. 예를 들어 하드 코딩 방식에서 뷰를 추가할 때는 다음과 같이 생각했었습니다.

A뷰는 좌표 (100,100)에 위치하고 (200,200)의 크기를 가진다.
B뷰는 좌표(130, 0)에 위치하고 (60, 40)의 크기를 가진다.

Auto Layout에서는 좌표 대신 인접한 다른 뷰나 상위 뷰와의 관계를 생각해야 합니다.

A뷰는 상위 뷰의 시작점과 100만큼의 여백을 가지고 200×200의 고정된 크기를 가진다.
B뷰는 화면 상단 중앙에 위치하고 포함된 내용에 따라 크기가 변경되도록 Intrinsic Size를 사용한다. 

처음에는 이 두가지 생각의 차이가 어떤 것이지 잘 느껴지지 않을수도 있습니다. 중요한 것은 뷰의 특성이나 뷰와 뷰 사이의 관계를 제약으로 구성할 수 있는 능력이고, 이 능력을 키우기 위해서는 다양한 제약을 구성하는 연습을 해야 합니다.

 

Constraints

제약(Constraint)은 “버튼은 화면 상단 중앙에 있어야 한다.”, “A뷰와 B뷰의 여백은 8pt 보다 커야한다.”, “컨테이너 뷰는 항상 화면 전체를 채워야 한다.”와 같은 요구사항들은 레이아웃 시스템이 이해할 수 있는 방식으로 표현한 요소입니다. 레이아웃 시스템은 이러한 제약들을 기반으로 가장 적합한 좌표와 크기를 계산하여 뷰에 할당합니다. 다시 한번 강조하지만 뷰의 좌표와 크기를 결정하는 것은 레이아웃 시스템입니다. 정말 중요한 개념입니다! 하드 코딩 방식에서는 뷰의 위치와 크기를 변경하기 위해서 frame, bound, center 중 어떤 속성을 사용할지를 먼저 고민했지만, 이제는 어떻게 제약으로 표현할지를 고민해야 합니다. 그동안 frame, bound, center와 많이 친해졌겠지만 이제는 새로운 친구와 친해져야 할 시간이 왔습니다.

Constraint Fomular

제약은 y = m*x + b라는 공식을 사용합니다. 이 공식을 우리에게 좀 더 익숙한 형태로 바꾸면 다음과 같은 공식이 됩니다.

targetView.attribute = multiplier * referenceView.attribute + constant

targetView는 제약을 추가할 대상 뷰이고 attribute는 제약을 적용할 속성입니다. referenceView는 대상 뷰의 제약을 적용할 때 참조할 뷰입니다. attribute에 사용할 수 있는 속성에는 left, right, top, bottom, leading, trailing, width, height, centerX, centerY, baseline이 있습니다.

multiplier는 참조 대상이 되는 뷰 속성에 곱해지는 배율로 CGFloat 자료형의 값입니다. 예를 들어 A버튼의 너비가 B버튼 너비의 2배라면 multiplier는 2.0이고 공식으로 표현하면 A.width = 2 * B.width + 0 이 됩니다. 이 값은 제약이 생성된 후에 변경될 수 없다는 것을 기억해 두시기 바랍니다.

constant는 참조되는 뷰 속성 값과 배율의 곱셈 결과에 더해지는 추가적인 CGFloat 값으로 레이아웃 시스템에서는 px가 아닌 pt값으로 해석됩니다. constant라는 이름과 달리 제약이 생성된 후에 새로운 값을 할당할 수 있기 때문에 런타임에 제약의 속성을 업데이트 하는데 활용됩니다. 예를 들어 A버튼의 가로 위치가 상위 뷰의 중앙에서 10pt 떨어진 위치라면 constant는 10이고 공식으로 표현하면 A.centerX = 1 * SuperView.centerX + 10 이 됩니다. 런타임에 constant 값을 20으로 변경하면 중앙에서 20pt 떨어진 위치로 이동하게 됩니다.

마지막으로 위의 공식에서는 쉬운 이해를 돕기 위해 =(같다)를 기준으로 설명했지만, >=(크거나 같다)와 <=(작거나 같다)도 사용할 수 있습니다.

NSLayoutConstraint

Auto Layout에서 제약은 NSLayoutConstraint 클래스의 인스턴스 입니다. 이 클래스가 제공하는 속성들을 살펴보면 Auto Layout의 장점을 조금 더 명확하게 알 수 있습니다. 특히 주목할 만한 속성은 priority, active, identifier 입니다.

firstItem, secondItem
제약 공식에서 targetView와 referenceView에 해당되는 속성입니다. firstItem은 필수 속성이지만 secondItem은 생략할 수 있습니다.

firstAttribute, secondAttribute
제약 공식에서 attribute에 해당되는 속성입니다. 이 속성의 자료형은  NSLayoutAttribute 열거형입니다.


firstAttribute, secondAttribute
제약 공식에서 =에 해당되는 속성입니다. NSLayoutRelation 열거형으로 =, >=, <=과 같이 세가지 관계를 표현할 수 있습니다.


multiplier
제약 공식에서 multiplier에 해당되는 속성입니다.


constant
제약 공식에서 constant에 해당되는 속성입니다. 제약을 생성한 후에 값을 변경할 수 있기 때문에 런타임에 제약을 동적으로 업데이트 하는데 활용됩니다. constant 값을 변경하는 것으로 요구사항을 충족시킬수 있다면 이전 제약을 제거하고 새로운 제약을 추가하는 것보다 constant 값을 변경한 후 제약을 업데이트하는 것이 성능에 유리합니다.


★ priority
제약의 우선순위를 지정하는 속성으로 1에 1000 사이의 값을 할당할 수 있습니다. 일반적으로 숫자 값을 직접 할당하지 않고 UILayoutPriority 열거형을 사용합니다.

제약은 할당된 우선 순위에 따라서 필수 제약과 옵션 제약으로 구분됩니다. 필수 제약은 우선 순위가 1000(UILayoutPriorityRequired)이고, 옵션 제약은 1~999 사이의 우선 순위를 가집니다. 옵션 제약은 더 높은 우선 순위를 가진 동일한 제약이 있을 경우 무시될 수 있다는 것을 의미합니다.

우선 순위를 사용할 때 주의할 점은 필수 제약을 옵션 제약으로 변경하거나 옵션 제약을 필수 제약으로 변경할 수 없다는 것입니다. 즉, 우선순위를 1000으로 할당한 제약은 런타임에 우선순위를 변경할 수 없고, 1~999 사이의 우선 순위 값으로 생성한 제약은 1000으로 변경할 수 없습니다. 만약 이런 시도를 한다면 무서운(?) 예외가 발생할 것입니다. 런타임에 우선 순위를 동적으로 변경할 제약을 생성할 때는 반드시 1000보다 작은 값으로 설정해야 합니다.

100보다 작은 우선 순위를 할당하면 레이아웃 시스템이 임의로 조절 할 수 있게 되고, 레이아웃 과정에서 발생한 오류를 레이아웃 시스템 스스로 해결하도록 할 수 있습니다.(물론 100이하로 할당한다고 해서 모든 오류를 해결할 수 있는 것은 아닙니다.)

★ activate
제약의 활성화 상태를 지정하는 속성입니다. 특정 조건에 따라서 동작하는 제약을 만드는 용도로 활용할 수 있습니다. 비활성화된 제약은 우선 순위에 관계없이 레이아웃 계산 과정에서 제외됩니다.

★ identifier
제약에 특별한 이름을 부여할 수 있는 속성입니다. 필수 속성은 아니지만 가독성이 높은 이름을 할당해두면 디버깅에 “엄청나게 큰” 도움이 됩니다.

이 클래스에 포함된 메소드와 사용법은 코드를 통해 Auto Layout를 사용하는 방법을 설명할 때 더욱 자세히 다룰 예정입니다.

Cumulative

제약은누적되는 성질을 가지고 있습니다. 쉽게 말해서 너비 제약을 가지고 있는 뷰에 새로운 너비 제약을 추가하면 이전에 존재하던 너비 제약이 업데이트되거나 삭제되지 않는다는 것입니다. 이처럼 하나의 뷰에 동일한 속성에 대한 제약이 2개 이상 존재하고, 각 제약의 우선 순위가 모두 동일하다면 레이아웃 오류가 발생합니다. 그러므로 새로운 제약을 추가하기 전에 동일한 성격과 우선 순위를 가지는 제약이 존재하는지 확인한 다음 직접 제거해야 합니다.

이전 제약을 제거하고 새로운 제약을 추가하는 방식은 오류를 발생시킬 확률이 높고 성능상 불리한 면이 있습니다. 제약의 constant 속성을 변경하여 원하는 효과를 얻을 수 있다면 이 방식을 사용하는 것이 좋습니다. 반드시 같은 종류의 제약을 2개 이상 추가해야 하는 경우라면 각 제약의 우선 순위에 차이를 두고, 각 우선 순위가 조건에 따라 적절히 업데이트 되도록 신경써야 합니다.

 

Intrinsic Content Size

Intrinsic content size를 우리말로 그대로 옮기면 “본질적인 내용의 크기”가 됩니다. 개발자에게 좀 더 친숙한 언어로 옮기면“뷰의 핵심적인 내용을 모두 표시할 수 있는 가장 작은 영역의 크기”라고 할 수 있습니다. 예를 들어 버튼의 Intrinsic size는 타이틀을 클리핑없이 모두 출력할 수 있는 크기입니다. 만약 버튼에 배경 이미지가 지정되어 있다면  타이틀과 배경 이미지를 모두 출력할 수 있는 크기가 됩니다. 또한 레이블의 Intrinsic size는 text 값을 클리핑없이 출력할 수 있는 크기입니다.

레이아웃 시스템은 레이아웃 과정에서 뷰가 Intrinsic size를 가지고 있는지, 그렇다면 그 크기는 얼마인지 확인합니다. 그리고 이 크기와 주변 뷰의 제약을 토대로 뷰의 크기를 계산합니다. 버튼이나 레이블과 같은 표준 뷰들은 대부분 Intrinsic size를 가지고 있고 뷰에 표시되는 내용이 변경될 때 이 값도 함께 변경됩니다. 슬라이더와 같은 일부 뷰들은 높이에 한해서만 Intrinsic size를 가지고 있습니다. 슬라이더에서 높이는 UI를 유지하기 위해서 특정 값으로 고정되지만, 너비는 어떠한 값을 가지더라도 슬라이더 자체를 표시하는데 큰 영향을 주지 않기 때문입니다. 컨테이너 역할을 하는 일부 뷰들은 Intrinsic size를 가지고 있지 않습니다.

뷰의 Intrinsic size를 확인할 때는 UIView의 intrinsicContentSize 속성을 사용합니다. 이 속성은 CGSize형이고, 뷰가 Intrinsic Size를 가지고 있지 않다면 CGSize(UIViewNoIntricsicMetric, UIViewNoIntrinsicMetric)를 리턴합니다. 슬라이더처럼 높이에 한해서만 Intrinsic size를 가지고 있다면 CGSize(UIViewNoIntricsicMetric, xx.x)를 리턴합니다.

커스텀 뷰를 구현하고 있다면 뷰의 내용이 업데이트 될 때마다 invalidateIntrinsicContentSize 메소드를 호출해야 합니다.

Intrinsic Size를 통해 얻게 되는 가장 큰 이점은 뷰의 내용에 따라 크기가 자동으로 변경되는 것입니다. 그러나 뷰에 고정된 너비 제약이나 높이 제약을 추가하게 된다면 이러한 이점을 더 이상 사용할 수 없게 됩니다. 그래서 Auto Layout을 사용할 때 피해야 할 안티 패턴 중 하나입니다. Auto Layout 고정된 너비/높이 제약을 사용해야 하는 경우는 Intrinsic Size가 디자인 요구사항에 부합하지 않을 때 뿐입니다. 사실 이런 경우에도 너비/높이 제약을 사용하는 것보다는 디자인 요구사항을 변경할 수 있는지를 먼저 검토해 보는것이 좋습니다. 검토 결과 반드시 사용해야 한다면 = 대신 <= 또는 >=를 사용하는 것이 좋습니다.

Compression Resistance & Content Hugging

Intrinsic Size를 가지고 있는 뷰는 런타임에 각 방향별로 두개의 제약을 자동으로 추가합니다. 예를 들어 버튼의 Intrinsic Size가 (100, 50)이라면 다음과 같은 제약들이 추가됩니다.

  • width <= 100
  • height <= 50
  • width >= 100
  • height >= 50

첫번째 제약은 버튼의 너비가 100보다 커지는 것을 막고, 두번째 제약은 높이가 50보다 커지는 것을 막습니다. 이렇게 Intrinsic Size를 통해최대 크기에 제한을 두는 것을 Content Hugging이라고 합니다.

세번째 제약은 버튼의 너비가 100보다 작아지는 것을 막고, 네번째 제약은 높이가 50보다 작아지는 것을 막습니다. 이처럼최소 크기에 제한을 두는 것을 Compression Resistance라고 합니다.

이와 관련된 내용은 글보다 예제를 통해 설명하는 것이 훨씬 이해하기 쉽기 때문에 이어지는 글에서 상세하게 설명하도록 하겠습니다. 지금은 여기에서 설명하는 내용만 기억해 주시기 바랍니다.

 

Pros and Cons

첫번째 글은 Auto Layout을 사용하면서 느꼈던 장점과 단점을 정리하는 것으로 마무리 할까 합니다.

Auto Layout은

  • 화면 크기와 디바이스 방향에 따라 유연하게 업데이트 되는 UI를 비교적 쉽게 구현할 수 있습니다.
  • 향후 새로운 해상도의 디바이스가 출시되더라도 업데이트 없이 일관된 UI를 유지할 수 있습니다.
  • 화면 좌표를 직접 계산하거나 수많은 분기 코드를 작성할 필요가 없습니다.
  • 우선 순위와 활성화 속성을 활용하여 특정 조건에 따라 업데이트 되는 UI를 구현할 수 있습니다.
  • 지역화 문자열을 사용할 때 문자열의 너비에 따라 버튼이나 레이블의 너비가 자동으로 업데이트 됩니다.
  • Content Hugging과 Compression Resistance의 우선 순위를 조절하여 동적인 UI를 더욱 세부적으로 제어할 수 있습니다.
  • 뷰 에니메이션, 모션 이펙트와 함께 사용할 수 있습니다.
  • 동일한 계층구조에 존재하지 않는 뷰 사이의 관계를 설정할 수 있습니다. (Spring and Struts 모델에서는 부모-자식 관계에 있는 뷰 사이의 관계만 설정할 수 있습니다. 특히, 부모 뷰의 크기나 위치는 자식 뷰에게 영향을 줄 수 있지만, 자식 뷰의 크기나 위치는 부모 뷰에게 영향을 주지 않습니다.)
  • 스토리보드에서 제약을 쉽게 추가할 수 있습니다. 코드를 통해 런타임에 동적으로 추가하거나 제거할 수 있습니다.

 

Auto Layout의 단점은 하드 코딩 방식에 대한 익숙함이 사라질 때까지 매우 불편하고 어렵다는 것 뿐입니다 :)

 

 

Filed under: iOS, Swift

  1. 닥서클
    정성어린 포스트입니다 감사히 잘보고있습니다 :)
  2. 와우! 정말 끝내주는 포스트입니다. 오토레이아웃을 기본정도만 알고 있었고 아무리 애플 세션과 다른 튜토리얼을 보았는데, 이 포스트 만큼 가려운 곳을 시원하게 긁어주는 포스트는 없네요. 시간과 노력이 정말 많이 들어가 있을텐데 1-3편까지 다 읽었습니다. 시간과 노력을 드려서 지식을 공유해주셔서 진심으로 감사합니다.
  3. 제게 오토 레이아웃은 ' 좀 알것같아'와 '헷갈려' 사이에 있었는데, iOS 8의 사이즈 클래스와 만나 많이 쓸만해진 것 같더군요. 알찬 글 감사드립니다. 덕분에 좀 더 이해하게 되었네요.
  4. […] Size와 Auto Layout의 콜라보가 진가를 발휘합니다.(Intrinsic Size에 대한 내용은 첫번째 글을 참고해 […]