안녕하세요~ 지우개입니다!
오늘은 CodeBase UI에 대해 배워봤습니다.
그동안 스토리보드로만 짜다보니 코드베이스가 어색하다고 생각했는데,
원리는 비슷하니 금방 익숙해질듯 합니다!
코드베이스 유아이를 다루기 전,
iOS에서 Layout을 다루는 흐름을 우선 이해하셔야 합니다.
Frame Based Layout -> Auto Layout -> Adaptive Layout
Frame Based Layout
맨 처음 등장했던 Frame Based Layout부터 살펴보겠습니다.
위 이미지처럼 뷰의 너비와 높이, 그리고 x, y좌표 위치를 계산해 직접 지정하는 것입니다.
직접 지정해야 하니 거의 모든 커스텀이 가능하다고 볼 수 있지만,
아이템 하나만 위치가 바뀌어도 위치를 다 다시 계산해야하는 등 유지보수에 불편함이 따릅니다.
그리고 결정적으로!
애플에서 기존 아이폰과 화면 크기가 다른 아이폰이 새로 나오면서
여러 크기를 가진 화면을 다루어야 하는 상황이 생깁니다.
위치를 지정해주어야 하는 Frame-Based Layout을 사용한다면
화면 크기에 상관없이 위치가 같게 되니까 UX적으로 아주 불편하겠죠? 😂😂
CodeBase UI는 공통적으로 다음 세 가지를 포함해야 합니다 :
- 뷰객체 프로퍼티 선언. 클래스의 인스턴스를 생성
- 뷰객체를 명시적으로 루트뷰에 추가
- 크기와 위치 및 속성 정의
위의 규칙들을 적용해서 코드를 살펴봅시다.
(전체 코드는 복+붙이 가능하도록 아래 붙여두었습니다.)
우선 뷰 객체 프로퍼티를 선언합니다.
그리고 선언된 뷰 객체를 루트뷰에 추가합니다
다음으로 크기와 위치같은 속성을 정의합니다.
여기에서, 속성을 정의할 수 있는 방법이 4가지 있습니다.
(1) Frame 기반
첫번째로 Frame 기반입니다.
x, y좌표와 가로 세로 길이를 지정해주고,
보더스타일과 배경색상까지 지정해주었습니다.
(2) NSLayoutContraints 기반 (isActive)
두번째로 NSLayoutContraints 기반입니다.
(위와 같이 passwordTextField 뷰 객체 프로퍼티를 지정해주고, 루트뷰에 추가한 다음 이부분을 봐주시면 됩니다!)
여기에서 주의할 점!
AutoResizingMask가 적용되지 않도록! false 처리를 해 주어야 합니다.
(3) NSLayoutConstraints (addConstraints)
addConstraints를 이용한 방법입니다.
최대한 정리해본 코드인데도.. 지저분합니다. (스토리보드 짱 ㅜㅜ)
어찌됐든 top, leading, trailing, height을 적용해준 뒤 addConstraints를 해주는 방식이라고 보시면 되겠습니다.
(4) NSLayoutAnchor
Anchor를 사용한 방법입니다.
위 방법보다는 한결 간결해졌습니다.
(마찬가지로 signButton을 추가해준뒤 루트뷰에 명시하고 왔습니다.)
AutoLayout
드디어 오토레이아웃이 나왔습니다.
여러 개의 제약 조건(series of constraints)을 통해 인터페이스를 구성하는 방법입니다.
쉽게 말해, 두 뷰 사이의 관계를 지정해주는 방식입니다.
어떤 크기의 화면이든지 leading, trailing이 20씩 떨어져있다면
화면 크기에 따라 뷰가 알아서 커졌다 작아졌다 하겠죠?
유지보수도 더 쉬워질 것 같네요.
코드로 살펴보겠습니다.
우선 뷰 추가하기
(여기서는 viewDidLoad가 많이 커지지 않도록 함수를 사용했습니다)
view.addSubview를 활용해 명시해주었습니다.
map이나 filter에 대해서는 나중에 다루어보겠습니다!
코드를 보면, safeArea와 같은 top을 가진다 / leadingMargin, trailingMargin이 20이다 / 비율로 높이를 잡는다 등,
스토리보드에서 사용하는 그대로 코드에 녹아있는 것을 볼 수 있습니다.
// class CodeViewController: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// 2. 뷰객체를 명시적으로 루트뷰에 추가
view.addSubview(emailTextField)
view.addSubview(passwordTextField)
view.addSubview(signButton)
// 3. 크기와 위치 및 속성 정의
// (1) Frame 기반
emailTextField.frame = CGRect(x: 50, y: 80, width: UIScreen.main.bounds.width - 100, height: 50)
emailTextField.borderStyle = .line
emailTextField.backgroundColor = .lightGray
// (2) NSLayoutContraints 기반 (isActive)
passwordTextField.translatesAutoresizingMaskIntoConstraints = false // AutoResizingMask 적용되지 않도록! 둘은 충돌이 발생할 수 있기 때문에 하나는 켜 주고 하나는 꺼 주어야 함
passwordTextField.backgroundColor = .lightGray
// (3) NSLayoutConstraints (addConstraints)
let top = NSLayoutConstraint(item: passwordTextField, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .top, multiplier: 1, constant: 100)
let leading = NSLayoutConstraint(item: passwordTextField, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 50)
let trailing = NSLayoutConstraint(item: passwordTextField, attribute: .trailing, relatedBy: .equal, toItem: emailTextField, attribute: .trailing, multiplier: 1, constant: 0)
let height = NSLayoutConstraint(item: passwordTextField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 50)
view.addConstraints([top, leading, trailing, height])
// (4) NSLayoutAnchor
signButton.translatesAutoresizingMaskIntoConstraints = false
signButton.backgroundColor = .systemIndigo
NSLayoutConstraint.activate([
signButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
signButton.widthAnchor.constraint(equalToConstant: 300),
signButton.heightAnchor.constraint(equalToConstant: 50),
signButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}
import UIKit
import SnapKit
class CodeSnapViewController: UIViewController {
let photoImageView: UIImageView = {
let view = UIImageView()
view.backgroundColor = .lightGray
view.contentMode = .scaleToFill
return view
}()
let titleTextField: UITextField = {
print("TEXTFIELD")
let view = UITextField()
view.borderStyle = .none
view.layer.borderColor = UIColor.black.cgColor
view.layer.borderWidth = 1
view.placeholder = "제목을 입력해주세요"
view.textAlignment = .center
view.font = .boldSystemFont(ofSize: 15)
return view
}()
let dateTextField: UITextField = {
let view = UITextField()
view.borderStyle = .none
view.layer.borderColor = UIColor.black.cgColor
view.layer.borderWidth = 1
view.placeholder = "날짜를 입력해주세요"
view.textAlignment = .center
view.font = .boldSystemFont(ofSize: 15)
return view
}()
let contentTextView: UITextView = {
let view = UITextView()
view.layer.borderColor = UIColor.black.cgColor
view.layer.borderWidth = 1
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
print(#function)
configureUI()
}
func configureUI() {
[photoImageView, titleTextField, dateTextField, contentTextView].forEach {
view.addSubview($0)
}
photoImageView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide)
make.leadingMargin.equalTo(20)
make.trailingMargin.equalTo(-20) // 오른쪽에서부터 마진
make.height.equalTo(view).multipliedBy(0.3) // 비율로 잡을 수도 있다
}
titleTextField.snp.makeConstraints { make in
make.top.equalTo(photoImageView.snp.bottom).offset(20)
make.leadingMargin.equalTo(20)
make.trailingMargin.equalTo(-20)
make.height.equalTo(50)
}
dateTextField.snp.makeConstraints { make in
make.top.equalTo(titleTextField.snp.bottom).offset(20)
make.leadingMargin.equalTo(20)
make.trailingMargin.equalTo(-20)
make.height.equalTo(50)
}
contentTextView.snp.makeConstraints { make in
make.top.equalTo(dateTextField.snp.bottom).offset(20)
make.leadingMargin.equalTo(20)
make.trailingMargin.equalTo(-20)
make.bottom.equalTo(view.safeAreaLayoutGuide)
}
}
}
다음 포스팅에서는 코드베이스 UI를 사용해 실제 화면을 만들어보겠습니다!
오늘도 화이팅~
새싹에서 배우고 이해한 내용을 정리합니다.
참고
'공부하자! > iOS' 카테고리의 다른 글
[TIL] GCD - Main/Global, sync/async 실습 (22.09.02) (3) | 2022.09.06 |
---|---|
[TIL] Codable (0) | 2022.08.29 |
[TIL] 타입캐스팅 (0) | 2022.08.18 |
[TIL] 모듈화, 프레임워크, 접근 제어 (0) | 2022.08.17 |
[iOS] TableView 테이블뷰에 대하여 (0) | 2022.08.11 |