SUINavigation has functions of getting and applying the URL which allows you to organize deep links without special costs. Modifier .navigationAction identical .navigation, but support navigate by append or replace from URL (URI). If you want custom navigate or use presentation type of navigation (alert, botomsheet, fullScreenCover, TabBar, etc) you can use part of .navigationAction as .navigateUrlParams. Modifiers .navigationAction as .navigateUrlParams have addition sets of params for customisation an URL representation.
You have two way to orginize deep links. The first involves automatic parsing with .navigationAction. The second manual change view state for navigation transition. Let's look at them.
You need organise your state object as inherited by NavigationParameterValue protocol:
struct ObjectDTO: Equatable {
var id: Int
var name: String
var date: Date
}
extension ObjectDTO: NavigationParameterValue {
static var defaultValue: ObjectDTO {
ObjectDTO(id: 0, name: "", date: dateFormatter.date(from: "2020-02-20")!)
}
init?(_ description: String) {
self.id = parse id from description
self.name = parse name from description
self.date = parse date from description
}
}And call .navigationAction with trigger item of this object:
struct TargetView: View {
@State
private var object: ObjectDTO? = nil
var body: some View {
ZStack {
contentView
}.navigationAction(item: $object, id: "objectPath" paramName: "objectParam") { objectValue in
ObjectView(object: objectValue)
}
}
}Next step you just handle mapped url at replace or appending method of the NavigationStorage API like that:
struct RootView: View {
@State
private var isTargetShowing = false
@OptionalEnvironmentObject
private var navigationStorage: NavigationStorage?
var body: some View {
ZStack{
contentView
}
.onOpenURL(perform: { url in
let targetURL = url.absoluteString.replacingOccurrences(of: "navigator://", with: "")
navigationStorage?.append(from: targetURL)
})
.navigationAction(isActive: $isTargetShowing, id: "target") {
TargetView()
}
}
}Manual parsing is no different from the example above. It does not require inheritance of ObjectDTO from NavigationParameterValue, although it does require calling an additional modifier where parsing and serialisation is performed.
You need call standart .navigation modifier for navigation transition and call .navigateUrlParams modifier for parsing and serialisation (if add optional save handler). For connection .navigation with .navigateUrlParams you need just use the same id and urlComponent params. Yuo can make it as const, for example objectPathId, so the example above will now look like this:
struct TargetView: View {
private let objectPathId = "objectPath"
@State
private var objectDTO: ObjectDTO? = nil
var body: some View {
ZStack {
contentView
}.navigation(item: $object, id: objectPathId) { objectValue in
ObjectView(object: objectValue)
}.navigateUrlParams(objectPathId) { path in
guard let id = path.getIntParam("object.id"),
let name = path.getStringParam("object.name"),
let rawDate = path.getStringParam("object.date"),
let date = ObjectDTO.dateFormatter.date(from: rawDate)
else {
return
}
objectDTO = ObjectDTO(id: id, name: name, date: date)
} save: { path in
guard let object = objectDTO else {
return
}
path.pushIntParam("object.id", value: object.id)
path.pushStringParam("object.name", value: object.name)
path.pushStringParam("object.date", value: ObjectDTO.dateFormatter.string(from: object.date))
}
}
}That's it. In a closures of navigateUrlParams you can use NavigationActionPathProtocol and NavigationActionSavePathProtocol path objects. Who can parse (getIntParam, getIntParam, getParam) and serialise (pushIntParam, pushStringParam, pushParam) your trigger object.
You can use .navigateUrlParams with TabBar, .fullScreenCover, and your custom approach of showing a screen. I Just show it in example, how it passible. More info find you in examples of the project.
var body: some View {
ContentView()
.fullScreenCover(item: $data) { value in
FirstView(string: value)
}
.navigateUrlParams("FirstView") { params in
if let value = params.popStringParam("firstModalParam") {
data = FirstModalData(value)
}
}
}enum MyTab: String, Hashable, NavigationParameterValue {
case first = "First Tab"
case second = "Second Tab"
// NavigationParameterValue implementation
init?(_ description: String) {
self.init(rawValue: description)
}
}
struct MyTabView: View {
@State
private var selectedTab: MyTab = .first
var body: some View {
NavigationStorageView{
TabView(selection: $selectedTab) {
FirstView()
.tag(MyTab.first)
SecondView()
.tag(MyTab.second)
}
.navigateUrlParams("MyTabView") { params in
if let tab: MyTab = params.popParam("tab") {
selectedTab = tab
}
}
}
}
}