Binding sends action when local state was unavailable #1224
-
I'm trying to implement file dragging into my application with the SwiftUI
How can I prevent this action from sending after the switch has happened? And why is it triggering at all since the view is nested in a My code, the child view: public struct ImageInputState: Equatable {
@BindableState var dragOver: Bool
public init(dragOver: Bool = true) {
self.dragOver = dragOver
}
}
public enum ImageInputAction: Equatable, BindableAction {
case binding(BindingAction<ImageInputState>)
case onDropImageStarted([NSItemProvider])
case onDropImageCompleted(Result<NSImage, OnDropImageClient.OnDropError>)
}
public struct ImageInputEnvironment {
public var mainQueue: AnySchedulerOf<DispatchQueue>
public var onDropImage: OnDropImageClient
public init(
mainQueue: AnySchedulerOf<DispatchQueue>,
onDropImage: OnDropImageClient
) {
self.mainQueue = mainQueue
self.onDropImage = onDropImage
}
}
public let imageInputReducer = Reducer<ImageInputState, ImageInputAction, ImageInputEnvironment> { state, action, env in
switch action {
case .binding:
return .none
case .onDropImageStarted(let providers):
return env.onDropImage.onDrop(providers)
.receive(on: env.mainQueue)
.catchToEffect()
.map(ImageInputAction.onDropImageCompleted)
case .onDropImageCompleted(.success(let image)):
return .none
case .onDropImageCompleted(.failure(let error)):
fatalError()
return .none
}
}.binding()
public struct ImageInputView: View {
let store: Store<ImageInputState, ImageInputAction>
public init(store: Store<ImageInputState, ImageInputAction>) {
self.store = store
}
public var body: some View {
WithViewStore(store) { viewStore in
ZStack {
Rectangle()
.foregroundColor(.clear)
.onDrop(
of: ["public.file-url"],
isTargeted: viewStore.binding(\.$dragOver),
perform: { providers in
viewStore.send(.onDropImageStarted(providers))
return true
}
)
FileDragIcon(store: store.scope(state: \.dragOver).actionless)
.font(.system(size: 75, weight: .light))
}
}
}
} Parent view: public struct ImageEditorState: Equatable {
public var settings: SettingsState
public var editorMinimalStep: MinimalStep
public var editorStep: Step {
get {
switch self.editorMinimalStep {
case .imageInput(let input):
return .imageInput(input)
case .imageProcessing(let processing):
return .imageProcessing(.init(sourceImage: processing.sourceImage, destinationImage: processing.destinationImage, settings: self.settings))
}
}
set {
switch newValue {
case .imageInput(let input):
self.editorMinimalStep = .imageInput(input)
case .imageProcessing(let processing):
self.editorMinimalStep = .imageProcessing(.init(sourceImage: processing.sourceImage, destinationImage: processing.destinationImage))
self.settings = processing.settings
}
}
}
public init(settings: SettingsState = .init(), editor: MinimalStep = .imageInput(.init())) {
self.settings = settings
self.editorMinimalStep = editor
}
public struct ImageProcessingMinimalState: Equatable {
var sourceImage: NSImage
var destinationImage: NSImage
}
public enum MinimalStep: Equatable {
case imageInput(ImageInputState)
case imageProcessing(ImageProcessingMinimalState)
}
public enum Step: Equatable {
case imageInput(ImageInputState)
case imageProcessing(ImageProcessingState)
public init() {
self = .imageInput(.init())
}
}
}
public enum ImageEditorAction: Equatable {
case imageInput(ImageInputAction)
case imageProcessing(ImageProcessingAction)
}
public struct ImageEditorEnvironment {
public var mainQueue: AnySchedulerOf<DispatchQueue>
public var onDropImage: OnDropImageClient
public var renderer: RendererClient
public var colorClamp: ColorClampClient
public init(
mainQueue: AnySchedulerOf<DispatchQueue>,
onDropImage: OnDropImageClient,
renderer: RendererClient,
colorClamp: ColorClampClient
) {
self.mainQueue = mainQueue
self.onDropImage = onDropImage
self.renderer = renderer
self.colorClamp = colorClamp
}
}
public let imageEditorReducer = Reducer<ImageEditorState, ImageEditorAction, ImageEditorEnvironment>.combine(
Reducer<ImageEditorState.Step, ImageEditorAction, ImageEditorEnvironment>.combine(
imageInputReducer.pullback(
state: /ImageEditorState.Step.imageInput,
action: /ImageEditorAction.imageInput,
environment: {
.init(
mainQueue: $0.mainQueue,
onDropImage: $0.onDropImage
)
}
).debug(),
imageProcessingReducer.pullback(
state: /ImageEditorState.Step.imageProcessing,
action: /ImageEditorAction.imageProcessing,
environment: {
.init(
renderer: $0.renderer,
colorClamp: $0.colorClamp
)
}
)
).pullback(
state: \ImageEditorState.editorStep,
action: /.self,
environment: { $0 }
),
Reducer<ImageEditorState, ImageEditorAction, ImageEditorEnvironment> { state, action, env in
switch action {
case .imageInput(.onDropImageCompleted(.success(let image))):
state.editorMinimalStep = .imageProcessing(.init(sourceImage: image, destinationImage: image))
return .none
case .imageInput(_):
return .none
case .imageProcessing(_):
return .none
}
}
)
public struct ImageEditorView: View {
let store: Store<ImageEditorState, ImageEditorAction>
public init(store: Store<ImageEditorState, ImageEditorAction>) {
self.store = store
}
public var body: some View {
GeometryReader { geo in
SwitchStore(self.store.scope(state: \.editorStep)) {
CaseLet(
state: /ImageEditorState.Step.imageInput,
action: ImageEditorAction.imageInput,
then: { scopedStore in
ImageInputView(store: scopedStore)
}
)
CaseLet(
state: /ImageEditorState.Step.imageProcessing,
action: ImageEditorAction.imageProcessing,
then: { scopedStore in
ImageProcessingView(store: scopedStore)
}
)
}.frame(width: geo.size.width, height: geo.size.height)
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
I think this is the same issue that with navigation, you can apply the remove duplicate extension on binding |
Beta Was this translation helpful? Give feedback.
I think this is the same issue that with navigation, you can apply the remove duplicate extension on binding