SWIFTUI @STATE

Ios_Roy·2023년 3월 10일
0

SWIFT UI 컴포넌트

목록 보기
4/4
post-thumbnail

State

UIKit 을 사용했을 때는 어떠한 변수에 변화가 생기면 해당 변화를 직접 관찰하고 반영 해 주어야 했는데 SwiftUI 에서는 property wrapper 를 활용해 이러한 작업을 자동화 할 수 있도록 했다.

  • 변화가 생기면 해당 변수의 값을 읽거나 새로 쓸 수 있음을 의미하는 property wrapper
  • SwiftUI 는 state 로 선언된 property 들의 저장소를 관리
  • State value 값이 변경되면 뷰는 해당 value 의 appearance 를 무효화 하고 다시 body 값을 계산
  • state 변수값이 변경되면 view 를 다시 랜더링 하기 때문에 항상 최신 값을 가짐
  • State 인스턴스는 그 자체로 값이 아닌, 값을 읽고 쓰는 것을 의미함
  • 현재 뷰 UI의 특정 상태를 저장하기 귀해 만들어진 것이기 때문에 보통 private
  • @State 속성으로 어떤 프로퍼티의 초기값을 지정했다면, 다른 값으로 재할당 불가, @Binding 변수를 통해서만 가능
// @state 값의 변화를 감지
    @State private var isActivated: Bool = false
    
    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    MyVstackView(isActivated: $isActivated)
                    MyVstackView(isActivated: $isActivated)
                    MyVstackView(isActivated: $isActivated)
                   
                }
                .padding(isActivated ? 50.0 :  10.0)
                .background(isActivated ? Color.yellow : Color.black)
                
                .onTapGesture {
                    print("Hstack 이 클릭")
                    withAnimation {
                        self.isActivated.toggle()
                    }
                }
                NavigationLink {
                    MyTextView(isActivated: $isActivated)
                } label: {
                    Text("네비게이션 ")
                        .fontWeight(.bold)
                        .font(.system(size: 40))
                        .padding()
                        .background(.orange)
                        .foregroundColor(.white)
                        .cornerRadius(30)
                }
                .padding(.top , 50)
            }
           
        }
    }
}

@State

앞전 게시물에서 수없이 나왔던 @State 와 Struct에 대해서 알아보겠습니다.

SwiftUI에서 가장 기본이되고 많이 사용하는 것들이니 꼭 알아두시길 바랍니다.

만약 사용자가 버튼을 누르거나 스크롤을 하거나 텍스트에 상자를 입력했다고 치면, 그 특정 행동은 State 즉, 상태를 변경합니다. 그 이후에 일어날 일은 State가 변경되면 자동으로 변환 시켜주는일을 합니다. 사용자 인터페이스를 업데이트 하는것이죠.

그렇다면 어떻게 이렇게 할 수 있을까요? View를 사용할때 ContentView가 실제로 View 프로토콜을 준수한다는것을 기억해야합니다. Body속성을 작성하죠. 이것이 View 프로토콜의 유일한 요구 사항입니다.

상태를 변경 할 때마다 body속성이 재설정 됩니다. 뷰 자체가 다시 렌더링 되는것이죠.

따라서 State를 변경 할 때마다 항상 새로운 View에서 렌더링 된다는 점을 기억하시면 됩니다. 그리고 사용자가 그것을 보게 되는것이죠.

사용자가 스위치를 켜고 끄는것처럼 상태를 변경 할때 그 값을 State안에 넣으면 뷰를 렌더링 하게 됩니다.

이것을 사용하기 위해서는 @State와 Struct를 필수적으로 사용해야합니다. Strcut 내부의 값이 전체 Struct를 변경할 때마다 전체 구조체가 자동적으로 변경됩니다. 마치 이름이나 성에 대한 키를 입력 할 때마다 새롭게 적용되게 해주는것처럼 말이죠.

아래 예시를 보며 확인해보겠습니다.

SWIFT

struct ContentView: View {
    
    var name = "로이"
    
    var body: some View {
        VStack {
            Text(name)
            Button(action: {
            }) {
                Text("이름 바꾸기")
            }
        }
    }
}

name을 변수로 지정해주고 텍스트로 이름과 버튼을 만들어줬습니다. 이상태에서 버튼을 클릭하면 아무런 변화가 없습니다.

하지만 @State를 넣어주고 아래와 같이 코드를 변경하면 어떻게 될까요?

SWIFT

struct ContentView: View {
    
    @State var name = "로이"
    
    var body: some View {
        VStack {
            Text(name)
            Button(action: {
                self.name = "리오"
            }) {
                Text("이름 바꾸기")
            }
        }
    }
}

버튼을 클릭하면 '서근' 에서 '포뇨'로 이름이 바뀝니다. @State를 지정해주면서 View가 업데이트 된것이죠.

여기서 self는 @State name을 의미합니다.

예제 1

만약 목록 상단에 버튼이 있고, 항목추가 버튼을 누를 때마다 자동으로 항목이 추가되고 뷰가 새로고침되게 해보겠습니다.

1.  새로운 Swift 파일을 만들어주고 이름은 Task로 정해주겠습니다. 그리고 다음과 같이 코드를 작성합니다.

TIP

💡UUID??유형, 인터페이스 및 기타 항목을 식별하는 데 사용할 수 있는 보편적으로 고유한 값입니다.

SWIFT

import Foundation
import SwiftUI

struct Task: Identifiable {
    let id = UUID()
    let name: String
}

SWIFT

import SwiftUI

struct ContentView: View {
    
    var tasks = [Task]()
    
    var body: some View {
        List {
            
            ForEach(tasks) { task in
                Text(task.name)
            }
        }
    }
}

이렇게 하고 런을 해주면 아무것도 없는 EmptyView가 됩니다. 코드를 더 추가해주도록 하겠습니다.

SWIFT

import SwiftUI

struct ContentView: View {
    //1.
    @State var tasks = [Task]()
    //4. addTask라는 함수 추가
    private func addTask() {
        self.tasks.append(Task(name: "로이 블로그 구독"))
    }
    
    var body: some View {
        //2.
        List {
            //5.
            Button(action: addTask) {
                HStack {
                    Image(systemName: "plus")
                    Text("할일 추가")
                }.foregroundColor(.blue)
            }
            
            //3.
            ForEach(tasks) { task in
                Text(task.name)
            }
        }
    }
}

예제 2

1. New file을 만들어 이름은 'Dish' 라고 정해주고, 아래 이미지를 Assets에 넣어줍니다. 그리고 Dish 라는 Extension값을 넣어주겠습니다. 매개변수 타입을 Identifiable 로 반드시 지정해줘야 합니다.

Dish.zip7.13MB

SWIFT

import Foundation
import SwiftUI
 
struct Dish: Identifiable {
    
    //Dish를 구별하기 위해 고유 ID를 저장해줌
    let id = UUID()
    let name : String
    let price : Double
    let imageURL : String
    //음식이 맵다면
    let isSpicy: Bool
}

extension Dish {
    static func all() -> [Dish] {
        return [
            
            Dish(name: "김치", price: 10000, imageURL: "Kimchi", isSpicy: true),
            Dish(name: "스파게티", price: 8500, imageURL: "Spagetti", isSpicy: true),
            Dish(name: "토스트", price: 3000, imageURL: "Toast", isSpicy: false),
            Dish(name: "초밥", price: 12000, imageURL: "SuShi", isSpicy: false),
            Dish(name: "스테이크", price: 25000, imageURL: "Steak", isSpicy: false),
            Dish(name: "치킨", price: 17000, imageURL: "Chicken", isSpicy: true)
            
        ]
    }
}

2. ContentView에 화면을 구성해보도록 하겠습니다. 우리는 음식 이미지와 이름을 나열하고 만약 그 음식이 매우면, 맵기를 표시하려고 합니다.

SWIFT

struct ContentView: View {
    var model = Dish.all()
    
    var body: some View {
        //1.
        List {
            //2.
            ForEach(model) { dish in
                //3.
                HStack {
                    //4.
                    Image(dish.imageURL)
                        .resizable()
                        .frame(width: 100, height: 100)
                    //5.
                    Text(dish.name)
                        .padding(.leading, 10)
                        .font(.title)
                        .lineLimit(nil)
                    //7.
                    Spacer()
                    
                    //8.만약 음식이 맵다면 아래 이미지 표시
                    if(dish.isSpicy) {
                        //6.
                        Image("spicy-icon")
                            .resizable()
                            .frame(width: 30, height: 30)
                    }
                    
                }
            }
        }
    }
}

음식 이미지와 이름은 정확히 불러왔지만, 매운정도에 따라 맵기 아이콘이 정상적으로 표시되지는 않습니다.

SWIFT

 List {
            
      Toggle(isOn: .constant(true)) {
       Text("매운맛")
                
            }

spicy-icon은 정상적으로 나타나게 되었습니다. 하지만 매운맛 버튼을 토글하면 아무런 작동이 되지 않습니다.

@State 를 추가해서 작동 될 수 있도록 하겠습니다.

SWIFT

struct ContentView: View {
    var model = Dish.all()
    //9.
    @State private var IsSpicy = false
    
    var body: some View {
       
        List {
        
            //10  .constant(true) -> $IsSpicy
            Toggle(isOn: $IsSpicy) {
                Text("매운맛")

이제 토글이 작동이 됩니다. 하지만 이미지에는 아무런 변화가 없죠. 자, 만약 토글을 off했을때 맵지않은 음식을 가리고 싶다면?

ForEach 문을 수정해야합니다. ForEach의 model 뒤에 filter를 추가해줍니다.

SWIFT

//11.
ForEach(model.filter { $0.isSpicy == self.IsSpicy}) { dish in

Filter 예문

여기에서 filter는 5글자 미만인 것만 나타낸다 라는 조건입니다.

SWIFT

let cast = ["Vivien", "Marlon", "Kim", "Karl"]
let shortNames = cast.filter { $0.count < 5 }
print(shortNames)
// Prints "["Kim", "Karl"]"

🔥참고 자료

SwiftUI : State에 대해 자세히 알아보기

https://github.com/Seogun95/SwiftUI_State_TUT

https://github.com/Seogun95/SwiftUI_State_TUT

profile
iOS 개발자 공부하는 Roy

0개의 댓글