[TIL] CodeBase UI + 코드 예제

안녕하세요~ 지우개입니다!

오늘은 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는 공통적으로 다음 세 가지를 포함해야 합니다 :

  1. 뷰객체 프로퍼티 선언. 클래스의 인스턴스를 생성
  2. 뷰객체를 명시적으로 루트뷰에 추가
  3. 크기와 위치 및 속성 정의

 

위의 규칙들을 적용해서 코드를 살펴봅시다.

(전체 코드는 복+붙이 가능하도록 아래 붙여두었습니다.)

 

 

우선 뷰 객체 프로퍼티를 선언합니다.

 

 

그리고 선언된 뷰 객체를 루트뷰에 추가합니다

 

 

 

다음으로 크기와 위치같은 속성을 정의합니다.

여기에서, 속성을 정의할 수 있는 방법이 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를 사용해 실제 화면을 만들어보겠습니다!

오늘도 화이팅~

 

 

 

새싹에서 배우고 이해한 내용을 정리합니다.


참고

애플 공식 문서 https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html

'공부하자! > 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