본문 바로가기
iOS - 실무관련/SwiftUI

@State - 상태변화 감지

by print_soo 2022. 7. 12.

이번 글은 이전 글의 코드를 이어서 진행하겠다. 

 

1. 탭 제스처를 사용자가 했을 때 HStack의 padding, Color가 변경되게 만들어보자.

 

우선 제스처를 했는지 확인하는 과정이 필요하다.

따라서 HStack의 속성에 탭 제스처를 했는지 확인후 탭 제스처를 했다면 해당 로직을 실행하는 속성을 추가해보자. 

 

.onTapGesture {
	//코드
}

 

 

내부에 탭을 했다면 "탭을 했습니다!" 라고 print 되도록 코드를 작성해보자. 

 

import SwiftUI

struct ContentView: View {
    var body: some View {
        HStack {
            
            MyVStackView()
            MyVStackView()
            MyVStackView()
            
        }
        .padding(10)
        .background(Color.red)
        .onTapGesture {
            print("탭 했습니다.")
        }
 
    }
}


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

 

Preview로는 print를 확인할 수 없다. 따라서 시뮬레이터를 돌려서 진행해봐야한다. 

 

시뮬레이터로 진행하면 아래처럼 잘 출력되는 것을 확인할 수 있다. 

 

 

이제 새로운 Bool 변수를 만들어서 탭을 했을 때 HStack의 백그라운드 색을 변경시켜보자. 

 

private var isActivated: Bool = false

 

 

새로운 변수 isActivated를 만들고 해당 변수의 상태에 따라서 백그라운드 색을 변경시켜보자.  - 삼항연산자 활용

삼항연산자를 활용해서 색을 변경하는 조건을 만들고 탭을 했을 때 .toggle()을 이용해서 변수의 상태를 변경해보자.

 

import SwiftUI

struct ContentView: View {

    private var isActivated: Bool = false

    var body: some View {
        HStack {
            
            MyVStackView()
            MyVStackView()
            MyVStackView()
            
        }
        .padding(10)
        .background(isActivated ? Color.red : Color.yellow)
        .onTapGesture {
            print("탭 했습니다.")
            self.isActivated.toggle()
            //toggle() - true이면 false로 false이면 true로 상태를 변경
        }
 
    }
}


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

 

 

위의 코드로 작성을 하면 아래의 오류가 뜬다.

👿 Cannot use mutating member on immutable value: 'self' is immutable

 

 

SwiftUI에서 변수를 변화시키기 위해서는 State라는 어노테이션을 사용해야한다.

import SwiftUI

struct ContentView: View {

    @State // 값의 변화를 감지
    private var isActivated: Bool = false

    var body: some View {
        HStack {
            
            MyVStackView()
            MyVStackView()
            MyVStackView()
            
        }
        .padding(10)
        .background(isActivated ? Color.red : Color.yellow)
        .onTapGesture {
            print("탭 했습니다.")
            self.isActivated.toggle()
            //toggle() - true이면 false로 false이면 true로 상태를 변경
        }
 
    }
}


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

 

💡 @State는 코드에서 값이 변경된 경우를 알아채고 ContentView를 다시 렌더링하여 업데이트를 하는 역할을 한다. 

 

padding값도 동일한 방법으로 변경해주면 된다. 

import SwiftUI

struct ContentView: View {

    @State // 값의 변화를 감지
    private var isActivated: Bool = false

    var body: some View {
        HStack {
            
            MyVStackView()
            MyVStackView()
            MyVStackView()
            
        }
        .padding(isActivated ? 50 : 10)
        .background(isActivated ? Color.red : Color.yellow)
        .onTapGesture {
            print("탭 했습니다.")
            self.isActivated.toggle()
            //toggle() - true이면 false로 false이면 true로 상태를 변경
        }
 
    }
}


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

 

 

padding값을 변경해주면 아래와 같이 딱딱 끊기게 화면이 보인다.

기능상의 문제는 없지만 미관상의 문제가 있기 때문에 애니메이션을 적용해보자.

 

 

이렇게 withAnimation으로 toggle을 감싸면 애니메이션 효과가 적용된다. 

import SwiftUI

struct ContentView: View {

    @State // 값의 변화를 감지
    private var isActivated: Bool = false

    var body: some View {
        HStack {
            
            MyVStackView()
            MyVStackView()
            MyVStackView()
            
        }
        .padding(isActivated ? 50 : 10)
        .background(isActivated ? Color.red : Color.yellow)
        .onTapGesture {
            print("탭 했습니다.")
            //애니메이션 
            withAnimation{
                //toggle() - true이면 false로 false이면 true로 상태를 변경
                self.isActivated.toggle()
            }
            
            
        }
 
    }
}


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

 

 

 

 

2. NavigationView를 활용해보자.

 

 

1. NavigationView로 HStack 자체를 포함시키자. (HStack은 VStack에 포함시키자.)

 

import SwiftUI

struct ContentView: View {

    @State // 값의 변화를 감지
    private var isActivated: Bool = false

    var body: some View {
        
        NavigationView {
            VStack {
                HStack {
                    
                    MyVStackView()
                    MyVStackView()
                    MyVStackView()
                    
                }
                .padding(isActivated ? 50 : 10)
                .background(isActivated ? Color.red : Color.yellow)
                .onTapGesture {
                    print("탭 했습니다.")
                    //애니메이션
                    withAnimation(){
                        //toggle() - true이면 false로 false이면 true로 상태를 변경
                        self.isActivated.toggle()
                    }
                    
                    
                }
            }
        }
        
 
    }
}


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

 

 

2. NavigationLink(destination: )을 이용해서 어떤 화면으로 넘어가질 정하고 클로저를 이용해서 네비게이션 버튼을 만들어주자.

 

여기서 destination은 네비게이션 버튼으로 넘어갔을 때의 UI이고 클로저의 내부는 버튼의 텍스트, 속성을 나타낸다.

 

import SwiftUI

struct ContentView: View {

    @State // 값의 변화를 감지
    private var isActivated: Bool = false

    var body: some View {
        
        NavigationView {
            VStack {
                HStack {
                    
                    MyVStackView()
                    MyVStackView()
                    MyVStackView()
                    
                }
                .padding(isActivated ? 50 : 10)
                .background(isActivated ? Color.red : Color.yellow)
                .onTapGesture {
                    print("탭 했습니다.")
                    //애니메이션
                    withAnimation(){
                        //toggle() - true이면 false로 false이면 true로 상태를 변경
                        self.isActivated.toggle()
                    }
                    
                    
                } // HStack
                
                //네비게이션 버튼(링크)
                NavigationLink(destination: Text("네비게이션 화면")) {
                    Text("Go Navi")
                }.padding(.top, 50)
                
            } // VStack
        } // NavigationView
        
 
    }
}


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

 

 

3. 네비게이션 버튼도 속성을 변경할 수 있다. 

 

import SwiftUI

struct ContentView: View {

    @State // 값의 변화를 감지
    private var isActivated: Bool = false

    var body: some View {
        
        NavigationView {
            VStack {
                HStack {
                    
                    MyVStackView()
                    MyVStackView()
                    MyVStackView()
                    
                }
                .padding(isActivated ? 50 : 10)
                .background(isActivated ? Color.red : Color.yellow)
                .onTapGesture {
                    print("탭 했습니다.")
                    //애니메이션
                    withAnimation(){
                        //toggle() - true이면 false로 false이면 true로 상태를 변경
                        self.isActivated.toggle()
                    }
                    
                    
                } // HStack
                
                //네비게이션 버튼(링크)
                NavigationLink(destination: Text("네비게이션 화면")) {
                    Text("Go Navi")
                        .fontWeight(.heavy) //폰트 굵기
                        .font(.system(size: 30)) //폰트 크기
                        .padding() //패딩
                        .background(Color.gray) //백그라운드 색상
                        .foregroundColor(Color.cyan) //텍스트 색상
                        .cornerRadius(30) //버튼의 각을 둥글게(30정도)
                }.padding(.top, 50)
                
            } // VStack
        } // NavigationView
        
 
    }
}


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

 

이제 문제는 버튼을 눌러서 넘어가면 "네비게이션 화면"이라는 텍스트만 있는데 앞으로 우리가 만들 앱은 텍스트만 있는 앱이 아닐 것이기 때문에 이전에 MyVStackView를 만든것 처럼 새로운 파일을 만들고 해당 파일에 View를 만들어보자.

 

 

4. 새로운 View 만들기

 

  1. 새파일 만들기 -MyTextView
  2. MyTextView라는 구조체 만들기
  3. 속성 추가 - 백그라운드 꽉채우기
struct MyTextView: View {
    var body: some View {
        VStack{
            //MARK: Spacer - 다른 object의 크기가 변하지 않는 선에서 본인의 크기를 최대한으로 늘리는 성질을 갖고 있음
            
            //따라서 Spacer를 위아래로 주게되면 위아래로 최대한 Text를 늘린다.
            Spacer()
            Text("배경 아이템 인덱스")
                .font(.system(size: 30))
                .fontWeight(.bold)
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            // 양옆으로 배경 꽉채우기
            
            Spacer()
        }.background(Color.red)
    }
}

struct Previews_MyTextView: PreviewProvider {
    static var previews: some View {
        MyTextView()
    }
}

 

 

5. 네비게이션 링크의 destination에 해당 View 적용하기

 

import SwiftUI

struct ContentView: View {

    @State // 값의 변화를 감지
    private var isActivated: Bool = false

    var body: some View {
        
        NavigationView {
            VStack {
                HStack {
                    
                    MyVStackView()
                    MyVStackView()
                    MyVStackView()
                    
                }
                .padding(isActivated ? 50 : 10)
                .background(isActivated ? Color.red : Color.yellow)
                .onTapGesture {
                    print("탭 했습니다.")
                    //애니메이션
                    withAnimation(){
                        //toggle() - true이면 false로 false이면 true로 상태를 변경
                        self.isActivated.toggle()
                    }
                    
                    
                } // HStack
                
                //네비게이션 버튼(링크)
                NavigationLink(destination: MyTextView()) {
                    Text("Go Navi")
                        .fontWeight(.heavy) //폰트 굵기
                        .font(.system(size: 30)) //폰트 크기
                        .padding() //패딩
                        .background(Color.gray) //백그라운드 색상
                        .foregroundColor(Color.cyan) //텍스트 색상
                        .cornerRadius(30) //버튼의 각을 둥글게(30정도)
                }.padding(.top, 50)
                
            } // VStack
        } // NavigationView
        
 
    }
}


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

 

6. State이용해서 MyTextView 색 변경해보기

import SwiftUI

struct MyTextView: View {
    
    @State
    private var index: Int = 0
    
    private let backgroundColorArray = [
        Color.red,
        Color.yellow,
        Color.blue,
        Color.green
    ]
    
    var body: some View {
        VStack{
            //MARK: Spacer - 다른 object의 크기가 변하지 않는 선에서 본인의 크기를 최대한으로 늘리는 성질을 갖고 있음
            
            //따라서 Spacer를 위아래로 주게되면 위아래로 최대한 Text를 늘린다.
            Spacer()
            Text("배경 아이템 인덱스 \(self.index)")
                .font(.system(size: 30))
                .fontWeight(.bold)
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            // 양옆으로 배경 꽉채우기
            
            Spacer()
        }
        .background(backgroundColorArray[index])
        .onTapGesture {
            // 한번의 탭이 될때 마다 index를 +1 해준다.
            self.index += 1
            // index의 갯수는 4개이기 때문에 index를 4로 나눈 나머지를 index로 넣어준다. - 인덱스 오류 방지
            self.index = self.index % 4
        }
    }
}

struct Previews_MyTextView: PreviewProvider {
    static var previews: some View {
        MyTextView()
    }
}

 

@State는 값이 변경된 것을 감지해 해당 View를 다시 랜더링 해줄 때 사용!!

'iOS - 실무관련 > SwiftUI' 카테고리의 다른 글

Set Image  (0) 2022.07.15
Text  (0) 2022.07.14
웹뷰 띄우기  (0) 2022.07.13
@Binding - 데이터 연동  (0) 2022.07.12
SwiftUI와 친해지기  (0) 2022.07.12