๐ Folder vs Group (Xcode)
| ํญ๋ชฉ | Folder (ํ๋์) |
Group (ํ์) |
|---|---|---|
| ๊ธฐ๋ณธ ์ ์ฉ ๋ฒ์ | Xcode 16๋ถํฐ ๊ธฐ๋ณธ | Xcode 15 ์ดํ์์ ๊ธฐ๋ณธ |
| ํ์ผ ์์คํ ๋ฐ์ | ์ค์ macOS ํ์ผ ์์คํ ์๋ ๋์ผํ ํด๋ ๊ตฌ์กฐ๋ก ์์ฑ๋จ | Xcode ํ๋ก์ ํธ ๋ด์์๋ง ์กด์ฌํ๋ฉฐ, ์ค์ ํ์ผ ์์คํ ๊ณผ ์ผ์นํ์ง ์์ ์ ์์ |
| Git ๋ณํฉ ์ถฉ๋ | ์๋์ ์ผ๋ก ์ ์ | ํ๋ก์ ํธ ํ์ผ (.xcodeproj) ์์ฒด ๋ณ๊ฒฝ์ด ๋ง์์ ธ ์ถฉ๋ ์ํ ๋์ |
| ํ๋ก์ ํธ ๋ด ์ ๋ ฌ | ์ด๋ฆ ๊ธฐ์ค ์๋ ์ ๋ ฌ (์ํ๋ฒณ ์) | ์๋ ์ ๋ ฌ ๊ฐ๋ฅ (๊ฐ๋ฐ์ ์์ ๋ฐฐ์น) |
| ํ์ ์ ์ฅ์ | Git ๋ณํฉ ์ถฉ๋ ์ต์ํ๋ก ํ์ ์ ์ ๋ฆฌ | ํ์ ์ ํ๋ก์ ํธ ํ์ผ ์ถฉ๋ ๊ฐ๋ฅ์ฑ ํผ |
| ๋จ์ | ํ์ผ ์์น๊ฐ ๊ณ ์ ์ ์ด๋ฉฐ, ์ปค์คํ ์ ๋ ฌ ์ด๋ ค์ | Git ์ถฉ๋ ์ ํด๊ฒฐ์ด ๊น๋ค๋กญ๊ณ , ์ค์ ๊ฐ๋ฅ์ฑ ์์ |
๊ฐ์์์ Git ์ถฉ๋์ ์ค์ด๊ธฐ ์ํด Folder ๋ฐฉ์์ ์ ํธ
ํ์ง๋ง ๊ฐ์ธ์ ์ผ๋ก ํ์ผ ์ ๋ ฌ์ ๋ ์ ์ฐํ๊ฒ ๊ด๋ฆฌํ๊ณ ์ถ๋ค๋ฉด Group ๋ฐฉ์๋ ๊ณ ๋ คํ ์ ์์
Single Responsibility Principle (๋จ์ผ ์ฑ ์ ์์น)
์ ์
ํ๋์ ํด๋์ค, ๊ตฌ์กฐ์ฒด, ๋ทฐ, ํ์ผ ๋ฑ์ ์ค์ง ํ๋์ ์ฑ
์๋ง์ ๊ฐ์ ธ์ผ ํ๋ฉฐ, ํ๋์ ์ด์ ๋ก๋ง ๋ณ๊ฒฝ๋์ด์ผ ํ๋ค๋ ์ํํธ์จ์ด ์ค๊ณ ์์น.
์์
๋์ ์
struct CurrencyView: View {
var body: some View {
// ํตํ ์์ด์ฝ ๋์์ธ
// ๊ทธ๋ฆฌ๋ ๋ ์ด์์ ์ฒ๋ฆฌ
// ํตํ ์ ํ ํ๋ฉด ์ ์ฒด ๊ตฌ์ฑ
}
}
๋ชจ๋ ์ญํ ์ด ํ๋์ ๋ทฐ์ ๋ชฐ๋ ค ์์ด ์ ์ง๋ณด์ ๋ฐ ์ฌ์ฌ์ฉ ์ด๋ ค์
์ข์ ์
struct CurrencyIconView: View {
// ํตํ ์์ด์ฝ ๋์์ธ๋ง ๋ด๋น
}
struct IconGridView: View {
// ์ฌ๋ฌ ๊ฐ์ ์์ด์ฝ์ ๊ทธ๋ฆฌ๋๋ก ๋ฐฐ์น
}
struct SelectCurrencyView: View {
// ์ ์ฒด ํ๋ฉด์ ๊ตฌ์ฑํ๋ฉฐ ์ ์ปดํฌ๋ํธ๋ฅผ ์กฐํฉ
}
๊ฐ ๋ทฐ๋ ํ ๊ฐ์ง ์ญํ ๋ง ํ๋ฉฐ, ๋ช ํํ ์ฑ ์ ๋ถ๋ฆฌ
์ฅ์
- ์ฝ๋ ๊ฐ๋ ์ฑ ํฅ์
- ์ ์ง๋ณด์ ์ฉ์ด
- ํ ์คํธ ๋จ์ ์์์ง โ ์ ๋ ํ ์คํธ ์ฉ์ด
- ์ฌ์ฌ์ฉ์ฑ๊ณผ ํ์ฅ์ฑ ํฅ์
์ ์ฐํ๊ฒ ์ ์ฉํ์
๋ฐ๋์ ์๊ฒฉํ ์ ์ฉํ ํ์๋ ์์.
์ํฉ๊ณผ ํ๋ก์ ํธ์ ์ฑ๊ฒฉ์ ๋ง๊ฒ ์ ์ฉํ๋ฉด ๋จ.
ํต์ฌ์ โํ์ผ/๊ตฌ์กฐ์ฒด/๋ทฐ๊ฐ ๋๋ฌด ๋ง์ ์ญํ ์ ํ์ง ์๋๋ก ํ๋ ๊ฒโ
์ฌ๊ธฐ๊น์ง๊ฐ ๊ฐ์์ ์ด๋ฐ์ ์ ๋ฆฌํ ๋ด์ฉ.
ํ๋ก์ ํธ์์๋
ํด๋ ๊ตฌ์ฑํ๊ธฐ
์ด๋ฒ์๋ ๊ธฐ์กด ํ๋ก์ ํธ์ ๋ฌ๋ฆฌ ํด๋๋ฅผ ๋ง๋ค๊ณ swiftํ์ผ๋ค์ ๊ด๋ฆฌํ๋ ค๊ณ ํ๋ค.
์ดํ, ํ๋ก์ ํธ์ ํ์ํ ํ์ผ์ ๋ฃ์ด์ฃผ์.
์ด๋ฒ์๋ ์ญ์ Swift ๋ฒ์ ์ 6์ผ๋ก.
๋ชจ๋ธ๋ง
์ด๋ฒ์๋ ์ญ์ json์ ์ฌ์ฉํ๊ธฐ์ ๋ชจ๋ธ๋ง์ ํด์ฃผ๋๋กํ๋ค.
struct Question: Decodable {
let id: Int
let question: String
let answer: String
let wrong: [String]
let book: Int
let hint: String
}
๋ชจ๋ธ๋ง์ ๋ค์๊ณผ ๊ฐ๋ค.
์ฑ ๊ตฌํ ํ๋ ์ธ์ฐ๊ธฐ
์ด๋ฒ์๋ ์ฒ์๋ถํฐ ํ๋์ ์ธ์ฐ๊ณ ๋ค์ด๊ฐ๋ค.
App Development Plan:
- Game Intro Screen
- Gameplay Screen
- Game logic (questions, scores, etc.)
- Celebration
- Audio
- Animations
- In-app purchases
- Store
- Instructions screen
- Books
- Persist scores
๊ธฐ๋ฅ์ ๋์ด์ ํด๋ณด์๋ค (์์๋ ๋ฌด๊ด)
์๋ง๋ ๊ฒฐ๊ณผ๋ ์ด๋ ๊ฒ ๋์ฌ๋ฏ
Instruction View ๋์์ธ
๋ฑํ ์ธ๊ธํ ๋ถ๋ถ์ด ์์ด์ ์ด๊ฑด ์ ์ฒด ์ฝ๋๋ก ๋์ฒดํ๋ค.
struct InstructionsView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
ZStack {
Image(.parchment)
.resizable()
.ignoresSafeArea()
.background(.brown)
VStack {
Image(.appiconwithradius)
.resizable()
.scaledToFit()
.frame(width: 100)
.padding(.top)
ScrollView {
Text("How To Play")
.font(.largeTitle)
.padding()
VStack(alignment: .leading, spacing: 20) {
Text("Welcome to HP Trivia! In this game you will be asked random questions from the HP books and you must guess the right answer or you will lose points!๐ฑ")
Text("Each question is worth 5 points, but if you guess a wrong answer, you lose 1 point.")
Text("If you are struggling with a question, there is an option to reveal a hint or reveal the book that answers the question. But beware! Using these also removes 1 point each.")
Text("When you select the correct answer, you will be awarded all the points left for that question and they will be added to your total score.")
}
.font(.title3)
.padding(.horizontal)
Text("Good Luck!")
.font(.title)
}
Button("Done") {
dismiss()
}
.font(.largeTitle)
.padding()
.buttonStyle(.borderedProminent)
.tint(.brown.mix(with: .black, by: 0.2))
.foregroundStyle(.white)
}
์ด๋ ๊ฒ ๊ธฐ์ด์์ ์ค ํ๋๊ฐ ๋๋ฌ๋ค.
PhaseAnimator
์ฐ์ ์ด๋ ๊ฒ ๋์์ธ์ ํด์ค๋ค.
struct ContentView: View {
var body: some View {
GeometryReader { geo in
ZStack {
Image(.hogwarts)
.resizable()
.frame(width: geo.size.width * 3, height: geo.size.height)
.padding(.top, 3)
}
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
}
}
์ฝ๋๋ ์ด๋ ๊ฒ ์์ฑ.
์ด์ ์ฌ๊ธฐ์ ์กฐ๊ธ ๋ UI๋ฅผ ๋ฐ์ ์ํค๊ธฐ์ํด phaseAnimator Modifier๋ฅผ ์ฌ์ฉํ๋ค.
padding ๋ฐ์ ์ถ๊ฐํด์ฃผ์๋ค.
.padding(.top, 3)
.phaseAnimator([false, true]) { content, phase in
content
.offset(x: phase ? geo.size.width/1.1 : -geo.size.width/1.1)
} animation: { _ in
.linear(duration: 60)
}
์คํํ๋ฉด ์๋์๊ฐ๋ค. (โป ์๋ ์์๋ duration์ ํ ์คํธ์ฉ์ผ๋ก 2์ด๋ก ์ค์ )
์ด๋ ๊ณผ ์ข์ฐ๋ก ์์ง์ธ๋ค.
- phaseAnimator ?
phaseAnimator๋ SwiftUI์์ ์ฌ์ฉํ๋ ๋ทฐ ์ ๋๋ฉ์ด์ ๋๊ตฌ๋ก, ์ฌ๋ฌ ์ํ(phase) ๊ฐ ์ ํ์ ์๋์ผ๋ก ๋ฐ๋ณตํ๋ฉด์ ์ ๋๋ฉ์ด์ ์ ์ ์ฉํ ์ ์๋ค. Docs}๋ ์ฌ๊ธฐ
- ์ฌ์ฉ ๋ชฉ์
- ํน์ ๋ทฐ ์์ฑ์ ์ํ๋ณ๋ก ๋ณํ์์ผ ๋ฐ๋ณต ์ ๋๋ฉ์ด์ ์์ฑ
- ์: ์ด๋ฏธ์ง์ ์ข์ฐ ์ด๋, ํฌ๋ช ๋ ๋ณํ, ํฌ๊ธฐ ๋ณํ ๋ฑ
| Parameters | ์ค๋ช |
|---|---|
phases |
์ํํ ์ํ(phase)๋ค์ ์ํ์ค. ๋น์ด ์์ผ๋ฉด ๋ฐํ์ ๊ฒฝ๊ณ ์ ์๊ฐ์ ๊ฒฝ๊ณ ๊ฐ ๋ฐ์ํจ |
content |
๋ ๊ฐ์ ์ธ์๋ฅผ ๋ฐ๋ ๋ทฐ ๋น๋ ํด๋ก์ : ์์ ๋ ๋ทฐ ํ๋ก์์ ํ์ฌ phase |
animation |
phase ๊ฐ ์ ํ ์ ์ฌ์ฉํ ์ ๋๋ฉ์ด์
์ ๋ฐํํ๋ ํด๋ก์ . nil์ด๋ฉด ์ ๋๋ฉ์ด์
์์ |
๊ฐ์์์๋ ์ผ๋ฐ์ ์ผ๋ก 2~3๊ฐ์ ์ํ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๋ผ๊ณ ํ๋ค.
๊ทธ๋ฆฌ๊ณ [false, true]๋ฅผ ์ฌ์ฉํ ์ด์ ๋ Apple์ ๊ณต์ ์์ ๋๋ถ๋ถ์ด false โ true์ ํ๋ฆ์ผ๋ก ๊ตฌ์ฑ๋์ด ์๊ธฐ ๋๋ฌธ์ด๋ค๋ผ๊ณ ํ๋ค.
Transition Animation
struct ContentView: View {
@State private var animateViewsIn = false // new
var body: some View {
GeometryReader { geo in
ZStack {
// ์๋ต
VStack {
VStack {
if animateViewsIn {
VStack {
Image(systemName: "bolt.fill")
.imageScale(.large)
.font(.largeTitle)
Text("HP")
.font(.custom("PartyLetPlain", size: 70))
.padding(.bottom, -50)
Text("Trivia")
.font(.custom("PartyLetPlain", size: 60))
}
.padding(.top, 70)
.transition(.move(edge: .top))
}
}
.animation(.easeInOut(duration: 0.7).delay(2), value: animateViewsIn)
Spacer()
}
}
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
.onAppear {
animateViewsIn = true
}
}
}
์ด๋ ๊ฒ ์ฝ๋๋ฅผ ๊ฐ์ฑํ๋ค.
- Transition
- SwiftUI์์
.transition(.move(edge: .top))์ ๋ทฐ๊ฐ ํ๋ฉด์ ๋ฑ์ฅํ๊ฑฐ๋ ์ฌ๋ผ์ง ๋ ์ ๋๋ฉ์ด์ ํจ๊ณผ๋ฅผ ์ ์ฉํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ๋จ, ๋ค์ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋์ด์ผ ์ ๋๋ก ์๋ํ๋ค:- ๋ทฐ๋
if์กฐ๊ฑด์ ํตํด ์ฝ์ /์ญ์ ๋์ด์ผ ํจ - ํด๋น
if๋ฅผ ๊ฐ์ธ๋ ์ธ๋ถ ๋ทฐ์.animation(_:value:)์ด ์์ด์ผ ํจ
- ๋ทฐ๋
- SwiftUI์์
- Transition ์๋์ ์ํ ํ์ ๊ตฌ์ฑ ์์
@State๋ก Boolean ๋ณ์ ์ ์ธ.onAppear์์ ์ํ๊ฐ ๋ณ๊ฒฝ (withAnimation์ฌ์ฉํ๊ฑฐ๋ ์ธ๋ถ์์.animation()์ ์ฉ)if๋ฌธ์ผ๋ก ๋ทฐ ์กฐ๊ฑด ๋ถ๊ธฐ.transition(.move(edge: .top))์ ์ฉ.animation(_:value:)์ if๋ฌธ์ ๊ฐ์ผ ์ธ๋ถ VStack์ ์ ์ฉ
Group ๋์ VStack์ ์ด ์ด์
Group์ ์ฌ๋ฌ ๋ทฐ๋ฅผ ๋ฌถ๋ ๋ฐ ์ฐ์ด๋ ๊ฒฝ๋ ์ปจํ ์ด๋์ด์ง๋ง, ์ ๋๋ฉ์ด์ ํธ๋ฆฌ๊ฑฐ์ ๊ด๋ จํด ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค:
- ๋ด๋ถ ์ํ ๋ณํ ๊ฐ์ง๊ฐ ๋ถ์์ ํ ์ ์์
.animation(_:value:)์ ๋ถ์ฌ๋ ๊ธฐ๋ํ ๋๋ก ๋์ํ์ง ์์ ์ ์์
๊ทธ๋์ ๊ฐ์์์๋ Group ๋์ VStack์ ์ฌ์ฉํ์ฌ ๋ ์์ ์ ์ผ๋ก ์๋ํ๊ฒ ํ๋ค.
์ฝ๋๋ฅผ ๋ณด๋ฉฐ ๊ฐ๋จํ ์๋์ ๋ํด ์์๋ณด๊ธฐ
GeometryReader { geo in
ZStack {
// ์๋ต
VStack {
VStack {
if animateViewsIn {
VStack {
// ์๋ต
}
.padding(.top, 70)
.transition(.move(edge: .top))
}
}
.animation(.easeInOut(duration: 0.7).delay(2), value: animateViewsIn)
Spacer()
}
}
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
.onAppear {
animateViewsIn = true
}
.transition()์if๋ด๋ถ์ ์๋ ๋ทฐ(VStack)์ ์ ์ฉ๋จ- ์ฌ๊ธฐ์ ์๋จ์์ ์์ง์ด๊ฒ ๋ค๋ ๊ฒ.
- .animation(_:value:)์ if๋ฅผ ๊ฐ์ธ๋ ์ธ๋ถ VStack์ ์ ์ฉ๋์ด์ผ ํ๋ฉฐ, ํด๋น VStack์ด ๋ํ๋ ๋ easeInOut ์ ๋๋ฉ์ด์ ์ด 2์ด ๋ค์ ์คํ๋จ
if๋ View๊ฐ ์๋๋ฏ๋ก modifier๋ฅผ ๋ถ์ผ ์ ์๊ณ , wrapping์ด ํ์ํจ- ๊ฐ์์์๋ Group ๋์ VStack์ ์ฌ์ฉํ์ฌ ์์ ์ ์ธ ์ ๋๋ฉ์ด์ ์ฒ๋ฆฌ๋ฅผ ์ ๋ํจ
.onAppear์์ ์ํ๊ฐ์ ๋ณ๊ฒฝํ์ฌ ์ ๋๋ฉ์ด์ ์ด ํธ๋ฆฌ๊ฑฐ๋จ
์ฆ, value์ ์ ๋ฌ๋ ์ํ(animateViewsIn)๊ฐ false์์ onAppear๋ true๋ก ๋ณ๊ฒฝ๋๋ฉด์ animation์ด ํธ๋ฆฌ๊ฑฐ๋๊ณ , ํด๋น ์์ ์ if ๋ด๋ถ์ VStack์ด ์์ฑ๋๋ฉด์ ์์์ ๋ด๋ ค์ค๋ ์ ๋๋ฉ์ด์
๊ณผ ํจ๊ป ํ๋ฉด์ ๋ํ๋๋ค.
๊ทธ๋ฆฌ๊ณ ํน์๋ ์ด๊ธฐ๊ฐ์ true๋ก ์ค์ ํ๋ฉด !animateViewsIn์ฒ๋ผ ๋ถ์ ์กฐ๊ฑด์ ์จ์ผ ํด์ ์ฝ๋ ํ๋ฆ์ด ์ง๊ด์ ์ผ๋ก ์ฝํ์ง ์๊ณ , ์ ํ ์์ ๋ ๋ช
ํํ์ง ์๋ค.
์คํํ๋ฉด ์๋์ ๊ฐ์ด ๋๋ค.
- ์ฐธ๊ณ
- Animations Docs
- Transition Docs
- animation(Method) Docs ์์ Docs์๋ ๋ค๋ฆ
- withAnimation Docs
.animation vs withAnimation
๊ฐ์๊ธฐ ๋ฌธ๋ ๊ฐ๋จํ ์ ๋ฆฌํ๋ฉด ์ข์๊ฒ ๊ฐ์ ์ฌ๊ธฐ์ ์ ์ด๋ณธ๋ค.
- withAnimation
- SwiftUI์ ํจ์ ๊ธฐ๋ฐ ์ ๋๋ฉ์ด์ ํธ๋ฆฌ๊ฑฐ
- ๋ด๋ถ ํด๋ก์ ์์ ๋ฐ์ํ๋ ์ํ ๋ณํ๊ฐ ๋ทฐ๋ฅผ ๋ฐ๊พธ๋ฉด, ํด๋น ๋ณํ์ ์ ๋๋ฉ์ด์ ์ ์ฉ
- ์ฃผ๋ก onAppear, onTapGesture, Button ์ก์ ๋ฑ ์ด๋ฒคํธ์ฑ ๋์๊ณผ ํจ๊ป ์ฌ์ฉ๋จ
์์:
withAnimation(.easeInOut) {
isVisible.toggle()
}
- isVisible์ ๋ณ๊ฒฝ์ผ๋ก ์ธํด if isVisible ์กฐ๊ฑด ํ์ ๋ทฐ๊ฐ ์ฝ์ ๋๋ ์ ๊ฑฐ๋๋ฉด, ํด๋น ๋ทฐ์ transition์ด ์ ์ฉ๋์ด ์ ๋๋ฉ์ด์ ๋ฐ์
.animation(_:value:)- SwiftUI์ modifier ๊ธฐ๋ฐ ์ ์ธ์ ์ ๋๋ฉ์ด์
- ํน์ ์ํ๊ฐ(value)์ ๋ณํ๊ฐ ํด๋น ๋ทฐ ํธ๋ฆฌ์ ์ธํ ๋ณํ๋ก ์ด์ด์ง ๊ฒฝ์ฐ, ์ ๋๋ฉ์ด์ ์ ์ฉ
- if ์กฐ๊ฑด์ด ๋ฐ๋๋ ๊ฒ์ฒ๋ผ, ๋ทฐ์ ๋ฑ์ฅ/ํด์ฅ์ด ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ฐ๋๋ ์ํฉ์ ์ ํฉ
์์:
.animation(.easeInOut, value: isVisible)
- isVisible์ด ๋ฐ๋ ๋, ์ด ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ทฐ ํธ๋ฆฌ ๋ณํ๊ฐ ์๊ธฐ๋ฉด transition๊ณผ ํจ๊ป ์ ๋๋ฉ์ด์ ์ฒ๋ฆฌ
์์ฝ
| ํญ๋ชฉ | withAnimation | .animation(_:value:) |
|---|---|---|
| ํ์ | ํจ์ | modifier |
| ์์น | ์ํ ๋ณ๊ฒฝ์ ๊ฐ์ธ๋ ํด๋ก์ | View ํธ๋ฆฌ์ ์ง์ ์ ์ฉ |
| ์ฃผ ์ฉ๋ | ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ ๋๋ฉ์ด์ ํธ๋ฆฌ๊ฑฐ | ์ํ ๋ณํ ๊ธฐ๋ฐ ๋ทฐ ์ ๋ฐ์ดํธ ๊ฐ์ง |