Samuel
Samuel

Reputation: 81

How can I change Color appearance of Segmented Picker in SwiftUI?

Does anyone know how to achieve this? I have four colors of red, green, yellow, and blue and I want my selected segment to have a background/tint color that is indicative of the color name. This is my code:

let objectColors = Color.allCases

@State private var selectedColorIndex = 0

//declared inside body view

Picker("Colors", selection: $selectedColorIndex) {

        ForEach(0..<objectColors.count){ index in

            Text(self.objectColors[index].rawValue).tag(index)

        }

    }
    .pickerStyle(SegmentedPickerStyle())
    .padding(10)
    .onAppear {
        UISegmentedControl.appearance().selectedSegmentTintColor = UIColor.generateUIColor(colorIndex: selectedColorIndex)        
    }

This is the list I'm pulling from

enum Color: String, CaseIterable {
    case red, yellow, green, blue
}

I've tried using onChange or onReceive (and Combine's 'Just()' for subview) instead of onAppear but they crash on playgrounds and don't work on Xcode. I also saw a WWDC video on UIAction that I think will work great for updating view changes but I have no idea how to translate it. Does anyone one have any suggestions or help, please? Thanks

Upvotes: 2

Views: 5726

Answers (3)

George
George

Reputation: 30461

Here is the simple version of what you need. This answer uses SwiftUI-Introspect to "Introspect underlying UIKit components from SwiftUI".

In addition, instead of UISegmentedControl.appearance() which is app-wide for all UISegmentedControls, this only affects this specific one which can be a lot more useful.

Here is the working code:

struct ContentView: View {
    
    let objectColors = SegmentColor.allCases
    @State private var selectedColor = SegmentColor.red
    
    var body: some View {
        Picker("Colors", selection: $selectedColor) {
            ForEach(objectColors) { color in
                Text(color.rawValue).tag(color)
            }
        }
        .introspectSegmentedControl { segmentedControl in
            segmentedControl.selectedSegmentTintColor = selectedColor.color
        }
        .pickerStyle(SegmentedPickerStyle())
        .padding(10)
        .onChange(of: selectedColor) { _ in }
    }
}


enum SegmentColor: String, CaseIterable, Identifiable {
    case red, yellow, green, blue
    
    var id: String { rawValue }
    
    var color: UIColor {
        switch self {
        case .red:      return .red
        case .yellow:   return .yellow
        case .green:    return .green
        case .blue:     return .blue
        }
    }
}

Result:

Result

Upvotes: -1

swiftPunk
swiftPunk

Reputation: 1

I just updated my old Answer with new Version of it, now you can just use AdvancedSegmentedPicker in your View, and work with it like a normal SegmentedPicker with this deference that you are able to change and update all colors on SegmentedPicker, like:

backgroundColor

selectedSegmentTintColor

selectedSegmentForegroundColor

normalSegmentForegroundColor


you could use like:

AdvancedSegmentedPicker(items:selectedItem:)

Or:

AdvancedSegmentedPicker(items:selectedItem:backgroundColor:selectedSegmentTintColor:selectedSegmentForegroundColor:normalSegmentForegroundColor:)

Also Notice that this AdvancedSegmentedPicker is not hard coded for just this project, as long as you gave items and selectedItem to AdvancedSegmentedPicker, you can use it in any project, and you could do your color customization as well.


Version 2.0.0:

enter image description here

import SwiftUI

struct ContentView: View {
    
    var items: [ColorEnum] = ColorEnum.allCases
    @State private var selectedItem: ColorEnum = ColorEnum.blue
    
    @State private var backgroundColor: UIColor?
    @State private var selectedSegmentTintColor: UIColor?
    @State private var selectedSegmentForegroundColor: UIColor?
    @State private var normalSegmentForegroundColor: UIColor?
    
    var body: some View {
        
        VStack(spacing: 30.0) {
            
            AdvancedSegmentedPicker(items: items, selectedItem: $selectedItem, backgroundColor: backgroundColor, selectedSegmentTintColor: selectedSegmentTintColor,
                                    selectedSegmentForegroundColor: selectedSegmentForegroundColor, normalSegmentForegroundColor: normalSegmentForegroundColor)
            
            Text("You selected: " + selectedItem.rawValue)
                .bold()
                .shadow(radius: 10.0)
                .padding()
            
            Button("update backgroundColor") {
                
                if backgroundColor == UIColor.gray { backgroundColor = nil }
                else { backgroundColor = UIColor.gray }
                
            }
            
            Button("update selectedSegmentForegroundColor") {
                
                if selectedSegmentForegroundColor == UIColor.white { selectedSegmentForegroundColor = nil }
                else { selectedSegmentForegroundColor = UIColor.white }
                
            }
            
            Button("update normalSegmentForegroundColor") {
                
                if normalSegmentForegroundColor == UIColor.white { normalSegmentForegroundColor = nil }
                else { normalSegmentForegroundColor = UIColor.white }
                
            }
            
        }
        .font(Font.body.bold())
        .onAppear() { selectedSegmentTintColor = selectedItem.ColorValue }
        .onChange(of: selectedItem) { newValue in selectedSegmentTintColor = newValue.ColorValue }
        .padding()
        .background(Color.black.opacity(0.1).cornerRadius(10.0))
        .padding()
        .statusBar(hidden: true)
        
    }
    
}

struct AdvancedSegmentedPicker<T: CustomStringConvertible & Hashable>: View {
    
    var items: [T]
    @Binding var selectedItem: T
    
    var backgroundColor: UIColor? = nil
    var selectedSegmentTintColor: UIColor? = nil
    var selectedSegmentForegroundColor: UIColor? = nil
    var normalSegmentForegroundColor: UIColor? = nil
    
    @State private var renderView: Bool = Bool()
    
    var body: some View {
        
        return Group {
            
            if renderView { SegmentedPickerView(items: items, selectedItem: $selectedItem, backgroundColor: backgroundColor, selectedSegmentTintColor: selectedSegmentTintColor,
                                                selectedSegmentForegroundColor: selectedSegmentForegroundColor, normalSegmentForegroundColor: normalSegmentForegroundColor) }
            else { SegmentedPickerView(items: items, selectedItem: $selectedItem, backgroundColor: backgroundColor, selectedSegmentTintColor: selectedSegmentTintColor,
                                       selectedSegmentForegroundColor: selectedSegmentForegroundColor, normalSegmentForegroundColor: normalSegmentForegroundColor) }
            
            
        }
        .onChange(of: backgroundColor) { _ in renderView.toggle() }
        .onChange(of: selectedSegmentTintColor) { _ in renderView.toggle() }
        .onChange(of: selectedSegmentForegroundColor) { _ in renderView.toggle() }
        .onChange(of: normalSegmentForegroundColor) { _ in renderView.toggle() }
        
    }
    
}

private struct SegmentedPickerView<T: CustomStringConvertible & Hashable>: View {
    
    var items: [T]
    @Binding var selectedItem: T
    
    init(items: [T], selectedItem: Binding<T>, backgroundColor: UIColor? = nil,
         selectedSegmentTintColor: UIColor? = nil, selectedSegmentForegroundColor: UIColor? = nil, normalSegmentForegroundColor: UIColor? = nil) {
        
        self.items = items
        self._selectedItem = selectedItem
        
        UISegmentedControl.appearance().backgroundColor = backgroundColor
        UISegmentedControl.appearance().selectedSegmentTintColor = selectedSegmentTintColor
        
        if let unwrappedSelectedSegmentForegroundColor: UIColor = selectedSegmentForegroundColor {
            UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: unwrappedSelectedSegmentForegroundColor], for: .selected)
        }
        else {
            UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.label], for: .selected)
        }
        
        if let unwrappedNormalSegmentForegroundColor: UIColor = normalSegmentForegroundColor {
            UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: unwrappedNormalSegmentForegroundColor], for: .normal)
        }
        else {
            UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.label], for: .normal)
        }
        
    }
    
    var body: some View {
        
        Picker("", selection: $selectedItem) {
            
            ForEach(items, id: \.self) { item in
                
                Text(String(describing: item))
                
            }
            
        }
        .pickerStyle(SegmentedPickerStyle())
        
    }
    
}

enum ColorEnum: String, CaseIterable, CustomStringConvertible { case red, yellow, green, blue
    
    var ColorValue: UIColor {
        
        get {
            
            switch self {
            case .red: return UIColor.red
            case .yellow: return UIColor.yellow
            case .green: return UIColor.green
            case .blue: return UIColor.blue
            }
            
        }
        
    }
    
    var description: String {
        
        get { return self.rawValue }
        
    }
    
}

Upvotes: 0

swiftPunk
swiftPunk

Reputation: 1

Here: Since you are using enum then selected type should be enum type as well, also you had wrong naming for your custom enum.


enter image description here

Version 1.0.0:

struct ContentView: View {
    
    @State private var selectedColor: ColorEnum = ColorEnum.blue
    
    @State private var renderView: Bool = Bool()

    var body: some View {
        
        Group {
            
            if renderView { SegmentedPickerView(selectedColor: $selectedColor) }
            else { SegmentedPickerView(selectedColor: $selectedColor) }
            
        }
        .onChange(of: selectedColor) { _ in renderView.toggle() }
        
    }
    
}

struct SegmentedPickerView: View {
    
    @Binding var selectedColor: ColorEnum

    init(selectedColor: Binding<ColorEnum>) {
        
        self._selectedColor = selectedColor
        
        UISegmentedControl.appearance().selectedSegmentTintColor = selectedColor.wrappedValue.ColorValue
        
    }

    var body: some View {
        
        VStack {
            
            Picker("", selection: $selectedColor) {
                
                ForEach(ColorEnum.allCases, id: \.self) { item in
                    
                    Text(item.rawValue)
                    
                }
                
            }
            .pickerStyle(SegmentedPickerStyle())

            Text("You selected: " + selectedColor.rawValue)
                .bold()
                .shadow(radius: 10.0)
                .padding()

        }
        .padding()
        .background(Color.black.opacity(0.1).cornerRadius(10.0))
        .foregroundColor(Color(selectedColor.ColorValue))
        .padding()
        
    }
}

enum ColorEnum: String, CaseIterable {
    
    case red, yellow, green, blue
    
    var ColorValue: UIColor {
        
        get {
            
            switch self {
            case .red: return UIColor.red
            case .yellow: return UIColor.yellow
            case .green: return UIColor.green
            case .blue: return UIColor.blue
            }
            
        }
        
    }
}

Upvotes: 2

Related Questions