0:00
/
0:00

💡 SwiftUI FYI #6 - TipKit

Quick TipKit Example

TipKit is a fantastic new framework that simplifies the process of teaching users about new features in your app, or guiding them to hidden gems.

In this post, I’ll show you the basics and follow with more advanced options in future posts.

You need to follow these steps:

  1. Create a new structure that conforms to Tip. This defines the content of your tip.

struct OnboardingTip: Tip {
    var title: Text {
        Text("New Note")
    }
    
    var message: Text? {
        Text("Tap here to create a new note")
    }
    
    var image: Image? {
        Image(systemName: "info.circle.fill")
    }
}
  1. Load all tips using Tips.configure(). This must happen before you’re able to show any tips.

        .task {
            do {          
                try Tips.configure()
            } catch {
                print("TipKit error: \(error.localizedDescription)")
            }
        }
  1. Initialize an instance of your tip.

  let addNoteTip = OnboardingTip()
  1. Display your tip. In our example we use popoverTip, which attaches our tip to a specific view.

Button {
        addNoteTip.invalidate(reason: .actionPerformed)
} label: {
       Image(systemName: "note.text.badge.plus")
          .font(.system(size: 80))
}
.popoverTip(addNoteTip, arrowEdge: .bottom)

Not that we call invalidate on our tip, which informs the system to dismiss the tip, and in this case for the reason .actionPerformed (i.e. the user tapped the target button)

🚨 Find out more about TipKit in Apple’s Highlighting App features with TipKit documentation.

🚨 Full code on Github

🤝 Find more about me on saidmarouf.com. I’m also on XThreads, and LinkedIn

Thanks for reading Your Weekly SwiftUI FYI 💡! Subscribe for free to receive new posts and support my work.


import SwiftUI

struct ContentView: View {
    
    @State var completed: Double = 0.0
    
    private var progress: Double {
        return completed / 1.0
    }
    
    var body: some View {
        VStack {
            
            CircleProgressView(progress: progress, lineWidth: 16)
                .frame(width: 200)
                .padding()
            
            CircleProgressView(progress: progress, lineWidth: 12)
                .frame(width: 100)
                .padding()
            
            CircleProgressView(progress: progress, lineWidth: 8)
                .frame(width: 40)
                .padding()
            
            CircleProgressView(progress: progress, lineWidth: 3)
                .frame(width: 40)
                .padding()
            
            Spacer()
            
            Button {
                completed += 0.1
            } label: {
                Text("Update Progress")
            }
            .buttonStyle(.borderedProminent)
        }
        .padding(.vertical, 40)
    }
}

struct CircleProgressView: View {
    
    var progress: Double
    var lineWidth: CGFloat
    
    var body: some View {
        
        ZStack {
            Circle().stroke(
                .secondary.opacity(0.4),
                lineWidth: lineWidth
            )

            Circle()
                .trim(from: 0, to: min(progress, 1.0))
                .stroke(
                    .green.gradient,
                    style: StrokeStyle(
                        lineWidth: lineWidth,
                        lineCap: .round
                    )
                )
            .rotationEffect(.degrees(-90))
            .animation(.easeOut(duration: 0.25), value: progress)
        }
    }
}

#Preview {
    ContentView()
}

Discussion about this video

User's avatar