Skip to content

Commit 4a9f853

Browse files
authored
Merge pull request #12 from Dean151/on-button-error-modifier
feat: added onButtonError modifier
2 parents 1a7fb26 + 4a6f815 commit 4a9f853

File tree

4 files changed

+79
-1
lines changed

4 files changed

+79
-1
lines changed

Demo/ButtonKitDemo/Buttons/ThrowableButtonDemo.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ struct ThrowableButtonDemo: View {
4949
.throwableButtonStyle(.none)
5050
}
5151
.buttonStyle(.borderedProminent)
52+
.onButtonError { error in
53+
// Do something with the error
54+
print(error)
55+
}
5256
}
5357
}
5458

Sources/ButtonKit/Button+Async.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public struct AsyncButton<P: TaskProgress, S: View>: View {
7878
@State private var state: AsyncButtonState = .idle
7979
@ObservedObject private var progress: P
8080
@State private var errorCount = 0
81+
@State private var lastError: Error?
8182

8283
public var body: some View {
8384
let throwableLabelConfiguration = ThrowableButtonStyleLabelConfiguration(
@@ -110,6 +111,7 @@ public struct AsyncButton<P: TaskProgress, S: View>: View {
110111
.allowsHitTesting(allowsHitTestingWhenLoading || !state.isLoading)
111112
.disabled(disabledWhenLoading && state.isLoading)
112113
.preference(key: AsyncButtonTaskPreferenceKey.self, value: state)
114+
.preference(key: AsyncButtonErrorPreferenceKey.self, value: lastError.flatMap { .init(increment: errorCount, error: $0) })
113115
.onAppear {
114116
guard let id else {
115117
return
@@ -150,6 +152,7 @@ public struct AsyncButton<P: TaskProgress, S: View>: View {
150152
try await action(progress)
151153
} catch {
152154
errorCount += 1
155+
lastError = error
153156
}
154157
// Reset progress
155158
await progress.ended()
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// Button+AsyncError.swift
3+
// ButtonKit
4+
//
5+
// Created by Thomas Durand on 12/01/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
// MARK: Public protocol
11+
12+
public typealias AsyncButtonErrorHandler = @MainActor @Sendable (Error) -> Void
13+
14+
extension View {
15+
public func onButtonError(_ handler: @escaping AsyncButtonErrorHandler) -> some View {
16+
modifier(OnAsyncButtonErrorChangeModifier(handler: { error in
17+
handler(error)
18+
}))
19+
}
20+
}
21+
22+
// MARK: - Internal implementation
23+
24+
struct AsyncButtonErrorPreferenceKey: PreferenceKey {
25+
static let defaultValue: ErrorHolder? = nil
26+
27+
static func reduce(value: inout ErrorHolder?, nextValue: () -> ErrorHolder?) {
28+
guard let newValue = nextValue() else {
29+
return
30+
}
31+
value = .init(increment: (value?.increment ?? 0) + newValue.increment, error: newValue.error)
32+
}
33+
}
34+
35+
struct ErrorHolder: Equatable {
36+
let increment: Int
37+
let error: Error
38+
39+
static func == (lhs: ErrorHolder, rhs: ErrorHolder) -> Bool {
40+
lhs.increment == rhs.increment
41+
}
42+
}
43+
44+
struct OnAsyncButtonErrorChangeModifier: ViewModifier {
45+
let handler: AsyncButtonErrorHandler
46+
47+
init(handler: @escaping AsyncButtonErrorHandler) {
48+
self.handler = handler
49+
}
50+
51+
func body(content: Content) -> some View {
52+
content
53+
.onPreferenceChange(AsyncButtonErrorPreferenceKey.self) { value in
54+
guard let error = value?.error else {
55+
return
56+
}
57+
#if swift(>=5.10)
58+
MainActor.assumeIsolated {
59+
onError(error)
60+
}
61+
#else
62+
onError(error)
63+
#endif
64+
}
65+
}
66+
67+
@MainActor
68+
func onError(_ error: Error) {
69+
handler(error)
70+
}
71+
}

Sources/ButtonKit/Modifiers/Button+AsyncTask.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ extension View {
5757
}
5858
}
5959

60-
// Internal implementation
60+
// MARK: - Internal implementation
6161

6262
struct AsyncButtonTaskPreferenceKey: PreferenceKey {
6363
static let defaultValue: AsyncButtonState = .idle

0 commit comments

Comments
 (0)