SwiftUI: настройка отдельных цветов для .toolbar ToolbarItem?

#swiftui

#swiftui

Вопрос:

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

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

 import SwiftUI

struct ContentView: View {
    
//    init() {
//        UIBarButtonItem.appearance(whenContainedInInstancesOf: [UIToolbar.self]).tintColor = .systemRed
//    }
    
    var body: some View {
        
        NavigationView {
            
            Text("Hello, world!")
                .toolbar {
                    
                    // To be colored RED
                    ToolbarItem(placement: .bottomBar) {
                        Button(action: {}, label: {Label("Icon One", systemImage: "stop.fill")})
                    }
                    
                    // To be colored BLACK
                    ToolbarItem(placement: .bottomBar) {
                        Button(action: {}, label: {Label("Icon Two", systemImage: "play.fill")})
                    }
                    
                }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
  

Ответ №1:

Да, я боролся с тем же самым. Похоже, что Buttons используется системный оттенок цвета и его стиль в целом.

Вот мое мнение:

 content
    .toolbar {
        ToolbarItem(placement: .navigationBarLeading) {
            HStack {
                StyledButton(image: .system("arrow.left")) { ... }
                Spacer(minLength: 0)    // important to not fell button to default styling
            }
        }
    }
  

Идея состоит в том, чтобы отображать не только кнопки, но и другие элементы пользовательского интерфейса. Для меня это похоже на ошибку SwiftUI. (Или просто пытаюсь перехитрить вас.)

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

1. Странно, но работает как шарм !!, Как примечание, проблема возникла только на устройствах iphone 11, onTapGesture не работал. Вместо этого нам нужно использовать кнопку.

Ответ №2:

Самое простое решение, которое я нашел, — отказаться Button и использовать .onTapGesture вместо этого.

 struct ContentView: View {
    
    var body: some View {
        NavigationView {
            Text("Hello World!")
                .toolbar {
                    
                    // To be colored RED
                    ToolbarItem(placement: .bottomBar) {
                        Label("Icon One", systemImage: "stop.fill")
                            .foregroundColor(.red)
                            .onTapGesture {
                                // Respond to tap
                            }
                    }
                    
                    // To be colored BLACK
                    ToolbarItem(placement: .bottomBar) {
                        Label("Icon One", systemImage: "play.fill")
                            .foregroundColor(.black)
                            .onTapGesture {
                                // Respond to tap
                            }
                    }
                    
                }
        }
    }
}
  

Вывод:

введите описание изображения здесь

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

1. ToolbarItem, похоже, игнорирует .onTapGesture . Другими словами, я не думаю, что ваше решение работает. Цвет меняется правильно, но ваша новая «кнопка» не вызывает никаких действий. Кроме того, метка не тускнеет во время нажатия, как обычная кнопка.

Ответ №3:

Похоже, что все стандартные типы (кнопка, изображение, текст и т. Д.) Перехватываются ToolbarItem и преобразуются в соответствующее внутреннее представление. Но пользовательский вид (например, на основе формы)… нет. Итак, смотрите Ниже демонстрацию возможного подхода.

Демо подготовлено и протестировано с Xcode 12 / iOS 14.

ДЕМОНСТРАЦИЯ

 ToolbarItem(placement: .bottomBar) {
    ShapeButton(shape: Rectangle(), color: .red) {
        print(">> works")
    }
}
  

и простая пользовательская кнопка на основе формы

 struct ShapeButton<S:Shape>: View {
    var shape: S
    var color: Color
    var action: () -> ()

    @GestureState private var tapped = false
    var body: some View {
        shape
            .fill(color).opacity(tapped ? 0.4 : 1)
            .animation(.linear(duration: 0.15), value: tapped)
            .frame(width: 18, height: 18)
            .gesture(DragGesture(minimumDistance: 0)
                .updating($tapped) { value, state, _ in
                    state = true
                }
                .onEnded { _ in
                    action()
                })
    }
}
  

Ответ №4:

Если вы зайдете в UIKit, у меня это работает.

 struct ButtonRepresentation: UIViewRepresentable {
    let sfSymbolName: String
    let titleColor: UIColor
    let action: () -> ()
    
    func makeUIView(context: Context) -> UIButton {
        let b = UIButton()
        let largeConfig = UIImage.SymbolConfiguration(scale: .large)
        let image = UIImage(systemName: sfSymbolName, withConfiguration: largeConfig)
        b.setImage(image, for: .normal)
        b.tintColor = titleColor
        b.addTarget(context.coordinator, action: #selector(context.coordinator.didTapButton(_:)), for: .touchUpInside)
        return b
    }
    
    func updateUIView(_ uiView: UIButton, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(action: action)
    }
    
    typealias UIViewType = UIButton
    
    class Coordinator {
        let action: () -> ()
        
        @objc
        func didTapButton(_ sender: UIButton) {
            self.action()
        }
        
        init(action: @escaping () -> ()) {
            self.action = action
        }
    }
}
  

Затем вы можете добавить отдельные представления кнопок с разными цветами.

 .toolbar {
    ToolbarItem(placement: .cancellationAction) {
        ButtonRepresentation(sfSymbolName: closeIcon, titleColor: closeIconColor) { 
        presentationMode.wrappedValue.dismiss()
    }
}
  

Ответ №5:

К сожалению, этот трюк работает только на iOS.

Но с другой стороны, это также работает и для Menu:

 Menu {
    Button("A title") {}
} label : {
    HStack {
            Label("Star"), systemImage: "star")
                .labelStyle(.iconOnly)
                .foregroundColor(.red)
                Spacer(minLength: 0)
    }
}