SwiftUI

SwiftUI ๋‹ค์–‘ํ•œ ํ† ๊ธ€ ํ˜•ํƒœ๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ๊ธฐ ์œ„ํ•œ ToggleStyle ํ”„๋กœํ† ์ฝœ

ํ•˜์ดD:) 2023. 6. 6. 16:24

# ๐Ÿฅจ  ์ฒดํฌ๋ฐ•์Šค ๋ฅผ  ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด

SwiftUI ์—์„œ ์ œ๊ณตํ•˜๋Š” Toggle ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ๋„ SF Symbol์˜ ์ด๋ฏธ์ง€๋ฅผ ํ™œ์šฉํ•ด ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค ์ „ํ™˜ํ•ด์ฃผ๋ฉด์„œ ์ฒดํฌ๋ฐ•์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. ์ฝ”๋“œ ์˜ˆ์‹œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

import SwiftUI

struct MyTag {
    let label : String
    let value : Int
}

struct ToggleStylePracticeView: View {
    @State var selectedTopics : [String] = ["tag1", "tag3"]
    
    let myTags : [MyTag] =  [
        MyTag(label: "tag1", value: 1),
        MyTag(label: "tag2", value: 2),
        MyTag(label: "tag3", value: 3),
        MyTag(label: "tag4", value: 4),
        MyTag(label: "tag5", value: 5),
    ]
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0){
            ForEach(myTags.map{$0.label}, id : \.self){el in
                VStack(spacing: 0){
                    HStack{
                        Button{
                            if(selectedTopics.contains(el)){
                                selectedTopics = selectedTopics.filter{topic in topic != el}
                            }else{
                                selectedTopics.append(el)
                            }
                        }label : {
                            HStack(alignment: .center){
                                
                                selectedTopics.contains(el)
                                ?  Image(systemName:  "checkmark.circle.fill")
                                    .imageScale(.large)
                                    .foregroundColor(.green)
                                :  Image(systemName:  "circle")
                                    .imageScale(.large)
                                    .foregroundColor(.green)
                                
                                
                                Text(el)
                                    .foregroundColor(.black)
                                Spacer()
                            }
                            .padding(5)
                        }
                        
                        Spacer()
                    }
                    
                    Divider()
                        .padding(.bottom,5)
                    
                }
            }
        }
    }
}

 

ํ•˜์ง€๋งŒ ๋งค๋ฒˆ ์ฒดํฌ๋ฐ•์Šค๋ฅผ ์ด๋ ‡๊ฒŒ ๋งŒ๋“ ๋‹ค๋ฉด ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ธฐ๋„ ํ•˜๊ณ  ์ •ํ™•ํžˆ ์ € ์ฝ”๋“œ์—์„œ ๋ฌด์—‡์„ ํ•˜๊ณ ์‹ถ์€์ง€ ์˜๋„๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์˜ค๋ž˜๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ToggleStyle ํ”„๋กœํ† ์ฝœ์„ ์ƒ์†๋ฐ›์€ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ชจ๋“ˆํ™” ํ•˜์—ฌ ๋‹ค์–‘ํ•œ ๋ชจ์–‘์˜ ์ฒดํฌ๋ฐ•์Šค, ํ† ๊ธ€ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด์ฃผ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

# ๐Ÿฅจ SwiftUI์—์„œ ์ œ๊ณตํ•˜๋Š” Toggle View ์‚ฌ์šฉํ•˜๊ธฐ

์• ํ”Œ ๊ณต์‹๋ฌธ์„œ์—์„œ๋Š” Toggle View๋ฅผ ์ด๋ ‡๊ฒŒ ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋‹ค.

https://developer.apple.com/documentation/swiftui/toggle

 

Toggle | Apple Developer Documentation

A control that toggles between on and off states.

developer.apple.com

 

์•„๋ž˜์˜ ์˜ˆ์‹œ์ฝ”๋“œ ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ  label ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ์ธ์ž๋Š” ํ›„ํ–‰ํด๋กœ์ €๋กœ์„œ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

import SwiftUI

struct ToggleStylePracticeView: View {
    @State var isOn = false
    
    var body: some View {
        VStack {
            Toggle(isOn: self.$isOn) {
                Text("Toggle: \(String(self.isOn))")
            }
            .toggleStyle(.automatic) // .automatic, .button, .switch ์„ธ๊ฐ€์ง€ ์Šคํƒ€์ผ๋งŒ ์ง€์›ํ•œ๋‹ค
        }
    }
}

 

ํ•˜์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ๊ธฐ๋ณธ ํ† ๊ธ€ ๋ทฐ์— ๋Œ€ํ•ด์„œ๋Š” ์œ„ ์ฝ”๋“œ๋ธ”๋Ÿญ์—์„œ ์ฃผ์„์œผ๋กœ ๋งํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ์„ธ๊ฐ€์ง€ ํ† ๊ธ€ ์Šคํƒ€์ผ๋งŒ์„ ์ง€์›ํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ๋•Œ๋ฌธ์— Toggle View๋ฅผ ํ™œ์šฉํ•ด์„œ ์—ฌ๋Ÿฌ ๋ชจ์–‘์˜ ์ฒดํฌ๋ฐ•์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•ด์ฃผ๋Š”๊ฒŒ ๋ฐ”๋กœ ToggleStyle ํ”„๋กœํ† ์ฝœ์ด๋‹ค.

 

# ๐Ÿฅจ ToggleStyle ํ”„๋กœํ† ์ฝœ์„ ํ™œ์šฉํ•˜์—ฌ ์ปค์Šคํ…€ ํ† ๊ธ€ ๋ฒ„ํŠผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•

https://developer.apple.com/documentation/swiftui/togglestyle

 

ToggleStyle | Apple Developer Documentation

The appearance and behavior of a toggle.

developer.apple.com

 

1) ์šฐ์„  ํ† ๊ธ€์— ์ปค์Šคํ…€ ์Šคํƒ€์ผ์„ ์ ์šฉํ•ด์ฃผ๋ ค๋ฉด ToggleStyle ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•˜๋Š” ๊ตฌ์กฐ์ฒด๋ฅผ ์ •์˜ํ•ด์ฃผ๊ณ , makeBody(configuration:) -> some View ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค.

struct ChecklistToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        // Return a view that has checklist appearance and behavior.
    }
}

 

2) ์›ํ•˜๋Š” ๋ชจ์–‘์˜ ํ† ๊ธ€๋ฒ„ํŠผ view๋ฅผ ๋„ฃ์–ด์ค€๋‹ค

struct CheckboxToggleStyle: ToggleStyle {
    
    func makeBody(configuration: Configuration) -> some View {
        Button(action: {
            configuration.isOn.toggle()
        }, label: {
            VStack(alignment: .leading, spacing: 0){
                HStack {
                    Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
                        .imageScale(.large)
                    
                    configuration.label
                        .foregroundColor(.black)
                }
                .padding(5)
            }
        })
    }
    
}

 

3) ๊ทธ๋ฆฌ๊ณ  ์‹ค์งˆ์ ์œผ๋กœ ํ•ด๋‹น ์Šคํƒ€์ผ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ด๋ ‡๊ฒŒ toggleStyle์— ๋„˜๊ฒจ์ฃผ๋ฉด๋œ๋‹ค.

import SwiftUI

struct ToggleStylePracticeView: View {
    @State var isOn = false
    
    var body: some View {
        VStack {
            Toggle(isOn: self.$isOn) {
                Text("Toggle: \(String(self.isOn))")
            }
            .toggleStyle(CheckboxToggleStyle())
        }
    }
}

 

# ๐Ÿฅจ  ๋ณด๋‹ค ๋” ํ™•์žฅ ๊ฐ€๋Šฅํ•œ custom toggle style ๋งŒ๋“ค๊ธฐ

์•ฑ์—์„œ ์› ๋ชจ์–‘, ์‚ฌ๊ฐํ˜• ๋ชจ์–‘ ๋“ฑ์˜ ์ฒดํฌ๋ฐ•์Šค๊ฐ€ ์žˆ์„ ๋•Œ, ์šฐ๋ฆฌ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์—ด๊ฑฐํ˜• (enum)์„ ์‚ฌ์šฉํ•˜์—ฌ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŒ๋“ค์–ด์ค„ ์ˆ˜ ์žˆ๊ณ ,

struct CheckboxToggleStyle: ToggleStyle {
    let style: Style
    
    func makeBody(configuration: Configuration) -> some View {
        Button(action: {
            configuration.isOn.toggle()
        }, label: {
            VStack(alignment: .leading, spacing: 0){
                HStack {
                    Image(systemName: configuration.isOn ? "checkmark.\(style.sfSymbolName).fill" : style.sfSymbolName)
                        .imageScale(.large)
                    
                    configuration.label
                        .foregroundColor(.black)
                }
                .padding(5)
            }
        })
    }
    
    enum Style {
        case square, circle

        var sfSymbolName: String {
            switch self {
            case .square:
                return "square"
            case .circle:
                return "circle"
            }
        }
    }
    
}

 

์ค‘๋ณต ์„ ํƒ ๊ฐ€๋Šฅํ•œ ์ฒดํฌ ๋ฆฌ์ŠคํŠธ ๋“ฑ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ˜•ํƒœ์˜ ํ† ๊ธ€, ์ฒดํฌ๋ฐ•์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผํ•  ๋•Œ๋„ ์กฐ๊ธˆ์˜ ๋กœ์ง๋งŒ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค๋ฉด ์–ด๋ ต์ง€ ์•Š๊ฒŒ ๊ธฐ์กด์— ๋งŒ๋“ค์–ด๋‘” ์ปค์Šคํ…€ ์Šคํƒ€์ผ์„ ์žฌ์‚ฌ์šฉํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

import SwiftUI

struct MyTag {
    let label : String
    let value : Int
}


struct ToggleStylePracticeView: View {
    @State var selectedTopics : [String] = ["tag1", "tag3"]
    
    let myTags : [MyTag] =  [
        MyTag(label: "tag1", value: 1),
        MyTag(label: "tag2", value: 2),
        MyTag(label: "tag3", value: 3),
        MyTag(label: "tag4", value: 4),
        MyTag(label: "tag5", value: 5),
    ]
    
    func binding(for key: String) -> Binding<Bool> {
        return Binding(
            get: {
                return self.selectedTopics.contains(key)
            }
            , set: {
                if($0 == false){
                    self.selectedTopics = self.selectedTopics.filter{topic in topic != key}
                } else{
                    self.selectedTopics.append(key)
                }
            }
        )
    }
    
    var body: some View {
        VStack{
            ForEach(myTags.map{$0.label}, id : \.self){el in
                HStack{
                    Toggle(el, isOn: binding(for:el))
                        .toggleStyle(CheckboxToggleStyle(style: .circle))
                        .foregroundColor(.green)
                    Spacer()
                }
                
            }
            
        }
        
    }
}