Skip to content

Commit

Permalink
Add tabbed example with nested navigations
Browse files Browse the repository at this point in the history
  • Loading branch information
dcvz committed Aug 31, 2021
1 parent f18bfcc commit a84be86
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 98 deletions.
12 changes: 8 additions & 4 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
9B1E81EE26DE4B8100A98082 /* SecondTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1E81ED26DE4B8100A98082 /* SecondTabView.swift */; };
9B1E81F026DE4CC000A98082 /* FirstDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1E81EF26DE4CC000A98082 /* FirstDetailView.swift */; };
9B1E81F226DE4E4300A98082 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1E81F126DE4E4300A98082 /* SettingsView.swift */; };
9B1E81F426DE4ED400A98082 /* SecondDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1E81F326DE4ED400A98082 /* SecondDetailView.swift */; };
9B1E81F426DE4ED400A98082 /* SecondDetailModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1E81F326DE4ED400A98082 /* SecondDetailModalView.swift */; };
9B1E81F926DE659B00A98082 /* SecondDetailModalDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1E81F826DE659B00A98082 /* SecondDetailModalDetailView.swift */; };
9B4EAD8D255C49A300E8FBEA /* XNavigation in Frameworks */ = {isa = PBXBuildFile; productRef = 9B4EAD8C255C49A300E8FBEA /* XNavigation */; };
9B72CF2B255C44270005ED3D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B72CF2A255C44270005ED3D /* AppDelegate.swift */; };
9B72CF2D255C44270005ED3D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B72CF2C255C44270005ED3D /* SceneDelegate.swift */; };
Expand All @@ -27,7 +28,8 @@
9B1E81ED26DE4B8100A98082 /* SecondTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondTabView.swift; sourceTree = "<group>"; };
9B1E81EF26DE4CC000A98082 /* FirstDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstDetailView.swift; sourceTree = "<group>"; };
9B1E81F126DE4E4300A98082 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
9B1E81F326DE4ED400A98082 /* SecondDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondDetailView.swift; sourceTree = "<group>"; };
9B1E81F326DE4ED400A98082 /* SecondDetailModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondDetailModalView.swift; sourceTree = "<group>"; };
9B1E81F826DE659B00A98082 /* SecondDetailModalDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondDetailModalDetailView.swift; sourceTree = "<group>"; };
9B72CF27255C44270005ED3D /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
9B72CF2A255C44270005ED3D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
9B72CF2C255C44270005ED3D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -74,7 +76,8 @@
isa = PBXGroup;
children = (
9B1E81ED26DE4B8100A98082 /* SecondTabView.swift */,
9B1E81F326DE4ED400A98082 /* SecondDetailView.swift */,
9B1E81F326DE4ED400A98082 /* SecondDetailModalView.swift */,
9B1E81F826DE659B00A98082 /* SecondDetailModalDetailView.swift */,
);
path = SecondTab;
sourceTree = "<group>";
Expand Down Expand Up @@ -202,7 +205,8 @@
9B1E81F226DE4E4300A98082 /* SettingsView.swift in Sources */,
9B72CF2B255C44270005ED3D /* AppDelegate.swift in Sources */,
9B1E81EC26DE4B7000A98082 /* FirstTabView.swift in Sources */,
9B1E81F426DE4ED400A98082 /* SecondDetailView.swift in Sources */,
9B1E81F426DE4ED400A98082 /* SecondDetailModalView.swift in Sources */,
9B1E81F926DE659B00A98082 /* SecondDetailModalDetailView.swift in Sources */,
9B1E81E826DE4B0300A98082 /* ExampleView.swift in Sources */,
9B72CF2D255C44270005ED3D /* SceneDelegate.swift in Sources */,
9B1E81EE26DE4B8100A98082 /* SecondTabView.swift in Sources */,
Expand Down
30 changes: 10 additions & 20 deletions Example/Example/Features/ExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,18 @@ import XNavigation

struct ExampleView: View {
@EnvironmentObject var navigation: Navigation

var body: some View {
NavigationView {
TabView {
FirstTabView()
.tabItem {
Label("First", systemImage: "01.circle.fill")
}

SecondTabView()
.tabItem {
Label("Second", systemImage: "02.circle.fill")
}
}
.navigationBarTitle("TabExample", displayMode: .large)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: {
navigation.pushView(SettingsView(), animated: true)
}) { Image(systemName: "gear") }
TabView {
FirstTabView()
.tabItem {
Label("First", systemImage: "01.circle.fill")
}

SecondTabView()
.tabItem {
Label("Second", systemImage: "02.circle.fill")
}
}
}
}
}
Expand Down
15 changes: 10 additions & 5 deletions Example/Example/Features/FirstTab/FirstDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@
import SwiftUI
import XNavigation

struct FirstDetailView: View {
struct FirstDetailView: View, DestinationView {
@EnvironmentObject var navigation: Navigation

var navigationBarTitleConfiguration = NavigationBarTitleConfiguration(title: "First Detail", displayMode: .inline)

var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
Button(action: {
navigation.pop(animated: true)
}) { Text("Press to pop!") }
VStack {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
Button(action: {
navigation.pop(animated: true)
}) { Text("Press to pop!") }
}
.navigationBarTitle(configuration: navigationBarTitleConfiguration)
}
}

Expand Down
20 changes: 15 additions & 5 deletions Example/Example/Features/FirstTab/FirstTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ struct FirstTabView: View {
@EnvironmentObject var navigation: Navigation

var body: some View {
VStack {
Text("First Tab!")
Button(action: {
navigation.pushView(FirstDetailView(), animated: true)
}) { Text("Press to push!") }
NavigationView {
VStack {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
Button(action: {
navigation.pushView(FirstDetailView(), animated: true)
}) { Text("Press to push!") }
}
.navigationBarTitle("First Tab", displayMode: .large)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: {
navigation.pushView(SettingsView(), animated: true)
}) { Image(systemName: "gear") }
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// SecondDetailModalDetailView.swift
// Example
//
// Created by David Chavez on 31.08.21.
//

import SwiftUI
import XNavigation

struct SecondDetailModalDetailView: View, DestinationView {
@EnvironmentObject var navigation: Navigation

var navigationBarTitleConfiguration = NavigationBarTitleConfiguration(title: "Second Detail Detail", displayMode: .inline)

var body: some View {
VStack {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
Button(action: {
navigation.pop(animated: true)
}) { Text("Press to pop!") }
}
.navigationBarTitle(configuration: navigationBarTitleConfiguration)
}
}

struct SecondDetailModalDetailView_Previews: PreviewProvider {
static var previews: some View {
SecondDetailModalDetailView()
}
}
35 changes: 35 additions & 0 deletions Example/Example/Features/SecondTab/SecondDetailModalView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// SecondDetailModalView.swift
// Example
//
// Created by David Chavez on 31.08.21.
//

import SwiftUI
import XNavigation

struct SecondDetailModalView: View {
@EnvironmentObject var navigation: Navigation

var body: some View {
NavigationView {
VStack {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
Button(action: {
navigation.dismiss(animated: true)
}) { Text("Press to dismiss!") }
Button(action: {
navigation.pushView(SecondDetailModalDetailView())
}) { Text("Press to push unto this modal!") }
.padding(.top, 40)
}
.navigationBarTitle("Second Detail", displayMode: .inline)
}
}
}

struct SecondDetailModalView_Previews: PreviewProvider {
static var previews: some View {
SecondDetailModalView()
}
}
26 changes: 0 additions & 26 deletions Example/Example/Features/SecondTab/SecondDetailView.swift

This file was deleted.

13 changes: 8 additions & 5 deletions Example/Example/Features/SecondTab/SecondTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ struct SecondTabView: View {
@EnvironmentObject var navigation: Navigation

var body: some View {
VStack {
Text("Second Tab!")
Button(action: {
navigation.present(SecondDetailView())
}) { Text("Press to present!") }
NavigationView {
VStack {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
Button(action: {
navigation.present(SecondDetailModalView())
}) { Text("Press to present!") }
}
.navigationBarTitle("Second Tab", displayMode: .large)
}
}
}
Expand Down
37 changes: 37 additions & 0 deletions Sources/XNavigation/Extensions/UIViewController+Hierarchy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// UIViewController+Hierarchy.swift
// XNavigation
//
// Created by David Chavez on 31.08.21.
//

import UIKit

extension UIViewController {
func findLastPresentedViewController() -> UIViewController? {
if let presentedViewController = presentedViewController?.presentedViewController {
return presentedViewController.findLastPresentedViewController()
} else {
return presentedViewController
}
}

func findNestedUINavigationController() -> UINavigationController? {
// presented modals should take precedence, as they're
// currently on-screen and likely where you tried to push/present.
if let lastPresentedViewController = findLastPresentedViewController() {
return lastPresentedViewController.findNestedUINavigationController()
}

if let nvc = self as? UINavigationController { return nvc }
else if let tbc = self as? UITabBarController {
return tbc.selectedViewController?.findNestedUINavigationController()
}

for child in children {
return child.findNestedUINavigationController()
}

return nil
}
}
50 changes: 17 additions & 33 deletions Sources/XNavigation/Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,29 @@ open class Navigation: ObservableObject {
self.window = window
}

public func dismiss(animated: Bool = true, completion: (() -> Void)? = nil) {
if let modal = window.rootViewController?.presentedViewController {
modal.dismiss(animated: animated, completion: completion)
} else {
window.rootViewController?.dismiss(animated: animated, completion: completion)
}
}

public func present<Content: View>(_ view: Content, animated: Bool = true) {
let controller = DestinationHostingController(rootView: view.environmentObject(self))
present(controller, animated: animated)
}

public func present(_ viewController: UIViewController, animated: Bool = true) {
DispatchQueue.main.async { [weak self] in
if let modal = self?.window.rootViewController?.presentedViewController {
modal.present(viewController, animated: animated)
if let lastPresentedViewController = self?.window.rootViewController?.findLastPresentedViewController() {
lastPresentedViewController.present(viewController, animated: animated)
} else {
self?.window.rootViewController?.present(viewController, animated: animated)
}
}
}

public func pop(animated: Bool = true) {
var nvc = window.rootViewController?.children.first?.children.first as? UINavigationController

if UIDevice.current.userInterfaceIdiom == .phone {
if let modal = window.rootViewController?.presentedViewController?.presentedViewController {
nvc = modal.children.first as? UINavigationController
}
} else {
if let modal = window.rootViewController?.presentedViewController {
nvc = modal.children.first as? UINavigationController
public func dismiss(animated: Bool = true, completion: (() -> Void)? = nil) {
DispatchQueue.main.async { [weak self] in
if let lastPresentedViewController = self?.window.rootViewController?.findLastPresentedViewController() {
lastPresentedViewController.dismiss(animated: animated, completion: completion)
} else {
self?.window.rootViewController?.dismiss(animated: animated, completion: completion)
}
}

nvc?.popViewController(animated: animated)
}

public func pushView<Content: View>(_ view: Content, animated: Bool = true) {
Expand All @@ -61,20 +47,18 @@ open class Navigation: ObservableObject {
}

public func pushViewController(_ viewController: UIViewController, animated: Bool = true) {
var nvc = window.rootViewController?.children.first?.children.first as? UINavigationController

if UIDevice.current.userInterfaceIdiom == .phone {
if let modal = window.rootViewController?.presentedViewController?.presentedViewController {
nvc = modal.children.first as? UINavigationController
}
} else {
if let modal = window.rootViewController?.presentedViewController {
nvc = modal.children.first as? UINavigationController
}
}
let nvc = window.rootViewController?.findNestedUINavigationController()

DispatchQueue.main.async {
nvc?.pushViewController(viewController, animated: animated)
}
}

public func pop(animated: Bool = true) {
let nvc = window.rootViewController?.findNestedUINavigationController()

DispatchQueue.main.async {
nvc?.popViewController(animated: animated)
}
}
}

0 comments on commit a84be86

Please sign in to comment.