본문 바로가기

카테고리 없음

iOS 프로그래밍 실무 11주차

오토레이아웃 방법 5가지(현재 실무 -> 스토리보드+NSLayoutAnchor 코드 병행)

이번엔 스토리보드 기반으로 실무

 

NSLayoutAnchor란?

NSLayoutAnchor는 코드로 오토레이아웃(Auto Layout)을 설정하는 방법이야.

쉽게 말하면:

“이 버튼을 화면 위에서 100 떨어뜨리고,
왼쪽에서 20 떨어뜨려라”

같은 제약조건(Constraint)을 코드로 만드는 방식.

왜 사용하는가?

스토리보드 없이도 화면 배치를 가능하게 함.

특히:

  • 동적으로 뷰 생성할 때
  • 코드 기반 UI 만들 때
  • 재사용 컴포넌트 만들 때

엄청 많이 사용됨.

기본 구조

 
뷰1.anchor.constraint(equalTo: 뷰2.anchor, constant: 값)
 

예시:

 
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 100)
 

뜻:

button의 위쪽을
부모 view의 위쪽 기준으로
100 떨어뜨린다


가장 많이 사용하는 Anchor 종류

Anchor의미
topAnchor
bottomAnchor 아래
leadingAnchor 왼쪽
trailingAnchor 오른쪽
widthAnchor 너비
heightAnchor 높이
centerXAnchor 가로 중앙
centerYAnchor 세로 중앙

가장 기본 예제

목표

화면 중앙에 버튼 하나 만들기.

 

서브파티 라이브러리(만들어진 소스를 활동가능함)

https://cocoapods.org/pods/SnapKit

 

SnapKit

Harness the power of auto layout with a simplified, chainable, and compile time safe syntax.

cocoapods.org

 

import SwiftUI

struct ContentView: View {

    var body: some View {

        VStack(alignment: .leading, spacing: 16) {

            // 제목 텍스트
            Text("안녕하세요!")
                .font(.largeTitle)
                .foregroundColor(.blue)

            // 버튼
            Button(action: {

                // 버튼 클릭 시 실행
                print("버튼이 눌렸습니다")

            }) {

                // 버튼 내부 텍스트
                Text("버튼 눌러보기")
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
        }
        .padding(24)
    }
}

SwiftUI의 오토레이아웃 방식

SwiftUI는 UIKit처럼:

 
NSLayoutConstraint
 

를 직접 작성하지 않아.

대신:

Stack + Spacer + Frame + Padding
 

같은 레이아웃 시스템으로 자동 배치함.

즉:

“제약조건을 직접 연결” 하는 방식이 아니라
“뷰의 구조를 선언” 하는 방식.


UIKit vs SwiftUI 차이

UIKitSwiftUI
NSLayoutConstraint Stack 기반
Anchor 연결 선언형 UI
직접 좌표 느낌 자동 배치
복잡함 간단함

가장 기본

VStack

예제

 
import SwiftUI

struct ContentView: View {

    var body: some View {

        VStack {

            Text("첫 번째")

            Text("두 번째")

            Text("세 번째")
        }
    }
}
 

결과

첫 번째
두 번째
세 번째
 

세로 방향 자동 배치.


SwiftUI 핵심 레이아웃 3개

레이아웃방향
VStack 세로
HStack 가로
ZStack 겹치기

1. VStack

세로 배치

 
VStack {
    Text("A")
    Text("B")
}
 

결과:

A
B
 

2. HStack

가로 배치

 
HStack {
    Text("A")
    Text("B")
}
 

결과:

A   B
 

3. ZStack

겹쳐서 배치

 
ZStack {

    Color.yellow

    Text("안녕하세요")
}
 

결과:

배경 위에 텍스트
 

실제 오토레이아웃 느낌 예제

화면 중앙 버튼

UIKit에서는:

 
centerXAnchor
centerYAnchor
 

사용했음.


SwiftUI 방식

 
import SwiftUI

struct ContentView: View {

    var body: some View {

        VStack {

            Spacer()

            Button("클릭") {

            }

            Spacer()
        }
    }
}
 

왜 가운데인가?

 
Spacer()
 

가 위/아래 공간을 균등 분배함.


개념 그림

↑ 남는 공간

[ 버튼 ]

↓ 남는 공간
 

그래서 중앙 정렬됨.


Spacer()

엄청 중요.

남는 공간을 밀어내는 역할.


예제

오른쪽 정렬

 
HStack {

    Spacer()

    Text("오른쪽")
}
 

결과:

                    오른쪽
 

예제

왼쪽 / 가운데 / 오른쪽

 
HStack {

    Text("왼쪽")

    Spacer()

    Text("오른쪽")
}
 

결과:

왼쪽                오른쪽
 

padding()

여백

 
Text("Hello")
    .padding()
 

자동 여백 추가.


특정 방향 padding

 
.padding(.top, 50)
 

위쪽만 50.


frame()

크기 지정

 
Text("Hello")
    .frame(width: 200, height: 100)
 

뷰 크기 설정.


frame + alignment

 
.frame(maxWidth: .infinity,
       alignment: .leading)
 

뜻:

  • 가로 최대 확장
  • 왼쪽 정렬

예제

전체 너비 버튼

 
Button("로그인") {

}
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
 

결과:

┌──────────────────┐
│      로그인       │
└──────────────────┘
 

safeArea

UIKit:

 
safeAreaLayoutGuide
 

SwiftUI:

자동 처리되는 경우 많음.


SafeArea 무시

 
.ignoresSafeArea()
 

화면 끝까지 확장.


실전 예제

로그인 화면

 
import SwiftUI

struct ContentView: View {

    var body: some View {

        VStack(spacing: 20) {

            Text("로그인")
                .font(.largeTitle)

            TextField("아이디", text: .constant(""))
                .textFieldStyle(.roundedBorder)

            SecureField("비밀번호", text: .constant(""))
                .textFieldStyle(.roundedBorder)

            Button("로그인") {

            }
            .frame(maxWidth: .infinity)
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)

        }
        .padding()
    }
}
 

레이아웃 개념

VStack
 ├── 제목
 ├── 아이디 입력
 ├── 비밀번호 입력
 └── 버튼
 

자동 세로 정렬.


SwiftUI 오토레이아웃 특징

특징설명
선언형 UI 어떻게 배치할지 선언
자동 반응형 화면 크기 자동 대응
Stack 중심 VStack/HStack
Constraint 거의 없음 Anchor 안 씀

UIKit와 비교

UIKit

 
button.topAnchor.constraint(...)
 

“어디에 붙여라”


SwiftUI

 
VStack {
    Button()
}
 

“구조를 어떻게 만들지”


핵심 Modifier

Modifier역할
padding() 여백
frame() 크기
background() 배경
cornerRadius() 둥글게
Spacer() 공간 밀기

가장 중요한 개념

SwiftUI는:

Constraint 기반
 

보다

구조 기반 레이아웃
 

에 가까움.

즉:

“좌표를 계산”하기보다
“뷰를 어떤 구조로 쌓을지”를 선언함.

 

 

용어 숙지(시험*)
Align(정렬)->뷰(View)를 어느 방향 기준으로 정렬할지 정하는 것
Pin(제약조건)->“뷰의 위치와 크기를 정하는 규칙”
Stackview->여러 뷰(View)를 정렬해서 쌓아주는 레이아웃 컨테이너
Size inspector

Size Inspector란?

Xcode의 Size Inspector는:

View(버튼, 라벨, 이미지 등)의 크기와 오토레이아웃(Constraints)을 설정하는 곳

이야.

역할

Size Inspector에서는:

  • Width(너비)
  • Height(높이)
  • X/Y 위치
  • Constraints(제약조건)

같은 걸 확인하고 수정 가능.

가장 중요한 기능

Constraints 확인

예:

Top = 20
Leading = 16
Width = 100
Height = 50
 

현재 뷰의 오토레이아웃 정보를 보여줌.

Top / Leading 뜻

Constraint의미
Top 위쪽 거리
Bottom 아래쪽 거리
Leading 왼쪽 거리
Trailing 오른쪽 거리

Width / Height

항목의미
Width 너비
Height 높이

Frame Rectangle

Size Inspector에서 자주 보이는 항목.

X
Y
Width
Height
 

의미

값의미
X 가로 위치
Y 세로 위치
Width 너비
Height 높이

하지만 중요

오토레이아웃 사용할 때는:

Frame 값보다 Constraints가 우선
 

임.

즉:

Constraint가 실제 위치 결정.

예시

버튼 선택 후:

Width = 120
Height = 50
 

Constraint 설정하면

실행 시 그 크기로 나타남.

빨간색 / 노란색 경고

Size Inspector에서 자주 보임.

빨간색

Constraint 부족 또는 충돌
 

오류.

노란색

현재 위치와 Constraint 위치 다름
 

경고.

해결 방법

보통:

Update Frames
 

버튼 눌러 해결.

Add New Constraints 와 관계

스토리보드 아래쪽:

Pin 버튼
 

으로 Constraint 추가.

그리고:

Size Inspector
 

에서 확인 가능.

 

우선순위 1000 750 250 ...으로 우선순위가 높을 수록 먼저 실행(고려)되게함

우선순위(Priority)란?

오토레이아웃에서 Priority는:

제약조건(Constraint) 중 어떤 조건을 더 중요하게 생각할지 정하는 값

이야.

쉽게 말하면:

조건이 충돌하면
누구 말을 더 우선할까?
 

를 결정함.


왜 필요한가?

가끔 Constraint끼리 충돌함.

예:

버튼 너비는 200이어야 함
근데 화면이 너무 작음
 

이럴 때 시스템이:

어떤 Constraint를 포기할지
 

결정해야 함.

그래서 Priority 사용.


Priority 값 범위

1 ~ 1000
 

가장 중요

1000 = Required
 

반드시 지켜야 함.


자주 사용하는 값

값의미
1000 필수(Required)
750 높음(High)
500 중간(Medium)
250 낮음(Low)

최고 우선순위가 1000 

 

 

글자수에 따라 레이블의 크기가 자동으로 결정됨-> 레이블에 들어가는 컨텐츠의 크기
콘텐츠 허깅 -> 늘어나지 않으려는 정도, 컴프레션(압축)-> 줄어들지 않으려는 정도

 

스택뷰 2가지 방법=> Vertical 수직, Horizontal 수평
스택뷰 3가지 정렬,배분,간격 정리
수동으로 수정하면 오류가 맞는지 경고 출력
오류를 자동으로 해결해주는 Resolve Auto Layout Issues(결과가 변경되어서 추천은 X)
label 두개를 스택뷰로
오른쪽 레이블 두개는 v스택뷰, 왼쪽 레이블과 오른쪽 스택뷰는 h스택뷰로 지정

 

cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm

cell.audiAccumulate.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiAcc

cell.audiCount.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].audiCnt

lines와 autoshrink를 통해 글자수가 레이블을 넘어가면 자동으로 크기를 줄이거나 다음 줄로 이동시킨다

 

이 코드는 UITableView에서
각 행(Cell)에 영화 정보를 넣어서 화면에 보여주는 부분이야.

즉:

테이블 한 줄을 어떻게 만들지 정의하는 함수
 

함수 전체 역할

 
func tableView(_ tableView: UITableView,
               cellForRowAt indexPath: IndexPath)
-> UITableViewCell
 

의미:

"indexPath.row 위치에 어떤 셀을 보여줄까요?"

테이블뷰가 한 줄씩 호출함.


예를 들어

0번째 줄 → 1위 영화
1번째 줄 → 2위 영화
2번째 줄 → 3위 영화
 

이렇게 반복됨.


전체 흐름

1. 셀 가져오기
2. 영화 데이터 꺼내기
3. 라벨에 넣기
4. 셀 반환
 

1. 셀 생성

 
let cell = tableView.dequeueReusableCell(
    withIdentifier: "myCell",
    for: indexPath
) as! MyTableViewCell
 

의미

테이블뷰가 재사용 가능한 셀 가져옴.


dequeueReusableCell

엄청 중요.

테이블뷰는 셀을 계속 새로 만들면 느려짐.

그래서:

사용 끝난 셀 재활용
 

함.


withIdentifier

 
"myCell"
 

스토리보드에서 설정한 Cell Identifier.


as! MyTableViewCell

다운캐스팅.

기본 UITableViewCell → 내가 만든 커스텀 셀로 변환.


왜 필요한가?

커스텀 라벨 사용하려고.

예:

 
cell.movieName
cell.audiCount
 

사용 가능해짐.


2. 영화 순위 가져오기

 
guard let mRank =
movieData?.boxOfficeResult
.dailyBoxOfficeList[indexPath.row].rank
else {
    return UITableViewCell()
}
 

의미

현재 row의 영화 순위 가져오기.


indexPath.row

현재 몇 번째 줄인지.

예:

row영화
0 1위
1 2위
2 3위

guard let

Optional 안전하게 언래핑.


왜 사용?

movieData가 nil일 수도 있기 때문.


실패하면?

 
return UITableViewCell()
 

빈 셀 반환.


3. 영화 제목 가져오기

 
guard let mName =
movieData?.boxOfficeResult
.dailyBoxOfficeList[indexPath.row].movieNm
else {
    return UITableViewCell()
}
 

movieNm

JSON의 영화 제목.

예:

미션 임파서블
 

4. 영화 제목 표시

 
cell.movieName.text = "[\(mRank)위] \(mName)"
 

결과

[1위] 미션 임파서블
 

문자열 보간(String Interpolation)

 
\(변수)
 

문자열 안 변수 넣기.


5. 어제 관객수 처리

 
if let aCnt =
movieData?.boxOfficeResult
.dailyBoxOfficeList[indexPath.row].audiCnt
 

audiCnt

어제 관객 수.

예:

15234
 

NumberFormatter 생성

 
let numF = NumberFormatter()
 

숫자 포맷 도구.


decimal 스타일

 
numF.numberStyle = .decimal
 

천 단위 콤마 추가.


예시

15234
↓
15,234
 

문자열 → Int 변환

 
let aCount = Int(aCnt)!
 

JSON 데이터는 String이라 숫자로 변환.


포맷 적용

 
let result =
numF.string(for: aCount)! + "명"
 

결과

15,234명
 

라벨 표시

 
cell.audiCount.text = "어제 : \(result)"
 

화면 결과

어제 : 15,234명
 

6. 누적 관객수 처리

구조는 거의 동일.


audiAcc

누적 관객 수.

예:

1523456
 

결과

누적 : 1,523,456명
 

마지막

 
return cell
 

완성된 셀 반환.


화면 최종 결과 느낌

[1위] 미션 임파서블
어제 : 15,234명
누적 : 1,523,456명
 

데이터 흐름 정리

movieData
   ↓
dailyBoxOfficeList
   ↓
indexPath.row
   ↓
현재 영화 정보
   ↓
Cell Label에 넣기
 

중요한 Swift 문법

문법의미
guard let 안전 언래핑
if let Optional 처리
( ) 문자열 보간
as! 다운캐스팅
dequeueReusableCell 셀 재사용

왜 NumberFormatter 쓰는가?

안 쓰면:

1523456명
 

가독성 안 좋음.

사용 후:

1,523,456명
 

훨씬 보기 좋음.


핵심 개념

이 함수는:

테이블뷰의 한 줄(Cell)을 구성하는 함수
 

라고 생각하면 됨.

테이블뷰가 줄마다 계속 호출함.

<콤마>

if let aAcc =
    movieData?.boxOfficeResult
        .dailyBoxOfficeList[indexPath.row].audiAcc {

    let numF = NumberFormatter()

    numF.numberStyle = .decimal

    let aAcc1 = Int(aAcc)!

    let result =
        numF.string(for: aAcc1)! + "명"

    cell.audiAccumulate.text = "누적 : \(result)"
}
 

이 코드는:

누적 관객수를 안전하게 꺼내서
콤마를 넣고
화면에 출력하는 코드
 

야.

한 줄씩 진짜 자세하게 볼게.


1. if let aAcc =

 
if let aAcc =
 

핵심 개념

if let은:

Optional 값을 안전하게 꺼내는 문법


왜 필요한가?

Swift에서는 값이 없을 수도 있는 타입을:

 
String?
 

처럼 Optional로 표현함.


현재 audiAcc 타입

JSON 데이터라:

 
audiAcc : String
 

인데 실제 접근은:

 
movieData?
 

부터 시작해서 Optional 체인이 생김.

즉 결과적으로:

 
String?
 

가 됨.


Optional이란?

값이:

  • 있을 수도 있고
  • nil일 수도 있음

예시

 
var name : String?
 

if let 의미

 
if let 변수 = Optional값 {

}
 

값이 있으면 꺼내서 실행
없으면 실행 안 함
 

현재 코드 의미

 
if let aAcc = ...
 

누적 관객수 데이터가 존재하면
aAcc에 넣고 아래 코드 실행
 

2. movieData?

 
movieData?
 

? 의미

Optional Chaining.


movieData가 nil이면
뒤 코드를 실행하지 않음
 

예시

 
movieData?.title
 

movieData가 있으면 title 접근
없으면 nil 반환
 

3. boxOfficeResult

 
boxOfficeResult
 

MovieData 안 객체 접근.


구조

MovieData
 └── boxOfficeResult
      └── dailyBoxOfficeList
 

4. dailyBoxOfficeList[indexPath.row]

 
dailyBoxOfficeList[indexPath.row]
 

의미

현재 row에 해당하는 영화 데이터 가져오기.


예시

row영화
0 1위 영화
1 2위 영화

indexPath.row

현재 몇 번째 셀인지 의미.


5. .audiAcc

 
.audiAcc
 

의미

누적 관객 수 가져오기.

예:

"1523456"
 

주의:

문자열(String)
 

임.


6. if문 실행 구조

만약 값이 있으면:

 
aAcc = "1523456"
 

됨.


7. NumberFormatter()

 
let numF = NumberFormatter()
 

역할

숫자 표시 형식(format) 설정 객체.


왜 쓰나?

그냥 출력하면:

1523456
 

읽기 어려움.


decimal 스타일 적용

 
numF.numberStyle = .decimal
 

결과

1,523,456
 

콤마 자동 추가.


8. Int(aAcc)!

 
let aAcc1 = Int(aAcc)!
 

왜 변환?

현재:

 
aAcc = "1523456"
 

문자열.


NumberFormatter는 숫자 필요

그래서:

String → Int 변환
 

Int(aAcc)

 
Int(aAcc)
 

결과 타입:

 
Int?
 

Optional Int.


왜 Optional?

문자열이 숫자가 아닐 수도 있으니까.

예:

 
Int("abc")
 

nil
 

! 의미

 
Int(aAcc)!
 

강제 언래핑.


반드시 숫자로 변환된다고 확신
 

현재는 안전한 이유

API에서 숫자 문자열만 오기 때문.

예:

"1523456"
 

9. numF.string(for:)

 
numF.string(for: aAcc1)
 

의미

숫자에 포맷 적용.


결과

1,523,456
 

반환 타입

 
String?
 

Optional String.


그래서 또 !

 
...!
 

사용.


10. +"명"

 
+ "명"
 

문자열 이어붙이기.


결과

1,523,456명
 

11. result 변수

 
let result = ...
 

최종 문자열 저장.


12. UILabel 출력

 
cell.audiAccumulate.text =
    "누적 : \(result)"
 

문자열 보간

 
\(result)
 

문자열 안 변수 삽입.


최종 결과

누적 : 1,523,456명
 

전체 흐름 요약

audiAcc 가져오기
↓
nil 체크(if let)
↓
String → Int 변환
↓
콤마 포맷 적용
↓
"명" 붙이기
↓
UILabel 출력
 

여기서 중요한 Swift 문법

문법의미
if let Optional 안전 언래핑
? Optional Chaining
! 강제 언래핑
Int() 문자열 → 숫자 변환
NumberFormatter 숫자 포맷
( ) 문자열 보간

가장 중요한 핵심

이 코드의 핵심 목적은:

Optional 데이터를 안전하게 처리하면서
숫자를 보기 좋게 화면에 출력하는 것