SwiftUI @State @Binding 초기화 파라미터 전달 방법 및 navigationBar 및 TabView 사용시 문제점 해결방법

(메인 사진으로 계속 동일한 SwiftUI를 하는 것 같아 직찍/직적 한 사진으로 업로드합니다.)
- 고, 은..
 
 
Introduction

  이번 포스팅은 SwiftUI를 이용하여 개발하면서 꼭 필요한 부분으로 @State, @Binding 을 이용하여 화면UI의 값을 전달하는 방법을 알아보도록 하겠습니다. 그 외에도 여러가지 Property가 있으니 더 자세한 내용은 도서 혹은 온라인 검색을 하여 찾아보시기 바랍니다. 
 
@State, @Binding

  SwiftUI를 이용하여 화면UI의 이동 등을 통해 값이 갱신되기도 합니다. 사용용도는 상위 뷰(A)의 값에 따라 하위 뷰(B)의 모양등이 변할 때 사용합니다. 설명보다는 코드가 조금 더 유용하겠죠? (코드는 잠시 후)
 
예제로는 제가 블로그로에 포스팅 하고 있는 채팅앱으로 설명드리도록 하겠습니다. 블로그에 포스팅할 때에는 SwiftUI의 인터페이스를 만들기위한 과정에 대해 설명을 하였습니다. 하지만, 제가 이후 작업을하면서 겪은 문제들에 대한 해결방법에 대해 이야기를 하지 않았는데 이번 포스팅을 통해 설명을 드리고자 합니다. 
 

 

 

 
 
NavigationBar / TabView 이용시 문제점

 
#참고
  • A: 메인페이지
  • B: TabView
 
상단 결과를 보시면, 첫 로그인 페이지(A)에서 두번째 페이지(B)로 넘어갑니다. 각각의 페이지에 차이점이 보이시나요? 가운데 보여지는 메인페이지들을 제외하고, NavigationBar에 나타나는 제목(Title)과 네비게이션 메뉴(front,end)는 각기 다르게 표현되고 있습니다. 이부분을 구현하기위해서 @State@Binding이 필요합니다. SwiftUI 이외에 기존에 Windows API/MF등을 개발할 때 익숙한 방법으로 singleton을 이용하여 값을 가지고 있고, 이에 대한 전역값을 이용하여 수정하려고 하였지만, 근본적인 문제로 사용방법을 새로 터득(?) 혹은 익숙하지 않음을 느꼈습니다. 그래서 SwiftUI에 맞춰 해결방법에 대해 찾아보고 이렇게 포스팅으로 정리합니다. 
 
채팅앱 코드를 기반으로 설명드리도록 하겠습니다. 블로그를 순차적으로 따라오시면 좋아요 :)
 
 
문제점 정의

본론으로 들어가기 전 문제점 정의에 대해 정확히 확인하고 넘어가도록 하겠습니다. 문제점은 다음과 같습니다.
 
  • NavigationBar 및 TabView를 이용하여 화면 전환할 경우, 각각의 TabView페이지에 제목 및 네비게이션 메뉴를 가시화 함
 
이를 해결하기위해 @State 및 @Binding을 이용하는 것입니다. 
 
 
 
작업 전 초기화면

 
 
작업 전 초기화면 입니다. 첫 로그인 화면에서 TabView로 넘어갑니다. 아직은 nagigationBar의 타이틀 및 상단 메뉴가 없습니다. 이제 코드를 하나씩 추가할테니 잘따라 오시기 바랍니다. 
 
 
# AppLoginUI.swift

 
#Setting
enum AppMenu : String {
    case FriendList    = "Friend List View"
    case ChattingRoom  = "Chatting Rooms List View"
    case Profile        = "Profile view"
}
일단 각각의 페이지에 대해, 열거체(enum)을 이용하여 정의를 합니다.
 
이제 제목을 변경하기 위해 아래의 코드를 추가합니다.  
    // @State 추가
    @State var TitleOfTab = AppMenu.FriendList.rawValue
전체적인 코드는 아래와 같습니다.
struct AppLoginUI: View {
    
    // @State 추가 
    @State var TitleOfTab = AppMenu.FriendList.rawValue
    
    var body: some View {
        return NavigationView {
            ZStack {
                Color.yellow.edgesIgnoringSafeArea(.all) // 전체화면 색상 변경
                VStack{
                    LoginAction
                }
            }   
            // navigation option
            .navigationBarHidden(true)
            .navigationBarBackButtonHidden(true)
        }
    }
}
@State를 이용하여 변수를 정의하면 이후 TabView 페이지에서 값을 수정할 수 있습니다. 
 
# MainTabView.swift 
struct MainTabView: View {
    @Binding var TitleofNavi: String // = "Friend List”
     // 생략… 
}
MainTabView에서는 변수명은 틀리지만, 아래와 같이 정의하였습니다. 값은 NavigationLink를 이용해 MainTabView를 호출 할 때, 파라미터로 전달하면 됩니다. 
 
 
값을 전달하는 방법은 다음과 같습니다. 
 
(AppLoginUI.swift 계속 수정합니다)
 
#기존 코드
            NavigationLink( destination: MainTabView()
                                .navigationBarHidden(false)
                                .navigationBarBackButtonHidden(true) ){
                Text("App Main Login View")
            }
 
#수정 된 코드
            NavigationLink( destination: MainTabView(TitleofNavi: $TitleOfTab )
                            .navigationBarHidden(false)
                            .navigationBarBackButtonHidden(true)
                            .navigationBarTitle(Text(TitleOfTab), displayMode: .inline)
                            .navigationBarItems(leading: ChangeleadingItem(TitleOfTab), trailing: ChangetrailingItem(TitleOfTab)) ){
                Text("App Main Login View")
            }
먼저, MainTabView를 호출 할때, 초기화를 통해 값을 넘겨주시면 됩니다. 이때, @State로 정의된 변수는 “$”를 붙여서 값을 전달하게 됩니다. “문법” 입니다. 그리고 제목(Title)을 변경하기위해서는 navigationBarTitle를 사용하시면 됩니다.  추가로 navigationBarItems를 이용하여 상단메뉴를 변경할 수 있습니다. 이때, TitleofTab변수 값에 따라 leadingItemtrailingItem을 변경할 수 있도록 함수 두개를 추가하였습니다. 함수 두개는 아래와 같이 구현하였습니다. 
 
# ChangeleadingItem()
private extension AppLoginUI {
    
    func ChangeleadingItem(_ TitleName:String ) -> AnyView {
        switch TitleName {
        case AppMenu.FriendList.rawValue: do {
                return AnyView(HStack{
                    Button(action: { print("Button 1") }) {
                        Image(systemName: "bell")
                    }
                })
            }
        case AppMenu.ChattingRoom.rawValue: do {
                return AnyView(HStack{
                    Button(action: { print("Button 1") }) {
                        Image(systemName: "bell")
                    }
                })
            }
        case AppMenu.Profile.rawValue: do {
                return AnyView(HStack{
                    Button(action: { print("Button 1") }) {
                        Image(systemName: "bell")
                    }
                })
            }
        default:do {
                return AnyView(HStack{
                    Button(action: { print("Button 1") }) {
                        Image(systemName: "bell")
                    }
                })
            }
        }
    }
}
 
방법은 AnyView를 이용하여 값을 return 하는 것이 목적으로 Button 에 들어가는 이미지는 SF simpbols app for macOS를 참조해주시기 바랍니다. 
 
# ChangetrailingItem()
private extension AppLoginUI {
    func ChangetrailingItem(_ TitleName:String ) -> AnyView {
        
        switch TitleName {
        case AppMenu.FriendList.rawValue: do {
                return AnyView(HStack{
                    Button(action: { print("Button 2") }) {
                      Image(systemName: "square.and.arrow.up")
                    }
                })
            }
        case AppMenu.ChattingRoom.rawValue:do {
                return AnyView(HStack{
                     Button(action: { print("Button 2") }) {
                       Image(systemName: "square.and.arrow.up")
                     }
                     Button(action: { print("Button 3") }) {
                       Image(systemName: "gear")
                     }
                    .imageScale(.large)
                })
            }
        case AppMenu.Profile.rawValue: do {
                return AnyView(HStack{
                    Button(action: { print("Button 1") }) {
                        Image(systemName: "bell")
                    }
                })
            }
        default:do {
                return AnyView(HStack{
                    Button(action: { print("Button 1") }) {
                        Image(systemName: "bell")
                    }
                })
            }
        }
    }
}
 
ChangetrailingItem 또한 ChangeleadingItem의 이름을 변경하여 사용하시면 됩니다. 
이제 MainTabView.swift 파일에서 수정되는 부분은 아래와 같습니다. 

 

 

 
MainTabView.swift

 
# 변경 전
struct MainTabView: View {
    
    private enum Tabs {
      case FriendList, ChattingRoom, Profile
    }
    
    @State private var selectedTab: Tabs = .FriendList
    
    var body: some View {
        ZStack {
            TabView(selection: $selectedTab) {
              Group {
                FriendListUI
                ChattingRoomUI
                ProfileUI
              }
            }
        }
    }
}
# 변경 후
struct MainTabView: View {
    
    private enum Tabs {
      case FriendList, ChattingRoom, Profile
    }
    
    @State private var selectedTab: Tabs = .FriendList
    @Binding var TitleofNavi: String // 추가
    
    var body: some View {
        ZStack {
            TabView(selection: $selectedTab) {
              Group {
                FriendListUI
                ChattingRoomUI
                ProfileUI
              }
            }
            // 추가
            .statusBar(hidden: selectedTab == .FriendList)
        }
    }
}
 
추가된 코드는 두줄 입니다. 처음에 이야기 하였던 @Binding 값을 지정하는 것과 TabView에 .statusBar() 를 설정하는 것입니다. 
그리고 extension으로 추가 작성한 코드에 .onAppear{} 에 주석처리한 부분을 다음과 같이 해제합니다. 
private extension MainTabView {
    var FriendListUI: some View {
        FriendListView()
        .tag(Tabs.FriendList)
        .tabItem {Image(systemName: "person.3").imageScale(.large)}
        .navigationBarHidden(false)
        .onAppear {
            // 아래 코드 주석 해제
            self.TitleofNavi = AppMenu.FriendList.rawValue
        }
    }
    // 이하 코드 동일하게 주석 해제 해주시기 바랍니다. 
}
 
코드가 동일하기때문에 이하 추가 Tab페이지들도 동일하게 처리해주시면 됩니다. 
 
 
결과

그럼 결과를 확인해볼까요? 
 
 
 
결과를 gif로 확인해볼까요? 
 
이제 결과와 같이 잘되시나요? 크게 어렵지 않죠? 나머지는 각각의 페이지에 맞춰 페이지를 구성하시면 됩니다. 
 
 
# 마무리 

  포스팅을 준비하면서, 기존에 작성한 자료들을 어떻게 할까 고민하다 마지막으로 작성한 포스팅에 맞춰 새롭게 작성하였습니다. 생각보다 조금 포스팅이 길어지기는 했지만, 조금 더 쉽게 작성할 수 있어서 다행이라 생각합니다. 하지만 작성된 글을 보는 분들 입장에서는 쉽게 설명이 되었을지 의문이 드네요. 소스코드는 조금 정리 후 github등을 통해 전체적으로 공유할까 합니다. 그래서 공부를 위해 블로그의 내용을 보고 조금씩 따라서 문제점을 해결해봐주셨으면 합니다. 
 
마지막으로 아래의 Reference를 참조해보시기 바랍니다.
 
Reference

이 글을 공유하기

댓글(0)

Designed by JB FACTORY