-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4011e48
commit d7b6097
Showing
15 changed files
with
308 additions
and
125 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
4 changes: 2 additions & 2 deletions
4
Sources/ScreenNavigatorKit/Internal/Helpers/AnySequence+first.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
// | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.