Git Product home page Git Product logo

pointfreeco / swift-composable-architecture Goto Github PK

View Code? Open in Web Editor NEW
11.4K 207.0 1.3K 124.84 MB

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.

Home Page: https://www.pointfree.co/collections/composable-architecture

License: MIT License

Makefile 0.18% Swift 99.82%
architecture composition modularity testability swiftui uikit

swift-composable-architecture's People

Contributors

bjford avatar bricklife avatar ccxla avatar dannyhertz avatar filblue avatar finestructure avatar hmhv avatar iampatbrown avatar jager-yoo avatar jeffersonsetiawan avatar jessetipton avatar kalupas226 avatar kgrigsby59 avatar konomae avatar lukeredpath avatar mackoj avatar mbrandonw avatar mluisbrown avatar nickkohrn avatar oronbz avatar peterkovacs avatar rhysm94 avatar ryu0118 avatar stephencelis avatar takehilo avatar tgrapperon avatar wendyliga avatar woxtu avatar x-0o0 avatar yimajo 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  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

swift-composable-architecture's Issues

How to coalesce shared object changes from two child states into a global property?

Let's say you have a global feature state that aggregates two other child states. Those child states are sharing some objects, let's say they can both show some of the same todo items. When the top level feature state is pulled back and assigned, the order of changes is lost (which child's change to the same object comes first?).

Here's some pseudocode, 3 items where one is shared between two lists:

let item1, item2, item3
let masterList = [item1, item2, item3]
let someListOfSharedItems = [item1, item2]
let anotherListOfSharedItems = [item1, item3]

the global state property of the feature:

var someFeature: SomeFeatureState {
    get {
        SomeFeatureState(firstList: someListOfSharedItems,
                         secondList: anotherListOfSharedItems)
    }
    set {
        let list1 = newValue.someListOfSharedItems
        let lsit2 = newValue.anotherListOfSharedItems
        
        // which list goes first into the global???
        self.masterList = updateFromList(list1)
        self.masterList = updateFromList(list2)
    }
}

That is, the 'setter' of the feature state won't know which of the two children made the change, and potentially one could overwrite the other as it is being assigned to some global property.

Is there a best practice here for when children share state objects that are coalesced at a top level state property?

Subscriptions

Continuing a discussion from the private repo between @opsb, @stephencelis and myself. I still think that adding explicit support for something like Elm's subscriptions is desirable.

Context (using the MotionManager example):

  • Subscriptions are long running effects whose lifecycle is determined purely based on the latest state. They are evaluated after each state change, diffing against the old subscriptions and cancelling old ones / subscribing new ones as appropriate. Think: whenever the latest call to subscriptions returns environment.motionManager.deviceMotionUpdates(), we are sure to be observing motion updates.
  • This is more powerful than the current way of doing long running effects. Currently, the lifecycle of long running effects depends on the history of sent Actions. Think: we are observing motion updates if in the past there was an Action (e.g. .onAppear) that caused reducer to return environment.motionManager.create() and an Action that caused reducer to return environment.motionManager.startDeviceMotionUpdates and there was no subsequent actions that caused reducer to return environment.motionManager.stopDeviceMotionUpdates(). In some usecases, there could also be a subsequent action that ended up cancelling some of those effects.
  • An app that properly uses subscriptions can recover from time-travel, i.e. jumping to any state.
  • Time-travel is the strongest but not the only argument. Deriving more of an app's behavior from the latest state as opposed to the whole history of execution greatly reduces complexity and is ideologically in-line with the latest trends (SwiftUI vs. UIKit, realtime databases like Firestore vs. polling a database) and the philosophy of Elm/composable-architecture.
  • subscriptions can be an optional part of the Reducer type and can support all of the same composability features. Most of the time, adopting subscriptions will result in less client code.
  • explicit support for subscriptions is more powerful than anything that can be achieved with higher-order reducers (since those still rely on at least a single "bootstrap" action making it through the particular child reducer)
  • Cons include:
    • larger footprint for the composable-architecture framework
    • another concept to explain to new users
    • a naming conundrum, where Reducer is no longer "just a Reducer"
    • sometimes its more practical to use regular Effects (think GET request in onAppear), so some apps may still not be time-travel-proof

Ive copied my prototype implementation from the old private repo to pteasima#1 . This doesnt yet have all the required functionality, especially the part around diffing old and new subscriptions. Its there to show that the changes required to the library arent that big, impact on existing client code can be 0, and to start a discussion on future steps.

Thoughts?

Merge the core library with the test support library

(NB: This issue subsumes #57 and #60)

Due to an SPM/Xcode bug we cannot have separate ComposableArchitecture and ComposableArchitectureTestSupport libraries. The bug is that SPM packages cannot vend two libraries where one depends on the other. The bug manifests itself by simply not compiling (if you split your app into multiple modules), or crashing the test suite (this happens most often on iOS 11.3, as seen in #52).

We've tried our hardest to work around this bug, but each thing we do seems to just cause another problem.

So, until the bug is fixed we have two options:

  • Split the test support library into own repository
  • Combine the test support code into the core library

The first option is a non-starter for us because it would be quite an extra burden for us to maintain two libraries that need parallel releases, and it would complicate how we structure the example apps in this repo.

The second option seems possible, but we are not sure how to accomplish it. The problem is that we can't put any XCTest code inside ComposableArchitecture since the library is meant to be deployed to devices. Fortunately, we are only using one single symbol from XCTest, and that is XCTFail. We've spent a few hours trying to figure out if it's possible to dynamically invoke XCTFail without importing XCTest or mentioning the symbol, but everything we've tried has come up short (including using dlopen and dlsym 😬).

Does anyone have any ideas on a path forward?

Expose Store via ViewStore

Given the issues with ViewStore and GeometryReader as explained here I'm finding myself keeping around the viewStore manually via a struct init.

    @ObservedObject var viewStore: ViewStore<State, Action>
    
    init(store: Store<State, Action>) {
        self.viewStore = ViewStore(store)
    }

This is actually a simple change because the init and the automatic memberwise init of the struct match so external users of the view don't have to change anything.

The problem comes when later (and quite often) I need to scope the store to pass it to another view.

        .sheet(
          isPresented: self.viewStore.binding(
            get: { ... },
            send: ...
          )
        ) {
          IfLetStore(
            self.store.scope(
                state: { .... },
                action: ...
            ),
            then: ...,
            else: EmptyView()
          )
        }

The viewStore let's us get a binding so the first part is alright. But for the scoping to the child view you need a Store type so you are forced now to keep it around just for this.

    private let store: Store<...>
    @ObservedObject var viewStore: ViewStore<State, Action>
    
    init(store: Store<State, Action>) {
        self.store = store
        self.viewStore = ViewStore(store)
    }

I'm finding myself having to deal with the workaround often enough that I would like to avoid this.

Two solutions come to mind:

  1. Expose the Store API from ViewStore (we probably don't want this)
  2. Expose the original store via ViewStore.

Option two would allow me to only keep the viewStore and simplify the init.

ComposableArchitecture.xcworkspace does not show up in list of recents

Describe the bug

ComposableArchitecture.xcworkspace does not show up in list of recents.

Would love to know if there's something weird with our workspace or if there is a workaround.

To Reproduce

If you open ComposableArchitecture.xcworkspace and close it, you will see that it does not show up in the list of recent projects in Xcode's initial screen or in File > Open Recent.

Expected behavior

ComposableArchitecture.xcworkspace should show up in list of recents.

Environment

  • Xcode [e.g. 11.4.1]

ForEach in LocalReducer needs more context

ForEach on Local Reducer error: Type of expression is ambiguous without more context

To Reproduce
Below code:

import ComposableArchitecture
import Contacts

typealias Contact = CNContact

extension Contact: Identifiable {} // So that ForEach, ForEachStore work

enum ContactAction: Equatable {
  case contactTapped
  case starTapped
}

struct ContactEnvironment {}

let contactReducer = Reducer<Contact, ContactAction, ContactEnvironment>.init { contact, action, environment in
    return .none
}

struct AppState: Equatable {
  var contacts: IdentifiedArrayOf<Contact> = []
  var editMode: EditMode = .inactive
}

enum AppAction: Equatable {
  case addContactTapped
  case contact(id: Int, action: ContactAction)
  case delete(IndexSet)
}

struct AppEnvironment {
  var newContact: () -> Contact
}

let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
  
  Reducer { state, action, environment in
    switch action {
    case .addContactTapped:
      state.contacts.insert(environment.newContact(), at: 0)
      return .none
    case .contact(id: let id, action: let action):
      return .none
    case .delete(let index):
      state.contacts.remove(atOffsets: index)
      return .none
    }
  }
//   FIXME: - LocalReducer is not able to TypeCheck! does not compile, ambiguous reference
  ,
  contactReducer.forEach(
    state: \AppState.contacts,
    action: /AppAction.contact(id:action:),
    environment: { _ in ContactEnvironment() }
  )
).debug()

Expected behavior
Give a clear and concise description of what you expected to happen.

Environment

  • Xcode 11.4.1
  • Swift 5.2

Binding textfield issue?

It seems binding doesn’t work with some of the other SwiftUI TextField overloads that use Value and formatting parameters.

Todos delete bug

Not sure if this is SwiftUI bug or a CA one.

  • Complete a todo while in edit mode
  • Before the sorting kicks in, swipe to reveal the Delete button
  • After the sorting, hit delete
  • The item in the old position gets deleted

Screenshots
bug

Environment

  • Xcode 11.5
  • Swift 5.3
  • Catalina

Undefined symbol trying to build a test.

This is all I have so far and I'm getting the compiler error. Am I missing something obvious?

   func testPodcastFavoriteToggle() {
        let store = TestStore(
            initialState: TopShowsState(topPodcasts: self.topPodcasts),
            reducer: topShowsReducer,
            environment: SystemEnvironment.live(environment:
                TopShowsEnvironment(dataClient: DataClient.mock())
            )
        )
    }

and my reducer's type:

public let topShowsReducer: Reducer<TopShowsState, TopShowsAction, SystemEnvironment<TopShowsEnvironment>> =

the error...

image

thanks.

Bug with explicitly defining LibraryType as 'dynamic'

Describe the bug
When including ComposableArchitecture in an existing SPM, tests are not able to run. The following error is given:

The bundle <TestFramework> couldn’t be loaded because it is damaged or missing necessary resources. Try reinstalling the bundle.

Library not loaded: @rpath/ComposableArchitecture.framework/ComposableArchitecture

I am able to eliminate this bug by removing the type: .dynamic, from both frameworks in the package.swift file. However, if I do this, the example projects do not build

To Reproduce
Todo.zip
Environment

  • Xcode [e.g. 11.4.1 & 11.3]
  • Swift [e.g. 5.2.2 & 5.1]

Doesn't work on Xcode 11.4.1

I tried to use it, and if I build and run it all works, but SwiftUI previews are broken:

warning: Could not read serialized diagnostics file: Cannot Load File: Failed to open diagnostics file (in target 'ComposableArchitecture' from project 'swift-composable-architecture') warning: Could not read serialized diagnostics file: Cannot Load File: Failed to open diagnostics file (in target 'ComposableArchitecture' from project 'swift-composable-architecture') warning: Could not read serialized diagnostics file: Cannot Load File: Failed to open diagnostics file (in target 'ComposableArchitecture' from project 'swift-composable-architecture') <unknown>:0: error: unable to execute command: Abort trap: 6 <unknown>:0: error: compile command failed due to signal 6 (use -v to see invocation)

I'm not sure if it's not prepared for this version or if it's some local config.

Linking TCA breaks on-device Xcode Previews

Describe the bug
Adding TCA as a dependency prevents on-device Xcode Previews from launching.

To Reproduce
This reproduces on the provided examples (I tried it on TicTacToe), but for a simple repro:

  1. Create a new SwiftUI project
  2. Observe that on-device Xcode Previews [kinda] work
  3. Add TCA as a dependency
  4. Observe that trying to start an on-device Xcode Preview fails with the following error:
    SchemeBuildError: Failed to build the scheme "test-tca"
    
    linker command failed with exit code 1 (use -v to see invocation)
    
    Link /Users/lenny/Library/Developer/Xcode/DerivedData/test-tca-guucwvzxaqerknctxnxofuhkuzfc/Build/Intermediates.noindex/Previews/test-tca/Products/Debug-iphoneos/PackageFrameworks/ComposableArchitectureTestSupport.framework/ComposableArchitectureTestSupport:
    ld: warning: Could not find or use auto-linked library 'XCTestSwiftSupport'
    ld: warning: Could not find or use auto-linked framework 'XCTest'
    Undefined symbols for architecture arm64:
      "__swift_FORCE_LOAD_$_XCTestSwiftSupport", referenced from:
          __swift_FORCE_LOAD_$_XCTestSwiftSupport_$_ComposableArchitectureTestSupport in ComposableArchitectureTestSupport.o
      "XCTest.XCTFail(_: Swift.String, file: Swift.StaticString, line: Swift.UInt) -> ()", referenced from:
          (extension in ComposableArchitectureTestSupport):ComposableArchitectureTestSupport.TestStore< where B: Swift.Equatable>.assert(_: ComposableArchitectureTestSupport.TestStore<A, B, C, D, E>.Step..., file: Swift.StaticString, line: Swift.UInt) -> () in ComposableArchitectureTestSupport.o
    ld: symbol(s) not found for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)

Note that this happens even if the ComposableArchitectureTestSupport is not added to any target.

Expected behavior
Xcode Previews continues to work as expected.

Screenshots
N/A

Environment

  • Xcode 11.4.1
  • Swift 5.2.2
  • OS (if applicable): 13.4.1

Additional context
I'm no expert on how the Xcode build system works, but I didn't see anything obviously wrong in the build config at first glance; I'm not sure how or why it's trying to link ComposableArchitectureTestSupport. I then assumed this was a bug with the Xcode build system, but after trying it on another repository with a test support target (https://github.com/spotify/Mobius.swift) it seems like they don't have the same issue, so maybe there's something deeper going on.

Eager navigation and load handling of cancel

Describe the bug
When you navigate back before the loading has finished, the behavior of the app gets broken.

To Reproduce

  1. In 03-Navigation-Lists-NavigateAndLoad.swift on line 46 set the delay a bit longer (e.g. 3 seconds)
  2. Run the demo
  3. Select "Load optional counter that starts from 1"
  4. Navigate back from the screen before the counter appears
  5. Now try to open the same counter again or any of the others

Observed behavior
Tapping same counter is unresponsive. Tapping other counter immediately navigates back.
Screen Recording 2020-05-24 at 11.36.09.zip
(Unfortunately GitHub won't let me upload screen recording unzipped.)

Expected behavior
Being able to use the demo as if you had waited until the counter had appeared.

Environment

  • Xcode 11.5
  • iOS 13.5
  • Simulator for iPhone SE (2nd generation)

Better way to pullback IdentifiedArray from child state?

My simple AppState below shows how I have an IdentifiedArray which I pass on to a child view. When I do the pullback for homeView property, I walk the array items and set them back into my array. Is there a reducer forEach trick I can use to not need to manually remap my child array items back into my parent array?

struct AppState: Equatable {
    var allEpisodes: IdentifiedArrayOf<EpisodeState>
    
    var homeView: HomeViewState {
        get {
            HomeViewState(episodes: allEpisodes)
        }
        set {
            for episode in newValue.episodes {
                allEpisodes[id: episode.id] = episode
            }
        }
    }
}

For ref, my appReducer:

homeViewReducer.pullback(
        state: \AppState.homeView,
        action: /AppAction.homeView,
        environment: { _ in HomeViewEnvironment() }
    )

FeatureState and FeatureAction

Hey guys,

I've been following the evolution of the TCA from the beginning and using it in my own project by simply copy pasting the code in a framework. Since the library is now open source, I'm actually removing the custom framework in favor of the Swift package so there's a lot of stuff to update.

By looking at the examples provided in the repo, I noticed that the notion of FeatureState and FeatureAction don't seem to exist anymore and I couldn't find anything on PointFree or in the repo about this. State look to be much more lightweight now (there were a lot of boilerplate with FeatureState) so I was wondering how can I achieve the same thing ?

Reducer not being applied to changes for array's elements

Hello there! First of all, I've been using TCA and I must say it's an amazing piece or work👏🏻👏🏻👏🏻

I'm facing an issue, which I hope you can help me get clarity on:

in my App State, I've got an array of some model:

struct AppState: Equatable {
    var array: [Model] = []
}

I then have a list that displays the element in the array and creates a scoped store for each element:

ForEachStore(
    self.store.scope(
        state: \.array,
        action: { ListAction.rowActionAction($0.1) }
    ),
    content:  { scopedStore in
        NavigationLink(
            destination: self.row(store: scopedStore),
            label: { Row(store: scopedStore) }
        )
    }
)

The app reducer is then composed with a reduced that works on individual elements like this:

var appReducer = Reducer.combine(
    Reducer<AppState, AppAction, AppEnvironment> { state, action, environment in
        // .........some work here.....
    },
    elementReducer.forEach(
            state: \.array, //the app reducer is called but not this one????
            action: /AppAction.listAction(index:action:),
            environment: { _ in ElementEnvironment() }
    )
)

Now the actions are properly sent, but elementReducer is never called (the reducer it is composed with is though)

Any clue?
I believe it has to do with how I compose the reducers, since the views that display data from the scoped store display the correct data

Any help is appreciated!🙏

Crash on TCA Test Support

Describe the bug
I'm getting a crash because of "Simultaneous accesses to state", apparently this is the culprit:

public func assert() {
  func runReducer(state: inout State, action: Action) {
      let effect = self.reducer.callAsFunction(&state, action, self.environment)
  ...
  }
 ...
 runReducer(state: &self.state, action: receivedAction)
 ...
}

I did this change in the assert function. by avoiding passing the state to the inner function the crash not longer happens.

    func runReducer(action: Action) {
        let effect = self.reducer.callAsFunction(&self.state, action, self.environment)
...
}

runReducer(action: self.fromLocalAction(action))

Here is the console log

Simultaneous accesses to 0x600003bbd780, but modification requires exclusive access.
Previous access (a modification) started at ComposableArchitectureTestSupport`TestStore<>.assert(_:file:line:) + 4049 (0x108adde81).
Current access (a read) started at:
0    libswiftCore.dylib                 0x00007fff51b80590 swift_beginAccess + 568
1    ComposableArchitectureTestSupport  0x0000000108ae0410 runReducer #1 <A, B, C, D, E>(state:action:) in TestStore<>.assert(_:file:line:) + 375
2    ComposableArchitectureTestSupport  0x0000000108adceb0 TestStore<>.assert(_:file:line:) + 4096

To Reproduce

import ComposableArchitectureTestSupport
import ComposableArchitecture
import XCTest

class ComposableArchitectureTests: XCTestCase {
    struct State: Equatable {}
    enum Action: Equatable { case didTap }
    struct Environment {}

    let reducer = Reducer<State, Action, Environment> {
        _, _, _ in

        return .none
    }

    func testComposableArchitecture() {
        let store = TestStore(
            initialState: State(),
            reducer: reducer,
            environment: Environment()
        )

        store.assert(
            .send(.didTap)
        )
    }
}

Expected behavior
This Should not crash.

Screenshots
If applicable, add screenshots to help explain your problem.

Environment

  • OS: iOS
  • Version: 13.0

Additional context
Add any other context about the problem here.

Consider adding xcpretty

Hi,

I was going through some failed github action, and thought that piping calls to xcodebuild with xcpretty would make for a much more pleasant experience.

Unless it was intentional? If you guys feel it's worth adding I would like to contribute with a PR.

Rename usage of AnyCancellable in Effect.async?

This was a question that @JimRoepcke brought up in a private conversation, and I think it's something worth discussing and making a decision on.

Some people may be surprised to find that the AnyCancellable that is returned from Effect.async's initialization closure is invoked when the effect is completed:

let effect = Effect.async { subscriber in 
  subscriber.send(completion: .finished)
  return AnyCancellable { 
    print("This will get called!")
  }
}

_ = effect.sink { _ in }

I would say I was surprised because the thing we are returning is an AnyCancellable yet the effect wasn't cancelled. However, the subscriber was deallocated, and the subscriber holds onto the cancellable, which then triggers the cancel.

You can even verify something similar in non-TCA, vanilla Combine code:

let subject = PassthroughSubject<Int, Never>()

var c: Cancellable? = subject.print().sink(receiveValue: { _ in })
c = nil

// receive subscription: (PassthroughSubject)
// request unlimited
// receive cancel

And on further reflection I believe that this is perfectly valid behavior. The AnyCancellable returned is supposed to be the place where we can clean up resources that are created for the effect to do its job. We want to do this for both cancellation and completion. If we don't do it for completion too then we could be missing out on opportunities to clean up resources if the effect is completed outside our control, for example if someone did a .prefix(0) on the effect in the reducer.

So, I think the docs could probably be improved a bit to mention some of this, and we should either:

  • Create a new object that serves the same purpose as AnyCancellable but has a different name to make the distinction a little clearer. One possible candidate is Disposable.

  • Leave the name as-is and be OK with AnyCancellable being called when the effect is both cancelled and completed.

Anyone have thoughts on this?

Error when including in a framework and app target

Describe the bug

⚠️ Edit: looks like this started failing with 0.1.3.

After upgrading to 0.1.4 build started to fail with:

Showing Recent Messages

Prepare build
note: Using new build system
note: Building targets in parallel
note: Planning build
note: Constructing build description


Build system information
error: Swift package product 'ComposableArchitecture' is linked as a static library by 'all-the-targets' and 'Core'. This will result in duplication of library code.

Build system information
error: Swift package product 'CasePaths' is linked as a static library by 'all-the-targets' and 'Core'. This will result in duplication of library code.



Build failed    15/05/2020, 15:45    1.1 seconds

To Reproduce

all-the-targets.zip

Build the app scheme

Expected behavior
☝️ to build

Environment

  • Xcode [e.g. 11.4.1]

Additional context

It looks like a related issue was fixed in 11.4.1. Not sure if this is a similar issue or if I'm truly holding it wrong 😄

0.1.3 is no longer dynamic? (my project breaks)

I just updated to 0.1.3 and my project won't compile any more:

Swift package product 'ComposableArchitecture' is linked as a static library by 'Library' and 5 other targets. This will result in duplication of library code.

I've been following the comments about changes required for testing, so is making the main package no longer dynamic part of that fix? It means I now have to wrap the package in a framework again, correct?

Should we reopen issues here?

There's a bunch of ongoing conversations on the original repo, do you want folks to just reopen them here? (perhaps with a link to the original)

Typing in a textfield recalls the whole body of View

In theBinding case studyif I type anything in textfield the whole View is recreated which make sense as we send a BindingBasicsAction.textChange causing state to change, but without using composable architecture, if I use @State for binding a value to textfield, it doesn't recreates the whole view.
According to my understanding, SwiftUI is smart enough to only re-render a particular subview when a value binded to it changes, in our case textfield.
Can we achieve this using composable architecture as this is more efficient while using complex views.

@ViewedStore missing from UIKIt?

I've been using a version with @ViewedStore for UIKit compatibility but this is not part of the release. Did I miss something where it was replaced with another mechanism?

Multiple levels of navigation

Hey guys,

I'm trying to do something rather simple using SwiftUI and TCA which is to have multiple levels of navigation. Basically, I have 3 screens :

  • First screen has a navigation link to the second screen
  • Second screen has a navigation link to the third screen
  • Third screen can only go back

The code for each screen is very similar so I'll post only the second one but here's the full project.

import ComposableArchitecture
import SwiftUI

struct SecondState {
    var third: ThirdState?
}

enum SecondAction {
//    case tappedBackButton
    case tappedNextButton
    case dismissedThird
    case third(ThirdAction)
}

struct SecondEnvironment {}

let secondReducer = Reducer.combine(
    Reducer<SecondState, SecondAction, SecondEnvironment> { state, action, environment in
        switch action {
//        case .tappedBackButton:
//            return .none
        case .tappedNextButton:
            state.third = ThirdState()
            return .none
//        case .dismissedThird,
//             .third(.tappedBackButton):
        case .dismissedThird:
            state.third = nil
            return .none
        }
    },
    thirdReducer.optional.pullback(
        state: \.third,
        action: /SecondAction.third,
        environment: { _ in ThirdEnvironment() }
    )
)

struct SecondView: View {
    struct ViewState: Equatable {
        let isThirdActive: Bool
    }

    enum ViewAction {
//        case tappedBackButton
        case tappedNextButton
        case dismissedThird
    }

    let store: Store<SecondState, SecondAction>

    var body: some View {
        WithViewStore(store.scope(
            state: \.view,
            action: SecondAction.view
        )) { viewStore in
            VStack {
                NavigationLink(
                    destination: IfLetStore(
                        self.store.scope(
                            state: \.third,
                            action: SecondAction.third
                        ),
                        then: ThirdView.init(store:)
                    ),
                    isActive: viewStore.binding(
                        get: \.isThirdActive,
                        send: { $0 ? .tappedNextButton : .dismissedThird }
                    )
                ) {
                    Text("Go to next screen")
                }
            }
            .navigationBarTitle(
                "Second",
                displayMode: .large
            )
//            .navigationBarBackButtonHidden(true)
//            .navigationBarItems(leading: Button("Back") {
//                viewStore.send(.tappedBackButton)
//            })
        }
    }
}

extension SecondState {
    var view: SecondView.ViewState {
        SecondView.ViewState(isThirdActive: third != nil)
    }
}

extension SecondAction {
    static func view(_ viewAction: SecondView.ViewAction) -> Self {
        switch viewAction {
//        case .tappedBackButton:
//            return .tappedBackButton
        case .tappedNextButton:
            return .tappedNextButton
        case .dismissedThird:
            return .dismissedThird
        }
    }
}

(I commented some code about a custom back button with an action attached to it but that didn't solve the issue either.)

Here's what it looks like when I try to navigate between the screens :

Here's the output of the console when tapping on both navigation links :

received action:
  AppAction.first(
    FirstAction.tappedNextButton
  )
  AppState(
    first: FirstState(
−     second: nil
+     second: SecondState(
+       third: nil
+     )
    )
  )

received action:
  AppAction.first(
    FirstAction.second(
      SecondAction.tappedNextButton
    )
  )
  AppState(
    first: FirstState(
      second: SecondState(
−       third: nil
+       third: ThirdState()
      )
    )
  )

received action:
  AppAction.first(
    FirstAction.dismissedSecond
  )
  AppState(
    first: FirstState(
−     second: SecondState(
−       third: ThirdState()
−     )
+     second: nil
    )
  )

received action:
  AppAction.first(
    FirstAction.second(
      SecondAction.dismissedThird
    )
  )
  AppState(
    first: FirstState(
      second: nil
    )
  )

As you can see, the second screen is dismissed when I try to navigate to the third one which is also dismissed because the second property is nil. I tried to follow the examples of the repo but couldn't find a similar example with multiple levels of navigation. Why is the dismissedSecond action sent when pushing another screen on top of the second screen ? Is it a SwiftUI bug ?

I'd love to hear your thoughts on the matter and if you already implemented something similar.

Add Id to environment transform for forEach

It might be useful to have the key, index, id provided with the global environment when transforming to a local environment. For example

  public func forEach<GlobalState, GlobalAction, GlobalEnvironment, ID>(
    state toLocalState: WritableKeyPath<GlobalState, IdentifiedArray<ID, State>>,
    action toLocalAction: CasePath<GlobalAction, (ID, Action)>,
    environment toLocalEnvironment: @escaping (ID, GlobalEnvironment) -> Environment
  ) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment> {
    .init { globalState, globalAction, globalEnvironment in
      guard let (id, localAction) = toLocalAction.extract(from: globalAction) else { return .none }
      return self.optional
        .reducer(
          &globalState[keyPath: toLocalState][id: id],
          localAction,
          toLocalEnvironment(id, globalEnvironment)
        )
        .map { toLocalAction.embed((id, $0)) }
    }
  }

These identifiers are often only locally unique (such as file names or view id's) and currently the environment has no way of knowing the scope of id's. It might be possible to construct a global path for a state by composing one with the stateTransform but this would mean that the local reducer would have knowledge about global context. For example if a reducer was implemented as per your VoiceMemo sample where the same static Id is used and then the container is extended to support having multiple players simultaneously then the local reducer would have to be modified as well. It might also be difficult if the type of the enclosing Id was different than the local Id, for example a user my have a email for an id while their todo items would be identified with a UUID. Constructing single global id from this might be a bit strange. Would it be a string or an array of any or a nesting structs?

Inserting an element to an IdentifiedArray via a subscript doesn't work. Intentional?

Describe the bug

When inserting an element in an IdentifiedArray via a subscript, the internal id is not updated. At about line 100 of IdentifiedArray.swift:

public subscript(id id: ID) -> Element? {
    get {
      self.dictionary[id]
    }
    set {
      self.dictionary[id] = newValue
      if newValue == nil {
        self.ids.removeAll(where: { $0 == id })
      }
    }
  }

To Reproduce

self.myArray[id: newElement.id] = newElement

Expected behavior
I am not sure if this is intentional or not. I know I can also use .insert()

API for constructing stateless ViewStore

I find that I occasionally need to use WithViewStore only for the purpose of being able to send actions, without ever accessing any state on the view store directly (generally this happens when I introduce some more local store somewhere down the tree, with a ForEachStore or an IfLetStore). For example, if I'm rendering a table section with some list elements, I might do something like this:

WithViewStore(self.store) { viewStore in
    Section(header: Text("Header Text")) {
    ForEachStore(self.store.scope(state: \.books.elements, action: ReadingListAction.rowInteraction)) { bookStore in
            WithViewStore(bookStore) { bookViewStore in
                BookRow(title: bookViewStore.title)
                    .onAppear { viewStore.send(.rowAppeared(bookViewStore.id)) }
                    .onDisappear { viewStore.send(.rowDisappeared(bookViewStore.id)) }
            }
        }
    }
}

In this case, I want the rowAppeared and rowDisappeared actions to be sent to the top-level reducer, so I need the global viewStore, however I never need to touch any of its state directly.

This works fine, however I'm now re-rendering the entire section every time any of my rows are updated, which is unnecessary. What I actually want is to have the top-level viewStore ignore any state changes and just provide an outlet to send actions.

In these cases, it seems like the correct thing to do is to scope the store down to an empty state struct. Doing so is a bit cumbersome, so I've been using a custom helper:

public extension Store {
    struct Stateless: Equatable {}
    func stateless() -> Store<Stateless, Action> {
        return self.scope(state: { _ in Stateless() })
    }
}

There's a handful of different ways this could be expressed in the API (a WithStatelessViewStore helper might be better, because I see no other purpose for a stateless store other than to feed a ViewStore). Maybe the problem I'm trying to solve is a code smell and I should just refactor my actions or views to avoid the need for this altogether; either way, curious to hear thoughts.

External data store and composable architecture?

I have a question regarding external data stores (e.g. a database) that could potentially contain a large amount of data and how to reconcile that with TCA.

Assuming that ALL data cannot be loaded into the app state (into memory), what best practices can be recommended for managing this?

For example, querying the database for subsets of data for things like paging. How does this reconcile with reducers and pullbacks, or at least, how do you recommend it reconcile?

Thanks.

module 'ComposableArchitecture' was not compiled for testing

I added TCA to my application (without adding TCA test support). I can't seem to get the SwiftUI previews working anymore. After pressing "Resume" on the canvas I get the "Build Failed" HUD. Tapping "Diagnostics" gives me this error:

SchemeBuildError: Failed to build the scheme "App"

Build target ComposableArchitectureTestSupport:
note: Set ENABLE_PREVIEWS=NO because SWIFT_OPTIMIZATION_LEVEL=-Owholemodule, expected -Onone (in target 'ComposableArchitectureTestSupport' from project 'swift-composable-architecture')


Compile Swift source files:
/Users/eimantas/Development/DerivedData/App-cvsbxwitwserczdehyykapbndqjo/SourcePackages/checkouts/swift-composable-architecture/Sources/ComposableArchitectureTestSupport/TestStore.swift:4:18: error: module 'ComposableArchitecture' was not compiled for testing
@testable import ComposableArchitecture

I understand this is not the problem with TCA itself, but with either Xcode or my project settings.

I turned off -Owholemodule optimisation in ALL of the targets (the app has a watch app counterpart). I also turned off ENABLE_PREVIEWS. Even with that setting it tells me to turn them off. The project builds and I can run tests, but when it comes to getting the previews - for some reason I can't get it to build.

It's also strange that tests run just fine (because they don't have dependency on TCA's Test Support library) and the app builds. But previews for some reason just don't show up.

I also just checked the settings with vanilla project that contains TCA and it's test support - the build settings are identical which makes me even more confused :(

Double Nested ForEachStore removal Bug

I have bug that I'm not sure if it's my implementation or CA. In the original app I found it I have a list of textfields that can have a carousel of images attached to them, as the user adds images they are added to the carousel. But they can also be removed, when removing the last item from the carousel it seems like there is an action that is still in the system looking for a now missing object in a ForEachStore Below is a minimum reproduction repo.

https://github.com/ryantstone/LoopTest

Installation section is a link to the repository?

I expected the installation section to detail some ways to install, but it just has a link to the repository? Are installation details coming later? Found this confusing is all, I don't necessarily have specific expectations of what should be here.

Combine enum case reducers

In my project, I have a Content enum with the cases .group and .request. I also have both GroupAction and RequestAction. Here is the contentReducer I came up with:

let contentReducer = Reducer<RequestCollection.Content, ContentAction, Void> { state, action, _ in
  switch state {
  case var .group(group):
    guard case let .group(groupAction) = action else { return .none }
    switch groupAction {
    case let .nameChanged(name):
      group.name = name
      state = .group(group)
    }
  case var .request(request):
    guard case let .request(requestAction) = action else { return .none }
    switch requestAction {
    case let .nameChanged(name):
      request.name = name
      state = .request(request)
    }
  }
  return .none
}

What I don't like about this is that I have to guard case let on the ContentAction where a .group case should only be possible to be sent to a Group. In an ideal scenario I'd like to compose this reducer from both groupReducer and requestReducer.
I'll try and prototype a higher order reducer for that purpose.

Did someone else already encounter something similar?

Extraneous View Redrawing

Note: This could be an issue with SwiftUI and not TCA, but most likely it's my fault!

I'm seeing an issue where ViewStore updates to child views are causing unnecessary re-renders of parent views. It seems to compound the deeper I nest. The issue this is causing is when I type into a text field that is nested and type quickly (or backspace quickly) it seems to drop characters. I notice that this issue does not exist in any of your Examples so it must be something I'm doing wrong.

Are there any debugging tools within TCA or that you've used while working on it that might help me figure out why I'm getting view renders in parent views whose state is not changing.

The output when I add debug logs to WithViewStore shows that when simply incrementing the counter on the child view, it does 3 view renders:

received action:
  AppAction.modalFlow(
    ModalFlowAction.incr
  )
  AppState(
    modalFlow: ModalFlowState(
−     counter: 0,
+     counter: 1,
      text: ""
    )
  )

ModalFlowView: Evaluating WithViewStore<ModalFlowState, ModalFlowAction, ...>.body
AppView: Evaluating WithViewStore<AppState, AppAction, ...>.body
ModalFlowView: Evaluating WithViewStore<ModalFlowState, ModalFlowAction, ...>.body

Zip of the runnable sample app.

The code for the small sample app is here:

AppCore.swift

struct AppState: Equatable {
  var modalFlow: ModalFlowState?
}

enum AppAction: Equatable {
  case flowDismissed
  case modalFlow(ModalFlowAction)
  case tapStartFlow
}

struct AppEnvironment {}

let appReducer: Reducer<AppState, AppAction, AppEnvironment> = Reducer.combine(
  Reducer { state, action, environment in
    switch action {
    case .flowDismissed:
      state.modalFlow = nil
      return .none

    case .modalFlow:
      return .none

    case .tapStartFlow:
      state.modalFlow = ModalFlowState()
      return .none
    }
  },

  modalFlowReducer.optional.pullback(
    state: \.modalFlow,
    action: /AppAction.modalFlow,
    environment: { _ in ModalFlowEnvironment() }
  )
)

AppView.swift

struct AppView: View {
  let store: Store<AppState, AppAction>

  init(store: Store<AppState, AppAction>) {
    self.store = store
  }

  var body: some View {
    WithViewStore(store) { viewStore in
      NavigationView {
        VStack {
          NavigationLink(
            destination: IfLetStore(
              self.store.scope(state: \.modalFlow, action: AppAction.modalFlow),
              then: ModalFlowView.init(store:)
            ),
            isActive: viewStore.binding(
              get: { $0.modalFlow != nil },
              send: { $0 ? .tapStartFlow : .flowDismissed }
            )
          ) {
            Text("Start flow")
          }
        }
      }
    }.debug(prefix: "RootView")
  }
}

ModalFlowCore.swift

struct ModalFlowState: Equatable {
  var counter = 0
  var text = ""
}

enum ModalFlowAction: Equatable {
  case incr
  case textChanged(String)
}

struct ModalFlowEnvironment {}

let modalFlowReducer = Reducer<
  ModalFlowState, ModalFlowAction, ModalFlowEnvironment
> { state, action, environment in
  switch action {
  case .incr:
    state.counter += 1
    return .none
  case let .textChanged(newText):
    state.text = newText
    return .none
  }
}

ModalFlowView.swift

struct ModalFlowView: View {
  let store: Store<ModalFlowState, ModalFlowAction>

  init(store: Store<ModalFlowState, ModalFlowAction>) {
    self.store = store
  }

  var body: some View {
    WithViewStore(store) {
      viewStore in
      VStack {
        Text("Counter: \(viewStore.counter)")
        Button("Incr") { viewStore.send(.incr) }
        TextField("Text", text: viewStore.binding(get: \.text, send: ModalFlowAction.textChanged))
          .padding()
      }
    }.debug(prefix: "CounterView")
  }
}

A demo of the app/issue (I type lots of characters into the text field and then just hold down on backspace):
ezgif com-video-to-gif(1)

IdentifiedArray.replaceSubrange causes a stack overflow

Describe the bug
Calling replaceSubrange on an identified array causes a stack overflow.

To Reproduce

  func testReplaceSubrange() {
    struct User: Equatable, Identifiable {
      let id: Int
      var name: String
    }

    var array: IdentifiedArray = [
      User(id: 3, name: "Blob Sr."),
      User(id: 2, name: "Blob Jr."),
      User(id: 1, name: "Blob"),
    ]

    array.replaceSubrange(0...1, with: [
      User(id: 4, name: "Flob IV"),
      User(id: 5, name: "Flob V")
    ])

    XCTAssertEqual(
      array,
      [User(id: 4, name: "Flob IV"), User(id: 5, name: "Flob V"), User(id: 1, name: "Blob")]
    )
  }

This results in repeated calls to

#69778	0x0000000111c933ed in protocol witness for RangeReplaceableCollection.replaceSubrange<A>(_:with:) in conformance <> IdentifiedArray<A, B> ()
#69779	0x00007fff5116a0f7 in RangeReplaceableCollection.replaceSubrange<A, B>(_:with:) ()

Environment

  • Xcode 11.5
  • Swift 5.2.4

Crash after remove

I'm not sure if this is a bug or if I'm doing something wrong. I'm trying to make a small macos app, which has a navigation view inside another navigation view, so it would be a 3 panes app, a groups list, an items list and a detail view. If I try to remove a group or an item the app crashes. I started with simple arrays and int indexes, but I switched to an IdentifiedArrayOf, but nothing works:

`
Fatal error: Can't update element with identifier B699DB06-EFA9-4957-B0E9-DAAE764EE938 with nil.

If you are trying to remove an element from the array, use the "remove(id:) method.":`

Rest assured, I am using the remove(id:) method.

I'm also removing the selection when I remove an item if it's the selected one, but that doesn't update the detail panel.

[Question] Compatibility iOS versions < 13

Hello!

First, thanks for making this open source, amazing work everyone!

Since I started watching the videos I was concerned about the use of Combine and how it would affect adoption for codebases that can't deploy to iOS >= 13.

I know we can achieve the same results using Rx/ReactiveSwift but a lot of change is necessary, and keeping forks with this doesn't sound like a good idea, so I would like to know what are your thoughts on this?

(If this is not the best place to ask this, let me know so we redirect)

Thanks again!

onAppear is called multiple times within an IfLetStore

Describe the bug
If I use an IfLetStore to swap between two views, and the "then" view includes an .onAppear modifier, the callback is called twice each time the view is presented. Replicating the same behavior using a regular if viewStore.optional != nil seems to work as expected.

To Reproduce
Here's a quick repro case:
TCABug.zip

The commented out chunk in ContentView.swift implements the functionality without IfLetStore, and appears to work. Using the IfLetStore version, you should see two logs every time you switch to the hello world view.

Expected behavior
I expect onAppear to only be called once.

Screenshots
N/A

Environment

  • OS: iOS
  • Version 13.4.1

Additional context
N/A

Animation doesn't work for actions sent to view stores inside bindings

Describe the bug
I am animating a state transition by sending actions to a store inside a binding (in a toggle)

To Reproduce
Both controls toggle the state and the view changes. Only the action sent by the button causes the change to be animated.

          Button(action: {
            withAnimation(.interactiveSpring(response: 0.25, dampingFraction: 0.1)) {
              viewStore.send(.surface(.bigMap))
            }
          }) {
            Text("Toggle")
          }
          Toggle(isOn: viewStore.binding(
            get: \.surfaceState.bigMap,
            send: {  _ in
              withAnimation(.interactiveSpring(response: 0.25, dampingFraction: 0.1)) {
                .surface(.bigMap)
              }
          }), label: {
            Text("Big Map")
          })

Expected behavior
Both actions should animate changes.

Saving store?

Would it be possible to keep the data in a store when you leave the view and come back?
Say I have a custom nav that switches between different screen of the app, every nav screen has its own Store() initiated in its main view. I send an Action on appear and save that data, but when you switch to a different screen/nav and come back the Store() is reinstated and the saved data is gone. Whats the best way to get around this issue? Thanks!

0101-swift-composable-architecture-tour-pt2

Describe the bug
Fails to copy the ComposableArchitecture framework because it is not build

To Reproduce
empty ~/Library/Developer
git clone https://github.com/pointfreeco/episode-code-samples.git
Open 0101-swift-composable-architecture-tour-pt2/Todos/Todos.xcodeproj
Run the target (pick any iOS simulator)

Expected behavior
0100-swift-composable-architecture-tour-pt1 checks out builds and runs fine

Environment

  • Xcode [e.g. 11.4.1]
  • Swift [e.g. 5.2.2]
  • OS (if applicable): [e.g. iOS 13]

Additional context
Add any more context about the problem here.

Publisher operators: where are we missing out?

I'd like to preface this issue by saying that I have not yet finished watching the episodes on TCA, and in fact I'm going to go back and watch them from the start very soon, so forgive me if I'm going wildly off course with this one.

Since I'm yet to come up with a well thought out description of the issue, and in fact may be prodding at something much different in scope to what my title suggests, I'll provide a concrete example: Cancellation.

There are a couple of ways through which effects can be cancelled in TCA:

  1. AnyCancellable deinitizalization: stores subscribe to effects, effects return AnyCancellable when subscribed to. Those cancellables are held onto by the store, and if the store deinitializes so do the cancellables. AnyCancellable performs clean up on deinit.

  2. Creating "cancellable" effects: effects can be made "cancellable" by calling the cancellable(id: AnyHashable, cancelInFlight: Bool) function on them. The id passed to cancellable can later be used to cancel the effect by calling the static cancel(id: AnyHashable) function, or by calling cancellable(id: AnyHashable, cancelInFlight: Bool) again with the same id and cancelInFlight == true, and then subscribing to that effect.

The second approach to effect cancellation, which is what I'd like to focus on, involves some additional state management behind the scenes which can be found in Cancellation.swift.

What stood out to me here is that we're replicating functionality that's (at least to some extent) achieved through various publisher flattening strategies. Let's suppose we have a button that when tapped, causes an API request to be made, the result of which is transformed into an action to update some state. There's also a caveat, which is that there should not be more than one API request happening at any one time.

This is simple enough to achieve in TCA: each button tap results in a new cancellable effect (created with cancelInFlight == true) being returned from a reducer, the result being that if a button is tapped while an API request is yet to complete, it will be cancelled and a new request will take its place. We can show this using a (bad) diagram where o denotes a value and | denotes completion:

Button taps:     ---o------------o----o-o---o---------->
API request #1:       ------o|
API request #2:                   ----
API request #3:                       --
API request #4:                         ----
API request #5:                             ------o|

Request 1 gets fired and completes, 2-4 all get fired but don't complete due to cancellation caused by button taps prior to completion, and 5 gets fired and completes.

The same sequence of API calls and the resulting output could instead be achieved using switchToLatest (flatMapLatest in RxSwift) n.b. pseudocode that has no obvious place in TCA:

didTapButton.switchToLatest {
    client.callSomeAPI()
}

Furthermore, let's suppose we want to slightly tweak the behaviour such that rather cancelling in flight API requests, we wait for them to complete before allowing new ones to be made. In the above example, we'd simply replace switchToLatest with flatMapFirst (that's RxSwift, it seems Combine doesn't have an equivalent yet). What about situations where we want to race inner publishers, merge them while limiting max concurrency, or throttle/debounce?

It isn't clear to me where these use cases fit into TCA.

Test fails on device but not in simulator

Describe the bug
I'm using the VoiceMemo tests in my own version of a voice recorder. The tests are failing on device but not in simulator. Seems related to receiving actions before they were expected.

To Reproduce
Here is the VoiceMemo app in its own project and fails tests on device. The screenshot as a hint to what might be going on?

device-test.zip

Screenshots

Screen Shot 2020-05-28 at 9 44 52 AM

Environment

  • Xcode 11.5
  • iOS 13.5
  • TCA 0.2 and 0.3

Pulling back an item from an array of arrays

I have an array of items. Those items also have an array of other child items. I sometimes need to present a single child item to the user, filtered from the top level array, but I'm not properly wrapping my head around the correct approach in pulling back the changes.

Should I be somehow nesting forEach reducers (both sets of arrays contain Identifiable items)?

API for EnvironmentLess Stores

Following along with creating Stateless and Actionless viewStores in #43 , what are the thoughts on extending Store and Reducer with functions where Environment is Void? I am finding that for most stores, I don't need an Environment object.

I think it would be nice to have, but could bloat TCA quite a bit as you would have extensions for 3 possible scenarios:

  • Environment is Void
  • LocalEnvironment is void (for scopes)
    -GlobalEnvironment is void (for pullbacks)

SwiftUICaseStudies app crashes

Describe the bug
The culprit is the cleanup function in Effects > Cancellation

To Reproduce

  1. Navigate to Elm-like Subscriptions
  2. Turn on the clock
  3. Navigate back to the root view

Screenshot
Screenshot 2020-05-26 at 23 18 48

Environment

  • Xcode 11.5
  • Swift 5.1
  • iOS 13.5

WithViewStore not refreshing

Describe the bug

While making a SwiftUI view everything was going okey until suddenly the view stopped updating after changes on the store happened.

I've added debug() both in the reducer and in the WithViewStore and the logs show the output I'm expecting. Although I would expect to receive a log for the reducer and then for the view update, currently there is always a final view update log missing.

It seems like SwiftUI is not observing correctly the changes. Maybe is related to GeometryReader, if I remove that view from the hierarchy then a single update happens haver loading data, but nothing else after that.

Note that keeping a self.viewStore as ObservedObject manually instead of using WithViewStore seems to resolve the problem.

To Reproduce
WithViewStoreNotRefreshing.zip

    var body: some View {
        WithViewStore(self.store) { viewStore in
            GeometryReader { proxy in //// <- REMOVING THIS LAYER HELPS A BIT (refreshes once so the else content is displayed, but no more refreshes after that)
                ZStack(alignment: .bottom) {
///// THIS JUST EVALUATES THE IF INITIALLY BUT NEVER AGAIN, SO ELSE IS NOT DISPLAYED
                    if viewStore.somestate == nil {
                        ...
                    } else {
                       ...
                    }
                }
            }
        }
    }

Expected behavior
Views should refresh after the effect has finished.

Environment

  • Xcode 11.4
  • Apple Swift version 5.2 (swiftlang-1103.0.32.1 clang-1103.0.32.29)
  • iOS 13.4

Additional context
I've reducer the problem from the App I'm building into the example attached. The original view is way more complex so I would have assumed something else was interfering but the reduced example is not that big so hopefully it helps. I will keep digging.

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.