Add `NavigationLinks` and `Bindings`

This gives us a more readable approach to set up navigation links
that mirrors other `SwiftUI` navigation paradigms such as `.sheet`.

We use the modifier to simplify navigation link setup for `TransactionHistoryView`

We also include some `Binding` extensions to help creating them.
This commit is contained in:
Daniel Haight 2021-11-10 01:25:40 +00:00
parent 0ce7d14c81
commit 9724d22235
4 changed files with 128 additions and 5 deletions

View File

@ -89,10 +89,12 @@
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */; };
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; };
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; };
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; };
F96B41E7273B501F0021B49A /* TransactionHistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41E3273B501F0021B49A /* TransactionHistoryStore.swift */; };
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41E5273B501F0021B49A /* TransactionDetailView.swift */; };
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41E6273B501F0021B49A /* TransactionHistoryView.swift */; };
F96B41EB273B50520021B49A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96B41EA273B50520021B49A /* Strings.swift */; };
F9C165B4274031F600592F76 /* Bindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9C165B3274031F600592F76 /* Bindings.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -200,10 +202,12 @@
66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingProgressIndicator.swift; sourceTree = "<group>"; };
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = "<group>"; };
66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = "<group>"; };
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = "<group>"; };
F96B41E3273B501F0021B49A /* TransactionHistoryStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionHistoryStore.swift; sourceTree = "<group>"; };
F96B41E5273B501F0021B49A /* TransactionDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionDetailView.swift; sourceTree = "<group>"; };
F96B41E6273B501F0021B49A /* TransactionHistoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionHistoryView.swift; sourceTree = "<group>"; };
F96B41EA273B50520021B49A /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
F9C165B3274031F600592F76 /* Bindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bindings.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -534,6 +538,8 @@
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
0DACFA8027208D940039EEA5 /* UInt+SuperscriptText.swift */,
F96B41EA273B50520021B49A /* Strings.swift */,
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */,
F9C165B3274031F600592F76 /* Bindings.swift */,
);
path = Util;
sourceTree = "<group>";
@ -887,6 +893,7 @@
0D18581B272728D60046B928 /* PhraseChip.swift in Sources */,
665C963F272C26E600BC04FB /* CircularFrameBackground.swift in Sources */,
0DB8AA81271DC7520035BC9D /* DesignGuide.swift in Sources */,
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */,
0D32282326C586A800262533 /* HistoryScreen.swift in Sources */,
0D864A0A26E154FD00A61879 /* InitFailedScreenViewModel.swift in Sources */,
0DA13CA526C1963000E3B610 /* Balance.swift in Sources */,
@ -916,6 +923,7 @@
663FAB9E271D875700E495F8 /* CreateButton.swift in Sources */,
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */,
0DA13C8F26C15D1D00E3B610 /* WelcomeScreen.swift in Sources */,
F9C165B4274031F600592F76 /* Bindings.swift in Sources */,
0D32282826C586E000262533 /* RequestZcashScreen.swift in Sources */,
F96B41EB273B50520021B49A /* Strings.swift in Sources */,
0D32283226C5877A00262533 /* BalanceScreen.swift in Sources */,

View File

@ -8,11 +8,11 @@ struct TransactionHistoryView: View {
WithViewStore(store) { viewStore in
List {
ForEach(viewStore.transactions) { transaction in
NavigationLink(
isActive: viewStore.bindingForSelectingTransaction(transaction),
destination: { TransactionDetailView(transaction: transaction) },
label: { Text("Show Transaction \(transaction.id)") }
)
Text("Show Transaction \(transaction.id)")
.navigationLink(
isActive: viewStore.bindingForSelectingTransaction(transaction),
destination: { TransactionDetailView(transaction: transaction) }
)
}
}
.navigationTitle(Text("Transactions"))

View File

@ -0,0 +1,88 @@
import SwiftUI
import CasePaths
/// taken largely from: https://github.com/pointfreeco/episode-code-samples/blob/main/0167-navigation-pt8/SwiftUINavigation/SwiftUINavigation/SwiftUIHelpers.swift
extension Binding {
func isPresent<Wrapped>() -> Binding<Bool>
where Value == Wrapped? {
.init(
get: { self.wrappedValue != nil },
set: { isPresented in
if !isPresented {
self.wrappedValue = nil
}
}
)
}
func isPresent<Enum, Case>(_ casePath: CasePath<Enum, Case>) -> Binding<Bool>
where Value == Enum? {
Binding<Bool>(
get: {
if let wrappedValue = self.wrappedValue, casePath.extract(from: wrappedValue) != nil {
return true
} else {
return false
}
},
set: { isPresented in
if !isPresented {
self.wrappedValue = nil
}
}
)
}
func `case`<Enum, Case>(_ casePath: CasePath<Enum, Case>) -> Binding<Case?>
where Value == Enum? {
Binding<Case?>(
get: {
guard
let wrappedValue = self.wrappedValue,
let `case` = casePath.extract(from: wrappedValue)
else { return nil }
return `case`
},
// swiftlint:disable:next unused_closure_parameter
set: { `case` in
if let `case` = `case` {
self.wrappedValue = casePath.embed(`case`)
} else {
self.wrappedValue = nil
}
}
)
}
func didSet(_ callback: @escaping (Value) -> Void) -> Self {
.init(
get: { self.wrappedValue },
set: {
self.wrappedValue = $0
callback($0)
}
)
}
init?(unwrap binding: Binding<Value?>) {
guard let wrappedValue = binding.wrappedValue
else { return nil }
self.init(
get: { wrappedValue },
set: { binding.wrappedValue = $0 }
)
}
func map<T>(extract: @escaping (Value) -> T, embed: @escaping (T) -> Value?) -> Binding<T> {
Binding<T>(
get: { extract(wrappedValue) },
set: {
guard let value = embed($0) else {
return
}
wrappedValue = value
}
)
}
}

View File

@ -0,0 +1,27 @@
import SwiftUI
extension View {
func navigationLink<Destination: View>(
isActive: Binding<Bool>,
destination: @escaping () -> Destination
) -> some View {
NavigationLink<Self, Destination>(
isActive: isActive,
destination: destination,
label: { self }
)
}
func navigationLinkEmpty<Destination: View>(
isActive: Binding<Bool>,
destination: @escaping () -> Destination
) -> some View {
return self.overlay(
NavigationLink<EmptyView, Destination>(
isActive: isActive,
destination: destination,
label: { EmptyView() }
)
)
}
}