Giter VIP home page Giter VIP logo

swift-composable-presentation's Introduction

Swift Composable Presentation

Swift v5.7 platforms iOS, macOS

Presentation and navigation helpers for SwiftUI applications build with ComposableArchitecture.

📝 Description

If you are familiar with ComposableArchitecture, you know how to build a fully-featured application from isolated components. You can combine reducers and scope stores to achieve that. However, when one component optionally embeds another one (or presents in navigation meaning), it could be tricky to handle side effects correctly.

The ComposablePresentation is a library that helps to compose reducers in apps built with ComposableArchitecture. You can use it whenever you need to conditionally present a component, like when presenting a sheet, navigating between screens, or displaying a list of components. It also comes with SwiftUI helpers for sheets, navigation links, for-each-store, and other presentations.

.presenting higher order reducer allows combining the reducer with a reducer of an optionally presented component. Once the component is dismissed (its optional state changes from honest value to nil), the effects returned by its reducer are automatically canceled.

More info about the concept can be found in the article: Thoughts on SwiftUI navigation.

🏛 Project structure

ComposablePresentation (Xcode Workspace)
 ├─ swift-composable-presentation (Swift Package)
 |   └─ ComposablePresentation (Library)
 └─ Example (Xcode Project)
     └─ Example (iOS Application)

📖 Usage

Use Swift Package Manager to add ComposablePresentation as a dependency to your project.

This repository contains an example iOS application built with SwiftUI and ComposableArchitecture.

  • Open ComposablePresentation.xcworkspace in Xcode.
  • Example source code is contained in the Example Xcode project.
  • Run the app using the Example build scheme.

SwiftUI example that shows how to present a sheet with content driven by a store with an optional state. The sheet is presented when the store's state has an honest value and dismisses when it becomes nil. All effects produced by the store's reducer while the content is presented are canceled when the sheet is dismissed.

SwiftUI example that shows how to present a full-screen cover with content driven by a store with an optional state. The cover is presented when the store's state has an honest value and dismisses when it becomes nil. All effects produced by the store's reducer while the content is presented are canceled when the cover dismisses.

SwiftUI example with .navigationDestination driven by a store with an optional state. The link is active when the store's state has an honest value, and inactive when it becomes nil. All effects produced by the store's reducer while the content is presented are canceled when the destination dismisses.

SwiftUI example with a list of components. When a component is deleted from the list, all effects produced by its reducer are canceled.

SwiftUI example that shows how to dismiss multiple navigation links at once (poping back to root view) in state-driven navigation. All effects produced by reducers of presented stores are canceled on dismiss.

In most cases, the presentation has a corresponding present and dismiss animations. When we drive it with an optional state, it becomes a problem. Let's say we want to programmatically dismiss a sheet, so we set its state to nil. It triggers the dismiss animation, but due to the fact that our state is already nil, we can't present the sheet content during this transition. As a workaround, ComposablePresentation provides replayNonNil function that can be passed to the optional mapState parameter of NavigationLinkWithStore, View.sheet, View. popover, and other SwiftUI helper functions.

SwiftUI example of a component that conditionally presents one of two child components. The state is modeled as an enum with two cases. All effects produced by the child component are canceled when the child component is removed from the parent component.

SwiftUI example of a component that presents a mutually-exclusive destination. The destination is represented by an optional enum case. Each case is presented using a different navigation pattern (navigation link, sheet, alert, etc.). All effects produced by the presented component are canceled when the destination is dismissed or changed. This design of modeling navigation is inspired by "Modern SwiftUI" video series by Point-Free.

Navigation composed with this library is driven by a declarative state. This makes it easy to handle deep links and navigate to any screen of the app. Several workarounds were applied to fix SwiftUI navigation issues (this was only possible thanks to swiftui-navigation library by Point-Free). Change the initial state in Example/App.swift to navigate to any screen when the app starts.

SwiftUI example of driving NavigationStack with a Store. The state provides an identifiable array of destination states, which indices make a navigation path. Navigation happens when the array is mutated. This example requires iOS ≥ 16.

🛠 Develop

  • Use Xcode (version ≥ 14).
  • Clone the repository or create a fork & clone it.
  • Open ComposablePresentation.xcworkspace in Xcode
  • Use ComposablePresentation scheme for building the library and running unit tests.
  • If you want to contribute:
    • Create a pull request containing your changes or bugfixes.
    • Make sure to add tests for the new/updated code.

☕️ Do you like the project?

Buy Me A Coffee

📄 License

Copyright © 2021 Dariusz Rybicki Darrarski

License: MIT

swift-composable-presentation's People

Contributors

darrarski avatar saroar 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

Watchers

 avatar  avatar  avatar  avatar

swift-composable-presentation's Issues

Showing All Messages The package product 'ComposableArchitecture' requires minimum platform version 10.15 for the macOS platform, but this target supports 10.10

I am using this package inside another package
when I start the test I am getting this error I can't run my test
here is my app link https://github.com/AddaMeSPB/AddaMeIOS/blob/feature/git_workflow/AddameSPM/Tests/EventViewTests/EventsViewTests.swift#L19

Steps to reproduce

you can just grab my app and run test

Actual behavior

test should run without any error

Attachments

Screenshot 2021-09-15 at 15 47 31

Showing All Messages The package product 'ComposableArchitecture' requires minimum platform version 10.15 for the macOS platform, but this target supports 10.10

Add support for macOS platform

Summary

Currently, the swift-composable-presentation package only supports the iOS platform. It should be possible to add support for the macOS platform, as most of the code included in the package is platform-independent. When needed, the code that only targets the iOS platform can be wrapped in #if os(iOS) and #endif.

Initial App State with routes populated isn't rendered correctly

Summary

I've experienced an issue where it seems that "deep linking" more than 1 screen (via NavigationLink) "down" doesn't work as expected.

Steps to reproduce

  1. Replace the body function in the PopToRootExample.swift with the following:
  var body: some View {
    NavigationView {
      RootView(store: Store(
        initialState: RootState(
            timer: .init(),
            first: .init(
                timer: .init(),
                second: .init(timer: .init())
            )
        ),
        reducer: Self.rootReducer.debug(),
        environment: ()
      ))
    }
    .navigationViewStyle(StackNavigationViewStyle())
  }
  1. Run the app
  2. Tap the "Pop To Root Example"

Expected behavior

I'd expect the app to display the Second Screen in the PopToRoot Example.

Actual behavior

The app opens up displaying the First Screen, and tapping the "Present Second" button doesn't do anything.

Hide Tababr

Here is some code we can follow :) working CODE

public struct HidableTabView<SelectionValue, Content>: View where SelectionValue: Hashable, Content: View {
  let isHidden: Bool
  let selection: Binding<SelectionValue>?
  let content: () -> Content

  @State private var currentTabBarHeight: CGFloat = 0

  init(
    isHidden: Bool,
    selection: Binding<SelectionValue>?,
    @ViewBuilder content: @escaping () -> Content
  ) {
    self.isHidden = isHidden
    self.selection = selection
    self.content = content
  }

  public init(
    isHidden: Bool,
    @ViewBuilder content: @escaping () -> Content
  ) where SelectionValue == Int {
    self.isHidden = isHidden
    self.selection = nil
    self.content = content
  }

  public var body: some View {
    tabView
      .stackNavigationViewStyle()
      .padding(.bottom, isHidden ? -self.currentTabBarHeight : 0)
  }

  @ViewBuilder var tabView: some View {
    if selection != nil {
      TabView(selection: self.selection!) { self._content }
    } else {
      TabView { self._content }
    }
  }

  // swiftlint:disable identifier_name
  @ViewBuilder var _content: some View {
    self.content()
      .background(tabBarHeightReader)
  }

  var screenBottomSafeAreaInset: CGFloat {
    (UIApplication.shared.windows.first { $0.isKeyWindow }?.safeAreaInsets.bottom ?? 34)
  }

  var tabBarHeightReader: some View {
    GeometryReader { proxy in
      Color.clear
        .onAppear {
          self.currentTabBarHeight = proxy.safeAreaInsets.bottom + screenBottomSafeAreaInset
        }
        .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
          self.currentTabBarHeight = proxy.safeAreaInsets.bottom + screenBottomSafeAreaInset
        }
    }
  }
}

Support ForEachStore

Summary

Add Reducer.presenting extension that works with ForEachStore and cancels effects when an element is dismissed (similarly to other Reducer.presenting extensions).

Improve library API

Summary

  • Adjusting function names to be more familiar with ComposableArchitecture API.
  • Add APIs for presenting popover and full-screen cover in SwiftUI and ComposableArchitecture.
  • ⚠️ Introduces breaking changes to the library API.

ForEachStoreExample Crash

Summary

When I click on Add Timer 16 times it crashed

Steps to reproduce

  1. Go to ForEachStoreExample
  2. Click on Add Timer at least 16 times

Support Custom transition

Hi guy
I want to make custom transition "flipHorizontal" like UIKit:

guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") else { return }
vc.modalTransitionStyle = .flipHorizontal
vc.modalPresentationStyle = .currentContext
show(vc, sender: self)

How can I make that transition with your library?

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.