Xcode로 앱 개발을 처음 시작했을 때, 나는 스토리보드가 일종의 마법이라고 생각했다. 서브뷰를 드래그 앤 드롭으로 원하는 자리에 놓기만 하면 되니 너무나 간단해서, 사람들이 어떻게 스토리보드 없이 뷰를 구성할 수 있는지 도저히 이해할 수 없었다.
하지만 언젠가 "진정한 마스터 프로그래머"가 되려면 뷰를 코드로 직접 구성하는 법을 배워야겠다고 결심하게 된다. 그런데 여기 한 가지 문제가 있었다. 바로 코드로 제약 조건(Constraint)을 작성하는 일이 굉장히 번거롭다는 것.
let horizontalConstraint = NSLayoutConstraint( item: newView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)위 코드는 서브뷰를 슈퍼뷰의 X축 중앙에 배치하는 단 하나의 수평 제약 조건이다. 읽기조차 힘든데, 모든 제약 조건마다 이런 식으로 작성해야 한다면 금방 지칠 수밖에 없다. 그래도 스토리보드를 버리고 싶었기에 대안을 찾아 헤매던 중 SnapKit을 발견했다.
SnapKit은 명확하고 간결한 API를 제공해서 코드로 제약 조건을 작성하는 일을 아주 손쉽게 만들어 준다. 이 글에서는 SnapKit으로 할 수 있는 기본적인 예제 몇 가지를 살펴본다.
이 글에서 다루는 내용
- 슈퍼뷰 안에 서브뷰 배치하기
- 서브뷰 간 상대적으로 배치하기
- 제약 조건 업데이트 및 애니메이션 적용하기
시작하기 전에
프로젝트에 SnapKit을 먼저 설치해야 한다. 필자는 서드파티 라이브러리 관리에 CocoaPods를 사용한다.
Podfile에 pod 'SnapKit'을 추가한 뒤 pod install을 실행하자. 그리고 SnapKit을 사용할 파일 상단에 import SnapKit을 작성하면 준비 끝이다.
슈퍼뷰 안에 서브뷰 배치하기
먼저 서브뷰를 슈퍼뷰의 가장자리에 고정해 보자.
let subview = UIView()view.addSubview(subview)subview.snp.makeConstraints { (make) in make.top.equalTo(view) make.bottom.equalTo(view) make.left.equalTo(view) make.right.equalTo(view)}이렇게 하면 서브뷰의 top, bottom, left, right 가장자리가 슈퍼뷰의 해당 가장자리에 constant 값 0으로 고정된다.

주목할 점은 제약 조건을 설정하기 전에 서브뷰를 먼저 슈퍼뷰에 추가했다는 것이다. 아직 슈퍼뷰에 추가되지 않은 뷰에 제약 조건을 작성하면 뷰가 로드될 때 치명적인 런타임 에러가 발생하니 반드시 순서를 지켜야 한다.
SnapKit 문법만으로도 표준 라이브러리보다 훨씬 읽기 편하지만, 사실 더 짧게 줄일 수 있다. SnapKit은 뷰를 슈퍼뷰 가장자리에 고정할 때 훨씬 간결한 방식도 제공한다.
let subview = UIView()view.addSubview(subview)subview.snp.makeConstraints { (make) in make.top.bottom.left.right.equalTo(view)}네 줄짜리 코드를 단 한 줄로 줄였는데도 결과는 위 예제와 완전히 동일하다.
서브뷰의 크기를 지정할 수도 있다. 아래 예제에서는 서브뷰의 너비와 높이를 설정하고 슈퍼뷰의 정중앙에 배치해 본다.
subview.snp.makeConstraints { (make) in make.width.equalTo(200) make.height.equalTo(200) make.centerX.equalTo(view) make.centerY.equalTo(view)}
예제는 간단하지만 같은 값을 반복해서 작성하고 있다. 동일한 값을 가진 제약 조건을 설정할 때는 SnapKit이 제공하는 체이닝(chaining) 기능을 활용하면 좋다.
subview.snp.makeConstraints { (make) in make.width.height.equalTo(200) make.centerX.centerY.equalTo(view)}이 코드 블록은 위 예제와 똑같은 제약 조건을 만들어 낸다. SnapKit이 어떻게 더 깔끔한 코드 작성을 돕는지 또 하나 확인할 수 있는 부분이다.
서브뷰 간 상대적으로 배치하기
하나의 뷰에 여러 서브뷰를 추가하다 보면 서브뷰들을 서로 기준으로 삼아 배치해야 하는 경우가 많다. 이번 예제에서는 다음과 같이 진행한다.
subview1을 추가하고 슈퍼뷰의 왼쪽 가장자리에 고정한다.subview2를 추가하고subview1의 오른쪽 가장자리에 고정한다.
let subview1 = UIView()let subview2 = UIView()view.addSubview(subview1)view.addSubview(subview2)subview1.snp.makeConstraints { (make) in make.width.height.equalTo(100) make.left.equalTo(view)}subview2.snp.makeConstraints { (make) in make.width.height.equalTo(subview1) make.left.equalTo(subview1.snp.right)}지금까지는 대상 뷰의 해당 제약 조건과 '같게(equal)' 설정하는 경우만 다뤘다. 예를 들어 make.left.equalTo(view)라고 작성하면 SnapKit은 subview1의 left를 view의 left에 맞춘다. 별도로 지정하지 않으면 SnapKit이 자동으로 같은 속성끼리 매칭해 주는 것이다.
하지만 subview2의 left 제약 조건은 subview1의 오른쪽 가장자리에 맞추고 싶다. 만약 make.left.equalTo(subview1)라고 작성하면 subview2의 왼쪽 가장자리가 subview1의 왼쪽 가장자리에 맞춰진다. 대신 subview1.snp.right처럼 작성해 subview1의 오른쪽 가장자리를 가져오면 된다.
이처럼 뷰 이름 뒤에 snp를 붙이면 해당 뷰의 어떤 제약 조건 속성이든 접근할 수 있다.

이제 subview2가 subview1의 오른쪽에 배치되었으니, 두 뷰 사이에 간격을 만들어 보자. subview2에 offset을 추가하면 된다.
subview2.snp.makeConstraints { (make) in make.width.height.equalTo(subview1) make.left.equalTo(subview1.snp.right).offset(50)}offset은 어떤 equalTo() 뒤에든 붙여서 해당 제약 조건의 constant 값을 변경할 수 있다. 이제 subview2의 left 제약 조건에는 50의 간격이 생긴다.

제약 조건 업데이트와 애니메이션 적용하기
여러분의 앱이 차세대 히트작이 되기를 바란다면 화면에 좀 더 다이내믹한 요소를 넣어야 한다! SnapKit은 제약 조건을 손쉽게 업데이트해 동적인 뷰를 만들 수 있는 방법을 제공한다.
SnapKit에서 제약 조건을 업데이트하는 방법은 새로 추가하는 것과 거의 동일하다. 앞선 예제의 subview1 제약 조건에서 left 가장자리의 constant 값을 0에서 50으로 바꿔 보자.
subview1.snp.updateConstraints { (make) in make.left.equalTo(50)}이게 전부다! 코드 블록이 실행되면 서브뷰가 새로운 제약 조건에 맞게 갱신된다.

subview1의 제약 조건만 수정했는데 subview2도 함께 움직인 것을 눈치챘을 것이다. subview2는 subview1의 오른쪽 가장자리에 제약되어 있기 때문이다. subview1이 움직이면 subview2는 subview1과의 left 제약 조건을 계속 유지하게 된다.
이제 전환을 부드럽게 만들어 줄 애니메이션을 추가해 보자. 뷰 애니메이션을 다뤄본 적이 있다면 낯익은 문법일 것이다.
UIView.animate(withDuration: 0.3) { subview1.snp.updateConstraints { (make) in make.left.equalTo(50) } self.view.layoutIfNeeded()}제약 조건 변경에 애니메이션을 적용할 때는 서브뷰의 부모 뷰에서 layoutIfNeeded()를 호출해야 한다. 여러 뷰를 동시에 애니메이션 처리한다면 해당 서브뷰들의 가장 가까운 공통 슈퍼뷰에서 호출해야 한다.

주의할 점도 있다. updateConstraints()는 이미 존재하는 제약 조건만 업데이트할 수 있다. 아직 설정되지 않은 제약 조건을 업데이트하려고 하면 치명적인 런타임 에러가 발생한다.
서브뷰에 새로운 제약 조건을 추가하고 싶다면 remakeConstraints()를 사용하자. remakeConstraints()는 해당 서브뷰에 설정된 기존 제약 조건을 모두 제거한 뒤 새로운 제약 조건을 추가할 수 있게 해준다.
제약 조건을 제거할 때는 deactivate()를 사용할 수도 있다. 아래 예제에서는 제약 조건을 생성하고 설정한 뒤 deactivate()로 제거해 본다.
var constraint: Constraint!let subview = UIView()subview.snp.makeConstraints { (make) in constraint = make.height.equalTo(100).constraint}constraint.deactivate()애니메이션 블록 안에서 deactivate를 호출하면 제약 조건이 제거되는 과정 역시 애니메이션으로 처리할 수 있다.
마치며: 스토리보드는 이제 필요 없을까?
이제 여러분도 코드로 제약 조건을 작성하는 마스터가 되었다. 스토리보드가 우스워 보이고, 다시는 돌아갈 이유가 없다고 생각하는가?

사실 꼭 그렇지는 않다. 스토리보드와 니브(nib)는 여전히 뷰 컨트롤러를 가장 빠르게 구성할 수 있는 방법이며, 비개발자와 협업할 때는 베타 버전을 보내지 않고도 앱의 모습을 보여줄 수 있는 최고의 수단이다.
필자는 두 가지를 함께 사용한다. 최근에 만든 앱에서는 SnapKit으로 코드에 제약 조건을 작성하는 동시에, 스토리보드와 니브에서 Auto Layout을 활용했고 때로는 같은 뷰에서 둘 다 사용하기도 했다. 결국 어떤 방식이 여러분이 만들려는 뷰에 가장 적합한지는 직접 판단해야 한다.
끝까지 읽어주셔서 감사합니다! 이 글이 도움이 되셨다면, 제품 관리·엔지니어링·디자인 관련 글을 공유하는 필자의 트위터도 팔로우해 주세요.