如何在 SwiftUI 中更改分段选取器的颜色外观?

Posted

技术标签:

【中文标题】如何在 SwiftUI 中更改分段选取器的颜色外观?【英文标题】:How can I change Color appearance of Segmented Picker in SwiftUI? 【发布时间】:2021-04-14 17:50:02 【问题描述】:

有谁知道如何做到这一点?我有红色、绿色、黄色和蓝色四种颜色,我希望我选择的片段具有指示颜色名称的背景/色调颜色。 这是我的代码:

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)        
    

这是我要从中提取的列表

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

我尝试过使用 onChange 或 onReceive(以及 Combine 的 'Just()' 用于子视图)而不是 onAppear,但它们在 Playground 上崩溃并且在 Xcode 上不起作用。 我还看到了一个关于 UIAction 的 WWDC 视频,我认为它非常适合更新视图更改,但我不知道如何翻译它。请问有人有什么建议或帮助吗?谢谢

【问题讨论】:

【参考方案1】:

这里:既然您使用的是enum,那么选择的类型也应该是enum 类型,而且您对自定义enum 的命名也有误。


版本 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
            
            
        
        
    


【讨论】:

【参考方案2】:

我刚刚用它的新版本更新了我的旧答案,现在你可以在你的视图中使用 AdvancedSegmentedPicker,并像普通的 SegmentedPicker 一样使用它您可以更改和更新 SegmentedPicker 上的所有颜色,例如:

背景颜色

selectedSegmentTintColor

selectedSegmentForegroundColor

normalSegmentForegroundColor


你可以这样使用:

AdvancedSegmentedPicker(items:selectedItem:)

或者:

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

还请注意,只要您将 itemsselectedItem 提供给 AdvancedSegmentedPicker,您可以在任何项目中使用它,也可以进行颜色自定义。


2.0.0 版:

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 
        
    
    


【讨论】:

此解决方案不适用于同一页面中的两个** Picker**【参考方案3】:

这是您需要的简单版本。这个答案使用SwiftUI-Introspect 来“从 SwiftUI 内省底层 UIKit 组件”。

此外,与适用于所有UISegmentedControls 的应用程序范围内的UISegmentedControl.appearance() 不同,这只影响可能更有用的特定应用程序。

这是工作代码:

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
        
    

结果:

【讨论】:

我不认为这是使用 pod 而不是真正的方式的最佳方式,我也投反对票。

以上是关于如何在 SwiftUI 中更改分段选取器的颜色外观?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 iOS 13 的 UISegmentedControl 中更改段的颜色?

SwiftUI - 动态分段选取器过滤器

如何在 SwiftUI 中创建分段按钮?

在 SwiftUI 中更改分段控制器属性

如何自定义 SwiftUI SegmentedPicker

如何在 SwiftUI 中更改表单的背景颜色?