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



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 종류
| 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 차이
| 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
| padding() | 여백 |
| frame() | 크기 |
| background() | 배경 |
| cornerRadius() | 둥글게 |
| Spacer() | 공간 밀기 |
가장 중요한 개념
SwiftUI는:
Constraint 기반
보다
구조 기반 레이아웃
에 가까움.
즉:
“좌표를 계산”하기보다
“뷰를 어떤 구조로 쌓을지”를 선언함.







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 뜻
| 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











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



이 코드는 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
현재 몇 번째 줄인지.
예:
| 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에 해당하는 영화 데이터 가져오기.
예시
| 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 데이터를 안전하게 처리하면서
숫자를 보기 좋게 화면에 출력하는 것