Giter Site home page Giter Site logo

canopas / uipilot Goto Github PK

View Code? Open in Web Editor NEW
298.0 7.0 24.0 1.75 MB

The missing typesafe SwiftUI navigation library

Home Page: https://canopas.github.io/UIPilot

License: MIT License

Swift 91.86% Ruby 8.14%
swift navigation navigationview router xcode ios macos swift5 ios-ui routing

uipilot's Introduction

UIPilot

Swift Platforms Swift Package Manager

Why another SwiftUI navigation library?

  • UIPilot is not a replacement for SwiftUI's NavigationView, it's rather a wrapper around it that you would have likely written. Thus all standard NavigationView features like title, swipe gesture, topbar, etc. are available by default.
  • APIs are inspired by the Android, flutter, and web-based routers - Very simple and easy to use.
  • Typesafe navigation - Routing to the wrong path will fail at compile time rather than runtime.
  • Typesafe parameters - Routing with wrong parameters will fail at compile time rather than runtime.
  • Very tiny library - it's barely 200 lines of code.

Documentation

Visit the website for documentation and more information.

Examples

Please have a look at the article and the examples to know more about different use cases of UIPilot.

Complex use cases

The library is designed to meet simple use cases as well as complex ones. You can also have nested UIPilot as many as you like!

For example, it's very easy to achieve split screen-like behavior.

Please have a look at the article for more information of the implementation.

Installation

Version 1.x - Uses SwiftUI NavigationView underneath.

Version 2.x - Uses UIKit UINavigationController underneath (recommended).

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler.

Once you have your Swift package set up, adding UIPilot as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/canopas/UIPilot.git", .upToNextMajor(from: "2.0.2"))
]

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate UIPilot into your Xcode project using CocoaPods, specify it in your Podfile:

target 'YourAppName' do
    pod 'UIPilot', '~> 2.0.2'
end

Bugs and Feedback

For bugs, questions, and discussions please use the Github Issues.

Credits

UIPilot is owned and maintained by the Canopas team. You can follow them on Twitter at @canopassoftware for project updates and releases.

Licence

UIPilot is released under the MIT license. See LICENSE for details.

uipilot's People

Contributors

cp-amisha-i avatar cp-radhika-s avatar jackrwright avatar jimmy0251 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

uipilot's Issues

Showing modal or alert

Hi ๐Ÿ‘‹
Is there any option to showing model or alert or bottom-sheet or toast with this lib?

Pilot PopTo Doesn't Work with Parameters

I have a project that utilizes parameters within its routing. While using pilot.push(.Locations(status: Status.none)) works, doing pilot.pop(.Locations(status: Status.none)) doesn't work. When I used the debugger, I noticed that the internal popTo function navigated to the the log statement UIPilot - Route not found.
If I had to take a guess on what is wrong, I think exact matching might with Equatable might not be right. This is because when doing .Locations(status: Status.none), it's creating a new unique identifier for that route? Maybe if it was checking for a route name instead, it might work?

iOS Target: 14.1
Swift: 5.7
UIPilot: 1.3.1

Passing state from the router view to subviews

When using UIPilot for navigation, state that is updated in the view that holds the UIPilotHost doesn't get properly passed to subviews. Is this a known limitation?

struct MyRootView: View {
    @StateObject var pilot: UIPilot<MyRoute> = UIPilot(initial: .preparing)
    @StateObject var vm: MyRootViewModel = .init()

    var body: some View {
        UIPilotHost(pilot) { route in
            switch route {
            case .foobar:
                return AnyView(MySubView(vm: vm))
...

struct MySubView: View {
    @ObservedObject var vm: MyRootViewModel

    var body: some View {
        Text(vm.somePropertyThatChanges)

When changing vm.somePropertyThatChanges (which is a @Published variable) using the above, the body of MySubView won't be re-rendered. It seems to work fine when using plain NavigationView. Is this a limitation with AnyView somehow?

Is UIPilot working with TabView?

Hi team,
I've been trying a few ways to achieve my goal. Which is is implement UIPilot for my complex app.
Currently, I have a MainTabview, it is the "middleware view" for me to navigate to different Views
For example:

   TabView(selection: $tabVM.tabSelection) {
   Tab1View()
      .tag("tab1")

   Tab2View()
      .tag("tab2")
   ...
   }

Each tab view has different duties and they are the root view for many other views. For example:
Tab1View -> Tab1DetailView -> WorksheetView -> ResultView -> CompleteView
...

My goal is to navigate from CompleteView -> Tab1. I've tried to create a new view (NewTab1View) to replace the Tab1View from MainTabView:

   enum EducationAppRoute: Equatable {
       case Tab1View
       case CompleteView
   }
   struct NewTab1View: View {
       @StateObject var pilot = UIPilot(initial: Tab1.Tab1View)

       var body: some View {
           UIPilotHost(pilot)  { route in
               switch route {
               case .Tab1View: Tab1View()
               case .CompleteView: CompleteView()
   ...

and, within the CompleteView, I have

   Button {
        pilot.popTo(.NewTab1View)
   } label: {
        ...
   }

However, this does not work, the button action does take me back to MainTab1VIew()
I have not change anything much ( NavigationView { ... }, NavigationLink between views ).

How to manage routes for tab bar based application?

I have one question, Do I have to create individual route for each tab?

If yes, then suppose there is one view which is part of 3rd tab but I want to push that view in 2nd tab. How can I push 3rd routes view to using 2nd route.

Add debug flag

It would be nice if we could pass a debug flag to UIPilot at some point to have it print what's going on.

iPad changing orientation is causing a pop route by the system. UIPilot - root route popped by system

I have an app that uses UIPilot and when we change from portrait to landscape or vice versa, that code is checked and executed.

func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {   
     if let stackSizeProvider = stackSizeProvider, stackSizeProvider() > navigationController.viewControllers.count {
        self.popHandler?()
     }
}

This is happening on the 2.0.2 version.

Manual `pop()` causes two routes to be deleted from the stack at once

I noticed that manually calling the pop() method causes two routes to be removed from the stack at once.

This is because manually calling this method causes the controller to close via popToViewController(), which in turn triggers a call to onSystemPop() due to an event from the PopAwareUINavigationController.

So, the controller is closed once, but the values are removed from the _routes stack twice. Then this leads to problems with navigation, because stacks are no longer synchronized with each other.

NavigationBarTitle Large does not display

Whenever I use .navigationBarTitleDisplayMode(.large) in any of my screens... it either doesn't display the title or makes it inline.

Do you know what might be the issue?

Can this support be added?

To check if a UIViewController is present in the navigation stack and then push or pop to it accordingly. Will be appreciated if yo you can provide a solution.

Setting initial view

I have been building a POC app with UIPilot and the library works great for me moving forwards and back in the app. My question is this: I have an authentication service set up and if the user is already logged in they should be shown to the main dashboard of the app. I have the library set up to have the initial view to be View1 for example but if the user for example kills the app and reopens it and they are still authenticated I want them to go to the dashboard view rather than View1 I have set in the initial property. Basically I dont want the initial view to always be View1.

Is there anyway of doing this?

Navigation Titles don't appear in tabs

I have a TabView with 3 different UIPilotHosts as their views. These pilot show navigation titles normally fine, but when I put them inside the tab bar container, the titles don't show up. Toolbar items show up fine, but the titles don't. Additionally, if I use normal NavigationViews in the tab bar container, the titles show up fine. Is there any suggestions for how to resolve this?

Provide a means to suppress intermediate transitions on a pop(to:)

Hello, great library! I would like to be able to suppress intermediate transitions when using pop(to:). I made a local change that seems to work (below), but may not be the best solution. If you prefer I can open a PR.

private var showTransition: Bool = true

    var paths: [Path<T>] = [] {
        didSet {
            if showTransition {
                updateViewState()
            }
        }
    }

    public func popTo(_ route: T, inclusive: Bool = false, showIntermediateTransitions: Bool = false) {
...
        if !showIntermediateTransitions {
            // remove intermediate paths supressing transition animations
            var numToPop = (found..<paths.endIndex).count - 1
            logger.log("UIPilot - Popping \(numToPop + 1) routes")
            showTransition = false
            paths.removeLast(numToPop)
            showTransition = true
            // pop the final path with a transition animation
            pop()
        } else {
            // The original code: pop showing all transitions
            for _ in found..<paths.count {
                logger.log("UIPilot - Route \(route) Popped.")
                pop()
            }
        }
...

Navigate to a new destination and pop backstack upto the new destination

In one of my android apps, I have set initial screen to SplashScreen. SplashScreen is responsible for a local database transaction and a couple of network transactions. After transactions, user is navigated to HomeScreen and SplashScreen is removed from the backstack. Is such a behaviour is possible with UIPilot?

One more use case is when navigating from SplashScreen to AuthScreen, then AuthScreen to OnBoardingScreen, then OnBoardingScreen to InitialSettingsScreen and finally from InitialSettingsScreen to HomeScreen. In this use case, all the screens in the backstack should be popped when HomeScreen is reached.

In android, there is a convenience method in NavigationController called "builder.popUpTo(route: String)" which does exactly the same thing that I have mentioned.

Reference from Android docs:
https://developer.android.com/reference/kotlin/androidx/navigation/NavOptions#popUpToRoute()

Destination view immediately pops back

In one of my SwiftUI views, I end the body with:

.task {
    pilot.push(.Feed)
}

That causes the navigation stack to pop to the previous path. However, the following works fine:

.task {
    // sleep for 3 seconds
    try! await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)
    pilot.push(.Feed)
}

Additionally, the following doesn't work:

.task {
    // sleep for 0.1 seconds
    try! await Task.sleep(nanoseconds: UInt64(0.1) * NSEC_PER_SEC)
    pilot.push(.Feed)
}

So I guess there's some timing/threading issues going on here, but I don't know what it is. Any ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.