SwiftUI | Picker — изменить выбранный цвет строки

#ios #swift #swiftui

#iOS #swift #swiftui

Вопрос:

Я хочу изменить цвет выбранной строки.

Как вы можете видеть, по умолчанию он имеет этот светло-серый цвет.

Я понятия не имею, как это сделать, поскольку я вообще не нашел способа получить доступ к этой строке. Есть ли какой-нибудь способ сделать это?

Демонстрационный код:

 struct ContentView: View {
    
    var data = Array(0...20).map { "($0)" }
    @State private var selected = 0

    
    var body: some View {
        VStack {
            Picker("", selection: $selected) {
                ForEach(0 ..< data.count) {
                    Text(data[$0])
                }
            }
        }
    }
}
  

Ответ UIKit также приветствовался бы, поскольку я тоже не нашел способа.

Спасибо!

Комментарии:

1. Это UIPickerView — прочитайте особые соображения в developer.apple.com/documentation/uikit/uipickerview /…

2. К сожалению, его здесь больше нет. Только для iOS с 7 по 13.

3. Вот что я имел в виду — вы не можете изменить выбор по умолчанию в этом элементе управления — создайте пользовательский, если вам не нравится стандартный.

4. Ха-ха, все в порядке, я смог немного настроить его, а также нашел обходной путь для некоторой настройки цвета.

Ответ №1:

Использование пакета Introspect Swift

Это нетривиально сделать в SwiftUI. Однако я провел некоторое исследование.
Базовый класс, стоящий за SwiftUI, Picker восходит к UIKit , и это UIPickerView . Чтобы настроить его поведение, вам нужен доступ к UIPickerView классу, а документация несколько отсутствует.
И, глядя на другие сообщения на SO, кажется, что способ настройки средства выбора проходит весь путь до ObjC, например, с setValue(_ value: Any?, forKey key: String) помощью функции. Я даже не смог найти список этих ключей! Однако попытка настроить цвет текста не сработала… Я обнаружил, что могу получить доступ к вложенным представлениям и мог бы решить эту проблему с цветом фона.

Это просто сказать, что я думаю, что лучший способ, который я нашел, — это использовать этот пакет Swift под названием Introspect.

Эти парни (и девушки) проделали действительно потрясающую работу.

  • Сначала установите пакет Swift в свой проект.

  • Затем добавьте код для самоанализа UIPickerView. Это не входит в настройки по умолчанию, поэтому вам нужно создать пользовательское расширение, например:

 import Introspect

extension View {
    public func introspectUIPickerView(customize: @escaping (UIPickerView) -> ()) -> some View {
        return inject(UIKitIntrospectionView(
            selector: { introspectionView in
                guard let viewHost = Introspect.findViewHost(from: introspectionView) else {
                    return nil
                }
                return Introspect.previousSibling(containing: UIPickerView.self, from: viewHost)
            },
            customize: customize
        ))
    }
}
  
  • Теперь в вашем коде у вас есть доступ к UIPickerView базовому классу UIKit вашего SwiftUI Picker . Добавьте это после средства выбора:
 Picker("Flavor", selection: $selectedFlavor) {
    ForEach(Flavor.allCases) { flavor in
        Text(flavor.rawValue.capitalized)
    }       
}
.pickerStyle(WheelPickerStyle()
.introspectUIPickerView { picker in
    picker.subviews[1].backgroundColor = UIColor.clear
}
  

Я использовал пример средства выбора из документов Apple и добавил .introspectUIPickerView модификатор.
Результат: больше не выделяется серый цвет.

Не все будет работать, потому что UIPickerView действительно старый, и некоторые вещи нелегко настроить. Так что, если вы хотите настроить другие параметры, ваш пробег может отличаться 🙂

Здесь красным цветом с :

 .introspectUIPickerView { picker in
                picker.subviews[1].backgroundColor = UIColor.red
                   .withAlphaComponent(0.2)
            }
  

Пример с красным

Ответ №2:

Здесь был бы способ несколько настроить его. Обходной путь заключается в том, чтобы просто поместить под ним округленный прямоугольник с выбранным вами цветом.

Это не идеально, поскольку наложение непрозрачности по умолчанию серого цвета находится поверх этого цвета, и нужно определить ограничения, основанные на фрейме.

 struct ContentView: View {
    
    var data = Array(0...20).map { "($0)" }
    @State private var selected = 0

    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 5)
                .frame(width: UIScreen.main.bounds.size.width-18, height: 32)
                .foregroundColor(.green)
            
            Picker("", selection: $selected) {
                ForEach(0 ..< data.count) {
                    Text(data[$0])
                }
            }
        }
    }
}
  

Ответ №3:

Я реализовал чистую реализацию средства выбора SwiftUI

MyPickerView

 struct MyPickerView<T: Equatable>: View {
    
    let options: [T]
    @Binding var selection: T
    @SwiftUI.State private var drag: Double = 0
    let cellAngles = 20.0
    let presentation: (T) -> String
    
    init(_ options: [T], _ selection: Binding<T>, _ presentation: @escaping (T) -> String = {"($0)"}) {
        self.options = options
        self._selection = selection
        self.presentation = presentation
    }
    
    var body: some View {
        let selectedIndex = getSelectedIndex()
        HStack {
            ZStack {
                ForEach(0..<options.count, id: .self) { index in
                    let item: T = options[index]
                    MyPickerCell(title: presentation(item), angle: Double(index - selectedIndex) * cellAngles   drag)
                }
                Color.gray.opacity(0.1)
                    .frame(height: 1)
                    .offset(y: 15)
                Color.gray.opacity(0.1)
                    .frame(height: 1)
                    .offset(y: -15)
            }
            .frame(width: 100, height: 200)
            .gesture(
                DragGesture()
                    .onChanged { gesture in
                        drag = gesture.translation.height
                    }
                    .onEnded { _ in
                        var newIndex = selectedIndex - Int(round(drag / cellAngles))
                        if newIndex < 0 {
                            newIndex = 0
                        }
                        if newIndex >= options.count - 1 {
                            newIndex = options.count - 1
                        }
                        selection = options[newIndex]
                        drag = 0
                    }
            )
        }
    }
    
    func getSelectedIndex() -> Int {
        return options.firstIndex(of: selection) ?? 0
    }
    
}

private struct MyPickerCell: View {
    
    let title: String
    let angle: Double
    
    var body: some View {
        if abs(angle) > 90 {
            EmptyView()
        }
        else {
            let sinValue = sin(rad(angle))
            let cosValue = cos(rad(angle))
            Text(title)
                .foregroundColor(.primary)
                .scaleEffect(y: cosValue)
                .offset(y: sinValue * 85)
                .opacity(abs(angle) < 5 ? 1.0 : cosValue/2)
            
        }
    }
    
    func rad(_ number: Double) -> Double {
        return number * .pi / 180
    }
    
}
  

Пример использования:

 struct ContentView: View {
    
    @State var selectedNumber: Int = 3
    
    var body: some View {
        MyPickerView(Array(0...20), $selectedNumber)
    }
}
  

Ответ №4:

Более простое использование:

.accentColor (ваш цвет)

 Picker("", selection: $selected) {
    ForEach(0 ..< data.count) {
        Text(data[$0])
    }
}
.accentColor(.black)