-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SwiftUI SideEffects implementation (#31)
* view effects * added view effect modifier * view side effects * store view imlementation * minor fix
- Loading branch information
1 parent
b3ec776
commit 41bfbf8
Showing
4 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
Sources/Puredux/SwiftUI/ViewsWIthStore/EffectModifier.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 22/08/2024. | ||
// | ||
|
||
import SwiftUI | ||
|
||
@available(iOS 13.0, *) | ||
struct EffectModifier<ViewState, Action>: ViewModifier { | ||
let store: Store<ViewState, Action> | ||
let createEffect: (AnyObject, Store<ViewState, Action>) -> Void | ||
|
||
private class ViewIsAlive { } | ||
|
||
@State private var viewIsAlive = ViewIsAlive() | ||
|
||
func body(content: Content) -> some View { | ||
content | ||
.onAppear { createEffect(viewIsAlive, store) } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 21/08/2024. | ||
// | ||
|
||
import SwiftUI | ||
import Dispatch | ||
import Combine | ||
|
||
@available(iOS 13.0, *) | ||
public struct StoreView<ViewState, Action, Props, Content: View>: View { | ||
let store: Store<ViewState, Action> | ||
let props: (ViewState, @escaping Dispatch<Action>) -> Props | ||
let content: (_ props: Props) -> Content | ||
|
||
private(set) var removeStateDuplicates: Equating<ViewState>? | ||
private(set) var presentationQueue: DispatchQueue = PresentationQueue.sharedPresentationQueue.dispatchQueue | ||
|
||
@State private var currentProps: Props? | ||
|
||
public var body: some View { | ||
makeContent() | ||
.effect(on: store, | ||
withDelay: .uiDebounce, | ||
removeStateDuplicates: removeStateDuplicates, | ||
on: presentationQueue) { state in | ||
|
||
Effect { | ||
let props = props(state, store.dispatch) | ||
DispatchQueue.main.async { currentProps = props } | ||
} | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 13.0, *) | ||
extension StoreView { | ||
@ViewBuilder | ||
func makeContent() -> some View { | ||
if let props = currentProps { | ||
content(props) | ||
} else { | ||
Color.clear | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 13.0, *) | ||
public extension StoreView { | ||
|
||
func removeStateDuplicates(_ equating: Equating<ViewState>) -> Self { | ||
var selfCopy = self | ||
selfCopy.removeStateDuplicates = equating | ||
return selfCopy | ||
} | ||
|
||
func usePresentationQueue(_ queue: PresentationQueue) -> Self { | ||
var selfCopy = self | ||
selfCopy.presentationQueue = queue.dispatchQueue | ||
return selfCopy | ||
} | ||
} | ||
|
||
@available(iOS 13.0, *) | ||
public extension StoreView { | ||
init(_ store: Store<ViewState, Action>, | ||
props: @escaping (ViewState, @escaping Dispatch<Action>) -> Props, | ||
content: @escaping (Props) -> Content) { | ||
self.store = store | ||
self.props = props | ||
self.content = content | ||
} | ||
} | ||
|
||
@available(iOS 13.0, *) | ||
public extension StoreView where Props == (ViewState, Dispatch<Action>) { | ||
|
||
init(_ store: Store<ViewState, Action>, | ||
content: @escaping (Props) -> Content) { | ||
self.store = store | ||
self.props = { state, store in (state, store) } | ||
self.content = content | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
Sources/Puredux/SwiftUI/ViewsWIthStore/ViewSideEffects.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 21/08/2024. | ||
// | ||
|
||
import SwiftUI | ||
|
||
@available(iOS 13.0, *) | ||
extension View { | ||
|
||
|
||
func forEachEffect<ViewState, Action, Effects>( | ||
on store: Store<ViewState, Action>, | ||
_ keyPath: KeyPath<ViewState, Effects>, | ||
on queue: DispatchQueue = .main, | ||
create: @escaping (ViewState, Effect.State) -> Effect) -> some View | ||
|
||
where Effects: Collection & Hashable, Effects.Element == Effect.State { | ||
|
||
effect(store) { anyObject, store in | ||
|
||
store.forEachEffect( | ||
anyObject, | ||
keyPath, | ||
on: queue, | ||
create: create | ||
) | ||
} | ||
} | ||
|
||
func effect<ViewState, Action>(on store: Store<ViewState, Action>, | ||
_ keyPath: KeyPath<ViewState, Effect.State>, | ||
on queue: DispatchQueue = .main, | ||
create: @escaping (ViewState) -> Effect) -> some View { | ||
|
||
effect(store) { anyObject, store in | ||
|
||
store.effect( | ||
anyObject, | ||
keyPath, | ||
on: queue, | ||
create: create | ||
) | ||
} | ||
} | ||
|
||
func effect<ViewState, Action, T>(on store: Store<ViewState, Action>, | ||
_ keyPath: KeyPath<ViewState, T>, | ||
on queue: DispatchQueue = .main, | ||
create: @escaping (ViewState) -> Effect) -> some View | ||
where T: Equatable { | ||
|
||
effect(store) { anyObject, store in | ||
|
||
store.effect( | ||
anyObject, | ||
keyPath, | ||
on: queue, | ||
create: create | ||
) | ||
} | ||
} | ||
|
||
func effect<ViewState, Action>( | ||
on store: Store<ViewState, Action>, | ||
_ keyPath: KeyPath<ViewState, Bool>, | ||
on queue: DispatchQueue = .main, | ||
create: @escaping (ViewState) -> Effect) -> some View { | ||
|
||
effect(store) { anyObject, store in | ||
|
||
store.effect( | ||
anyObject, | ||
keyPath, | ||
on: queue, | ||
create: create | ||
) | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 13.0, *) | ||
extension View { | ||
func effect<ViewState, Action>(on store: Store<ViewState, Action>, | ||
withDelay interval: TimeInterval, | ||
removeStateDuplicates: Equating<ViewState>?, | ||
on dispatchQueue: DispatchQueue, | ||
create: @escaping (ViewState) -> Effect) -> some View { | ||
|
||
effect(store) { anyObject, store in | ||
|
||
store.effect( | ||
anyObject, | ||
withDelay: interval, | ||
removeStateDuplicates: removeStateDuplicates, | ||
on: dispatchQueue, | ||
create: create | ||
) | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 13.0, *) | ||
private extension View { | ||
func effect<ViewState, Action>( | ||
_ store: Store<ViewState, Action>, | ||
createEffect: @escaping (AnyObject, Store<ViewState, Action>) -> Void) -> some View { | ||
|
||
modifier( | ||
EffectModifier( | ||
store: store, | ||
createEffect: createEffect | ||
) | ||
) | ||
} | ||
} | ||
|