HP Trivia (4)
Single Responsibility Principle
이전 글에서 간단히 언급했던 부분이지만, 이번엔 이 주제를 조금 더 구체적으로 짚고 넘어가려 한다.
현재 ContentView를 보면 모든 UIComponents를 직접 작성하여 코드의 가독성이 상당히 떨어지고 있다.
이제 View들을 새롭게 파일을 만들어서 코드를 옮김으로써 유지, 보수에 좀 더 용이하게 하려고 한다.
크게 어렵지는 않다. 그 View영역만 잘라내서 붙여넣기하면 끝.
Background
AnimatedBackgroundView라는 새로운 파일을 만들고 거기에 관련 코드를 옮겨준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
var body: some View {
Image(.hogwarts)
.resizable()
.frame(width: geo.size.width * 3, height: geo.size.height)
.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)
}
}
그런데 새로운 파일로 옮긴 직후, geo가 정의되지 않았다는 에러가 발생한다.
geo는 원래 GeometryReader 내부에서 제공되는 값이므로, 분리한 View에서는 직접 주입해주어야 한다.
이게 geo의 타입이다.
그래서 AnimatedBackgroundView에 그냥 만들어 주면 된다.
let geo: GeometryProxy
이렇게 만들어서 사용하면된다.
그리고 preview의 경우 우리간 이전에 NavigtionStack을 감싸듯이
1
2
3
4
5
6
7
#Preview {
GeometryReader { geo in
AnimatedBackgroundView(geo: geo)
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
}
이렇게 해주면 끝
GameTitle, RecentScores
위와 상동
여기서 언급할만한건
@Binding var animateViewsIn: Bool
contentview의 값과 같아야 하므로 바인딩을 해주는것이 포인트
1
2
3
#Preview {
GameTitleView(animateViewsIn: .constant(true))
}
preview에는 굳이 바인딩을 넘길필요가 없어서 true를 상시 유지하도록 하였다.
Button들
이건 background, gametitle을 모두 합친것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@State private var showInstructions = false
@Binding var animateViewsIn: Bool
let geo: GeometryProxy
var body: some View {
VStack {
if animateViewsIn {
Button {
showInstructions.toggle()
} label: {
Image(systemName: "info.circle.fill")
.font(.largeTitle)
.foregroundStyle(.white)
.shadow(radius: 5)
}
.transition(.offset(x: -geo.size.width / 4))
}
}
.animation(.easeInOut(duration: 0.7).delay(2.7), value: animateViewsIn)
.sheet(isPresented: $showInstructions) {
InstructionsView()
}
}
그리고 이때 sheet는 해당 버튼에서만 작동하기에 여기에 옮겨준다.
뭐 딱히 언급할만한건 없다.
ButtonBarView
이렇게 나눈 버튼을 한꺼번에 관리하는 view도 만들고
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct ButtonBarView: View {
@Binding var animateViewsIn: Bool
let geo: GeometryProxy
var body: some View {
HStack {
Spacer()
InstructionsButton(animateViewsIn: $animateViewsIn, geo: geo)
Spacer()
PlayButton(animateViewsIn: $animateViewsIn, geo: geo)
Spacer()
SettingsButton(animateViewsIn: $animateViewsIn, geo: geo)
Spacer()
}
.frame(width: geo.size.width)
}
}
이렇게 관리해준다.
그러면 이렇게 메인화면의 ContentView의 코드가 상당히 줄어 들었음을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct ContentView: View {
@State private var audioPlayer: AVAudioPlayer!
@State private var animateViewsIn = false
var body: some View {
GeometryReader { geo in
ZStack {
AnimatedBackgroundView(geo: geo)
VStack {
GameTitleView(animateViewsIn: $animateViewsIn)
Spacer()
RecentScoresView(animateViewsIn: $animateViewsIn)
Spacer()
ButtonBarView(animateViewsIn: $animateViewsIn, geo: geo)
Spacer()
}
}
.frame(width: geo.size.width, height: geo.size.height)
}
.ignoresSafeArea()
.onAppear {
animateViewsIn = true
//playAudio()
}
}
private func playAudio() {
//생략
}
}
이제 각 UI 컴포넌트는 자신만의 파일로 분리되었기 때문에, 수정이나 기능 확장도 훨씬 명확하고 관리하기 쉬워졌다.