swifty
swifty

Reputation: 1151

How to change selected segment color in SwiftUI Segmented Picker

I want to set the selected segment color in a SwiftUI segmented picker and change the text color to white.

I have tried both using the modifiers for the picker view and modifying the tint color from the appearance proxy. None of them seem to work, unfortunately.


import SwiftUI

struct PickerView: View {

    @State var pickerSelection = 0

    init() {
        UISegmentedControl.appearance().tintColor = UIColor.blue
    }

    var body: some View {
        Picker(selection: $pickerSelection, label: Text("")) {
            Text("Active").tag(0).foregroundColor(Color.white)
            Text("Completed").tag(1)
        }.pickerStyle(SegmentedPickerStyle()).foregroundColor(Color.orange)
    }
}

Is there any way to do this in SwiftUI, or should I just use the UISegmentedControl by using UIViewControllerRepresentable?

Upvotes: 72

Views: 42663

Answers (6)

griv
griv

Reputation: 2245

As pointed out above by @Sangsom, you can use the SwiftUI Introspect package to achieve this.

Since the updated syntax on how to do this (using v1.3.0 of Introspect) is not in any of the current answers, I'll provide it below:

  1. Use SPM to add the Introspect package to your project file.

https://github.com/siteline/swiftui-introspect

Introspect SPM

  1. Import it at the top of your file
import SwiftUI
import SwiftUIIntrospect // Here
  1. Create your custom segmented picker and adjust it how you see fit.
@State private var selectedOption: Int = 0 // Tab Options

// Segmented Picker
Picker(selection: $selectedOption, label: Text("")) {
    Text("Option 1").tag(0)
    Text("Option 2").tag(1)
}
.introspect(.picker(style: .segmented), on: .iOS(.v16, .v17, .v18)) { segmentedControl in
    segmentedControl.backgroundColor = .clear
    segmentedControl.selectedSegmentTintColor = UIColor(.black)
    segmentedControl.setTitleTextAttributes([
        .foregroundColor: UIColor.white,
        .font: UIFont.systemFont(ofSize: 17)
    ], for: .selected)
    segmentedControl.setTitleTextAttributes([
        .foregroundColor: UIColor.black,
        .font: UIFont.systemFont(ofSize: 17)
    ], for: .normal)
}
.pickerStyle(SegmentedPickerStyle())
.cornerRadius(8)
.padding(.horizontal, 50)
.frame(height: 35)
.onChange(of: selectedOption) { newValue in
    switch newValue {
    case 0:
        print("Switched to Option 1 Tab")
    case 1:
        print("Switched to Option 2 Tab")
    default:
        break
    }
}

Result:

Picker Example

Upvotes: 0

Maria N.
Maria N.

Reputation: 660

Here is a manual implementation of segmented picker functionality:

@ViewBuilder func viewSegmentedButtons(arr: [String], selIndex: Int, baseColor: Color, closure:@escaping (_ selectedIndex: Int) -> Void) -> some View {
    
    let columns = Array(repeating: GridItem(spacing: 1), count:arr.count)
    LazyVGrid(columns: columns, spacing: 1.0) {
        ForEach(Array(arr.enumerated()), id: \.element) { index, translation in
            Button {
                closure(index)
            } label: {
                ZStack {
                    Rectangle()
                        .foregroundColor(index == selIndex ? baseColor : Color(.systemBackground))
                        .cornerRadius(radius: index==0 ? cRadius : 0, corners: [.topLeft, .bottomLeft])
                        .cornerRadius(radius: index==arr.count-1 ? cRadius : 0, corners: [.topRight, .bottomRight])
                    Text(translation)
                        .padding(.vertical, 10)
                        .font(.footnote)
                        .foregroundColor(index != selIndex ? baseColor : Color(.systemBackground) )
                }
            }
        }
    }
    .foregroundColor(baseColor)
    .overlay(
        RoundedRectangle(cornerRadius: cRadius)
            .stroke(baseColor, lineWidth: 2)
    )
    .font(.callout)
    .background(baseColor)
    .cornerRadius(cRadius)
    .padding(.bottom, 10)
}

Usage example:

@State private var levelIndex: Int = 0
var body: some View {
    VStack {
        Text("Level:")
        
        viewSegmentedButtons(arr: ["Easy", "Meduim", "Hard"], selIndex: levelIndex, baseColor: .orange) { selectedIndex in
            withAnimation {
                levelIndex = selectedIndex
            }
        }
    }
}

Result: enter image description here

Upvotes: 3

Sangsom
Sangsom

Reputation: 391

Recently I had a similar issue that I needed a custom background and foreground colors for SwiftUI SegmentedControl I've found this post that also was helpful, but I also wanted to share other solution that I've found and I'm using now. There is a very nice package called SwiftUI Introspect that allows to, well...

Introspect underlying UIKit components from SwiftUI

With this package I created the following SegmentedControl enter image description here With this code:

VStack {
    Picker("Activity view", selection: $selectedActivity) {
        ForEach(ActivityView.allCases) { activity in
            Text(activity.description)
        }
    }
    .introspectSegmentedControl { segmentedControl in
        segmentedControl.backgroundColor = .clear
        segmentedControl.tintColor = TCHColors.SegmentedControl.backgroundSelected
        segmentedControl.selectedSegmentTintColor = TCHColors.SegmentedControl.backgroundSelected
        segmentedControl.setTitleTextAttributes([
            NSAttributedString.Key.foregroundColor: TCHColors.SegmentedControl.selectedText
        ], for: .selected)
        segmentedControl.setTitleTextAttributes([
            NSAttributedString.Key.foregroundColor: TCHColors.SegmentedControl.normalText
        ], for: .normal)
    }
    .pickerStyle(.segmented)
}
.padding(.horizontal, 16)

And you can use this package to access many other underlying components from SwiftUI.

Upvotes: 9

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 120022

Native ( but limited )

SwiftUI is not currently supporting native SegmentedPicker styling (see the bottom of the answer for the working workaround). But there is a limited way to apply a color to the segmented picker using .colorMultiply() modifier:

enter image description here


Full control using UIAppearance

selectedSegmentTintColor is available since beta 3 for changing the color of the selected segment.

For changing the textColor, you should use setTitleTextAttributes for .selected state and .normal state (unselected).

So it will be like:

init() {
    UISegmentedControl.appearance().selectedSegmentTintColor = .blue
    UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
    UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.blue], for: .normal)
}

Segmented Controll

Also as mike mentioned, you can set the background color too like:

UISegmentedControl.appearance().backgroundColor = .yellow

Background demo

Also, don't forget you can set SwiftUI colors too! For example:

UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(Color.accentColor)

Upvotes: 141

nikost
nikost

Reputation: 858

This answered helped me, but there is something I would like to add. The color I used was an argument to the view, so placing these methods in an init did not allow me to access the color.

Alternatively, you can use an onAppear method directly on the Segmented Picker, like so.

import SwiftUI

struct PickerView: View {
    var color: UIColor   
    @State var pickerSelection = 0

    var body: some View {
        Picker(selection: $pickerSelection, label: Text("")) {
            Text("Active").tag(0).foregroundColor(Color.white)
            Text("Completed").tag(1)
        }.pickerStyle(SegmentedPickerStyle()).foregroundColor(Color.orange)
        .onAppear {
            UISegmentedControl.appearance().tintColor = color
        }
    }
}

Upvotes: 1

indrit saveta
indrit saveta

Reputation: 147

Here is my solution that works in SwiftUI:

init(){

    UISegmentedControl.appearance().selectedSegmentTintColor = .green
    UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.black], for: .selected)

    }

Upvotes: 1

Related Questions