The app

Dad Jokes & Puns is a mobile app that showcases all my favorite dad jokes. When you open the app, you'll see a list of jokes. Tapping on a joke will take you to the joke detail screen. The app also supports widgets and deep linking from the widget to the joke detail screen.

dadjokes_1706017715.mp4

Behind the scenes, I utilized a custom URL scheme to handle deep linking. Here's all you need to support deep linking with a custom URL scheme.

The model

struct Joke: Identifiable, Hashable {
	var id: Int
	let conetnt: String
}

The views

enum Screen: Hashable {
  case detail(Joke)
}

struct JokeListView: View {

  @State private var presentedScreen: [Screen] = []

	var jokes: [Joke] = [...]

  var body: some View {
    NavigationStack(path: $presentedScreen) {
      List(jokes) { joke in
        Text(joke.content)
          .onTapGesture {
            presentedScreen = [.detail(joke)]
          }
      }
      .navigationDestination(for: Screen.self) { screen in
        switch screen {
        case .detail(let joke):
          JokeDetailView(joke: joke)
        }
      }
    }
		.onOpenURL { url in
      handleOpenURL(url)
    }
  }
}

struct JokeDetailView: View {

  var joke: Joke

  var body: some View {
    Text(joke.content)
      .font(.title)
      .multilineTextAlignment(.center)
      .padding()
  }
}

The custom URL scheme

CleanShot 2024-01-23 at 22.10.10@2x_1706019043.png

The deeplink URL

dadjokes://open?id=123

The widget

struct SimpleEntry: TimelineEntry {
  let date: Date
  let joke: Joke
}

struct DadJokesWidgetEntryView : View {

  var entry: Provider.Entry

  var jokeDeeplink: URL? {
    URL(string: "dadjokes://open?id=\\(entry.joke.id)")
  }

  var body: some View {
    VStack {
      Text(entry.joke.content)
    }
    .widgetURL(jokeDeeplink)
  }
}

The handleOpenURL(_:)

func handleOpenURL(_ url: URL) {
  if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
     components.scheme == "dadjokes",
     let jokeId = components.queryItems?.first(where: { $0.name == "id"} )?.value,
     let joke = jokes.first(where: { $0.id == Int(jokeId) }) {
    presentedScreen = [.detail(joke)]
  }
}

The sample app

https://github.com/dqhieu/DadJokesExample