[웹개발자의 IOS 탐방기] 4. VStack HStack을사용하여설정창 UI 구현

서론

IOS의 NavigationView와 NavigationLink로 이동된 화면에서는 IOS 자체적으로 지원하는 뒤로가기 버튼이 표시된다. 하지만 우리는 좀 더 친숙한 UI를 구현하기 위해 뒤로 가기 버튼부터 시작하여 설정창에 필요한 기능들을 아래와 구현하려고 한다. SwiftUI에서 화면을 그릴때는 주로 VStack과 HStack을 사용한다.

오마주는 당근마켓 설정화면을 기준으로 하였다.

 

VStack, HStack이란?

VStack: 수직으로 뷰들을 쌓아 준다. 위에서 아래로 UI 요소들을 일렬로 배열하고 싶을 때 사용한다.
HStack: 수평으로 뷰들을 쌓아 준다. 왼쪽에서 오른쪽으로 UI 요소들을 일렬로 배열하고 싶을 때 쓰면 된다.
간단하게 말하면, VStack은 세로로, HStack은 가로로 뷰들을 정렬해 줄 때 사용한다.

SettingView.swift 파일 수정 - Header 구현

Header에 관한 내용을 먼저 정의할 것이다. Header, Content 등을 위에서 아래로 쌓아줘야 하기 때문에 가장 큰 단위는 VStack으로 감싼 후, Header에 들어갈 내용인 "뒤로가기" 버튼과 "설정" 텍스트는 왼쪽에서 오른쪽으로 배열하여야 하기 때문에 HStack으로 감싸준다.

Spacer()는 SwiftUI에서 뷰들 사이에 공간을 만들기 위해 사용하는 구성 요소로, Spacer()는 가능한 모든 사용 가능한 공간을 차지하려고 한다. HStack이나 VStack 안에서 사용될 때, 주위의 뷰들을 밀어내거나 당기는 역할을 해서 뷰들 사이에 동적인 간격을 만들어 주는데 위의 코드에서 HStack 안에 있는 첫 번째 Spacer()는 버튼과 "설정" 텍스트 사이에 가능한 모든 공간을 차지하게 되어, 버튼은 왼쪽으로 밀리고 "설정" 텍스트는 중앙으로 이동한다.

Button에 입힌 Image는 사용자 지정 이미지를 넣고 싶을 경우 assets폴더에 사용하고자 하는 이미지를 넣은 후, systemName이 아닌, 사용자 지정 이미지명을 텍스트로 넣어주면 된다. 다른 System Icon을 사용하고 싶을 경우 xcode 우측 상단 [+] 버튼을 클릭하여 show the symbols library에서 검색하여 원하는 symbol을 사용하면 된다.

마지막 Rectangle로 설정 하단에 구분선을 명확하게 그어준다. 완성된 모습은 아래와 같다.

SettingView.swift 파일 수정 - Content 구현 : 계정관리 섹션

로그인/로그아웃 기능을 구현하려면 WebView에서 실행하는 MobileWeb에 해당 기능이 이미 구현되어 있어야 한다. 그러므로 이번에는 단순히 버튼을 클릭했을 때 로그인 및 로그아웃 액션이 어떻게 작동하는지 살펴볼 예정이다.

Header를 제외한 나머지 부분을 Content 영역이라고 부를 것이다. 이 Content 영역은 스크롤이 가능해야 하므로, 전체를 ScrollView로 감싸 스크롤 가능하게 만들었다.

로그인과 로그아웃 기능은 '계정관리'라는 타이틀 아래에 묶기로 하였다. 이를 위해 AccountManageView라는 구조체를 정의했다. 이 구조체를 호출하는 SettingView에는 isLogin이라는 @State를 추가할 것이며, 이 상태를 AccountManageView와 공유하기 위해 @Binding을 사용한다.

struct AccountManageView: View {
    @Binding var isLogin: Bool
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text("계정관리")
                .font(.headline)
            
            HStack {
                // 이미지와 '계정관리' 텍스트를 포함하는 VStack
                VStack(alignment: .leading, spacing: 10) {
                    Image(systemName: "person") // SwiftUI의 Image 뷰를 사용
                        .resizable()
                        .frame(width: 60, height: 60)
                        .clipShape(Circle())
                        .overlay(
                            Circle().stroke(Color.white, lineWidth: 1)
                        )
                        .shadow(radius: 1)
                }
                .frame(width: .infinity , height: 104)
                
                VStack(alignment: .leading, spacing: 10) {
                    if isLogin {
                        Text("김민규")
                            .font(.body)
                        Text("환영합니다")
                            .font(.subheadline)
                    } else {
                        Text("서비스를 이용하시려면\n로그인 해주세요.")
                            .foregroundColor(Color(red: 0.6, green: 0.6, blue: 0.6))
                            .font(.custom("AppleSDGothicNeo-Medium", size: 16))
                            .lineSpacing(19.2 - 16)
                            .multilineTextAlignment(.leading)
                    }
                }
                .padding(.leading,16)
                
                Spacer()
                
                VStack(alignment: .trailing, spacing: 10) {
                    Button(action: {
                        if isLogin {
                            isLogin = false
                        } else {
                            isLogin = true
                        }
                        
                    }) {
                        Text(isLogin ? "로그아웃" : "로그인")
                    }
                }
            }
        }
    }
}

AccountManageView 구조체 정의가 완료되었다면 이제 SettingView의 ScrollView 안에서 호출한다.

struct SettingView: View {    
    // 로그인 유무 관리용 변수
    @State private var isLogin = false
    
    var body: some View {
        VStack {
            // Header
            HeaderView()
            // End Header
            
            // Content
            ScrollView {
                AccountManageView(isLogin: $isLogin)
                    .padding(20)
                Divider()
            }
            // End Content
            
        }
    }
}

SettingView.swift 파일 수정 - Content 구현 :알림설정 섹션

여기서는 Toogle을 사용하여 알림 on & off를 구현하였다.

struct NotificationView: View {
    @State private var isNotificationEnabled: Bool = false
    @State private var isNotificationEnabled2: Bool = false

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text("알림설정")
                .font(.headline)
            Spacer().frame(height: 20)
            HStack {
                Text("알림1")
                Toggle(isOn: $isNotificationEnabled) {
                    
                }
            }
            Spacer().frame(height: 10)
            HStack {
                Text("알림2")
                Toggle(isOn: $isNotificationEnabled2) {
                    
                }
            }
        
            if isNotificationEnabled {
                // 알림1 켜짐
            } else {
                // 알림1 꺼짐
            }
            
            if isNotificationEnabled2 {
                // 알림2 켜짐
            } else {
                // 알림2 꺼짐
            }
        }
    }
}

내장 NavigationBar 제거 및 뒤로가기 기능 추가

마지막으로 설정 버튼을 클릭하여 SettingView로 넘어가는 ToolbarView에서, IOS 내장 navigationbar를 숨기기 위해 navigationBarHidden(true) 를 추가해준다.

NavigationLink(destination: SettingView().navigationBarHidden(true), isActive: $showSettings) {
    Button(action: {
        showSettings = true
    }) {
        Text("설정")
    }
}

또한, SettingView의 HeaderView 뒤로가기 버튼 클릭시, 이전 화면으로 돌아가기 위해 @Environment 속성을 사용하여 presentationMode를 얻어온 후, .wrappedValue.dismiss() 메서드를 호출하여 뷰를 닫아준다.

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

var body: some View {
// ...
    Button(action: {
            self.presentationMode.wrappedValue.dismiss()
        })
// ...
}

결과물