Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tabbed screen #71

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4fb9084
Refactor goTo and didAppear
ohitsdaniel May 11, 2021
fab3880
Implements setActive
ohitsdaniel May 12, 2021
0999668
Implements lastOccurrenceOf
ohitsdaniel May 25, 2021
250c504
Uses lastOccurrence(of:) in Navigator.DataSource
ohitsdaniel May 25, 2021
e46bf2e
Implements setActive(screen:)
ohitsdaniel May 25, 2021
5dec240
Implements dismiss
ohitsdaniel May 26, 2021
6800b66
Refactors TabScreen
ohitsdaniel May 27, 2021
a31bff9
Implements dismissSuccessor(of:)
ohitsdaniel May 27, 2021
5e56938
Refactors goBack(to: id)
ohitsdaniel May 28, 2021
ce15fb8
Implements replaceContent(of:, with:)
ohitsdaniel May 28, 2021
585ac64
Introduces ActiveNavigationPath / ActiveNavigationTree
ohitsdaniel May 31, 2021
5b17f1d
Implement gotToPath and replacePath
ohitsdaniel Jul 11, 2021
7b3ca05
Adds replacePath and goToPath tests
ohitsdaniel Jul 12, 2021
dc13aae
Migrate ComposableDeeplinking to ActiveNavigationPath
ohitsdaniel Jul 25, 2021
25886b6
Extract NavigationTreeUpdates from tabbed paths
ohitsdaniel Jul 25, 2021
4f180e3
First TabbedNode draft
ohitsdaniel Jul 25, 2021
76df5fd
Further work on TabbedNode
ohitsdaniel Aug 8, 2021
5715506
Initialise default tab contents
ohitsdaniel Aug 8, 2021
512c900
Add convenience data source init
ohitsdaniel Aug 12, 2021
d42e8b7
Move Tabbed extension to NavigationTree
ohitsdaniel Aug 12, 2021
c94b980
Attach tags to NavigationView in each tab
ohitsdaniel Aug 12, 2021
ff01f8e
Adds tab bar to example app
ohitsdaniel Aug 12, 2021
885e9c1
Hide outer nav bar in each tab
ohitsdaniel Aug 12, 2021
c3bc940
Adjust visibilities for TabbedNodeItem fields and path(for tab)
ohitsdaniel Aug 16, 2021
bb56606
Adjust Tab field visibilities
ohitsdaniel Aug 16, 2021
ea1074e
Exposes initializeDefaultContents and DefaultTabContent
ohitsdaniel Aug 17, 2021
037abc2
Attaches screen IDs to tab content
ohitsdaniel Aug 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions Example/Example/Deeplinking/DetailsDeeplink.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import ComposableDeeplinking

extension DeeplinkParser {
/// example://detail?id={id}
static let details = DeeplinkParser(
parse: { deeplink in
guard deeplink.components.count == 1,
deeplink.components[0].name == "detail",
case let .value(id) = deeplink.components[0].arguments?["id"]
else {
return nil
}
/// example://detail?id={id}
static let details = DeeplinkParser(
parse: { deeplink in
guard deeplink.components.count == 1,
deeplink.components[0].name == "detail",
case let .value(id) = deeplink.components[0].arguments?["id"]
else {
return nil
}

return [
HomeScreen().eraseToAnyScreen(),
DetailScreen(detailID: id).eraseToAnyScreen()
]
}
)
return [
.screen(HomeScreen().eraseToAnyScreen()),
.screen(DetailScreen(detailID: id).eraseToAnyScreen())
]
}
)
}
34 changes: 17 additions & 17 deletions Example/Example/Deeplinking/DetailsSettingsDeeplink.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import ComposableDeeplinking

extension DeeplinkParser {
/// example://detail?id={id}/settings
static let detailSettings = DeeplinkParser(
parse: { deeplink in
guard deeplink.components.count == 2,
deeplink.components[0].name == "detail",
case let .value(id) = deeplink.components[0].arguments?["id"],
deeplink.components[1].name == "settings"
else {
return nil
}
/// example://detail?id={id}/settings
static let detailSettings = DeeplinkParser(
parse: { deeplink in
guard deeplink.components.count == 2,
deeplink.components[0].name == "detail",
case let .value(id) = deeplink.components[0].arguments?["id"],
deeplink.components[1].name == "settings"
else {
return nil
}

return [
HomeScreen().eraseToAnyScreen(),
DetailScreen(detailID: id).eraseToAnyScreen(),
SettingsScreen().eraseToAnyScreen()
]
}
)
return [
.screen(HomeScreen().eraseToAnyScreen()),
.screen(DetailScreen(detailID: id).eraseToAnyScreen()),
.screen(SettingsScreen().eraseToAnyScreen())
]
}
)
}
30 changes: 15 additions & 15 deletions Example/Example/Deeplinking/HomeSettingsDeeplink.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import ComposableDeeplinking

extension DeeplinkParser {
/// example://home/settings
static let homeSettings = DeeplinkParser(
parse: { deeplink in
guard deeplink.components.count == 2,
deeplink.components[0].name == "home",
deeplink.components[1].name == "settings"
else {
return nil
}
/// example://home/settings
static let homeSettings = DeeplinkParser(
parse: { deeplink in
guard deeplink.components.count == 2,
deeplink.components[0].name == "home",
deeplink.components[1].name == "settings"
else {
return nil
}

return [
HomeScreen().eraseToAnyScreen(),
SettingsScreen().eraseToAnyScreen()
]
}
)
return [
.screen(HomeScreen().eraseToAnyScreen()),
.screen(SettingsScreen().eraseToAnyScreen())
]
}
)
}
7 changes: 6 additions & 1 deletion Example/Example/ExampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ struct ExampleApp: App {

init() {
dataSource = Navigator.Datasource(
root: HomeScreen()
root: ActiveNavigationPathElement.tabbed(
ActiveNavigationPathElement.ActiveTab(
active: HomeTabs.list,
path: [.screen(HomeScreen().eraseToAnyScreen())]
)
)
)

navigator = Navigator(dataSource: dataSource).debug()
Expand Down
108 changes: 71 additions & 37 deletions Example/Example/Home Screen/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ struct HomeView: View {
}
}

enum HomeTabs: Activatable {
case list
case settings
}

struct HomeScreen: Screen {
let presentationStyle: ScreenPresentationStyle = .push

Expand Down Expand Up @@ -126,46 +131,75 @@ struct HomeScreen: Screen {
}

var builder: some PathBuilder {
Screen( // /home
HomeScreen.self,
onAppear: { _ in print("HomeView appeared") },
content: { HomeView(store: homeStore) },
nesting: {
If { (screen: DetailScreen) in
IfLetStore(
store: detailStore(for: screen.detailID),
then: { store in
DetailScreen.Builder(
store: store,
settingsStore: settingsStore
)
.onDismiss { (screen: DetailScreen) in
print("Dismissed \(screen)")
let viewStore = ViewStore(homeStore)
// Only if the current detail state represents the dismissed screen, set the state to nil.
if viewStore.state.selectedDetail?.id == screen.detailID {
ViewStore(homeStore).send(.binding(.set(\.selectedDetail, nil)))
}
Tabbed(
.init(
tag: HomeTabs.list,
contentBuilder: {
Screen( // /home
HomeScreen.self,
onAppear: { _ in print("HomeView appeared") },
content: { HomeView(store: homeStore) },
nesting: {
If { (screen: DetailScreen) in
IfLetStore(
store: detailStore(for: screen.detailID),
then: { store in
DetailScreen.Builder(
store: store,
settingsStore: settingsStore
)
.onDismiss { (screen: DetailScreen) in
print("Dismissed \(screen)")
let viewStore = ViewStore(homeStore)
// Only if the current detail state represents the dismissed screen, set the state to nil.
if viewStore.state.selectedDetail?.id == screen.detailID {
ViewStore(homeStore).send(.binding(.set(\.selectedDetail, nil)))
}
}
}
)
.beforeBuild {
let viewStore = ViewStore(homeStore)
// we do not navigate to invalid navigation paths (i.e. example://detail?id=123)
if viewStore.elements.contains(screen.detailID),
viewStore.state.selectedDetail?.id != screen.detailID {
viewStore.send(.binding(.set(\.selectedDetail, DetailState(id: screen.detailID))))
}
}
}

SettingsScreen.Builder(
store: settingsStore,
entrypoint: "home"
).onDismiss(of: SettingsScreen.self) {
print("Dismissed settings")
}
}
)
.beforeBuild {
let viewStore = ViewStore(homeStore)
// we do not navigate to invalid navigation paths (i.e. example://detail?id=123)
if viewStore.elements.contains(screen.detailID),
viewStore.state.selectedDetail?.id != screen.detailID {
viewStore.send(.binding(.set(\.selectedDetail, DetailState(id: screen.detailID))))
}
}
}
},
tabItem: {
Image(systemName: "list.triangle")
Text("List")

SettingsScreen.Builder(
store: settingsStore,
entrypoint: "home"
).onDismiss(of: SettingsScreen.self) {
print("Dismissed settings")
}
}
},
defaultContent: .screen(HomeScreen().eraseToAnyScreen())
),
.init(
tag: HomeTabs.settings,
contentBuilder: {
SettingsScreen.Builder(
store: settingsStore,
entrypoint: "home"
).onDismiss(of: SettingsScreen.self) {
print("Dismissed settings")
}
},
tabItem: {
Image(systemName: "gear")
Text("Settings")
},
defaultContent: .screen(SettingsScreen().eraseToAnyScreen())
)
)
}
}
Expand Down Expand Up @@ -202,7 +236,7 @@ let homeReducer = Reducer<
to: [
DetailScreen(detailID: element).eraseToAnyScreen(),
SettingsScreen().eraseToAnyScreen(),
],
].map(ActiveNavigationPathElement.screen),
on: screenID
)
}
Expand Down
14 changes: 7 additions & 7 deletions Example/Example/Navigation Shortcuts/NavigationShortcuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ struct NavigationShortcuts: View {
action: {
navigator.replace(
path: [
HomeScreen().eraseToAnyScreen(),
DetailScreen(detailID: "0").eraseToAnyScreen(),
NavigationShortcutsScreen(presentationStyle: .push).eraseToAnyScreen()
.screen(HomeScreen().eraseToAnyScreen()),
.screen(DetailScreen(detailID: "0").eraseToAnyScreen()),
.screen(NavigationShortcutsScreen(presentationStyle: .push).eraseToAnyScreen())
]
)
},
Expand All @@ -33,7 +33,7 @@ struct NavigationShortcuts: View {
HomeScreen().eraseToAnyScreen(),
DetailScreen(detailID: "0").eraseToAnyScreen(),
SettingsScreen().eraseToAnyScreen()
]
].map(ActiveNavigationPathElement.screen)
)
},
label: { Text("Go to [home/detail?id=0/settings]") }
Expand All @@ -48,7 +48,7 @@ struct NavigationShortcuts: View {
DetailScreen(detailID: "0").eraseToAnyScreen(),
SettingsScreen().eraseToAnyScreen(),
NavigationShortcutsScreen(presentationStyle: .push).eraseToAnyScreen()
]
].map(ActiveNavigationPathElement.screen)
)
},
label: { Text("Go to [home/detail?id=0/settings/shortcuts?style=push]") }
Expand All @@ -65,7 +65,7 @@ struct NavigationShortcuts: View {
NavigationShortcutsScreen(
presentationStyle: .sheet(allowsPush: true)
).eraseToAnyScreen()
]
].map(ActiveNavigationPathElement.screen)
)
},
label: { Text("Go to [home/detail?id=0/settings/shortcuts?style=sheet]") }
Expand All @@ -79,7 +79,7 @@ struct NavigationShortcuts: View {
path: [
HomeScreen().eraseToAnyScreen(),
SettingsScreen().eraseToAnyScreen()
]
].map(ActiveNavigationPathElement.screen)
)
},
label: { Text("Go to [home/settings]") }
Expand Down
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ let package = Package(
dependencies: [],
exclude: [
"NavigationTree/NavigationTreeBuilder+AnyOf.swift.gyb",
"NavigationTree/NavigationTreeBuilder+Tabbed.swift.gyb",
"PathBuilder/PathBuilders/PathBuilder+AnyOf.swift.gyb",
"PathBuilder/Nodes/Tabbed/TabbedNode.swift.gyb"
]
),
.target(
Expand Down
4 changes: 2 additions & 2 deletions Sources/ComposableDeeplinking/DeeplinkHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public struct DeeplinkHandler {
}

public func handle(deeplink: Deeplink) {
if let path = parser.parse(deeplink) {
navigator.replace(path: path)
if let activeNavigationPath = parser.parse(deeplink) {
navigator.replace(path: activeNavigationPath)
}
}
}
6 changes: 3 additions & 3 deletions Sources/ComposableDeeplinking/DeeplinkParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import ComposableNavigator
/// If a deeplink parser handles the input `Deeplink`, it returns a `navigation path` in the form of an `AnyScreen` array.
/// If the deeplink parser is not responsible for parsing the deeplink, it returns nil.
public struct DeeplinkParser {
private let _parse: (Deeplink) -> [AnyScreen]?
private let _parse: (Deeplink) -> ActiveNavigationPath?

public init(parse: @escaping (Deeplink) -> [AnyScreen]?) {
public init(parse: @escaping (Deeplink) -> ActiveNavigationPath?) {
_parse = parse
}

/// Parses a Deeplink to a navigation path
///
/// - Returns: If the DeepLinkParser is responsible for the passed deeplink, it returns the built navigation path. Else nil.
public func parse(_ deeplink: Deeplink) -> [AnyScreen]? {
public func parse(_ deeplink: Deeplink) -> ActiveNavigationPath? {
_parse(deeplink)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public extension DeeplinkParser {
///
/// In bigger, modularly designed applications, features often have entrypoints. This Deeplink Parses allows you to navigate to the feature's entrypoint before the performing the navigation defined in the deeplink.
static func prepending(
path pathToEntrypoint: [AnyScreen],
path pathToEntrypoint: ActiveNavigationPath,
to parser: DeeplinkParser
) -> DeeplinkParser {
DeeplinkParser(
Expand Down
Loading