코드 베이스 UI

동그라미·2024년 11월 21일
4
post-thumbnail

스토리보드 vs 코드베이스

  • 코드베이스란 스토리보드 같은 인터페이스 빌더를 사용하지 않고, 코드 작성으로만 UI 구성을 하는 것.
  • 스토리보드에서 하던 뷰의 size, constraint 및 모든 속성들을 코드로 작성한다.
  • 팀끼리 협업할 때, github에 코드를 올리고 서로 코드 리뷰를 하게 되는데, 스토리보드로 작성한 UI 를 github에 올린 것보다, swift로 작성한 UI 코드를 github에 올린 것이 가독성이 더 좋음.
  • 다만 스토리보드처럼 눈에 보이는 상태로 UI를 구성하는 것이 아니기 때문에, 코드 베이스로 UI를 작성하면 반드시 실행시켜서 확인해야 한다는 단점이 존재
  • 좌: 스토리보드 UI를 github에 올린 모습.
  • 우: 코드베이스 UI를 github에 올린 모습. → Swift로 UI가 작성되었기 때문에 코드 파악하기가 용이.

스토리보드 삭제

-> 스토리보드를 일절 사용하지 않고, 코드만으로 UI를 구성하기 위해 프로젝트에서 스토리보드를 완전히 삭제합니다.

  1. Main이라는 이름의 스토리보드 삭제. Move to Trash로 삭제할 것.

  2. into.plist라는 파일에서 ctrl+F로 검색 → main 검색해서 Storyboard Name 항목 삭제.

  3. 프로젝트 파일에서 TARGETS 선택 후 → Build Settings로 이동 → ctrl+F로 main 검색 → UIKit Main Storyboard File Base Name 항목 삭제.

  4. 앱에게 맨 처음 시작할 뷰를 알려줘야 하므로 SceneDelegate.swift 파일에 다음과 같은 코드 작성.

코드 및 설명.

// SceneDelegate.swift

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
		// 윈도우. 앱에 반드시 한 개는 필요한 가장 근본이 되는 뷰. 이 위에 뷰가 쌓이기 시작.
    var window: UIWindow?

		// 앱을 시작할때 세팅해줄 코드를 작성하는 곳.
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // UIWindow 객체 생성.
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
        
        // window 에게 루트 뷰 지정. 
        window.rootViewController = ViewController()
        
         // 이 메서드를 반드시 작성해줘야 윈도우가 활성화 됨.
        window.makeKeyAndVisible()
        self.window = window
    }
  1. 실행해 봤을때 컴파일 에러 없이 잘 실행된다면 스토리보드 삭제 및 코드 베이스 UI 작성 준비 완료.

NSLayoutConstraint

  • NSLayoutConstraint로 Constraint 작성 가능.
  • Constraint는 뷰와 뷰 사이의 제약조건을 의미. 오토 레이아웃에서 중요한 개념.
  • leadingAnchor trailingAnchor topAnchor bottomAnchor widthAnchor heightAnchor
  • centerXAnchor : 뷰에 가로선 그었을 때 그 중간을 의미.
  • centerYAnchor : 뷰에 세로선 그었을 때 그 중간을 의미.
  • NSLayoutConstraint.activate([제약조건들]) : 제약조건들을 넣고 코드 작성하면 제약조건 활성화.

UILabel

▪️ NSLayoutConstraint를 이용해서 UILabel 그려보기.

  • let label = UILabel() 코드로 라벨을 선언.
  • configureUI()라는 메서드를 정의하고, 그 안에 UI를 세팅하는 코드들을 담습니다.
  • view는 ViewController가 기본적으로 갖고 있는 기본 view를 의미.
  • view에 label을 추가해야 합니다. view.addSubview(label)
  • 코드로 여러 가지 속성들을 부여. text, textColor 등.
  • label.translatesAutoresizingMaskIntoConstraints = false 이 코드는 오토 레이아웃을 활성화시키기 위해 작성 필요.
  • NSLayoutConstraint.activate 안에 제약조건들을 작성.
import UIKit

class ViewController: UIViewController {
    
    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        label.text = "안녕하세요"
        label.textColor = .black
        label.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(label)
        NSLayoutConstraint.activate([
            label.widthAnchor.constraint(equalToConstant: 80),
            label.heightAnchor.constraint(equalToConstant: 40),
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
 }

UIButton

▪️ NSLayoutConstraint를 이용해서 UIButton 그려보기.

  • let button = UIButton() 코드로 버튼을 선언.
  • setTitle, setTitleColor, backgroundColor 등 속성을 코드로 부여.
  • 버튼뿐 아니라 모든 뷰들의 테두리를 둥글게 하고 싶다면 layer.cornerRadius 속성 사용.
import UIKit

class ViewController: UIViewController {
    
    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        // 버튼 타이틀 지정.
        button.setTitle("Click", for: .normal)
        // 버튼 타이틀 컬러 지정.
        button.setTitleColor(.white, for: .normal)
        // 버튼 색상 지정.
        button.backgroundColor = .red
        button.translatesAutoresizingMaskIntoConstraints = false
		    // 버튼 테두리 둥글게 지정.
        button.layer.cornerRadius = 10
        
        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.widthAnchor.constraint(equalToConstant: 120),
            button.heightAnchor.constraint(equalToConstant: 60),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
 }

UIImageView

▪️ NSLayoutConstraint를 이용해서 UIImageView 그려보기.

  • let imageView = UIImageView() 코드로 이미지 뷰를 선언.
  • image, backgroundColor, contentMode 등 속성을 코드로 부여.
  • 전에 배웠듯, 미리 이미지 파일을 프로젝트에 추가해야 함.
import UIKit

class ViewController: UIViewController {
    
    let imageView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    private func configureUI() {
        view.backgroundColor = .white
        imageView.image = UIImage(named: "cat")
        imageView.backgroundColor = .black
        imageView.contentMode = .scaleAspectFit
        imageView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(imageView)
        NSLayoutConstraint.activate([
            imageView.widthAnchor.constraint(equalToConstant: 300),
            imageView.heightAnchor.constraint(equalToConstant: 300),
            imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

SnapKit


SnapKit 이란?

SnapKit 이란 코드베이스로 UI를 작성할 때, 조금 더 간결한 문법을 사용하도록 도와주는 서드파티 라이브러리. NSLayoutConstraint 을 사용할 때보다 편하게 코드를 작성할 수 있게 도와준다.
🧑🏻‍💻 현업에서 가장 많이 사용하는 필수 라이브러리 중 하나.

Swift Package Manager (SPM)

  • Swift Package Manager 란, 프로젝트에 서드파티 라이브러리를 가져와서 사용할 수 있도록 도와주는 도구.
  • Cocoapods, Carthage 등 다른 도구도 있지만, SPM 이 애플에서 지원하는 퍼스트 파티 도구.
  • SPM 을 통해서 SnapKit 을 프로젝트에 추가해 봅시다.

<프로젝트에 SPM으로 서드파티 라이브러리 추가하는 방법>

  1. 프로젝트 파일에서 TARGETS 선택 → General → Frameworks, Libraries 영역에서 십자(+) 버튼 클릭

  2. Add Other… 클릭 후 Add Package Dependency 클릭.

  3. 구글에 사용할 라이브러리 검색 → github 들어가서 Code → HTTPS 복사.

  4. 검색창에 사용할 라이브러리의 주소 붙여 넣기 → Add Package.

    1. 네비게이터 영역에 Package Dependencies에 SnapKit 생긴 것 확인. 코드에 import SnapKit 이 가능해진 것 확인.

링크텍스트

UILabel

▪️ SnapKit을 이용해서 UILabel 그려보기.

  • let label = UILabel() 코드로 라벨을 선언.
  • 라벨의 속성을 부여하는 코드는 NSConstraintLayout 때와 동일.
  • label.translatesAutoresizingMaskIntoConstraints = false 이 코드가 SnapKit에서는 필요하지 않음.
  • NSLayoutConstraint.activate로 작성하던 제약조건들을 SnapKit 문법으로 작성.
  • inset: 슈퍼뷰의 경계로부터 내부 여백을 설정합니다.
  • offset: 특정 뷰나 위치로부터 외부 여백을 설정합니다.
import UIKit
import SnapKit

class ViewController: UIViewController {
    
    let label = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    private func configureUI() {
        view.backgroundColor = .white
        label.text = "안녕하세요"
        label.textColor = .black
        
        view.addSubview(label)
        label.snp.makeConstraints {
            $0.width.equalTo(80)
            $0.height.equalTo(40)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }
}

아래처럼 작성할 수도 있지만, Swift 문법에서 클로저의 인자는 $0으로 축약할 수 있기 때문에 축약.

// (1) 축약하지 않은 코드.
label.snp.makeConstraints { make in
    make.width.equalTo(80)
    make.height.equalTo(40)
    make.centerX.equalToSuperview()
    make.centerY.equalToSuperview()
}

// (2) 축약할 수 있으니 이렇게 축약합시다.
label.snp.makeConstraints {
    $0.width.equalTo(80)
    $0.height.equalTo(40)
    $0.centerX.equalToSuperview()
    $0.centerY.equalToSuperview()
}

같은 내용의 NSLayoutConstraint 와 비교했을 때 SnapKit 코드가 가독성이 더 좋음.
구성해야 할 레이아웃 종류들이 많아질수록 체감이 커짐.
translatesAutoresizingMaskIntoConstraints를 신경 쓰지 않아도 됨.

// (1) NSLayoutConstraint 로 작성한 코드.
NSLayoutConstraint.activate([
    label.widthAnchor.constraint(equalToConstant: 80),
    label.heightAnchor.constraint(equalToConstant: 40),
    label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])

// (2) SnapKit 으로 작성한 코드.
label.snp.makeConstraints {
    $0.width.equalTo(80)
    $0.height.equalTo(40)
    $0.centerX.equalToSuperview()
    $0.centerY.equalToSuperview()
}

UIButton

import UIKit
import SnapKit

class ViewController: UIViewController {
    
    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        // 버튼 타이틀 지정.
        button.setTitle("Click", for: .normal)
        // 버튼 타이틀 컬러 지정.
        button.setTitleColor(.white, for: .normal)
        // 버튼 색상 지정.
        button.backgroundColor = .red
        // 버튼 테두리 둥글게 지정.
        button.layer.cornerRadius = 10
        
        view.addSubview(button)
        button.snp.makeConstraints {
            $0.width.equalTo(120)
            $0.height.equalTo(60)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }
}

💡 IBAction 에서 했었던, 버튼 클릭 이벤트 추가하는 방법.

  • button.addTarget(**self**, action: **#selector**(buttonClicked), for: .touchDown)
    addTarget 메서드 사용.
  • #selector() 내부에 버튼 클릭 시 어떤 로직을 수행할 건지 담긴 메서드 작성.
  • for: 버튼의 어떤 이벤트에 로직을 수행할 것인지.
  • touchDown : 사용자가 버튼을 터치하는 순간 발생.
  • touchUpInside: 사용자가 버튼을 터치한 후 손가락을 떼는 순간 발생.
import UIKit
import SnapKit

class ViewController: UIViewController {
    
    let button = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .white
        // 버튼 타이틀 지정.
        button.setTitle("Click", for: .normal)
        // 버튼 타이틀 컬러 지정.
        button.setTitleColor(.white, for: .normal)
        // 버튼 색상 지정.
        button.backgroundColor = .red
        // 버튼 테두리 둥글게 지정.
        button.layer.cornerRadius = 10
        // 버튼 클릭 이벤트 추가.
        button.addTarget(self, action: #selector(buttonClicked), for: .touchDown)
        
        view.addSubview(button)
        button.snp.makeConstraints {
            $0.width.equalTo(120)
            $0.height.equalTo(60)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }
    
    // #selector() 안에 넣기 위해서는 @objc 키워드 붙여야 함.
    @objc 
    private func buttonClicked() {
        print("버튼이 클릭되었음.")
    }
}

UIImageView

import UIKit
import SnapKit

class ViewController: UIViewController {
    
    let imageView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    private func configureUI() {
        view.backgroundColor = .white
        imageView.image = UIImage(named: "cat")
        imageView.backgroundColor = .black
        imageView.contentMode = .scaleAspectFit
        
        view.addSubview(imageView)
        
        imageView.snp.makeConstraints {
            $0.width.equalTo(300)
            $0.height.equalTo(300)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
    }
}
  • SnapKit을 이용해서 UIImageView와 UILabel을 제약조건과 함께 배치해 봅시다.
import UIKit
import SnapKit

class ViewController: UIViewController {
    
    let imageView = UIImageView()
    let label = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    private func configureUI() {
        view.backgroundColor = .white
        imageView.image = UIImage(named: "cat")
        imageView.backgroundColor = .black
        imageView.contentMode = .scaleAspectFit
        label.text = "고양이"
        label.textColor = .black
        label.font = UIFont.boldSystemFont(ofSize: 30)
        
        [imageView, label]
            .forEach { view.addSubview($0) }
        
        imageView.snp.makeConstraints {
            $0.width.equalTo(300)
            $0.height.equalTo(300)
            $0.centerX.equalToSuperview()
            $0.centerY.equalToSuperview()
        }
        
        label.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(imageView.snp.bottom).offset(16)
        }
    }
}
  • SnapKit을 이용해서 다음과 같은 UI를 구성해 봅시다.
import UIKit
import SnapKit

class ViewController: UIViewController {
    
    let imageView = UIImageView()
    let imageView2 = UIImageView()
    let label = UILabel()
    let label2 = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    private func configureUI() {
        view.backgroundColor = .white
        imageView.image = UIImage(named: "cat")
        imageView.backgroundColor = .black
        imageView.contentMode = .scaleAspectFit
        label.text = "고양이"
        label.textColor = .black
        label.font = UIFont.boldSystemFont(ofSize: 30)
        imageView2.image = UIImage(named: "dog")
        imageView2.backgroundColor = .black
        imageView2.contentMode = .scaleAspectFit
        label2.text = "강아지"
        label2.textColor = .black
        label2.font = UIFont.boldSystemFont(ofSize: 30)
        
        [imageView, label, imageView2, label2]
            .forEach { view.addSubview($0) }
        
        imageView.snp.makeConstraints {
            $0.width.equalTo(160)
            $0.height.equalTo(160)
            $0.leading.equalToSuperview().inset(16)
            $0.centerY.equalToSuperview()
        }
        
        label.snp.makeConstraints {
            $0.centerX.equalTo(imageView.snp.centerX)
            $0.top.equalTo(imageView.snp.bottom).offset(16)
        }
        
        imageView2.snp.makeConstraints {
            $0.width.equalTo(160)
            $0.height.equalTo(160)
            $0.trailing.equalToSuperview().inset(16)
            $0.centerY.equalToSuperview()
        }
        
        label2.snp.makeConstraints {
            $0.centerX.equalTo(imageView2.snp.centerX)
            $0.top.equalTo(imageView2.snp.bottom).offset(16)
        }
    }
}

UI 디버깅 팁

코드 실행 중에 (런타임에) 화살표 표시한 버튼을 누르면 뷰의 계층구조를 파악하며 디버깅할 수 있음.

profile
맨날 최선을 다하지는 마러라. 피곤해서 못산다.

1개의 댓글

comment-user-thumbnail
2024년 11월 22일

동글님,,,!!!!!! 벨로그 구경왔는데 상당한 , , , 썸넬 장인이시군뇨!!!!! 넘 뽀짝하고 재미있어요 ㅎㅅㅎ 직접 만드시는건가욥 !!!!!

답글 달기