
[ad_1]
Deep links and navigation just got a lot easier
Greetings! That’s exactly what I said seeing the new navigation stack being announced at WWDC this year, and I couldn’t be happier about it. The basis of my happiness was that I knew exactly what I was going to do with it!
Right there in my mind, I had an idea: Deeplink navigation at last! And since that day I didn’t have time to do it but the time has come and this will be my first article on Medium and will start the series on all the new SwiftUI features in iOS 16. So let’s get the party started!
For those not familiar with the topic, I’ll do a quick test of how navigation and deep-linking looked in previous versions of SwiftUI. So before we as developers were left with the use of NavigationView
And NavigationLink
Which would give us the ability for users to navigate through the app and it would look something like this:
struct Feature: Identifiable, Hashable, Decodable {
var id: String = UUID().uuidString
let title: String
let description: String
let type: FeatureType
}enum FeatureType: String, Decodable {
case charts
case gauge
}final class ContentViewModel: ObservableObject {@Published var selectedFeature: Feature?
@Published var showFeatureScreen: Bool = false@ViewBuilder func showFeature() -> some View {
struct ContentView: View {
if let feature = selectedFeature {
switch feature.type {
case .charts:
ChartsScreen(feature: feature)
case .gauge:
GaugeScreen(feature: feature)
}
}
}
}
@StateObject var viewModel = ContentViewModel()var body: some View {
NavigationView {
ZStack {
NavigationLink(isActive: $viewModel.showFeatureScreen) {
viewModel.showFeature()
} label: {}ScrollView {
LazyVStack(spacing: 8) {
ForEach(viewModel.features) { feature in
FeatureCell(feature: feature) {
viewModel.selectedFeature = feature
viewModel.showFeatureScreen = true
}
}
}
}
.navigationTitle("iOS16 Features 🚀")
.onOpenURL { url in
print("🚀 \(url)")guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return }
let query = components.queryItems ?? []
let host = components.host
let scheme = components.schemeif scheme == "showdownRouting" && host == "feature" {
var jsonQuery = query.map { "\"\($0.name)\":\"\($0.value ?? "")\"" }.joined(separator: ",")
jsonQuery = "{\(jsonQuery)}"guard let jsonData = jsonQuery.data(using: .utf8) else {
return
}do {
let feature = try JSONDecoder.shared.decode(Feature.self, from: jsonData)
viewModel.selectedFeature = feature
viewModel.showFeatureScreen = true
} catch {
print(error)
}
}
}
}
}
}
}

This way you will handle simple deep links the old way and as you can see the problem with this approach is that there can always be only one screen opened at a time which is a hassle in the long run. And then when you will open some navigation in the tree you will face the problem of lack of pop-to-route method. And you have to invalidate all bindings associated with navigation tree.
OK, this is problematic but what can we do with it? NavigationStack
,
New NavigationStack
And NavigationPath
UIKit will be very familiar to developers and they will love:
enum FeatureType: String, Decodable {
case charts
case gauge
}struct Feature: Identifiable, Hashable, Decodable {
var id: String = UUID().uuidString
let title: String
let description: String
let type: FeatureType
}
final class ContentViewModel: ObservableObject {
@Published var navigationPath = NavigationPath()
var features: [Feature] = [
Feature(title: "Charts 📊",
description: "Use a chart to build expressive and dynamic data visualizations inside a SwiftUI view.",
type: .charts),
Feature(title: "Gauge 🔜",
description: "SwiftUI introduces a new view called Gauge for displaying progress. In the most basic form, a gauge has a default range from 0 to 1.",
type: .gauge)
]
func showFeature(_ feature: Feature) {
navigationPath.append(feature)
}
}
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
NavigationStack(path: $viewModel.navigationPath) {
ScrollView {
LazyVStack(spacing: 8) {
ForEach(viewModel.features) { feature in
FeatureCell(feature: feature) {
viewModel.showFeature(feature)
}
}
.navigationDestination(for: Feature.self) { feature in
switch feature.type {
case .charts:
ChartsScreen(feature: feature)
case .gauge:
GaugeScreen(feature: feature)
}
}
.navigationTitle("iOS16 Features 🚀")
}
.onOpenURL { url in
print("🚀 \(url)")
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return }
let query = components.queryItems ?? []
let host = components.host
let scheme = components.scheme
if scheme == "showdownRouting" && host == "feature" {
var jsonQuery = query.map { "\"\($0.name)\":\"\($0.value ?? "")\"" }.joined(separator: ",")
jsonQuery = "{\(jsonQuery)}"
guard let jsonData = jsonQuery.data(using: .utf8) else {
return
}
do {
let feature = try JSONDecoder.shared.decode(Feature.self, from: jsonData)
viewModel.showFeature(feature)
} catch {
print(error)
}
}
}
}
}
}
}

As you can see we have NavigatonPath
object in ViewModel
And we can forward all kinds of data on it, for example, a feature, and then react to it, in which case, I just check what kind of simplicity it is. This approach is great for SwiftUI, focusing on modeling data and state.
This gives us the ability to add more to the stack and easily pass to the root of the stack like this navigationPath
Below the views we push to the NavigationPath:
.navigationDestination(for: Feature.self) { feature in
switch feature.type {
case .charts:
ChartsScreen(feature: feature, navigationPath: $viewModel.navigationPath)
case .gauge:
GaugeScreen(feature: feature)
}
}// Charts Screen implementation:
struct ChartsScreen: View {
var feature: Feature
@Binding var navigationPath: NavigationPath
@StateObject var viewModel = ChartsViewModel()var body: some View {
VStack {
Text(feature.description)
.font(.footnote)
.padding()
.multilineTextAlignment(.center)
.onTapGesture {
navigationPath.removeLast(navigationPath.count)
}Spacer()
HStack {
Button {
withAnimation {
viewModel.team = viewModel.team.sorted { $0.seniority < $1.seniority }
}
} label: {
Text("Sort")
.foregroundColor(.white)
.padding()
.background(.green)
.cornerRadius(10)
}Button {
withAnimation {
viewModel.team = viewModel.team.shuffled()
}
} label: {
Text("Shuffle")
.foregroundColor(.white)
.padding()
.background(.red)
.cornerRadius(10)
}
}ScrollView {
barChart
lineChart
areaChart
}
}
.navigationTitle(feature.title)
.navigationBarTitleDisplayMode(.inline)
}
And here’s the effect:


As you can see the implementation is very simple and it makes me very happy that we will be able to use it in the near future while the new iPhones are just coming!
All source code is public and will be updated with the next episodes of the series regarding new features.
If you are curious about what will happen next etc. Just follow it on GitHub. Thanks for reading.
[ad_2]
Source link
#SwiftUI #NavigationStack #Deep #Link #Whats #Lukasz #Stachnik #August