Tools for making SwiftUI navigation simpler, more ergonomic and more precise.

Overview

SwiftUI Navigation

CI

Tools for making SwiftUI navigation simpler, more ergonomic and more precise.

Motivation

SwiftUI comes with many forms of navigation (tabs, alerts, dialogs, modal sheets, popovers, navigation links, and more), and each comes with a few ways to construct them. These ways roughly fall in two categories:

  • "Fire-and-forget": These are initializers and methods that do not take binding arguments, which means SwiftUI fully manages navigation state internally. This makes it is easy to get something on the screen quickly, but you also have no programmatic control over the navigation. Examples of this are the initializers on TabView and NavigationLink that do not take a binding.

  • "State-driven": Most other initializers and methods do take a binding, which means you can mutate state in your domain to tell SwiftUI when it should activate or deactivate navigation. Using these APIs is more complicated than the "fire-and-forget" style, but doing so instantly gives you the ability to deep-link into any state of your application by just constructing a piece of data, handing it to a SwiftUI view, and letting SwiftUI handle the rest.

Navigation that is "state-driven" is the more powerful form of navigation, albeit slightly more complicated, but unfortunately SwiftUI does not ship with all the tools necessary to model our domains as concisely as possible and use these navigation APIs.

For example, to show a modal sheet in SwiftUI you can provide a binding of some optional state so that when the state flips to non-nil the modal is presented. However, the content closure of the sheet is handed a plain value, not a binding:

struct ContentView: View {
  @State var draft: Post?

  var body: some View {
    Button("Edit") {
      self.draft = Post()
    }
    .sheet(item: self.$draft) { (draft: Post) in
      EditPostView(post: draft)
    }
  }
}

struct EditPostView: View {
  let post: Post
  var body: some View { ... }
}

This means that the Post handed to the EditPostView is fully disconnected from the source of truth draft that powers the presentation of the modal. Ideally we should be able to derive a Binding<Post> for the draft so that any mutations EditPostView makes will be instantly visible in ContentView.

Another problem arises when trying to model multiple navigation destinations as multiple optional values. For example, suppose there are 3 different sheets that can be shown in a screen:

struct ContentView: View {
  @State var draft: Post?
  @State var settings: Settings?
  @State var userProfile: UserProfile?

  var body: some View {
    /* Main view omitted */

    .sheet(item: self.$draft) { (draft: Post) in
      EditPostView(post: draft)
    }
    .sheet(item: self.$settings) { (settings: Settings) in
      SettingsView(settings: settings)
    }
    .sheet(item: self.$userProfile) { (userProfile: Profile) in
      UserProfile(profile: userProfile)
    }
  }
}

This forces us to hold 3 optional values in state, which has 2^3=8 different states, 4 of which are invalid. The only valid states is for all values to be nil or exactly one be non-nil. It makes no sense if two or more values are non-nil, for that would representing wanting to show two modal sheets at the same time.

Ideally we'd like to represent these navigation destinations as 3 mutually exclusive states so that we could guarantee at compile time that only one can be active at a time. Luckily for us Swift’s enums are perfect for this:

enum Route {
  case draft(Post)
  case settings(Settings)
  case userProfile(Profile)
}

And then we could hold an optional Route in state to represent that we are either navigating to a specific destination or we are not navigating anywhere:

@State var route: Route?

This would be the most optimal way to model our navigation domain, but unfortunately SwiftUI's tools do not make easy for us to drive navigation off of enums.

This library comes with a number of Binding transformations and navigation API overloads that allow you to model your domain as concisely as possible, using enums, while still allowing you to use SwiftUI's navigation tools.

For example, powering multiple modal sheets off a single Route enum looks like this with the tools in this library:

struct ContentView {
  @State var route: Route?

  enum Route {
    case draft(Post)
    case settings(Settings)
    case userProfile(Profile)
  }

  var body: some View {
    /* Main view omitted */

    .sheet(unwrapping: self.$route, case: /Route.draft) { $draft in
      EditPostView(post: $draft)
    }
    .sheet(unwrapping: self.$route, case: /Route.settings) { $settings in
      SettingsView(settings: $settings)
    }
    .sheet(unwrapping: self.$route, case: /Route.userProfile) { $userProfile in
      UserProfile(profile: $userProfile)
    }
  }
}

The forward-slash syntax you see above represents a case path to a particular case of an enum. Case paths are our imagining of what key paths could look like for enums, and every concept for key paths has an analogous concept for case paths:

  • Each property of an struct is naturally endowed with a key path, and so each case of an enum is endowed with a case path.
  • Key paths are constructed using a back slash, name of the type and name of the property (e.g., \User.name), and case paths are constructed similarly, but with a forward slash (e.g., /Route.draft).
  • Key paths describe how to get and set a value in some root structure, whereas case paths describe how to extract and embed a value into a root structure.

Case paths are crucial for allowing us to build the tools to drive navigation off of enum state.

Tools

This library comes with many tools that allow you to model your domain as concisely as possible, using enums, while still allowing you to use SwiftUI's navigation APIs.

Navigation API overloads

This library provides additional overloads for all of SwiftUI's "state-driven" navigation APIs that allow you to activate navigation based on a particular case of an enum. Further, all overloads unify presentation in a single, consistent API:

  • NavigationLink.init(unwrapping:case:)
  • View.alert(unwrapping:case:)
  • View.confirmationDialog(unwrapping:case:)
  • View.fullScreenCover(unwrapping:case:)
  • View.popover(unwrapping:case:)
  • View.sheet(unwrapping:case:)

For example, here is how a navigation link, a modal sheet and an alert can all be driven off a single enum with 3 cases:

enum Route {
  case add(Post)
  case alert(Alert)
  case edit(Post)
}

struct ContentView {
  @State var posts: [Post]
  @State var route: Route?

  var body: some View {
    ForEach(self.posts) { post in
      NavigationLink(unwrapping: self.$route, case: /Route.edit) { $post in 
        EditPostView(post: $post)
      } onNavigate: { isActive in 
        self.route = isActive ? .edit(post) : nil 
      } label: {
        Text(post.title)
      }
    }
    .sheet(unwrapping: self.$route, case: /Route.add) { $post in
      EditPostView(post: $post)
    }
    .alert(
      title: { Text("Delete \($0.title)?") },
      unwrapping: self.$route,
      case: /Route.alert
      actions: { post in
        Button("Delete") { self.posts.remove(post) }
      },
      message: { Text($0.summary) }
    )
  }
}

struct EditPostView: View {
  @Binding var post: Post
  var body: some View { ... }
}

Navigation views

This library comes with additional SwiftUI views that transform and destructure bindings, allowing you to better handle optional and enum state:

  • IfLet
  • IfCaseLet
  • Switch/CaseLet

For example, suppose you were working on an inventory application that modeled in-stock and out-of-stock as an enum:

enum ItemStatus {
  case inStock(quantity: Int)
  case outOfStock(isOnBackorder: Bool)
}

If you want to conditionally show a stepper view for the quantity when in-stock and a toggle for the backorder when out-of-stock, you're out of luck when it comes to using SwiftUI's standard tools. However, the Switch view that comes with this library allows you to destructure a Binding<ItemStatus> into bindings of each case so that you can present different views:

struct InventoryItemView {
  @State var status: ItemStatus

  var body: some View {
    Switch(self.$status) {
      CaseLet(/ItemStatus.inStock) { $quantity in
        HStack {
          Text("Quantity: \(quantity)")
          Stepper("Quantity", value: $quantity)
        }
        Button("Out of stock") { self.status = .outOfStock(isOnBackorder: false) }
      }

      CaseLet(/ItemStatus.outOfStock) { $isOnBackorder in
        Toggle("Is on back order?", isOn: $isOnBackorder)
        Button("In stock") { self.status = .inStock(quantity: 1) }
      }
    }
  }
}

Binding transformations

This library comes with tools that transform and destructure bindings of optional and enum state, which allows you to build your own navigation views similar to the ones that ship in this library.

  • Binding.init(unwrapping:)
  • Binding.case(_:)
  • Binding.isPresent() and Binding.isPresent(_:)

For example, suppose you have built a BottomSheet view for presenting a modal-like view that only takes up the bottom half of the screen. You can build the entire view using the most simplistic domain modeling where navigation is driven off a single boolean binding:

struct BottomSheet<Content>: View where Content: View {
  @Binding var isActive: Bool
  let content: () -> Content

  var body: some View {
    ...
  }
}

Then, additional convenience initializers can be introduced that allow the bottom sheet to be created with a more concisely modeled domain.

For example, an initializer that allows the bottom sheet to be presented and dismissed with optional state, and further the content closure is provided a binding of the non-optional state. We can accomplish this using the isPresent() method and Binding.init(unwrapping:):

extension BottomSheet {
  init<Value, WrappedContent>(
    unwrapping value: Binding<Value?>,
    @ViewBuilder content: @escaping (Binding<Value>) -> WrappedContent
  )
  where Content == WrappedContent?
  {
    self.init(
      isActive: value.isPresent(),
      content: { Binding(unwrapping: value).map(content) }
    )
  }
}

An even more robust initializer can be provided by providing a binding to an optional enum and a case path to specify which case of the enum triggers navigation. This can be accomplished using the case(_:) method on binding:

extension BottomSheet {
  init<Enum, Case, WrappedContent>(
    unwrapping enum: Binding<Enum?>,
    case casePath: CasePath<Enum, Case>,
    @ViewBuilder content: @escaping (Binding<Case>) -> WrappedContent
  )
  where Content == WrappedContent?
  {
    self.init(
      unwrapping: `enum`.case(casePath),
      content: content
    )
  }
}

Both of these more powerful initializers are just conveniences. If the user of BottomSheet does not want to worry about concise domain modeling they are free to continue using the isActive boolean binding. But the day they need the more powerful APIs they will be available.

Examples

This repo comes with lots of examples to demonstrate how to solve common and complex navigation problems with the library. Check out this directory to see them all, including:

  • Case Studies
    • Alerts & Confirmation Dialogs
    • Sheets & Popovers & Fullscreen Covers
    • Navigation Links
    • Routing
    • Custom Components
  • Inventory: A multi-screen application with lists, sheets, popovers and alerts, all driven by state and deep-linkable.

Learn More

SwiftUI Navigation's tools were motivated and designed over the course of many episodes on Point-Free, a video series exploring functional programming and the Swift language, hosted by Brandon Williams and Stephen Celis.

You can watch all of the episodes here.

video poster image

Installation

You can add SwiftUI Navigation to an Xcode project by adding it as a package dependency.

https://github.com/pointfreeco/swiftui-navigation

If you want to use SwiftUI Navigation in a SwiftPM project, it's as simple as adding it to a dependencies clause in your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.1.0")
]

Documentation

The latest documentation for the SwiftUI Navigation APIs is available here.

License

This library is released under the MIT license. See LICENSE for details.

Comments
  • Add SwiftUI's View.navigate() + CaseStudy

    Add SwiftUI's View.navigate() + CaseStudy

    SwiftUI mechanism for Navigations can be weird for good old UIKit developers.

    And for those who wants to make all their Views isolated, and make them not aware of any navigation context, it can be hard to achieve this with the current SwiftUI implementation.

    So, that's my reflexions here :

    A basic navigation is used like this :

    struct CustomView: View {
      var body: some View {
        VStack {
          // ...View contents
          NavigationLink("MyNavigationButton") {
            MyDestinationView()
          }
          // ...View contents
        }
      }
    }
    

    That NavigationLink must be in the view that makes navigation. But what if we want to make our Custom view completely isolated from Navigation context ? For example, the navigation is triggered by some Button, but we don't want to do this directly via NavigationLink, because it can have multiple destinations, depending on app context.

    So, swiftui-navigation is a good start for this. We can use pointfree's amazing tools as a start. Using the new inits with unwrapping / case parameters and a Route enum, we can easily drive the navigation.

    But there's the last problem : Our view still knows about the NavigationLink.

    Here's the solution : we can make a custom ViewModifier to wrap that binding-controlled NavigationLink in a new Custom view, which automatically embeds your CustomView, with the desired NavigationLink. As the NavigationLink view is an empty view, we can easily hides it with the modifier (see the implementation in PR).

    So then, we can have something like this :

    struct MyApp: App {
      var body: some View {
        CustomView()
          .navigate(when: $route, is: /Route.destination) { _ in
            MyDestination()
          }
      }
    }
    

    For a more complete example, look at the 10 CaseStudy included in the PR. This is a first draft for this feature. Sure it can be improved.

    opened by jtouzy 19
  • Using IfLet requires cancellation/confirmation twice

    Using IfLet requires cancellation/confirmation twice

    Describe the bug I am driving a detail screen with an Edit/Confirm logic. Entering the "edit" works as expected, but the Confirm- and the Cancel-button need to be tapped twice in order to end editing. The editing itself is bound to an optional string that is set when the Edit-button is tapped.

    The view is driven off of an IfLet.

    Please forgive me if this is something explained in one of your videos–as a subscriber of your content feel free to just point me to a related video if available :-)

    To Reproduce This is a sample project mimicking the behaviour I see in my project.

    import SwiftUI
    import SwiftUINavigation
    
    @main
    struct so_IfLetIssueApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
    struct ContentView: View {
        @State private var title = "Some fancy title"
        @State private var editingTitle: String?
        
        var body: some View {
            NavigationStack {
                Form {
                    IfLet($editingTitle) { $editingTitle in
                        TextField("Title", text: $editingTitle)
                    } else: {
                        Text(title)
                    }
                }
                .toolbar {
                    ToolbarItem(placement: .confirmationAction) {
                        Button(editingTitle == nil ? "Edit" : "Confirm") {
                            if let editingTitle {
                                title = editingTitle
                                self.editingTitle = nil
                            } else {
                                editingTitle = title
                            }
                        }
                    }
                    if editingTitle != nil {
                        ToolbarItem(placement: .cancellationAction) {
                            Button("Cancel") {
                                editingTitle = nil
                            }
                        }
                    }
                    ToolbarItem(placement: .bottomBar) {
                        Text("editingTitle is \"\(editingTitle == nil ? "nil" : editingTitle!)\"")
                    }
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
        
    }
    
    

    Expected behavior Tapping the "Confirm" or the "Cancel" button once should end editing by setting the optional binding to nil.

    Screencast

    https://user-images.githubusercontent.com/91844195/202385876-bbebb8e8-f50c-4c07-9302-ca7d594b7f98.mp4

    Environment

    • swiftui-navigation version [e.g. 0.1.0]
    • Xcode Version 14.1 (14B47b)
    • Swift 5.5
    • OS: macOS 12.6.1 (21G217)
    apple bug 
    opened by appfrosch 4
  • onNavigate being called twice on tapping default back button of NavigationLink

    onNavigate being called twice on tapping default back button of NavigationLink

    Describe the bug onNavigate being called twice on tapping the default back button of NavigationLink

    To Reproduce

    struct ContentView: View {
    
        enum Route {
            case a
            case b
            case c
        }
    
        @State var route: Route?
    
        var body: some View {
            NavigationView {
                NavigationLink(
                    unwrapping: $route,
                    case: /Route.a,
                    destination: { _ in Text("A detail") },
                    onNavigate: {
                        route = $0 ? .a : nil }
                ) { Text("A row") }
            }
        }
    }
    
    

    Expected behavior onNavigate should be called only once when we tap on back button

    Screenshots NA

    Environment

    • swiftui-navigation version: Main branch
    • Xcode: 13.0
    • Swift: 5.5
    • OS: 11.6.1

    Additional Comment

    In PointFree demo project, if I comment this: .navigationBarBackButtonHidden(true), then this same issue can be reproduced in that demo project as well.

    Screen Shot 2021-12-20 at 1 14 16 pm apple bug 
    opened by inal 4
  • "Add" button unresponsive in Inventory Example

    Describe the bug When running the Inventory example, the Add button sometime becomes unresponsive after the "Add" modal sheet is dismissed. Not sure when or what triggers it because it doesn't happen all the time.

    To Reproduce Run the example, open the "Add" modal, save an item and tap "Add" again. Repeat.

    Expected behavior The "Add" button to work all the time.

    Environment

    • swiftui-navigation version 0.1.0
    • Xcode 13.1
    • Swift 5.5
    • OS: iOS 15 Simulator
    opened by mbuchetics 4
  • Adds custom Equatable implementation for ButtonState

    Adds custom Equatable implementation for ButtonState

    The new ButtonState has an id with default UUID() value which breaks test in a TCA application. ("// Not equal but no difference detected:"). The custom equality check ignores the id

    opened by mmllr 2
  • `Binding.case(_:)` modifier behavior changed unexpectedly in v0.4.x

    `Binding.case(_:)` modifier behavior changed unexpectedly in v0.4.x

    Describe the bug Updates to Binding.swift in #28 changed behavior in the way I'm using the case(_:) modifier to help drive navigation to nested routes in my iOS 15+ app.

    It's possible I'm using this modifier based on incorrect expectations, but since this presents itself as a regression based on my current usage I'm using the bug template. Please let me know what additional information I can provide.

    To Reproduce Run the contrived app below. Both buttons should present a sheet that indicates their selection. The sheet correctly displays using library version 0.3.x and never displays when using version 0.4.x

    import SwiftUI
    import SwiftUINavigation
    
    @main
    struct Application: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
    struct ContentView: View {
        enum Route {
            case component(Component.Route)
        }
    
        @State var route: Route?
    
        var body: some View {
            VStack {
                Text("Hello user!")
    
                Component(route: $route.case(/Route.component))
            }
            .sheet(unwrapping: $route, case: /Route.component) { $route in
                Text("You selected \(route == .optionA ? "Option A" : "Option B")")
            }
        }
    }
    
    struct Component: View {
        enum Route {
            case optionA
            case optionB
        }
    
        @Binding var route: Route?
    
        var body: some View {
            VStack {
                Button("Option A") {
                    route = .optionA
                }
    
                Button("Option B") {
                    route = .optionB
                }
            }
        }
    }
    

    Expected behavior I'd expect this modifier to behave in v0.4.x as it did in v0.3.0, based on my understanding of the usage of that modifier.

    Screenshots n/a

    Environment

    • swiftui-navigation version 0.3.0 v 0.4.4
    • Xcode 14.1
    • Swift 5.7
    • OS: iOS 15+

    Additional context n/a

    opened by maciesielka 1
  • Directly modify the view in `bind` instead of relying on a `ViewModifier`

    Directly modify the view in `bind` instead of relying on a `ViewModifier`

    This seems to fix #44. For some reason, it doesn't works with a wrapper view _Bind<ModelValue: _Bindable, ViewValue: _Bindable, Content: View>: View. I initially thought that the escaping closure of WithState was providing enough invalidation to make the changes effective, but it also works if one removes WithState (and the hasAppeared logic), so I don't really know what's happening here. Maybe the stored _Bindable are interfering with SwiftUI's diffing algorithm and are not properly observed/handled. This PR is mostly there to serve as a basis for discussion, as many things are still nebulous.

    opened by tgrapperon 1
  • Remove `id` from `ButtonState` and `AlertState`

    Remove `id` from `ButtonState` and `AlertState`

    If I'm not mistaken, Identifiable is only used to simplify ForEach iteration in the actions.

    This PR removes the uncontrolled Identifiable conformance for ButtonState and AlertState. The button's array is indexed before iterating over it, and theses indices are used as unique identifiers. Because of the nature of Alert and Confirmation dialog, this likely won't have unexpected side-effects. We could probably use enumerated() instead of zip here and I can update the PR if you want.

    It also restores automatic Equatable and Hashable conformance for ButtonState. In my experience, using a partial Equatable conformance inevitably lead to incoherent situations and invariants being broken in the long run.

    Please let me know if I'm missing something and Identifiable is used in other places than these loops.

    opened by tgrapperon 1
  • Add `Binding.removeDuplicates()`

    Add `Binding.removeDuplicates()`

    Because this library makes it easy to add logic around navigation, it's probably also a good idea to ship with helpers that work around some surprising bugs/behaviors that currently exist in SwiftUI. For example, as noticed by #10, NavigationLink writes nil to its binding twice on dismissal.

    It's probably not appropriate for us to automatically filter duplicate writes, but we can at least ship a Binding.removeDuplicates() that makes it easy to achieve this behavior.

    opened by stephencelis 1
  • Use recommended NavagationLink init

    Use recommended NavagationLink init

    Apple's online documentation says that the version of NavigationLink.init being uses is deprecated.

    https://developer.apple.com/documentation/swiftui/navigationlink/init(destination:isactive:label:)

    opened by john-flanagan 1
  • Case study for list of navigation links.

    Case study for list of navigation links.

    Thought it would be nice to have a case study showing how to deal with a list of navigation links. This is the simplest kind (i.e. each row has a route enum and we only pass binding to child view), but it's at least something. Maybe in the future we demonstrate a more complicated one like what is in the inventory case study.

    opened by mbrandonw 1
  • Allow Binding<Case?> to write into nil or when the case matches

    Allow Binding to write into nil or when the case matches

    Allowing Binding.case to always be writable may result in unwanted behaviour. If a binding changes case, it may be subsequently set to nil after the previous case is dismissed. Writing when the enum is nil or when the case matches could be a solution. I'm unsure if this is a common use case or if this change introduces some other unwanted behaviour. I just noticed a difference while replacing some customer helpers.

    An example:

    struct ContentView: View {
      @State var route: Route?
    
      enum Route { case foo, bar }
    
      var body: some View {
        VStack {
          Button("Show Foo") { self.route = .foo }
        }
        .confirmationDialog(
          title: { Text("Foo") },
          unwrapping: self.$route.case(/Route.foo),
          actions: { Button("Show Bar") { self.route = .bar } },
          message: { Text("Foo") }
        )
        .sheet(unwrapping: self.$route.case(/Route.bar)) { _ in
          Text("Bar")
        }
      }
    }
    
    opened by iampatbrown 3
  • Docc compilation error

    Docc compilation error

    Hello, I am trying to build the documentation of my app (Product -> Build Documentation). However, it fails to compile for the _SwiftUINavigationState target in this project. image.

    It looks like it is working for other TCA libraries, just this target in particular has this problem.

    opened by PierreCapo 0
  • Are accessibility labels supported within ConfirmationDialogState?

    Are accessibility labels supported within ConfirmationDialogState?

    Let's say I want to initialize a ConfirmationDialogState object within a reducer like this:

    state.confirmationDialog = .init(
      title: .init("Skip tutorial?"),
        buttons: [
          .destructive(
            .init("Yes, skip"), action: .send(.confirmDeletionButtonTapped, animation: .default)
            ),
          .cancel(.init("No, resume"), action: .send(.dismissDeletionPrompt))
        ]
    )
    

    In addition, I want to add accessibility labels (for example, I want to have a confirmation dialog display a timestamp but for VoiceOver users, I want it to not say 'zero colon 54'). I think the way to do this the SwiftUI way would be:

    state.confirmationDialog = .init(
      title: .init("Skip tutorial?"),
        buttons: [
          .destructive(
            .init("Yes, skip").accessibilityLabel("HELLO WORLD"), action: .send(.confirmDeletionButtonTapped, animation: .default)
            ),
          .cancel(.init("No, resume").accessibilityLabel("HOWDY"), action: .send(.dismissDeletionPrompt))
        ]
    )
    

    Doing this however does not actually pass accessibility labels as I'd expect, and it might have to do with how the convenience initializers and helper functions setup the buttons. But I do see that TCA includes accessibility support in TextState.swift. Perhaps I'm missing how to properly use accessibility labels? Or is there an issue with how ConfirmationDialogState supports accessibility labels? I'd be happy to debug this myself and create a PR, but I want to make sure I'm not missing anything. Thanks!

    opened by acosmicflamingo 4
Releases(0.4.5)
  • 0.4.5(Dec 14, 2022)

    What's Changed

    • Fixed regression: Binding.case can rewrite cases again (https://github.com/pointfreeco/swiftui-navigation/pull/54).

    Full Changelog: https://github.com/pointfreeco/swiftui-navigation/compare/0.4.4...0.4.5

    Source code(tar.gz)
    Source code(zip)
  • 0.4.4(Dec 14, 2022)

    What's Changed

    • Fixed: ButtonState.init should not be ambiguous when action is omitted (https://github.com/pointfreeco/swiftui-navigation/pull/49).
    • Fixed: SwiftUI.Alert.Button.init is now passed an empty closure, not nil, when the action is omitted to prevent disabling the button on older platforms (iOS <15) (thanks @ski-u, https://github.com/pointfreeco/swiftui-navigation/pull/50).

    New Contributors

    • @ski-u made their first contribution in https://github.com/pointfreeco/swiftui-navigation/pull/50

    Full Changelog: https://github.com/pointfreeco/swiftui-navigation/compare/0.4.3...0.4.4

    Source code(tar.gz)
    Source code(zip)
  • 0.4.3(Dec 5, 2022)

    What's Changed

    • Fixed: macOS focus state synchronized with View.bind should now synchronize correctly (thanks @tgrapperon, https://github.com/pointfreeco/swiftui-navigation/pull/46).
    • Infrastructure: Confirmation dialog iPad/popover case study improvement (thanks @Jager-yoo, https://github.com/pointfreeco/swiftui-navigation/pull/38); README improvements (thanks @qmoya, https://github.com/pointfreeco/swiftui-navigation/pull/34); other case study improvements (thanks @Jager-yoo, https://github.com/pointfreeco/swiftui-navigation/pull/42); documentation typo fix (thanks @Shinolr, https://github.com/pointfreeco/swiftui-navigation/pull/43).

    New Contributors

    • @qmoya made their first contribution in https://github.com/pointfreeco/swiftui-navigation/pull/34
    • @Shinolr made their first contribution in https://github.com/pointfreeco/swiftui-navigation/pull/43
    • @tgrapperon made their first contribution in https://github.com/pointfreeco/swiftui-navigation/pull/46

    Full Changelog: https://github.com/pointfreeco/swiftui-navigation/compare/0.4.2...0.4.3

    Source code(tar.gz)
    Source code(zip)
  • 0.4.2(Nov 22, 2022)

    What's Changed

    • Exclude id from ButtonState's equatable conformance by @konomae in https://github.com/pointfreeco/swiftui-navigation/pull/33

    New Contributors

    • @konomae made their first contribution in https://github.com/pointfreeco/swiftui-navigation/pull/33

    Full Changelog: https://github.com/pointfreeco/swiftui-navigation/compare/0.4.1...0.4.2

    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Nov 21, 2022)

    What's Changed

    • Some small fixes by @mbrandonw in https://github.com/pointfreeco/swiftui-navigation/pull/31

    Full Changelog: https://github.com/pointfreeco/swiftui-navigation/compare/0.4.0...0.4.1

    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Nov 21, 2022)

    What's Changed

    • Fix typos in README and CaseStudies by @Jager-yoo in https://github.com/pointfreeco/swiftui-navigation/pull/30
    • Support navigationDestination, alerts, confirmation dialogs, and more! in https://github.com/pointfreeco/swiftui-navigation/pull/28. Read our blog post for more information, and check out the new documentation.

    New Contributors

    • @Jager-yoo made their first contribution in https://github.com/pointfreeco/swiftui-navigation/pull/30

    Full Changelog: https://github.com/pointfreeco/swiftui-navigation/compare/0.3.0...0.4.0

    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Oct 19, 2022)

    • Change: Swift tools version has been bumped to 5.5.
    • Change: Switch will now runtime warn instead of breakpoint when a case is left unhandled.
    • Change: NavigationLink's onNavigate and destination closures have been flipped. The original order is now deprecated.
    • Change: Many APIs that required escaping closures no longer do.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Jul 13, 2022)

    • Added: Binding.removeDuplicates(), for ignoring the upstream binding's set when the wrapped value remains unchanged.
    • Fixed: IfCaseLet's else branch will now render (thanks @junebash).
    • Infratructure: README fixes (thanks @AnasAlhasani), and case study improvements.
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Nov 16, 2021)

Owner
Point-Free
A video series exploring Swift and functional programming.
Point-Free
A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles while pushing or popping a view controller for all orientations. And you don't need to write any line of code for it, it all happens automatically.

Zhouqi Mo 3.3k Dec 21, 2022
FlowStacks allows you to hoist SwiftUI navigation and presentation state into a Coordinator

FlowStacks allow you to manage complex SwiftUI navigation and presentation flows with a single piece of state. This makes it easy to hoist that state into a high-level coordinator view. Using this pattern, you can write isolated views that have zero knowledge of their context within the navigation flow of an app.

John Patrick Morgan 471 Jan 3, 2023
SwiftUINavigator: a lightweight, flexible, and super easy library which makes SwiftUI navigation a trivial task

The logo is contributed with ❤️ by Mahmoud Hussein SwiftUINavigator is a lightwe

OpenBytes 22 Dec 21, 2022
sRouting - The lightweight navigation framework for SwiftUI.

sRouting The lightweight navigation framework for SwiftUI. Overview sRouting using the native navigation mechanism in SwiftUI. It's easy to handle nav

Shiro 8 Aug 15, 2022
Change SwiftUI Navigation Bar Color for different View

SwiftUINavigationBarColor Change SwiftUI NavigationBar background color per screen. Usage For NavigationBarColor to work, you have to set the Navigati

Hai Feng Kao 18 Jul 15, 2022
🧭 SwiftUI navigation done right

?? NavigationKit NavigationKit is a lightweight library which makes SwiftUI navigation super easy to use. ?? Installation ?? Swift Package Manager Usi

Alex Nagy 152 Dec 27, 2022
Navigation helpers for SwiftUI applications build with ComposableArchitecture

Swift Composable Presentation ?? Description Navigation helpers for SwiftUI applications build with ComposableArchitecture. More info about the concep

Dariusz Rybicki 52 Dec 14, 2022
Make SwiftUI Navigation be easy

VNavigator VNavigator is a clean and easy-to-use navigation in SwiftUI base on UINavigationController in UIKit Installation From CocoaPods CocoaPods i

Vu Vuong 10 Dec 6, 2022
SwiftUINavigation provides UIKit-like navigation in SwiftUI

SwiftUINavigation About SwiftUINavigation provides UIKit-like navigation in Swif

Bhimsen Padalkar 1 Mar 28, 2022
A lightweight iOS mini framework that enables programmatic navigation with SwiftUI, by using UIKit under the hood.

RouteLinkKit A lightweight iOS mini framework that enables programmatic navigation with SwiftUI. RouteLinkKit is fully compatible with native Navigati

Αθανάσιος Κεφαλάς 4 Feb 8, 2022
Simple iOS app to showcase navigation with coordinators in SwiftUI + MVVM.

SimpleNavigation Simple iOS app to showcase the use of the Coordinator pattern using SwiftUI and MVVM. The implementation is as easy as calling a push

Erik Lopez 7 Dec 6, 2022
Custom navigation swiftui NavigationLink NavigationView

Custom navigation swiftui Experimenting with navigation link. if you find this idea interesting you can take and expend it into a more powerful soluti

Igor 5 Dec 2, 2022
Backported SwiftUI navigation APIs introduced in WWDC22

Navigation Backport This package uses the navigation APIs available in older SwiftUI versions (such as NavigationView and NavigationLink) to recreate

John Patrick Morgan 532 Dec 29, 2022
Easily hide and show a view controller's navigation bar (and tab bar) as a user scrolls

HidingNavigationBar An easy to use library (written in Swift) that manages hiding and showing a navigation bar as a user scrolls. Features Usage Custo

Tristan Himmelman 1k Dec 21, 2022
A wrapper for NavigationView and NavigationLink that makes programmatic navigation a little friendlier.

NavigatorKit A wrapper for NavigationView and NavigationLink that makes programmatic navigation a little friendlier. NavigatorKit is an opinionated wr

Gyuri Grell 2 Jun 16, 2022
Simple and integrated way to customize navigation bar experience on iOS app.

NavKit Simple and integrated way to customize navigation bar experience on iOS app. It should save our time that we usually use to make abstraction of

Wilbert Liu 37 Dec 7, 2022
Models UI navigation patterns using TCA

Composable Navigation The Composable Navigation is a Swift Package that builds on top of The Composable Architecture (TCA, for short). It models UI na

Michael Heinzl 41 Dec 14, 2022
An iOS view-controller navigation management. No inherit, using one line code to integrate.

KGNavigationBar Example An iOS view-controller navigation management. No inherit, using one line code to integrate. 一个 iOS 控制器导航管理库. 无需继承, 一行代码即可实现集成。

VanJay 5 Sep 6, 2021
Powerful navigation in the Composable Architecture via the coordinator pattern

TCACoordinators The coordinator pattern in the Composable Architecture TCACoordinators brings a flexible approach to navigation in SwiftUI using the C

John Patrick Morgan 231 Jan 7, 2023