Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Setting individual colors for .toolbar ToolbarItem?

Tags:

swiftui

Generally I like to stick with default color options for accessibility reasons, but I have a specific use case and I am unable to change an individual .toolbar ToolbarItem color.

I can override all ToolbarItem colors by using the commented out code at top of my snippet, but I want to color individual icons.

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()
    }
}
like image 488
SunriseHurts Avatar asked Sep 20 '20 13:09

SunriseHurts


3 Answers

Yep, I've been struggling with the same thing. It looks that Buttons are using system tint color and its style overall.

Here is my take:

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

The idea is to not display Button only, but together with other UI elements. Looks like a SwiftUI bug to me. (Or just trying to out-smart you.)

like image 118
pleshis Avatar answered Sep 27 '22 18:09

pleshis


The simplest solution I've found is to ditch Button and use .onTapGesture instead.

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
                            }
                    }
                    
                }
        }
    }
}

Output:

enter image description here

like image 42
ryanholden8 Avatar answered Sep 27 '22 20:09

ryanholden8


It looks like all standard types (button, image, text, etc) are intercepter by ToolbarItem and converted into appropriate internal representation. But custom view (eg. shape based)... is not. So see below a demo of possible approach.

Demo prepared & tested with Xcode 12 / iOS 14.

demo

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

and simple custom Shape-based button

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()
                })
    }
}

backup

like image 29
Asperi Avatar answered Sep 27 '22 18:09

Asperi