Auto Layout을 코드로 구현해봅시다
이번 포스트에서는 Auto Layout을 코드로 구현하는 방법에 대해 알아보고 Auto Layout을 유용하게 사용할 수 있는 라이브러리인 SnapKit을 사용하여 구현하는 방법에 대해 알아보도록 하겠습니다.
1. Storyboard에서 Auto Layout을 구현하는 방법과 Code로 구현하는 방법의 차이
우리가 처음 iOS 개발을 공부하면 대부분 예제가 Storyboard를 사용하여 View를 구성합니다.
직관적이고 간편하게 마우스 컨트롤을 통해 Auto Layout을 적용할 수 있다는 점이 매력적입니다.
하지만, 장점이 있다면 단점도 존재하는 법! Storyboard로 Auto Layout을 구현하는 방법이 갖는 단점에 대해 살펴보고 코드로 구현하는 방법과 어떤 차이가 있는지 알아보도록 하겠습니다.
1) Storyboard는 Index 시간, Build 시간에 악영향을 준다
Storyboard로 오토 레이아웃을 구현하다보면, 초기 프로젝트 Index 시간과 Build 시간에 악영향을 주게 됩니다.
Index는 코드 자동 완성에 결정적인 역할을 하는 프로세스이기 때문에 Index 과정이 끝난 이후 코드 작성하는 것이 보다 효율적입니다.
1-2개의 간단한 레이아웃이면 차이가 거의 없을 수 있으나 프로젝트 크기가 커질수록 이러한 문제를 더욱 더 부각됩니다.
2) XCode 자체 오류로 인해 Storyboard 내 View Controller가 보이지 않을 경우
XCode 자체 오류로 인해 Storyboard에서 구현한 화면이 보이지 않을 경우가 있는데 이 때는 시스템 재시동 혹은 XCode 재시동 외에는 뾰족한 수가 없습니다.
촉박한 일정의 압박 속에 효율적으로 업무를 진행하려면 IDE의 오류 현상을 최소화하면서 높은 품질의 코드를 생산해내야 하는데 Storyboard의 경우 언급한 문제점으로 인해 업무에 지장이 생길 수 있습니다.
3) Storyboard의 Inspector에서 제공하지 않는 옵션의 경우 불편
layer.cornerRadius나 layer.borderWidth와 같은 속성 값은 Inspector에서 기본적으로 제공하지 않습니다.
적용하고 싶을 경우 직접 코드로 IBOutlet 변수에 추가하거나 아니면 Identity Inspector에 있는 User Defined Runtime Attributes에 직접 추가해주어야 합니다.
대표적인 속성이 view.layer.cornerRadius나 view.layer.masksToBound 와 같은 속성들이 있을 것입니다.
4) Swift UI를 위해 코드로 View를 구현하는 연습은 도움이 된다.
Apple은 Storyboard를 없애고 있습니다. 사용하지 않는 것을 권장하고 있습니다. iOS 13에 처음으로 발표한 Swift UI를 보면 100% Swift Code로 View Layout을 구현하는 방식을 취하고 있습니다.
이는 머릿 속에 혹은 미리 어느 정도 뷰를 설계한 후 코드로 레이아웃을 구성하는 연습을 해놓는 것이 도움이 될 수 있다는 것입니다.
Auto Layout의 동작 방식의 미묘한 차이가 존재하지만 기본적인 사항들은 동일하며 View를 설계하는 방식에 있어서도 다를 점이 없기 때문에 Code로 구현하는 연습은 추후 Swift UI를 빠르게 학습하여 적용하는 데에도 도움이 될 수 있습니다.
2. Code로 Auto Layout 구현해보기
그렇다면, 이제 Code로 Auto Layout을 구현하는 방법을 간단한 예제를 통해 살펴보도록 하겠습니다.
간단하게 Login View를 구성하는 것으로 설명해보도록 하겠습니다.
Auto Layout은 Active 옵션을 true로 설정해주어야 해당 Constraint가 View에 적용됩니다.
Pure Swift로 Auto Layout Constraint를 추가하는 코드를 다음과 같은 형태를 갖습니다.
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.topAnchor.constraint(equalTo:
titleView.bottomAnchor, constant: 8.0).isActive = true
먼저 translatesAutoresizingMaskIntoConstraints 옵션을 false로 설정함으로써 코드로 직접 수정 가능하도록 설정하면서 코드 오토레이아웃은 시작됩니다.
많은 옵션들이 있지만 위 코드를 보면 topAnchor에 대한 제약조건을 추가한 것을 볼 수 있습니다.
contentView의 Top 제약을 titleView의 Bottom을 기준으로 8pt 만큼 간격을 주도록 하고 있습니다.
코드로 구현할 때 한 가지 유의할 점은 오른쪽 방향이 (+)이고 왼쪽 방향이 (-)입니다. 또한 아래 방향이 (+)이며 위 방향이 (-)입니다.
즉, 예를 들어 rightAnchor를 Parent View의 leftAnchor를 기준으로 5만큼 간격을 주고 싶다면 (-5)를 지정해주어야 합니다.
자 이제 간단하게 알아보았으니 로그인 뷰를 간단하게 추가해보도록 하겠습니다.
위에 보이는 것과 같이 Label, TextField, Button으로 이루어진 간단한 View입니다.
우선 코드로 레이아웃을 설정할 수 있도록 해봅시다.
class ViewController: UIViewController {
private var titleView: UILabel = UILabel()
private var idField: UITextField = UITextField()
private var pwField: UITextField = UITextField()
private var loginButton: UIButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
setupMainLayout()
}
private func setupMainLayout() {
view.addSubview(titleView)
view.addSubview(idField)
view.addSubview(pwField)
view.addSubview(loginButton)
titleView.translatesAutoresizingMaskIntoConstraints = false
idField.translatesAutoresizingMaskIntoConstraints = false
pwField.translatesAutoresizingMaskIntoConstraints = false
loginButton.translatesAutoresizingMaskIntoConstraints = false
}
}
View를 각각 정의하고 설정하는 코드입니다. 이제 상세 제약조건들을 지정해보도록 합시다. 아래 코드를 따라해보세요.
class ViewController: UIViewController {
...
private func setupMainLayout() {
...
titleView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
titleView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16).isActive = true
titleView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16).isActive = true
titleView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16).isActive = true
titleView.heightAnchor.constraint(equalToConstant: 44).isActive = true
titleView.backgroundColor = .systemPink
titleView.text = "Service Login"
titleView.font = .systemFont(ofSize: 28)
titleView.textAlignment = .center
idField.topAnchor.constraint(equalTo: titleView.bottomAnchor, constant: 16).isActive = true
idField.leadingAnchor.constraint(equalTo: titleView.leadingAnchor).isActive = true
idField.trailingAnchor.constraint(equalTo: titleView.trailingAnchor).isActive = true
idField.heightAnchor.constraint(equalToConstant: 44).isActive = true
idField.backgroundColor = .white
pwField.topAnchor.constraint(equalTo: idField.bottomAnchor, constant: 16).isActive = true
pwField.leadingAnchor.constraint(equalTo: idField.leadingAnchor).isActive = true
pwField.trailingAnchor.constraint(equalTo: idField.trailingAnchor).isActive = true
pwField.heightAnchor.constraint(equalToConstant: 44).isActive = true
pwField.backgroundColor = .white
idField.placeholder = "Enter your ID."
pwField.placeholder = "Enter your PW."
loginButton.topAnchor.constraint(equalTo: pwField.bottomAnchor, constant: 16).isActive = true
loginButton.leadingAnchor.constraint(equalTo: pwField.leadingAnchor).isActive = true
loginButton.trailingAnchor.constraint(equalTo: pwField.trailingAnchor).isActive = true
loginButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
loginButton.setTitle("LOGIN", for: .normal)
loginButton.setTitleColor(.white, for: .normal)
loginButton.backgroundColor = .systemBlue
loginButton.layer.cornerRadius = 10
}
}
하나씩 분석해가면서 어떤 속성인지 살펴보면 됩니다.
이렇듯 Storyboard를 전혀 사용하지 않아도 view를 충분히 구현할 수 있습니다.
하지만 코드 양이 비대해지는 경우가 생기게 되는데 이를 위해 SnapKit이라는 유용한 라이브러리를 소개하고자 합니다.
3. SnapKit
SnapKit은 iOS 및 OS X에서 Auto Layout을 쉽게 하기 위한 DSL(Domain-specific Language)입니다.
Cocoapods으로 사용할 수도 있고 Swift Package Manager (SPM)으로 사용할 수도 있습니다.
저는 SPM으로 사용하도록 하겠습니다.
File -> Swift Packages -> Add Package Dependency
에 가서 SnapKit을 추가해줍니다.
import SnapKit
아까 ViewController에 위와 같이 SnapKit을 import 해줍니다.
4. SnapKit으로 Auto Layout 구현해보기
위에서 코드로 구현한 로그인 화면을 SnapKit으로 동일하게 구현해보도록 하겠습니다.
private func setupMainLayoutWithSnapKit() {
view.addSubview(titleView)
view.addSubview(idField)
view.addSubview(pwField)
view.addSubview(loginButton)
titleView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(16)
make.centerX.equalToSuperview()
make.leading.equalToSuperview().offset(16)
make.trailing.equalToSuperview().offset(-16)
make.height.equalTo(44)
}
titleView.backgroundColor = .systemPink
titleView.text = "Service Login"
titleView.font = .systemFont(ofSize: 28)
titleView.textAlignment = .center
idField.snp.makeConstraints { make in
make.top.equalTo(titleView.snp.bottom).offset(16)
make.leading.equalTo(titleView.snp.leading)
make.trailing.equalTo(titleView.snp.trailing)
make.height.equalTo(44)
}
pwField.snp.makeConstraints { make in
make.top.equalTo(idField.snp.bottom).offset(16)
make.leading.equalTo(idField.snp.leading)
make.trailing.equalTo(idField.snp.trailing)
make.height.equalTo(44)
}
idField.placeholder = "Enter your ID."
pwField.placeholder = "Enter your PW."
loginButton.snp.makeConstraints { make in
make.top.equalTo(pwField.snp.bottom).offset(16)
make.leading.equalTo(pwField.snp.leading)
make.trailing.equalTo(pwField.snp.trailing)
make.height.equalTo(44)
}
loginButton.setTitle("LOGIN", for: .normal)
loginButton.setTitleColor(.white, for: .normal)
loginButton.backgroundColor = .systemBlue
loginButton.layer.cornerRadius = 10
}
실행해보면 동일한 결과를 볼 수 있습니다.
이번 포스트에서는 코드로 간단하게 View를 구현하는 방법에 대해 살펴보았습니다.