Search code examples
swiftuiswiftui-navigationlinkviewmodifier

How to style a view when used as a NavigationLink


struct Item: Identifiable {
    let id = UUID()
    let title: String
}

struct ContentView: View {
    
    let items = [Item(title: "One"), Item(title: "Two"), Item(title: "Three")]
    
    var body: some View {
        NavigationStack {
            ScrollView (showsIndicators: false) {
                ForEach(items) { item in
                    NavigationLink {
                        ItemDetailView(item: item)
                    } label: {
                        ItemView(item: item)
                    }
                    //how to disable the default NavigationLink pressed state styling?
                }
            }
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

struct ItemView: View {
    let item: Item
    @State var isPressed = false //how to change this?
    
    var body: some View {
        HStack {

            Text(item.title)
                .foregroundStyle(.purple)
                .multilineTextAlignment(.leading)
            
            Spacer()
        }
        .frame(maxWidth: .infinity)
        .padding()
        .background(isPressed ? .blue : .green)
        .clipShape(RoundedRectangle(cornerRadius: 10))
        .border(.red, width: isPressed ? 2 : 0)
        .padding(3)
        
    }
}

struct ItemDetailView: View {
    let item: Item
    var body: some View {
        Text(item.title)
    }
}

enter image description here

I don't want the default grayed-out pressed state of the NavigationLink. When the view is pressed, I want the background color to change to blue and the red border to be displayed. The scrollview should still work - once the user starts scrolling the view should go back to its normal, unpressed state (as it normally does).


Solution

  • You can apply a ButtonStyle to a NavigationLink. This lets you apply a different style when the button is pressed.

    So, all you need to do is move the styling from ItemView to a custom ButtonStyle. If you want the red border to have rounded corners (like the button background) then you need to apply it as an overlay.

    struct MyNavViewButton: ButtonStyle {
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .frame(maxWidth: .infinity)
                .padding()
                .foregroundStyle(.purple)
                .background(configuration.isPressed ? .blue : .green)
                .clipShape(RoundedRectangle(cornerRadius: 10))
                .overlay {
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(.red, lineWidth: configuration.isPressed ? 2 : 0)
                }
                .padding(3)
        }
    }
    
    struct ItemView: View {
        let item: Item
    
        var body: some View {
            HStack {
                Text(item.title)
                    .multilineTextAlignment(.leading)
                Spacer()
            }
        }
    }
    
    // ContentView
    
    ForEach(items) { item in
        NavigationLink {
            ItemDetailView(item: item)
        } label: {
            ItemView(item: item)
        }
        .buttonStyle(MyNavViewButton())
    }
    

    Animation