티스토리 뷰

가이드

SwiftUI 커스텀 바인딩 활용하기

Sunghyun Kim 2022. 4. 13. 00:04

SwiftUI에는 @State 값을 기반으로 뷰의 상태를 조절할 수 있다. 단순히 몇가지 값을 기반으로 표현하는 뷰의 경우 이 값 바인딩이 사용하기 편리하며 가독성을 높여준다. 그러나 Picker나 Toggle등의 값 변화에 동작을 추가하고 싶을 때는 번거로운 작업이 예상되는 경우가 많다. 이럴 땐 기존의 델리게이트 방식을 사용하는 것이 편리한 것 같다.

다행히 SwiftUI의 바인딩은 Get, Set 동작을 커스텀할 수 있도록 해준다. 이를 통해 애플이 제작한 바인딩 기반 뷰들이 방출하는 값을 관찰하거나 뷰가 표시될 시점을 조절할 수 있다.

바인딩 Get 커스텀하기

Bool 바인딩 기반 뷰를 표시하고 싶지만 State가 Bool이 아닐 때

struct ContentView: View {
    @State private var currentUser = "default"

    var body: some View {
        TextField("Username", text: $currentUser)
            .alert("User Error", isPresented: .init(
                get: { currentUser.isEmpty },
                set: { _ in }
            )) {
                Button("Ok") {}
            } message: {
                Text("Enter username.")
            }
    }
}

currentUser가 빈 문자열인 경우에 경고창을 띄우게 된다.

Simulator Screen Recording - iPhone 13 Pro - 2022-04-13 at 00.37.24

바인딩 Set 커스텀하기

바인딩 기반 뷰가 방출하는 값을 관찰하고 싶을 때

struct ContentView: View {
    @State private var flip = false
    @State private var flipCount = 0

    var body: some View {
        VStack {
            Text("Flip Count: \(flipCount)")
            Toggle("Flip!", isOn: .init(
                get: { flip },
                set: { newValue in
                    flip = newValue
                    flipCount += 1
                }
            ))
        }
    }
}

위와 같이 바인딩을 커스텀하면 스위치가 토글될 때마다 flipCount를 증가시킬 수 있다.

파일 정렬 Picker 메뉴 예제

struct File: Identifiable {
    var name: String
    var size: Int
    var id: String { name }
}

File을 갖는 배열을 Picker 선택에 따라 정렬하는 코드를 짤 것이다.

@State private var orderBy: OrderBy = .name

var body: some View {
    VStack {
        Picker("Order By", selection: $orderBy) {
            ForEach(OrderBy.allCases) { order in
                Text(order.rawValue)
            }
        }
        List(sortedFiles) { file in
            HStack {
                Text(file.name)
                Spacer()
                Text("\(file.size)").font(.caption)
            }
        }
    }
}

Picker는 selection 바인딩에 사용자가 선택한 값(뷰의 태그 또는 뷰의 순서)을 자동적으로 Set해준다. 그러면 변경된 orderBy에 맞춰 정렬된 리스트를 불러와 표시한다.

Simulator Screen Recording - iPhone 13 Pro - 2022-04-12 at 23.56.27

Picker 항목을 다시 선택했을 때 오름차순과 내림차순을 전환하고싶다면?

@State private var ascending: Bool = true

우선 오름차순 내림차순 여부를 확인할 상태값을 생성한다.

Picker("Order By", selection: .init(
    get: { orderBy },
    set: { newValue in
        if orderBy == newValue {
            ascending.toggle()
        } else {
            orderBy = newValue
            ascending = true
        }
    }
)) {
    ForEach(OrderBy.allCases) { order in
        Text(order.rawValue)
    }
}

바인딩을 커스텀하여 다시 선택됨을 감지하게 작성한다. 이제 다시 선택되면 정렬 순서를 뒤집는다.

Simulator Screen Recording - iPhone 13 Pro - 2022-04-12 at 23.59.10

전체 코드

struct File: Identifiable {
    var name: String
    var size: Int
    var id: String { name }
}

import SwiftUI

struct ContentView: View {
    private var files = [
        File(name: "Apple Myeongdong.jpg", size: 3_034_579),
        File(name: "Be Kind.mp3", size: 7_048_172),
        File(name: "Code.swift", size: 13_667)
    ]

    enum OrderBy: String, CaseIterable, Identifiable {
        case name = "Name"
        case size = "Size"
        var id: OrderBy { self }
    }

    @State private var orderBy: OrderBy = .name
    @State private var ascending: Bool = true

    private var sortedFiles: [File] {
        switch orderBy {
        case .name:
            return ascending ?
            files.sorted { $0.name < $1.name } :
            files.sorted { $0.name > $1.name }
        case .size:
            return ascending ?
            files.sorted { $0.size < $1.size } :
            files.sorted { $0.size > $1.size }
        }
    }

    var body: some View {
        VStack {
            Picker("Order By", selection: .init(
                get: { orderBy },
                set: { newValue in
                    if orderBy == newValue {
                        ascending.toggle()
                    } else {
                        orderBy = newValue
                        ascending = true
                    }
                }
            )) {
                ForEach(OrderBy.allCases) { order in
                    Text(order.rawValue)
                }
            }
            List(sortedFiles) { file in
                HStack {
                    Text(file.name)
                    Spacer()
                    Text("\(file.size)").font(.caption)
                }
            }
        }
    }
}
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함