본문 바로가기
SwiftUI 독학으로 기본기 익히기

03. 로또 번호 생성기 앱 만들기

by daco2020 2025. 10. 28.

이번 글에서는 사용자가 버튼을 누르면 화면의 내용이 바뀌는 "로또 번호 생성기" 앱을 만들어 보겠습니다. 

 

사용자의 행동에 앱이 반응하도록 만드는 인터랙션(Interaction)을 구현하는 과정을 통해 SwiftUI의 중요한 개념인 @State를 배워보겠습니다.

 

 


 

 

@State 는 값이 바뀌면 화면을 자동으로 업데이트해주는 기능을 합니다.

 

ContentView 파일의 모든 코드를 지우고 아래의 코드를 작성해주세요. 코드를 그냥 복사/붙여넣기 하기 보다는, 직접 한 줄씩 타이핑하면서 코드의 의미를 생각해 보세요. 자동완성 기능의 도움을 받는 것은 좋습니다.

import SwiftUI

struct ContentView: View {
    // 1. 데이터를 저장할 '상태' 변수를 선언합니다.
    // @State 키워드가 붙었기 때문에, 이 변수의 값이 바뀌면 화면이 자동으로 새로고침됩니다.
    @State private var generatedNumbers: [Int] = []

    var body: some View {
        VStack(spacing: 20) { // 요소들 사이에 20만큼 간격을 줍니다.
            Text("로또 번호 생성기")
                .font(.largeTitle) // 글씨를 큰 제목 스타일로
                .fontWeight(.bold) // 글씨를 굵게

            // 2. State 변수에 담긴 숫자를 화면에 보여줍니다.
            // 처음에는 배열이 비어있으므로 안내 문구가 보입니다.
            if generatedNumbers.isEmpty {
                Text("버튼을 눌러 번호를 생성하세요")
                    .foregroundStyle(.gray)
            } else {
                // 숫자를 보기 좋게 텍스트로 변환하여 보여줍니다.
                Text(generatedNumbers.map { String($0) }.joined(separator: ", "))
                    .font(.title)
            }

            // 3. 버튼을 만듭니다.
            // action 부분에 버튼을 눌렀을 때 실행될 코드를 넣습니다.
            Button(action: {
                // 여기에 번호 생성 로직을 넣습니다.
                generateLottoNumbers()
            }) {
                // label 부분에 버튼이 어떻게 보일지 디자인합니다.
                Text("행운의 번호 뽑기!")
                    .font(.headline)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .padding()
    }

    // 로또 번호를 생성하는 함수(기능)
    func generateLottoNumbers() {
        var newNumbers = Set<Int>() // 중복되지 않는 숫자를 뽑기 위해 Set을 사용합니다.
        while newNumbers.count < 6 {
            let randomNumber = Int.random(in: 1...45)
            newNumbers.insert(randomNumber)
        }
        // Set을 정렬된 배열로 바꿔서 @State 변수에 저장합니다.
        // 바로 이 코드가 실행되는 순간, 화면이 바뀝니다!
        generatedNumbers = Array(newNumbers).sorted()
    }
}

// 아래 코드가 있어야 Preview 패널이 동작합니다.
#Preview {
    ContentView()
}

 

 

이해하기 쉽게 설명해보자면 "이 generatedNumbers라는 변수를 잘 지켜봐 줘. 만약 이 변수의 내용이 바뀌면, 이 변수를 사용하고 있는 화면(UI) 부분을 전부 자동으로 새로 그려줘!" 라고 말할 수 있습니다.

 

우리가 일일이 "숫자가 바뀌었으니 글자를 지우고 새로 써라"라고 명령할 필요가 없습니다. 그냥 @State 변수의 값을 바꾸기만 하면 화면은 저절로 바뀝니다.

 

 

1. Button(action: { ... }) { ... }

 

Button은 크게 두 부분으로 구성됩니다.

 

- action: { ... } 는 사용자가 버튼을 눌렀을 때 실행될 코드를 넣는 곳입니다. 우리는 여기에 generateLottoNumbers() 함수를 호출하도록 했습니다.

 

- label: { ... }  (위 코드에서 action 뒤에 바로 따라오는 부분): 버튼이 어떻게 보일지 디자인하는 곳입니다. 우리는 파란 배경에 흰 글씨를 가진 버튼을 만들었습니다. 

 

'label' 매개변수를 생략한 이유

더보기

함수의 마지막 인자가 클로저일 경우, 인자값 형식으로 작성하는대신 함수 의 뒤에 꼬리처럼 붙일 수 있는 Swift 문법입니다. 이를 트레일링 클로저(trailing closure)라고 부릅니다. 

 

클로저란, {}로 둘러싸인 코드 블록으로 변수나 상수를 기억하고 함수가 끝난 후에도 그 값에 접근할 수 있는 익명 함수 입니다. 클로저는 함수의 인자로 전달되어 비동기 처리나 콜백 등에서 활용합니다. 


만약, 트레일링 클로저 문법을 사용하지 않고 일반적인 형태로 매개변수 이름을 표시한다면 다음처럼 작성할 수 있습니다. 아래 코드는 동일한 동작을 수행합니다.

Button(action: {
    generateLottoNumbers()
}, label: {  // <-- 'label' 매개변수를 명확하게 표시
    Text("행운의 번호 뽑기!") 
        .font(.headline)
        .padding()
        .background(Color.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
})

 

 

 

2. generateLottoNumbers() 함수

 

이 함수는 1부터 45 사이의 중복되지 않는 숫자 6개를 뽑아서 정렬한 뒤, @State 변수인 generatedNumbers에 그 결과를 저장(=)하는 역할을 합니다.

 

함수가 실행되면 generatedNumbers의 값이 새로운 로또 번호로 바뀌게 됩니다. 이것이 @State의 역할입니다. SwiftUI는 이 상태 변화를 감지하고, generatedNumbers를 사용하고 있는(화면에 로또 번호를 표시하는) Text 부분을 자동으로 새로 그려주는 것입니다.

 


 

 

로또 번호 생성기 완성!

 

Xcode 오른쪽의 프리뷰 캔버스에서 버튼을 직접 눌러보세요. 번호가 나타나고 계속 바뀌는 것을 볼 수 있습니다.

시뮬레이터(Command + R)를 실행해서 실제 아이폰에서 동작하는 것처럼 테스트해 볼 수 있습니다.

 

버튼을 누르면?

 

로또 번호가 생성된다!

 

 

이제 우리는 @State을 통해 상태를 저장하고 사용자와 상호작용하는 버튼을 만들 수 있게 되었습니다. 간단하지만 실제 동작하는 앱을 만들어본 셈이죠!

 

점점 더 재밌어지네요. 다음 글에서는 여러 화면을 가진 앱을 만들어 보겠습니다.