인프런 커뮤니티 질문&답변

(알 수 없음)님의 프로필 이미지

작성한 질문수

Part1: 진짜 왕초보 iOS 배우기(SwiftUI, SwiftData, 2024)

@State 특성(?) 질문입니다

해결된 질문

24.09.05 23:58 작성

·

50

1

import SwiftUI

struct ContentView: View {
    @State private var ToggleCheck: Bool = false // @State 없으면 에러 왜? / 연산자 토글을 누른 후의 숫자 입력인지 체크하기 위한 변수
    @State private var isAdditionActive = false // 덧셈 토글 체크
    @State private var isSubtractionActive = false // 뺄셈 토글 체크
    @State private var isMultiplicationActive = false // 곱셈 토글 체크
    @State private var isDivisionActive = false // 나눗셈 토글 체크
    
    @State private var printNumber: String = "0" // 계산식에 사용 or 화면에 출력한 내용을 담은 변수
    @State private var totalNumber: Int = 0 // 토탈 계산 값
    @State private var op: Character = " "
    @State private var buttonData: [[String]] = [ // 키패드 모음 2차원 배열
        ["AC", "+/-", "%", "÷"],
        ["7", "8", "9", "X"],
        ["4", "5", "6", "-"],
        ["1", "2", "3", "+"],
        ["0", ".", "="]]
    
    var body: some View {
        ZStack{
            Color.black.ignoresSafeArea() // 화면 전체 여백 없이 배경색 지정
            
            VStack {
                Spacer()
                HStack{
                    Spacer()
                    Text("\(Int(printNumber)!)") // 계산기의 숫자 출력
                        .padding()
                        .font(.system(size: 73))
                        .foregroundColor(.white)
                }
                ButtonPosition() // C, +/-, %를 제외한 버튼을 화면에 추가
            }
        }
    }
    
    func Reset(){
        printNumber = "0" // 출력된 계산 숫자 UI 초기화
        totalNumber = 0 // 연산의 토탈값을 담고 있는 변수도 당연히 초기화
        ToggleCheck = false
        op = " "
        buttonData[0][0] = "AC"
        isAllfalse() // 버튼의 토글값 및 연산자 버튼 토글 UI도 모두 초기화
    }
    
    func isAllfalse(){ // 연산자의 토글 버튼을 전부 해제 하기 위한 함수
        isAdditionActive = false
        isSubtractionActive = false
        isMultiplicationActive = false
        isDivisionActive = false
    }
    
    func opIfSwitch(){ // 실질적인 계산을 위한 switch문
        if op == " "{
            totalNumber = Int(printNumber)!
        }
        else{
            switch op{
            case "÷":
                totalNumber /= Int(printNumber)!
                if printNumber == "0"{ // 비정상 종료.. 왜?
                    printNumber = "오류"
                }
            case "X":
                totalNumber *= Int(printNumber)!
            case "-":
                totalNumber -= Int(printNumber)!
            case "+":
                totalNumber += Int(printNumber)!
            default:
                break
            }
        }
        printNumber = String(totalNumber)
    }
    
    // 내 코드의 계산 방식 작성할 것 이따가
    func Division(){ // 나눗셈
        opIfSwitch()
    }
    
    func Multiplication(){ // 곱셈
        opIfSwitch()
    }
    
    func Minus(){ // 뺄셈
        opIfSwitch()
    }
    
    func Plus(){ // 덧셈
        opIfSwitch()
    }
    
    func Equal(){ // 등호
        opIfSwitch()
        isAllfalse()
        ToggleCheck = false // =을 누른 후 결과 값에 추가로 연산을 진행할 때 이 코드 없으면 정상적으로 진행되지 않음
    }
    
    func ButtonPosition() -> some View{ // 버튼 디자인 및 포지셔닝 함수
        ForEach(buttonData, id: \.self){ line in
            HStack{
                ForEach(line, id: \.self){ row in
                    switch row{
                    case "AC", "C", "+/-", "%":
                        ButtonDesign(row, .gray, .black)
                    case "÷", "X", "-", "+", "=":
                        ButtonDesign(row, .orange, .white)
                    default:
                        ButtonDesign(row, .init("NumberButton"), .white)
                    }
                }
            }
        }
    }
    
    func ButtonDesign(_ value: String, _ backcolor: Color, _ fcolor: Color) -> some View{ // 공통 버튼 디자인
        Button {
            switch value {
            case "÷":
                isDivisionActive = true // 나눗셈 버튼 토글
                isAdditionActive = false
                isSubtractionActive = false
                isMultiplicationActive = false
            case "X":
                isMultiplicationActive = true // 곱셈 버튼 토글
                isDivisionActive = false
                isAdditionActive = false
                isSubtractionActive = false
            case "-":
                isSubtractionActive = true // 뺄셈 버튼 토글
                isAdditionActive = false
                isMultiplicationActive = false
                isDivisionActive = false
            case "+":
                isAdditionActive = true // 덧셈 버튼 토글
                isSubtractionActive = false
                isMultiplicationActive = false
                isDivisionActive = false
            default:
                break
            }
            ButtonAction(value)
        } label: {
            Text(value)
                .frame(width: value == "0" ? 160 : 80, height: 80, alignment: value == "0" ? .leading : .center)
                .background((value == "÷" && isDivisionActive) || (value == "X" && isMultiplicationActive) || (value == "-" && isSubtractionActive) || (value == "+" && isAdditionActive) ? Color.white : backcolor)
            //.background(value == "÷" && isDivisionActive ? Color.white : backcolor)
            //.background(value == "X" && isMultiplicationActive ? Color.white : backcolor)
                .foregroundColor(value == "÷" && isDivisionActive || (value == "X" && isMultiplicationActive) || (value == "-" && isSubtractionActive) || (value == "+" && isAdditionActive) ? Color.orange : fcolor)
                .font(.system(size: 33))
                .cornerRadius(40)
        }
    }
    
    func ButtonAction(_ value: String){
        switch value { // 버튼을 누르면
        case "AC", "C": // C를 누르면
            Reset() // 초기화 버튼을 누르면 각종 상수 및 변수들이 초기화 됨
        case "÷": // 나누기를 누르면
            Division() // 실질적인 계산을 하는 함수
            op = "÷"
            ToggleCheck = false
        case "X": // 곱하기를 누르면
            Multiplication() // 실질적인 계산을 하는 함수
            op = "X"
            ToggleCheck = false
        case "-": // 빼기를 누르면
            Minus() // 실질적인 계산을 하는 함수
            op = "-"
            ToggleCheck = false
        case "+": // 더하기를 누르면
            Plus() // 실질적인 계산을 하는 함수
            op = "+"
            ToggleCheck = false
        case "+/-":
            printNumber = String(Int(printNumber)! * -1)
        case "=":
            Equal() // 최종 결과를 출력하기 위한 스위치문 호출
            op = " " // 내가 원하는 기능을 구현하기 위해 꼭 필요한 코드
            printNumber = String(totalNumber) // 연산의 결과 값을 출력
            totalNumber = Int(printNumber)!
        default:
            if printNumber == "0"{
                printNumber = value // 숫자 첫 입력시 화면에 출력
                buttonData[0][0] = "C"
            }
            else if (isDivisionActive || isMultiplicationActive || isSubtractionActive || isAdditionActive) && ToggleCheck == false{
                printNumber = value
                ToggleCheck = true // 연산자 버튼이 눌러짐
            }
            else{
                if printNumber.count < 9{
                    printNumber += value
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

안녕하세요! 다름이 아니라 강의에 있는 프로젝트 외에 따로 계산기를 하나 만들어보고 있습니다 근데 @State 어노테이션에 대해 이해가 안되는 부분이 있습니다 구글링도 해보고 여러 방면으로 알아봤는데 원하는 답을 얻지 못해서 질문 올립니다

 

@State는 뷰의 상태 변화를 체크하는 어노테이션이라고 알고 있습니다. 변수 선언부의 주석을 보시면 @State 없으면 에러 왜? 이런 내용의 주석이 달려 있는 변수가 지금 저의 의문점입니다

 

ToggleCheck 변수와 totalNumber 변수는 뷰의 상태 변경에 직접적인 영향을 미치지 않기 때문에 @State가 필요 없다고 생각합니다. 근데 저 두 변수에 @State를 지우면 에러가 발생합니다. 일단 뷰의 변화는 실질적으로 totalNumber 변수의 값을 대입 받는 printNumber 변수이니 printNumber 변수에만 @State가 필요하다고 생각하는데 왜 에러가 발생하는 걸까요

 

마찬가지로 ToggleCheck 변수도 마찬가지로 체크용으로 사용하고 저 값이 변한다고 해서 직접적으로 뷰에 미치는 영향이 없는데 왜 @State가 필요한 걸까요... 혹시 알고 계시면 꼭 답변 부탁드리겠습니다...!

감사합니다!

답변 2

0

애구마(agmma)님의 프로필 이미지
애구마(agmma)
지식공유자

2024. 09. 06. 01:40

안녕하세요!

SwiftUI에서 View는 값타입이기때문에 내부의 변수를 수정할 수가 없습니다.

@State 변수는 뷰에 영향을 주는 의미도 있지만, View의 프로퍼티를 다른곳에 저장해두어 수정을 가능하게 해주는 역할도 갖고 있습니다.

 

현재 ToggleCheck는 뷰의 영향을 주지 않더라도, 다른 곳에서 수정이 일어나기 때문에, 이때는 @State 를 써주어야 합니다. 만약 @State를 쓰고 싶지 않다면 ToggleCheck를 관리하는 다른 클래스를 생성하여 그 클래스의 ToggleCheck 프로퍼티를 수정하도록 하면 됩니다.

감사합니다~

(알 수 없음)님의 프로필 이미지

2024. 09. 06. 02:15

답변 주셔서 감사합니다!!

SwiftUI에서 View는 값타입이기때문에 내부의 변수를 수정할 수가 없습니다.

@State 변수는 뷰에 영향을 주는 의미도 있지만, View의 프로퍼티를 다른곳에 저장해두어 수정을 가능하게 해주는 역할도 갖고 있습니다.라고 말씀주셨는데 아직 제가 초보자다 보니 이 내용을 이해하는 데 어려움이 있습니다 ㅠㅠ 그.. 밑에 인프런 AI 인턴의 글에 대한 내용은 어느 정도 이해가 되는데 혹시 AI 인턴이 말한 내용이 올바른 정보일까요...?

애구마(agmma)님의 프로필 이미지
애구마(agmma)
지식공유자

2024. 09. 06. 18:02

안녕하세요

네 AI인턴이 말한 내용도 맞는 내용입니다. 말해준 내용대로 이해하셔도 괜찮습니다.

다만 뷰에 영향을 주지 않더라도 @State 를 요구하는데 그때에는 제가 말씀드린 대로 이해하시면 됩니다. 감사합니다.

(알 수 없음)님의 프로필 이미지

2024. 09. 06. 23:56

답변 주셔서 감사합니다! 좋은 하루 보내세요~

0

인프런 AI 인턴님의 프로필 이미지

2024. 09. 06. 00:01

안녕하세요, 인프런 AI 인턴입니다.

@State가 필요한 이유에 대해 궁금해하시는군요. @State 프로퍼티 래퍼는 SwiftUI에서 뷰의 상태를 관리하는 데 사용됩니다. SwiftUI의 뷰는 불변(immutable)이기 때문에, 뷰 내부에서 변경을 일으키려면 특별한 프로퍼티 래퍼가 필요합니다.

@State를 붙인 변수는 값이 변할 때마다 해당 뷰와 그 뷰의 바디를 다시 그리도록 만듭니다. 즉, @State 래퍼를 통해 선언된 변수의 값이 변하면, SwiftUI는 뷰의 body 프로퍼티를 새로 호출하여 화면을 갱신합니다.

질문하신 ToggleChecktotalNumber 변수가 @State로 선언되어야 하는 이유는, 경우에 따라 이 변수들의 값 변경이 화면에 보여지는 뷰의 상태 변경을 유발하기 때문입니다. 예를 들어 ToggleCheck의 변화가 어떤 특정 조건에서 뷰의 특정 부분을 다른 방식으로 표현하도록 하거나, totalNumber의 값이 변해 이를 화면에 출력하는 printNumber 변수에 영향을 준다면, 이들 변수는 뷰의 상태 변화에 직접적인 영향을 준다고 할 수 있습니다.

printNumber 변수에만 @State가 필요하다고 생각하셨던 부분에 대해, @State는 단순히 뷰에 직접적으로 표현되는 값 뿐만 아니라, 뷰의 상태 변화를 유발할 수 있는 모든 변수에 필요할 수 있습니다. ToggleChecktotalNumber 같은 경우, 이들 값이 변화할 때 조건부 렌더링 또는 계산 결과를 업데이트하는 등으로 간접적으로 화면에 변화를 일으키기 때문에 @State가 필요합니다.

@State와 SwiftUI 뷰의 상태 관리 에서 유사한 주제에 대한 자세한 설명을 확인할 수 있습니다. 여기서는 @State의 역할과 구조체 내 @State 변수가 어떻게 뷰의 상태 관리에 사용되는지를 설명하고 있습니다.

이 답변이 궁금증을 해결하는 데 도움이 되기를 바랍니다. 더 궁금한 점이 있으시면 언제든지 질문 남겨주세요!

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.