포스트

SwiftUI (3)

Dicee App 만들기

1. ZStack을 사용하여 배경화면 설정하기.

1
2
3
4
5
6
7
8
9
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
        }
    }
}

이렇게 해서 assets에 있는 이미지를 가져오고 설정을 해준다.

2. VStack을 사용하여 이미지 쌓기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
            VStack {
                Image("diceeLogo")
                Image("dice1")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            }
        }
    }
}

이렇게 VStack을 사용하여 이미지를 쌓아주었다.

CleanShot 2024-09-08 at 13 12 28@2x

3. 모듈화하여 관리하기

주사위 이미지를 담당하는 부분을 따로 서브뷰로 추출하여 좀 더 관리하기 쉽게 만들어 본다.

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
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
            VStack {
                Image("diceeLogo")
                HStack {
                    DiceView(n: 1)
                }
            }
        }
    }
}

struct DiceView: View {
    
    let n: Int
    
    var body: some View {
        Image("dice\(n)")
            .resizable()
            .aspectRatio(contentMode: .fit)
    }
}

그리고 주사위 이미지를 좀 더 유기적으로 관리하기위해서 n이라는 상수를 하나 만들어 주었다. 그리고 현재 n에는 1의 값을 부여 해줌으로써 주사위 이미지는 점이 1인 주사위가 나온다.

4. HStack을 사용하여 주사위를 하나 더 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
            VStack {
                Image("diceeLogo")
                HStack {
                    DiceView(n: 1)
                    DiceView(n: 1)
                }
            }
        }
    }
}

CleanShot 2024-09-08 at 13 16 12@2x

이렇게 해주니 이제 좀 느낌이 나기 시작한다

5. Padding을 주어 이미지간의 간극 조절

1
2
3
4
5
6
7
8
VStack {
                Image("diceeLogo")
                HStack {
                    DiceView(n: 1)
                    DiceView(n: 1)
                }
                .padding(.horizontal)
            }

이때 안에 어떠한 값도 주지 않으면 4변이 다 패딩이 들어가므로 이를 원하지 않는다면 특정 값을 주도록 하자.

여기선 가로만 패딩을 주기위해 horizontal을 사용.

6. Button을 추가하고 디자인

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
38
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
            VStack {
                Image("diceeLogo")
                HStack {
                    DiceView(n: 1)
                    DiceView(n: 1)
                }
                .padding(.horizontal)
                Button("Roll")
                {
                    
                }
                .font(.system(size: 50))
                .fontWeight(.heavy)
                .foregroundStyle(.white)
                .background(.red)
                .padding()
            }
        }
    }
}

struct DiceView: View {
    
    let n: Int
    
    var body: some View {
        Image("dice\(n)")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .padding()
    }
}

버튼 바로 다음에 .을 붙여서 해보았으나 되지 않았다.

강의 버전과는 달리 버튼의 text는 바로 적을 수 있고, 버튼의 폰트나 이런 외적인 요소는 액선 뒤에다가 해줘야 적용이 된다.

CleanShot 2024-09-08 at 13 24 09@2x

그리고 주사위 이미지간의 간격을 주기위해

Diceview에도 똑같이 패딩을 준다.

7. Spacer를 사용하여 이미지간 간격을 더 주기.

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
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
            VStack {
                Image("diceeLogo")
                Spacer()
                HStack {
                    DiceView(n: 1)
                    DiceView(n: 1)
                }
                .padding(.horizontal)
                Button("Roll")
                {
                    
                }
                .font(.system(size: 50))
                .fontWeight(.heavy)
                .foregroundStyle(.white)
                .background(.red)
                .padding()
            }
        }
    }
}

이렇게 스페이서를 넣어주니

CleanShot 2024-09-08 at 13 29 09@2x

둘다 양끝으로 붙어 버린다.

그래서 버튼과 hstack 사이에도 스페이서를 주어 간격을 준다.

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
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
            VStack {
                Image("diceeLogo")
                Spacer()
                HStack {
                    DiceView(n: 1)
                    DiceView(n: 1)
                }
                .padding(.horizontal)
                Spacer() // new
                Button("Roll")
                {
                    
                }
                .font(.system(size: 50))
                .fontWeight(.heavy)
                .foregroundStyle(.white)
                .background(.red)
                .padding()
            }
        }
    }
}

CleanShot 2024-09-08 at 13 30 30@2x

이렇게 균등하게 분포가 되었음을 알 수 있다.

8. button을 활용하여 주사위의 숫자를 바꾸기

먼저 해야할것이 좌,우측 주사위의 숫자를 정해줘야한다.

그래서 변수를 하나 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
struct ContentView: View {
    
    var leftDiceNumber = 1
    var rightDiceNumber = 1
    
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea(.all)
            VStack {
                Image("diceeLogo")
                Spacer()
                HStack {
                    DiceView(n: leftDiceNumber)
                    DiceView(n: rightDiceNumber)
                }
                .padding(.horizontal)
                Spacer()
                Button("Roll")
                {
                    
                }
                .font(.system(size: 50))
                .fontWeight(.heavy)
                .foregroundStyle(.white)
                .background(.red)
                .padding()
            }
        }
    }
}

이때 바뀐점은 HStack에서 DiceView의 n이 1이었지만 이제는 left,rightDiceNumber가 되었다는 것이다.

이것을 활용하여 버튼의 Action Section에 랜덤함수를 이용하여 숫자를 1~6 사이의 숫자로 바뀌게 할것이다.

아래는 Action에 관한 Code

1
2
3
4
5
Button("Roll")
                {
                    leftDiceNumber = Int.random(in: 1...6)
                    rightDiceNumber = Int.random(in: 1...6)
                }

CleanShot 2024-09-08 at 16 35 03@2x

하지만 에러가 발생한다.

바꿀 수 없다는 것이다.

  • Struct안에 있는 변수는 Immutable이 라는 것이다.
    • 값타입이기 때문이다.

그래서 이전에 작성한 글에서도 우리는 mutating을 사용하여 값을 바꿀 수 있게 하였다.

  • 그러면 변수에도 똑같이 하면 되지않을까?
    • 답은 No

CleanShot 2024-09-08 at 16 39 56@2x

애석하게도 mutating이라는 녀석은 함수를 선언할때만 가능하다.

그렇다면 해결책은?

바로 @state 이다.

간단하게 정의를 하면 변수를 업데이트하고 contentview를 다시 생성하게 한다.

CleanShot 2024-09-08 at 16 46 14@2x

더 자세한 내용은 Docs 확인.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.