Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ernest0-Production committed May 14, 2022
1 parent 4011e48 commit d7b6097
Show file tree
Hide file tree
Showing 15 changed files with 308 additions and 125 deletions.
1 change: 0 additions & 1 deletion Diagram.drawio

This file was deleted.

4 changes: 2 additions & 2 deletions Example/Example/ExampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct ExampleApp: App {
pushActions: [
PushAction(title: "push") {
$0.push(
screenTag: "Screen 2",
tag: "Screen 2",
ExampleScreenView(
title: "Screen 2",
pushActions: [
Expand Down Expand Up @@ -56,7 +56,7 @@ struct ExampleApp: App {
],
presentActions: [
PresentAction(title: "present") {
$0.present(.fullScreenCover, screenTag: "Screen 2", ExampleScreenView(
$0.present(.fullScreenCover, tag: "Screen 2", ExampleScreenView(
title: "Screen 2",
presentActions: [
PresentAction(title: "Update global state \(globalState)") { _ in
Expand Down
268 changes: 189 additions & 79 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,113 +1,223 @@
# ScreenNavigatorKit

# API
`NavigationStack`
- push
- push(screenTag:)
- pop
- pop(screenTag:)
- popLast(_ k:)
- popToRoot
Framework that provide convenient environment for manage navigation in SwiftUI.

`ModalStack`
- present(_ presentationStyle:)
- present(_ presentationStyle:screenTag:)
- dismiss
- dismiss(screenTag:)
- dismissLast(_ k:)
- dismissAll
- `PresentationStyle`
- sheet
- fullScreenCover
## Pros:
- 🤢 No boolean flag such as `@State var isActive`
- 🤮 No `enum` flag such as `@State var route: RouteAction?` with big `switch-case` statement
- 🤡 No implicit `UIKit` hacks with `UIViewController`
- 💩 No singleton/shared/global presenter of application

### 🔩 Requirements

- iOS 14.0+
- Xcode 12.0+
- Swift 5.3+

# 🧐 How it work?!

Framework has only two `state object`, each of which isolates "toggle-work" of `@State var isActive: Bool` and `@State var isPresent: Bool` flags.

# Usage
## 1. `NavigationStack`

Like `UINavigationController`, it **store stack state** and **provide stack transformation** using `push` and `pop` methods:
```swift
import ScreenNavigatorKit
let navigationStack = NavigationStack()

@main
struct ExampleApp: App {
@StateObject var navigationStack = NavigationStack()

var body: some Group {
WindowGroup {
// under-the-hood NavigationView
NavigationStackView(navigationStack) {
RootScreenView()
}
// Standard usage

navigationStack.push(Text("My View"))
navigationStack.pop()
navigationStack.popToRoot()

// Advanced usage

enum Screen: Hashable {
case detail
...
}

navigationStack.push(tag: Screen.detail, DetailView())
navigationStack.pop(to: Screen.detail)
```
Its companion is `NavigationStackView` – wrapper over `NavigationView` that bind `NavigationStack` with it:
```swift
struct ContentView: View {
@StateObject var navigationStack = NavigationStack()

var body: some View {
NavigationStackView(navigationStack) {
RootView(
showDetails: { model in
navigationStack.push(DetailView(model: model))
},
showSettings: {
navigationStack.push(SettingsView())
}
)
}
}
}

// RootScreenView.swift
// Another usage with automatic initialized NavigationStack

struct ContentView: View {
var body: some View {
NavigationStackView { navigationStack in
RootView(
showDetails: { model in
navigationStack.push(DetailView(model: model))
},
showSettings: {
navigationStack.push(SettingsView())
}
)
}
}
}
```

struct RootScreenView: View {
Any pushed view has access to `NavigationStack` of `NavigationStackView` through `EnvironmentObject`:
```swift
struct DetailView: View {
let model: Model
@EnvironmentObject var navigationStack: NavigationStack

var body: View {
var body: some View {
VStack {
Text("Root")

Button("Push Screen 1") {
navigationStack.push(FirstScreenView())
}

Button("Present Screen 2") {
navigationStack.push(
screenTag: "Screen2",
SecondScreenView()
)
Text(model.title)
Button("pop to root") {
navigationStack.popToRoot()
}
}
}
}
```
**💫 EXTRA FEATURE:** You can tag any pushed view using any `Hashable` type. It allow refer to specific screen on pop:
```swift
navigationStack.push(tag: "Screen 1", Screen1()))
navigationStack.pop(to: "Screen 1")
```

// FirstScreenView.swift
## 2. `ModalStack`

struct FirstScreenView: View {
@EnvironmentObject var navigationStack: NavigationStack
@StateObject var modalStack = ModalStack()
Like `NavigationStack`, the `ModalStack` **control modal stack hierarchy** and **provide stack transformation** using `present` and `dismiss` methods:
```swift
let modalStack = ModalStack()

var body: View {
VStack {
Text("First")

Button("Pop") {
navigationStack.pop()
}
// Standard usage

Button("Pop To Root") {
navigationStack.popToRoot()
}
modalStack.present(.sheet, Text("My View"))
modalStack.present(.fullScreenCover, Text("Another View"))
modalStack.dismiss()
modalStack.dismissAll()

Button("Present Modal Screen") {
modalStack.present(
.sheet,
Text("Modal Screen Example")
)
}
}
.definesPresentationContext(with: modalStack)
}
// Advanced usage

enum Screen: Hashable {
case detail
...
}

// SecondScreenView.swift
modalStack.present(.sheet, tag: Screen.detail, DetailView())
modalStack.dismiss(to: Screen.detail)
```

struct SecondScreenView: View {
@EnvironmentObject var navigationStack: NavigationStack
**🚧 NOTE:** `SwiftUI` not allow dismiss multiple views at once! Therefore, methods such as `dismissAll()` or `dismiss(to:)`/`dismiss(from:)` will close all views **sequentially**.

var body: View {
VStack {
Text("Second")
To attach `ModalStack` to a `view`, you need to declare a **root view** on top of which all views will be presented using the method `definesPresentationContext(with:)`:
```swift
struct ExampleApp: App {
@StateObject var modalStack = ModalStack()

Button("Pop from taggeg screen") {
navigationStack.pop(from: "Screen 2")
}
var body: some Scene {
WindowGroup {
RootView()
.definesPresentationContext(with: modalStack)
// or just call .definesPresentationContext()
}
}
}
```
Any presented view has access to `ModalStack` through `EnvironmentObject` too:
```swift
struct RootView: View {
@EnvironmentObject var modalStack: ModalStack

Button("Pop last 2 screen") {
screenNavigator.popLast(2)
var body: some View {
VStack {
Text("Home screen")
Button("FAQ") {
modalStack.present(.sheet, FAQView())
}
Button("Auhorize") {
modalStack.present(.fullScreenCover, LoginView())
}
}
}
}
```
```
> 💫 Just like in `NavigationStack` you can tag presented views when present with `ModalStack`
# API
`NavigationStack`
- push
- push(tag:)
- pop
- pop(tag:)
- popLast(_ k:)
- popToRoot

`ModalStack`
- present(_ presentationStyle:)
- present(_ presentationStyle:tag:)
- dismiss
- dismiss(tag:)
- dismissLast(_ k:)
- dismissAll
- `PresentationStyle`
- sheet
- fullScreenCover

# FAQ

> Can i mix this framework with existing navigation approach in my project?
**Yes, you can**. The framework does not affect navigation built in other ways, such as through the standard `@State var isActive: Bool` flags or through UIKit hacks.\
`NavigationStack` and `ModalStack` create local state and and manage only their own state.

> What about `Alert`?
Unfortunately, the framework **does not support** such a mechanism for working with `Alert`, BUT **you can implement it yourself by analogy** with `ModalStack`.\
Your project can have many different custom presentations (`popup`, `snackbar`, `toats`, `notifications`) and each of them require specific logic for handle hierarchy, depending on their implementation.\
So adding new presentation methods to the framework **is not planned**.

# 📦 Installation

#### [Swift Package Manager](https://github.com/apple/swift-package-manager)

Create a `Package.swift` file.

```swift
// swift-tools-version:5.3

import PackageDescription

let package = Package(
name: "YOUR_PROJECT_NAME",
dependencies: [
.package(url: "https://github.com/Ernest0-Production/ScreenNavigatorKit.git", from: "0.0.3")
],
targets: [
.target(name: "YOUR_TARGET_NAME", dependencies: ["ScreenNavigatorKit"])
]
)
```

### Credits

- [Telegram](https://t.me/Ernest0n)

### License

ScreenNavigatorKit is released under the MIT license. See [LICENSE](https://github.com/Ernest0-Production/ScreenNavigatorKit/blob/main/LICENSE.md) for details.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// File.swift
//
// AnySequence+first.swift
// ScreenNavigatorKit
//
// Created by Ernest Babayan on 09.05.2022.
//
Expand Down
4 changes: 2 additions & 2 deletions Sources/ScreenNavigatorKit/Internal/Helpers/View+asAny.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// File.swift
//
// View+asAny.swift
// ScreenNavigatorKit
//
// Created by Ernest Babayan on 03.05.2022.
//
Expand Down
4 changes: 2 additions & 2 deletions Sources/ScreenNavigatorKit/Internal/LinkedList.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// WeakLinkedList.swift
//
// LinkedList.swift
// ScreenNavigatorKit
//
// Created by Ernest Babayan on 04.05.2022.
//
Expand Down
Loading

0 comments on commit d7b6097

Please sign in to comment.