아요 개발 일기

[iOS] Code base로 UI 구현 (StoryBoard를 사용하지 않고 UI 구현하기) 본문

iOS

[iOS] Code base로 UI 구현 (StoryBoard를 사용하지 않고 UI 구현하기)

소진이 2023. 1. 2. 10:55

안녕하세요! 소진입니다 ㅎㅎ

그동안 Story Board로 UI 구현을 많이했었는데, 코드만으로도 UI를 구현하는 방법이 있다고 해서 정리해보려구요!

여러가지 방법으로 UI 구현하는 방법을 알면 더 좋은 방법으로 골라 사용할 수 있겠죠??

시작해봅시다!


Storyboard vs Code base

우선!

두 가지 방법의 차이점을 알아야겠죠??

 

  Storyboard Code base
장점 - 결과물을 예측하기 쉬움
- 속성 확인 가능
- 소스 코드를 일일히 파악하지 않아도 됨
- 가벼움
- 모니터 크기에 제약이 없음
- 상대적으로 Diff를 알아보기 쉬움
- Confict 발생 가능성이 상대적으로 낮음
단점 - 무거움
- 링크가 끊어졌을 때 알기 힘듬
(IBOutlet, IBAction)
- 모니터가 작을 시 불편함
- Diff로는 개발 내용을 알기 힘듬
- Conflict
- Component의 속성을
어느정도 숙지하고 있어야 함
- Source Code이기 때문에
화면을 예측하기 어려움
- Code가 상당히 길어짐

 

짠! 

보기 쉽게 표로 정리해 보았습니다 :)

 


 

Code base UI 프로젝트 설정

일단, Story board로 프로젝트를 생성합니다.

 

저희는 Code base 이므로 Story board는 필요 없겠죠???

과감히 Main.storyboard 파일을 지워줍시다!!

 

그리고

UI code를 짤 MainViewController를 만들어줍니다

 

 

저희가 story board로 UI를 짤때

처음 뜨는 view controller를 지정해 주었던거 기억하시나요?

 

 

위 이미지 처럼요!!

그러면 code로 짤때도 저희가 

Initial view Controller를 설정해 주어야겠네요!

 

1. SceneDelgate 수정

 

파일 목록을 보면 SceneDelegate 가 있죠?

SceneDelegate는 화면의 라이프 사이클을 관리하는 곳입니다.

 

 

저희는 저기 표시해 놓은 곳 코드를 수정해줄겁니다!

해당 부분 함수를 살펴 보자면

UISceneSession lifecycle에서 가장 먼저 호출되는 메소드인 scene에서 UIWindow 객체를 생성하고,

window의 rootViewController를 설정하는 역할을 합니다.

 

 

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    guard let windowScene = (scene as? UIWindowScene) else { return }

    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = MainViewController()
    self.window = window

    window.makeKeyAndVisible()
}

 

rootViewContoller를 설정해주었으니, 

실행해볼까요~

 

 

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Could not find a storyboard named 'Main' in bundle NSBundle 

 

아마 위와 동일한 크래시가 발생할껍니다

Main 스토리보드를 찾을 수 없다는 에러 로그네요!!

 

해결하러 갑시다!

 

3. Info.plist 수정

 

프로젝트 Info에서 Storyboard Name을 삭제해줍니다.

 

 

Build Settings에서 Main을 검색하고

UIKit Main Storyboard File Base Name에서

Main이라고 적혀있는 부분을 지워줍니다

 

 

끝!!

실행해봅시당

 

 

짠!

오류 없이 잘 실행되네요 ㅎㅎ

 

이번에는 직접 Code로 UI를 짜보려고하는데

이왕이면 처음에 봤었던 Code base UI 의 단점들을

해결 할 수 있는 방법으로 코드를 짜보겠습니다!!

 


 

Code base 단점 극복하기!

 

1. Component 속성 숙지

Document 자주 보기!

(Cmd + Shift + O)

 

2. 화면을 예측하기 어려운 부분

Auto Layout 사용하면되는데,

구현 방법에대해 알아보도록 하겠습니다! :)

 

 

 

일단 저는 view를 위 이미지처럼 구성하였습니다.

view는 각자 자유롭게 구성하셔도됩니다!!

 

공통 코드

아래 코드들은 공통적으로 있는 부분들입니다!

자세히 봅시다!

 

private let titleLabel: UILabel = {
    let titleLabel = UILabel()
    titleLabel.textColor = .black
    titleLabel.font = .preferredFont(forTextStyle: .title1, compatibleWith: .none)
    titleLabel.text = "정보 입력"

    return Titlelabel
}()

 

View에 label, button, field등을 어떻게 보여줄지!

디자인? 을 하는 부분입니다

(폰트나 글씨 크기, text 정보, 모양 등등)

 

override func viewDidLoad() {
    super.viewDidLoad()

    setup()
    setConstraint()
}

func setup() {
    view.backgroundColor = .white
    addViews()
}

func addViews() {
    view.addSubview(titleLabel)
    view.addSubview(nameLabel)
    view.addSubview(nameField)
    view.addSubview(dateLabel)
    view.addSubview(datePicker)
    view.addSubview(button)
}

 

view.addSubview를 하여 만든 것들을 추가해주고 background 설정을 해줍니다.

 

setup()과 addViews()함수에 있는 부분들은 viewDidLoad()에 직접 선언해줘도 됩니다!

하지만 저는 역할별로 나눠주기위해서 각 함수를 만들어 주었습니다 :)

 

이 부분은 코드가 동일하구요!

이제 Autolayout 설정해보도록하겠습니다 ㅎㅎ

 


 

구현 방법

 

 

1. NSLayoutConstraint를 일일히 작성

Titlelabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    NSLayoutConstraint(item: Titlelabel, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 30),
    NSLayoutConstraint(item: Titlelabel, attribute: .top, relatedBy: .equal, toItem: view, attribute: .topMargin, multiplier: 1.0, constant: 100),
    NSLayoutConstraint(item: Titlelabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1.0, constant: 170),
    NSLayoutConstraint(item: Titlelabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1.0, constant: 80)

])

 

 

2. Visual Format Language 사용

- 독특한 문법

- 가운데 정렬이 매우 귀찮음

 - 가독성이 아주 뛰어나다고 하기 힘듬

 

nameField.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate(
 [

// top과의 높이
NSLayoutConstraint.constraints(withVisualFormat: "V:|-(250)-[textField(200)]",
options: .alignAllTop,
metrics: nil,
views: ["textField": nameField]),

// leading
NSLayoutConstraint.constraints(withVisualFormat: "H:[textField(200)]",
options: .alignAllLeading,
metrics: nil,
views: ["textField": nameField]),

 // width
 NSLayoutConstraint.constraints(withVisualFormat: "H:[textField(250)]",
 options: .alignAllCenterX,
 metrics: nil,
 views: ["textField": nameField]),

 // height
 NSLayoutConstraint.constraints(withVisualFormat: "V:[sv]-(<=1.0)-[textField(30)]",
 options: .alignAllCenterX,
 metrics: nil,
 views: ["sv": view!, "textField": nameField])

 ].flatMap({ $0 })
)

 

 

3. Anchor 사용

- 앞의 방법보다 가독성이 뛰어남

- priority의 설정이 귀찮음 (isActive)

 

namelabel.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
    namelabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 250),
    namelabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 50),
    namelabel.widthAnchor.constraint(equalToConstant: 100),
    namelabel.heightAnchor.constraint(equalToConstant: 30)
])

 

 

4. 라이브러리 사용

SnapKit

 

유명한 SnapKit 라이브러리를 사용한 코드입니다

SnapKit은 클로저를 활용하여 구현하는 것 같고,

위의 코드랑 비교했을때 확실히 가독성이 좋네요!!ㅎㅎㅎ

 

 SnapKit 라이브러리

namelabel.snp.makeConstraints{ (make) in
    make.top.equalTo(Titlelabel.snp.bottom).offset(20)
    make.leading.equalTo(self.view).offset(50)
    make.width.equalTo(50)
    make.height.equalTo(30)
}


nameField.snp.makeConstraints{ (make) in
    make.top.equalTo(Titlelabel.snp.bottom).offset(20)
    make.leading.equalTo(namelabel.snp.trailing).offset(10)
    make.width.equalTo(250)
    make.height.equalTo(30)
}



datelabel.snp.makeConstraints{ (make) in
    make.top.equalTo(namelabel.snp.bottom).offset(20)
    make.leading.equalTo(self.view).offset(50)
    make.width.equalTo(70)
    make.height.equalTo(30)
}



datePicker.snp.makeConstraints{ (make) in            
make.top.equalTo(nameField.snp.bottom).offset(20)
    make.leading.equalTo(datelabel.snp.trailing).offset(10)
    make.width.equalTo(80)
}


button.snp.makeConstraints{ (make) in
    make.top.equalTo(datePicker.snp.bottom).offset(20)
    make.leading.equalTo(self.view).offset(270)
    make.width.equalTo(80)
    make.height.equalTo(30)
}

 

 

 

SuperEasyLayout

 

SuperEasyLayout 라이브러리를 사용한 코드입니다

SnapKit보다는 사용하는 사람이 적은 것 같지만 

개인적으로 엄청 깔끔하다고 생각이 듭니다!ㅎㅎㅎ

 

SuperEasyLayout 라이브러리

Titlelabel.centerX == view.centerX + 35
Titlelabel.top == view.top + 150
Titlelabel.width == 170.0
Titlelabel.height == 80.0

namelabel.top == Titlelabel.bottom + 20
namelabel.leading == view.leading + 50
namelabel.width == 50.0
namelabel.height == 30.0

nameField.top == Titlelabel.bottom + 20
nameField.leading == namelabel.trailing + 10
nameField.width == 250.0
nameField.height == 30.0

datelabel.top == namelabel.bottom + 20
datelabel.leading == view.leading + 50
datelabel.width == 70.0
datelabel.height == 30.0


datePicker.top == nameField.bottom + 20
datePicker.leading == datelabel.trailing + 10
datePicker.width == 80

button.top == datePicker.bottom + 20
button.leading == view.leading + 270
button.width == 80.0
button.height == 30.0

 

 

참고 사이트 

http://letswift.kr/2019/resources/Session10_JungwooLEE.pdf 

 

전체 코드

https://github.com/a/TIL/tree/master/iOS/UI/Coad_base/CodebaseUI

https://github.com/sojin2/TIL/tree/master/iOS/UI/Coad_base/SnapKitUI

https://github.com/sojin2/TIL/tree/master/iOS/UI/Coad_base/SuperEasyLaoutUI

 

'iOS' 카테고리의 다른 글

[iOS] 델리 게이트 - Delegate  (0) 2023.01.03
[iOS] Cocoa Touch Framework  (2) 2023.01.02
[iOS] UICollectionView  (0) 2023.01.02
[iOS] ViewController Life - cycle (생명주기)  (0) 2023.01.02
[iOS] Apple's MVC 패턴  (0) 2023.01.02