Replies: 7 comments
-
Hi @yimajo, are you sure this is TCA-specific behavior? Have you tried reproducing in vanilla SwiftUI with |
Beta Was this translation helpful? Give feedback.
-
Hi @mbrandonw. Thank you for your response. Since you responded, I tried it with vanilla SwiftUI. The same thing did not happen. When import SwiftUI
@MainActor
struct SampleFeature2View: View {
@State var counter: Counter?
var body: some View {
VStack {
Button("Present ChildView") {
counter = Counter(count: 1)
}
}
.sheet(
isPresented: Binding<Bool>(
get: { counter != nil },
set: {
if !$0 {
counter = nil
}
}
)
) {
NavigationStack {
ChildFeature2View(counter: $counter)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
counter = nil
}
}
}
}
}
}
}
struct ChildFeature2View: View {
@Binding var counter: Counter?
init(counter: Binding<Counter?>) {
self._counter = counter
}
var body: some View {
VStack {
Text("Counter: \(String(describing: counter?.count))")
if counter == nil {
Text("counter is nil")
.onAppear {
// When printed, it is working properly.
print("observe", self.counter?.count, #line)
}
}
}
}
}
@Observable class Counter {
var count: Int
init(count: Int) {
self.count = count
}
} |
Beta Was this translation helpful? Give feedback.
-
Hi @yimajo, thanks for that, but unfortunately I'm not sure if that is a faithful recreation in vanilla. You should have a parent Also, aside from the repro, have you been able to determine if there are any negative effects of this additional observation? We need to prioritize investigating this issue amongst all of our other work, and if there are no negative side effects we may just consider it "expected behavior" for the time being and close out this issue. |
Beta Was this translation helpful? Give feedback.
-
Hi @mbrandonw. Thank you for your response. I’ll proceed under the assumption that you’d like me to provide an example. The following is an example of a camera app where state is set to nil, and observe is triggered, but observe itself cannot handle the situation correctly. It monitors the switching of captureSession when switching between multiple camera lenses or video cameras, initializes the preview view when the captureSession changes, and calls the Reducer’s Action based on that change. // Here's my example.
override func viewDidLoad() {
super.viewDidLoad()
store.send(.view(.onAppear))
observe { [weak self] in
guard let self else { return }
guard let session = store.captureSession else { return }
// Clear PreviewLayer
....
// Create PreviewLayer
previewView = createPreviewLayer(size: view.bounds.size, session: session)
view.addSubview(previewView)
store.send(.view(.didCreatePreviewLayer))
}
} when setting the Presents state to nil causes the camera screen to close, observe is triggered, causing the view to reinitialize and the Action to be re-issued. |
Beta Was this translation helpful? Give feedback.
-
Hi @yimajo, thanks for the real world use case. That definitely helps us understand better how you are using these tools. It looks like we need to improve the documentation here a bit because currently your code snippet is doing some dangerous things inside the struct MyView: View {
@State var count = 0
var body: some View {
let _ = count += 1 // ❌ Not correct to do!
URLSession.shared.data(…) // ❌ Also not correct to do!
}
} In your case, sending an action to the store during And further, even the observe { [weak self] in
guard let self else { return }
if let session = store.captureSession, previewView == nil {
previewView = createPreviewLayer(size: view.bounds.size, session: session)
view.addSubview(previewView)
} else if let previewView, store.captureSession == nil {
previewView.removeFromSuperView()
previewView = nil
}
} And this kind of tricky logic is what our fully general destination(item: $store.captureSession) { session in
createPreviewLayer(size: view.bounds.size, session: session)
} present: { previewView in
view.addSubview(previewView)
} dismiss: { previewView in
previewView.removeFromSuperview()**
} And then you wouldn't have to deal with With all that said, I do believe that this isn't really an issue with the library and so I am inclined to convert it to a discussion. I feel that if you removed the side-effects from your |
Beta Was this translation helpful? Give feedback.
-
Hi @mbrandonw. Thank you for your response.
You mentioned removing side effects from observe, but please take another look at the example I initially provided: #3367 (comment) In this case, when the state is set to nil, self is still not nil and the observation is triggered. Is there really a side effect happening here? Also, I don’t mind if this issue is moved to a discussion. I hope my point is clear. |
Beta Was this translation helpful? Give feedback.
-
Hi @yimajo, sorry I did not mean to imply that the side-effects are causing this problem. I meant to say that the side-effect is what is making this problem noticeable. Without the side effect you probably would not have noticed that the And I will convert this to a discussion for now because I'm not sure it is a real problem right now. If it can be shown that this causes a real, unavoidable problem, then we can reconsider it. But for right now it seems like an innocent extra observation, and currently we are going to prioritize other things over investigate this anymore. |
Beta Was this translation helpful? Give feedback.
-
Description
When the parent Reducer declares the ViewController's State with
@Presents
, the observe in the ViewController is incorrectly triggered once when the parent Reducer sets the ViewController's State to nil.Checklist
main
branch of this package.Expected behavior
I expected that when the parent Reducer sets the child's ViewController's State to nil, the observe in the ViewController would not be triggered at all.
The observe should not execute any logic when the state is set to nil.
Actual behavior
When the parent Reducer declares the ViewController's State with
@Presents
, the observe in the ViewController is incorrectly triggered once when the parent Reducer sets the ViewController's State to nil.Steps to reproduce
Minimal code to reproduce the issue:
The Composable Architecture version information
1.14.0
Destination operating system
iOS 17.5
Xcode version information
Xcode 15.4
Swift Compiler version information
Beta Was this translation helpful? Give feedback.
All reactions