Skip to content

Commit 5e7dde1

Browse files
committed
chore!: stop supporting Swift 5.9
1 parent aee173c commit 5e7dde1

File tree

7 files changed

+157
-59
lines changed

7 files changed

+157
-59
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.9
1+
// swift-tools-version: 5.10
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ With ButtonKit, you'll have access to an `AsyncButton` view, accepting a `() asy
77

88
## Requirements
99

10-
- Swift 5.9+ (Xcode 15+)
10+
- Swift 5.10+ (Xcode 15.3+)
1111
- iOS 15+, iPadOS 15+, tvOS 15+, watchOS 8+, macOS 12+, visionOS 1+
1212

1313
## Installation
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// Button+Reader.swift
3+
// ButtonKit
4+
//
5+
// MIT License
6+
//
7+
// Copyright (c) 2025 Thomas Durand
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in all
17+
// copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
// SOFTWARE.
26+
//
27+
28+
import SwiftUI
29+
30+
public struct AsyncButtonReader<Content: View>: View {
31+
@State var latestEvent: StateChangedEvent? = nil
32+
let content: (StateChangedEvent?) -> Content
33+
34+
public var body: some View {
35+
content(latestEvent)
36+
.onButtonStateChange { latestEvent = $0 }
37+
}
38+
39+
public init(@ViewBuilder content: @escaping (StateChangedEvent?) -> Content) {
40+
self.content = content
41+
}
42+
}
43+
44+
#Preview {
45+
AsyncButtonReader { event in
46+
Group {
47+
AsyncButton("I succeed") {
48+
try await Task.sleep(nanoseconds: 2_000_000_000)
49+
}
50+
51+
AsyncButton("I fail") {
52+
throw NSError()
53+
}
54+
55+
AsyncButton("I'm random") {
56+
try await Task.sleep(nanoseconds: 2_000_000_000)
57+
if Bool.random() {
58+
throw NSError()
59+
}
60+
}
61+
}
62+
.buttonStyle(.bordered)
63+
64+
if let event {
65+
VStack {
66+
Text(verbatim: "Button \(event.buttonID)")
67+
switch event.state {
68+
case .started:
69+
Text("In Progress")
70+
case .ended(.completed):
71+
Text("Completed")
72+
case .ended(.cancelled):
73+
Text("Cancelled")
74+
case let .ended(.errored(_, count)):
75+
Text("Errored \(count) time(s)")
76+
}
77+
}
78+
}
79+
}
80+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Button+Async.swift
2+
// Button.swift
33
// ButtonKit
44
//
55
// MIT License
@@ -100,7 +100,6 @@ public struct AsyncButton<P: TaskProgress, S: View>: View {
100100
private var triggerButton
101101

102102
private let role: ButtonRole?
103-
private let uuid = UUID()
104103
private let id: AnyHashable?
105104
private let action: @MainActor (P) async throws -> Void
106105
private let label: S
@@ -109,6 +108,7 @@ public struct AsyncButton<P: TaskProgress, S: View>: View {
109108
// Environmnent lies when called from triggerButton
110109
// Let's copy it in our own State :)
111110
@State private var isDisabled = false
111+
@State private var uuid = UUID()
112112
@State private var state: AsyncButtonState? = nil
113113
@ObservedObject private var progress: P
114114
@State private var numberOfFailures = 0

Sources/ButtonKit/Modifiers/Button+Events.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,25 @@ import SwiftUI
3232
public typealias ButtonStateChangedHandler = @MainActor @Sendable (StateChangedEvent) -> Void
3333
public typealias ButtonStateErrorHandler = @MainActor @Sendable (ErrorOccurredEvent) -> Void
3434

35+
#if swift(>=6.2)
3536
@MainActor
3637
public struct StateChangedEvent: @MainActor Equatable {
3738
public let buttonID: AnyHashable
3839
public let state: AsyncButtonState
3940
let time: Date = .now
4041
}
42+
#else
43+
@MainActor
44+
public struct StateChangedEvent: @preconcurrency Equatable {
45+
public let buttonID: AnyHashable
46+
public let state: AsyncButtonState
47+
let time: Date = .now
48+
49+
public static func ==(lhs: StateChangedEvent, rhs: StateChangedEvent) -> Bool {
50+
lhs.buttonID == rhs.buttonID && lhs.state == rhs.state && lhs.time == rhs.time
51+
}
52+
}
53+
#endif
4154

4255
public struct ErrorOccurredEvent {
4356
public let buttonID: AnyHashable
@@ -138,13 +151,9 @@ struct OnButtonLatestStateChangeModifier: ViewModifier {
138151
func body(content: Content) -> some View {
139152
content
140153
.onPreferenceChange(ButtonLatestStatePreferenceKey.self) { state in
141-
#if swift(>=5.10)
142154
MainActor.assumeIsolated {
143155
handler(state)
144156
}
145-
#else
146-
handler(state)
147-
#endif
148157
}
149158
}
150159
}
Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// ThrowableStyle+Shake.swift
2+
// AsyncStyle+SymbolEffect.swift
33
// ButtonKit
44
//
55
// MIT License
@@ -25,9 +25,10 @@
2525
// SOFTWARE.
2626
//
2727

28+
import Symbols
2829
import SwiftUI
2930

30-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
31+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
3132
public struct SymbolEffectAsyncButtonStyle<Effect: SymbolEffect&IndefiniteSymbolEffect>: AsyncButtonStyle {
3233
let effect: Effect
3334

@@ -37,80 +38,86 @@ public struct SymbolEffectAsyncButtonStyle<Effect: SymbolEffect&IndefiniteSymbol
3738
}
3839
}
3940

40-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
41+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
4142
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<AppearSymbolEffect> {
4243
public static func symbolEffect(_ effect: AppearSymbolEffect) -> some AsyncButtonStyle {
4344
SymbolEffectAsyncButtonStyle(effect: effect)
4445
}
4546
}
46-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
47-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<BounceSymbolEffect> {
48-
public static func symbolEffect(_ effect: BounceSymbolEffect) -> some AsyncButtonStyle {
47+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
48+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<DisappearSymbolEffect> {
49+
public static func symbolEffect(_ effect: DisappearSymbolEffect) -> some AsyncButtonStyle {
4950
SymbolEffectAsyncButtonStyle(effect: effect)
5051
}
5152
}
52-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
53-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<BreatheSymbolEffect> {
54-
public static func symbolEffect(_ effect: BreatheSymbolEffect) -> some AsyncButtonStyle {
53+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
54+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<PulseSymbolEffect> {
55+
public static func symbolEffect(_ effect: PulseSymbolEffect) -> some AsyncButtonStyle {
5556
SymbolEffectAsyncButtonStyle(effect: effect)
5657
}
5758
}
58-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
59-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<DisappearSymbolEffect> {
60-
public static func symbolEffect(_ effect: DisappearSymbolEffect) -> some AsyncButtonStyle {
59+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
60+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<ScaleSymbolEffect> {
61+
public static func symbolEffect(_ effect: ScaleSymbolEffect) -> some AsyncButtonStyle {
6162
SymbolEffectAsyncButtonStyle(effect: effect)
6263
}
6364
}
64-
@available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, *)
65-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<DrawOffSymbolEffect> {
66-
public static func symbolEffect(_ effect: DrawOffSymbolEffect) -> some AsyncButtonStyle {
65+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
66+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<VariableColorSymbolEffect> {
67+
public static func symbolEffect(_ effect: VariableColorSymbolEffect) -> some AsyncButtonStyle {
6768
SymbolEffectAsyncButtonStyle(effect: effect)
6869
}
6970
}
70-
@available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, *)
71-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<DrawOnSymbolEffect> {
72-
public static func symbolEffect(_ effect: DrawOnSymbolEffect) -> some AsyncButtonStyle {
71+
72+
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
73+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<BounceSymbolEffect> {
74+
public static func symbolEffect(_ effect: BounceSymbolEffect) -> some AsyncButtonStyle {
7375
SymbolEffectAsyncButtonStyle(effect: effect)
7476
}
7577
}
76-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
77-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<PulseSymbolEffect> {
78-
public static func symbolEffect(_ effect: PulseSymbolEffect) -> some AsyncButtonStyle {
78+
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
79+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<BreatheSymbolEffect> {
80+
public static func symbolEffect(_ effect: BreatheSymbolEffect) -> some AsyncButtonStyle {
7981
SymbolEffectAsyncButtonStyle(effect: effect)
8082
}
8183
}
82-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
84+
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
8385
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<RotateSymbolEffect> {
8486
public static func symbolEffect(_ effect: RotateSymbolEffect) -> some AsyncButtonStyle {
8587
SymbolEffectAsyncButtonStyle(effect: effect)
8688
}
8789
}
88-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
89-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<ScaleSymbolEffect> {
90-
public static func symbolEffect(_ effect: ScaleSymbolEffect) -> some AsyncButtonStyle {
90+
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
91+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<WiggleSymbolEffect> {
92+
public static func symbolEffect(_ effect: WiggleSymbolEffect) -> some AsyncButtonStyle {
9193
SymbolEffectAsyncButtonStyle(effect: effect)
9294
}
9395
}
94-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
95-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<VariableColorSymbolEffect> {
96-
public static func symbolEffect(_ effect: VariableColorSymbolEffect) -> some AsyncButtonStyle {
96+
97+
#if swift(>=6.2)
98+
99+
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, *)
100+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<DrawOffSymbolEffect> {
101+
public static func symbolEffect(_ effect: DrawOffSymbolEffect) -> some AsyncButtonStyle {
97102
SymbolEffectAsyncButtonStyle(effect: effect)
98103
}
99104
}
100-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
101-
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<WiggleSymbolEffect> {
102-
public static func symbolEffect(_ effect: WiggleSymbolEffect) -> some AsyncButtonStyle {
105+
@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, *)
106+
extension AsyncButtonStyle where Self == SymbolEffectAsyncButtonStyle<DrawOnSymbolEffect> {
107+
public static func symbolEffect(_ effect: DrawOnSymbolEffect) -> some AsyncButtonStyle {
103108
SymbolEffectAsyncButtonStyle(effect: effect)
104109
}
105110
}
106111

107-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
112+
#endif
113+
114+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
108115
#Preview("Indeterminate") {
109116
AsyncButton {
110117
try await Task.sleep(nanoseconds: 30_000_000_000)
111118
} label: {
112119
Image(systemName: "ellipsis")
113120
}
114121
.buttonStyle(.borderedProminent)
115-
.asyncButtonStyle(.symbolEffect(.variableColor))
122+
.asyncButtonStyle(.symbolEffect(.pulse))
116123
}

Sources/ButtonKit/Style/Throwable/ThrowableStyle+SymbolEffect.swift

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// ThrowableStyle+Shake.swift
2+
// ThrowableStyle+SymbolEffect.swift
33
// ButtonKit
44
//
55
// MIT License
@@ -25,9 +25,10 @@
2525
// SOFTWARE.
2626
//
2727

28+
import Symbols
2829
import SwiftUI
2930

30-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
31+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
3132
public struct SymbolEffectThrowableButtonStyle<Effect: SymbolEffect&DiscreteSymbolEffect>: ThrowableButtonStyle {
3233
let effect: Effect
3334

@@ -37,50 +38,51 @@ public struct SymbolEffectThrowableButtonStyle<Effect: SymbolEffect&DiscreteSymb
3738
}
3839
}
3940

40-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
41+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
4142
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<BounceSymbolEffect> {
4243
public static func symbolEffect(_ effect: BounceSymbolEffect) -> some ThrowableButtonStyle {
4344
SymbolEffectThrowableButtonStyle(effect: effect)
4445
}
4546
}
46-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
47-
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<BreatheSymbolEffect> {
48-
public static func symbolEffect(_ effect: BreatheSymbolEffect) -> some ThrowableButtonStyle {
47+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
48+
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<PulseSymbolEffect> {
49+
public static func symbolEffect(_ effect: PulseSymbolEffect) -> some ThrowableButtonStyle {
4950
SymbolEffectThrowableButtonStyle(effect: effect)
5051
}
5152
}
52-
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
53-
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<PulseSymbolEffect> {
54-
public static func symbolEffect(_ effect: PulseSymbolEffect) -> some ThrowableButtonStyle {
53+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
54+
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<VariableColorSymbolEffect> {
55+
public static func symbolEffect(_ effect: VariableColorSymbolEffect) -> some ThrowableButtonStyle {
5556
SymbolEffectThrowableButtonStyle(effect: effect)
5657
}
5758
}
58-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
59-
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<RotateSymbolEffect> {
60-
public static func symbolEffect(_ effect: RotateSymbolEffect) -> some ThrowableButtonStyle {
59+
60+
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
61+
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<BreatheSymbolEffect> {
62+
public static func symbolEffect(_ effect: BreatheSymbolEffect) -> some ThrowableButtonStyle {
6163
SymbolEffectThrowableButtonStyle(effect: effect)
6264
}
6365
}
64-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
65-
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<VariableColorSymbolEffect> {
66-
public static func symbolEffect(_ effect: VariableColorSymbolEffect) -> some ThrowableButtonStyle {
66+
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
67+
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<RotateSymbolEffect> {
68+
public static func symbolEffect(_ effect: RotateSymbolEffect) -> some ThrowableButtonStyle {
6769
SymbolEffectThrowableButtonStyle(effect: effect)
6870
}
6971
}
70-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
72+
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
7173
extension ThrowableButtonStyle where Self == SymbolEffectThrowableButtonStyle<WiggleSymbolEffect> {
7274
public static func symbolEffect(_ effect: WiggleSymbolEffect) -> some ThrowableButtonStyle {
7375
SymbolEffectThrowableButtonStyle(effect: effect)
7476
}
7577
}
7678

77-
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, *)
79+
@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
7880
#Preview {
7981
AsyncButton {
8082
throw NSError() as Error
8183
} label: {
8284
Label("Hello", systemImage: "link")
8385
}
8486
.buttonStyle(.borderedProminent)
85-
.throwableButtonStyle(.symbolEffect(.wiggle.down))
87+
.throwableButtonStyle(.symbolEffect(.bounce))
8688
}

0 commit comments

Comments
 (0)