Dismiss Tip

Is it possible to disable the ability for the user to dismiss a tip? If not, is there a way to activate a tip again after it has been dismissed? Can the tip status be reset without deleting the datastore? I want to show a tip for password help after 3 failed login attempts but I never want it to be invalidated by the user.

Post not yet marked as solved Up vote post of emeadeosu Down vote post of emeadeosu
722 views

Replies

It's not possible to hide the Close button with one of the default tip styles. However, you can create your own custom tip style that does not display a close button. This should prevent the user from dismissing your tip.

Another approach would be to use the built-in style, but provide a dynamic ID for your tip. That way even if the user dismisses the tip once, the tip will keep reappearing since it is treated as a brand new tip every time. Here's a small example:

struct SampleDynamicTip: Tip {
    var contentID: String

    var id: String {
        return contentID // return a dynamic value instead of a static string
    }

    var title: Text {
        Text("Sample Tip Title")
    }

    var message: Text? {
        Text("This is a tip with content ID: \(contentID)")
    }
}

Then when you want to construct the tip, you would pass in a dynamic value like a UUID:

let sampleTip = SampleDynamicTip(contentID: UUID().uuidString)

Please share which of these approaches works better in your situation.

I made a custom tip style without the close button assuming that was going to be the recommendation. I didn't think to use a dynamic id. I will probably end up using both approaches. But I think the dynamic id is best suited to my initial use case for the password help. Thank you!

This is my code for the custom tip style. I tried to match the metrics of the TipView() as closely as possible.

public struct TipWithoutDismissViewStyle: TipViewStyle {
    var tipBackgroundColor: Color = .clear
    var imageTintColor: Color = .accentColor
    
    public func makeBody(configuration: Configuration) -> some View {
        HStack(alignment: .top, spacing: 6) {
            configuration.image?
                .font(.system(size: 45.5))
                .foregroundColor(imageTintColor)
            VStack(alignment: .leading) {
                configuration.title.font(.headline)
                configuration.message?.font(.subheadline).foregroundStyle(.secondary)
                VStack(alignment: .leading, spacing: 11) {
                    ForEach(configuration.actions.indices, id: \.self) { index in
                        Divider()
                        Button(action: configuration.actions[index].handler, label: {
                            if index == 0 {
                                configuration.actions[index].label().bold()
                            } else {
                                configuration.actions[index].label()
                            }
                        })
                    }
                }
            }
        }.padding(.leading, 9)
            .padding([.top, .bottom], 15)
            .background(tipBackgroundColor)
    }
    
    public func tipBackground(_ color: Color) -> TipWithoutDismissViewStyle {
        var copy = self
        copy.tipBackgroundColor = color
        return copy
    }
    
    public func tint(_ color: Color) -> TipWithoutDismissViewStyle {
        var copy = self
        copy.imageTintColor = color
        return copy
    }
}

Thanks for sharing your approach, @emeadeosu. I'm glad you got it to work. For anyone reading this that is curious about how to use a custom TipViewStyle, you can define it as shown above, and then attach it to your TipView like this:

TipView(myTip)
   .tipViewStyle(TipWithoutDismissViewStyle())