Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions Examples/Packages/CrossPlatform/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import PackageDescription

let atoms = Target.Dependency.product(name: "Atoms", package: "swiftui-atom-properties")
let swiftSettings: [SwiftSetting] = [
.enableUpcomingFeature("ExistentialAny")
]

let package = Package(
name: "CrossPlatformExamples",
Expand All @@ -25,11 +28,12 @@ let package = Package(
atoms,
"ExampleCounter",
"ExampleTodo",
]
],
swiftSettings: swiftSettings
),
.target(name: "ExampleCounter", dependencies: [atoms]),
.testTarget(name: "ExampleCounterTests", dependencies: ["ExampleCounter"]),
.target(name: "ExampleTodo", dependencies: [atoms]),
.testTarget(name: "ExampleTodoTests", dependencies: ["ExampleTodo"]),
.target(name: "ExampleCounter", dependencies: [atoms], swiftSettings: swiftSettings),
.testTarget(name: "ExampleCounterTests", dependencies: ["ExampleCounter"], swiftSettings: swiftSettings),
.target(name: "ExampleTodo", dependencies: [atoms], swiftSettings: swiftSettings),
.testTarget(name: "ExampleTodoTests", dependencies: ["ExampleTodo"], swiftSettings: swiftSettings),
]
)
22 changes: 13 additions & 9 deletions Examples/Packages/iOS/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import PackageDescription

let atoms = Target.Dependency.product(name: "Atoms", package: "swiftui-atom-properties")
let swiftSettings: [SwiftSetting] = [
.enableUpcomingFeature("ExistentialAny")
]

let package = Package(
name: "iOSExamples",
Expand All @@ -26,15 +29,16 @@ let package = Package(
"ExampleMap",
"ExampleVoiceMemo",
"ExampleTimeTravel",
]
],
swiftSettings: swiftSettings
),
.target(name: "ExampleMovieDB", dependencies: [atoms]),
.testTarget(name: "ExampleMovieDBTests", dependencies: ["ExampleMovieDB"]),
.target(name: "ExampleMap", dependencies: [atoms]),
.testTarget(name: "ExampleMapTests", dependencies: ["ExampleMap"]),
.target(name: "ExampleVoiceMemo", dependencies: [atoms]),
.testTarget(name: "ExampleVoiceMemoTests", dependencies: ["ExampleVoiceMemo"]),
.target(name: "ExampleTimeTravel", dependencies: [atoms]),
.testTarget(name: "ExampleTimeTravelTests", dependencies: ["ExampleTimeTravel"]),
.target(name: "ExampleMovieDB", dependencies: [atoms], swiftSettings: swiftSettings),
.testTarget(name: "ExampleMovieDBTests", dependencies: ["ExampleMovieDB"], swiftSettings: swiftSettings),
.target(name: "ExampleMap", dependencies: [atoms], swiftSettings: swiftSettings),
.testTarget(name: "ExampleMapTests", dependencies: ["ExampleMap"], swiftSettings: swiftSettings),
.target(name: "ExampleVoiceMemo", dependencies: [atoms], swiftSettings: swiftSettings),
.testTarget(name: "ExampleVoiceMemoTests", dependencies: ["ExampleVoiceMemo"], swiftSettings: swiftSettings),
.target(name: "ExampleTimeTravel", dependencies: [atoms], swiftSettings: swiftSettings),
.testTarget(name: "ExampleTimeTravelTests", dependencies: ["ExampleTimeTravel"], swiftSettings: swiftSettings),
]
)
8 changes: 4 additions & 4 deletions Examples/Packages/iOS/Sources/ExampleMap/Atoms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import Atoms
import CoreLocation

final class LocationObserver: NSObject, ObservableObject, CLLocationManagerDelegate, @unchecked Sendable {
let manager: LocationManagerProtocol
let manager: any LocationManagerProtocol

deinit {
manager.stopUpdatingLocation()
}

init(manager: LocationManagerProtocol) {
init(manager: any LocationManagerProtocol) {
self.manager = manager
super.init()
manager.delegate = self
Expand All @@ -32,13 +32,13 @@ final class LocationObserver: NSObject, ObservableObject, CLLocationManagerDeleg
}
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
print(error.localizedDescription)
}
}

struct LocationManagerAtom: ValueAtom, Hashable {
func value(context: Context) -> LocationManagerProtocol {
func value(context: Context) -> any LocationManagerProtocol {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
return manager
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CoreLocation

protocol LocationManagerProtocol: AnyObject {
var delegate: CLLocationManagerDelegate? { get set }
var delegate: (any CLLocationManagerDelegate)? { get set }
var desiredAccuracy: CLLocationAccuracy { get set }
var location: CLLocation? { get }
var authorizationStatus: CLAuthorizationStatus { get }
Expand All @@ -12,7 +12,7 @@ protocol LocationManagerProtocol: AnyObject {
extension CLLocationManager: LocationManagerProtocol {}

final class MockLocationManager: LocationManagerProtocol, @unchecked Sendable {
weak var delegate: CLLocationManagerDelegate?
weak var delegate: (any CLLocationManagerDelegate)?
var desiredAccuracy = kCLLocationAccuracyKilometer
var location: CLLocation? = nil
var authorizationStatus = CLAuthorizationStatus.notDetermined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Atoms
import UIKit

struct APIClientAtom: ValueAtom, Hashable {
func value(context: Context) -> APIClientProtocol {
func value(context: Context) -> any APIClientProtocol {
APIClient()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import Foundation
@MainActor
final class MovieLoader: ObservableObject {
@Published
private(set) var pages = AsyncPhase<[PagedResponse<Movie>], Error>.suspending
private let api: APIClientProtocol
private(set) var pages = AsyncPhase<[PagedResponse<Movie>], any Error>.suspending
private let api: any APIClientProtocol
let filter: Filter

init(api: APIClientProtocol, filter: Filter) {
init(api: any APIClientProtocol, filter: Filter) {
self.api = api
self.filter = filter
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ private extension APIClient {
}

final class MockAPIClient: APIClientProtocol, @unchecked Sendable {
var imageResponse = Result<UIImage, Error>.failure(URLError(.unknown))
var filteredMovieResponse = Result<PagedResponse<Movie>, Error>.failure(URLError(.unknown))
var creditsResponse = Result<Credits, Error>.failure(URLError(.unknown))
var searchMoviesResponse = Result<PagedResponse<Movie>, Error>.failure(URLError(.unknown))
var imageResponse = Result<UIImage, any Error>.failure(URLError(.unknown))
var filteredMovieResponse = Result<PagedResponse<Movie>, any Error>.failure(URLError(.unknown))
var creditsResponse = Result<Credits, any Error>.failure(URLError(.unknown))
var searchMoviesResponse = Result<PagedResponse<Movie>, any Error>.failure(URLError(.unknown))

func getImage(path: String, size: ImageSize) async throws -> UIImage {
try imageResponse.get()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
@propertyWrapper
struct Failable<T: Decodable & Sendable>: Decodable & Sendable {
struct Failable<T: Decodable & Sendable>: Decodable, Sendable {
var wrappedValue: T?

init(wrappedValue: T?) {
self.wrappedValue = wrappedValue
}

init(from decoder: Decoder) throws {
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let wrappedValue = try? container.decode(T.self)
self.init(wrappedValue: wrappedValue)
}
}

extension Failable: Encodable where T: Encodable {
func encode(to encoder: Encoder) throws {
func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ struct CastList: View {
@ViewContext
var context

var casts: AsyncPhase<[Credits.Person], Error> {
var casts: AsyncPhase<[Credits.Person], any Error> {
context.watch(CastsAtom(movieID: movieID).phase)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct NetworkImage: View {
@ViewContext
var context

var image: Task<UIImage, Error> {
var image: Task<UIImage, any Error> {
context.watch(ImageAtom(path: path, size: size))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct RecordingData: Equatable {

@MainActor
struct VoiceMemoActions {
let context: AtomContext
let context: any AtomContext

func toggleRecording() {
let isRecording = context.read(IsRecordingAtom())
Expand Down Expand Up @@ -68,13 +68,13 @@ struct VoiceMemoActionsAtom: ValueAtom, Hashable {
}

struct AudioSessionAtom: ValueAtom, Hashable {
func value(context: Context) -> AudioSessionProtocol {
func value(context: Context) -> any AudioSessionProtocol {
AVAudioSession.sharedInstance()
}
}

struct AudioRecorderAtom: ValueAtom, Hashable {
func value(context: Context) -> AudioRecorderProtocol {
func value(context: Context) -> any AudioRecorderProtocol {
AudioRecorder {
context[IsRecordingFailedAtom()] = true
context[RecordingDataAtom()] = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ struct AudioPlayerAtom: ValueAtom {
voiceMemo.url
}

func value(context: Context) -> AudioPlayerProtocol {
func value(context: Context) -> any AudioPlayerProtocol {
AudioPlayer(
onFinish: {
context[IsPlayingAtom(voiceMemo: voiceMemo)] = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ final class AudioPlayer: NSObject, AVAudioPlayerDelegate, AudioPlayerProtocol {
}
}

func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: (any Error)?) {
onFail()
}
}

final class MockAudioPlayer: AudioPlayerProtocol, @unchecked Sendable {
private(set) var isPlaying = false
var playingError: Error?
var playingError: (any Error)?

func play(url: URL) throws {
if let playingError = playingError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ final class AudioRecorder: NSObject, AVAudioRecorderDelegate, AudioRecorderProto
}
}

func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: (any Error)?) {
onFail()
}
}

final class MockAudioRecorder: AudioRecorderProtocol, @unchecked Sendable {
var isRecording = false
var recordingError: Error?
var recordingError: (any Error)?
var currentTime: TimeInterval = 10

func record(url: URL) throws {
Expand Down
12 changes: 10 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import PackageDescription

let swiftSettings: [SwiftSetting] = [
.enableUpcomingFeature("ExistentialAny")
]

let package = Package(
name: "swiftui-atom-properties",
platforms: [
Expand All @@ -14,10 +18,14 @@ let package = Package(
.library(name: "Atoms", targets: ["Atoms"])
],
targets: [
.target(name: "Atoms"),
.target(
name: "Atoms",
swiftSettings: swiftSettings
),
.testTarget(
name: "AtomsTests",
dependencies: ["Atoms"]
dependencies: ["Atoms"],
swiftSettings: swiftSettings
),
],
swiftLanguageModes: [.v5, .v6]
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ struct UserView: View {
| |Description|
|:----------|:----------|
|Summary |Initiates a throwing `Task` from the given `async throws` function.|
|Output |`Task<T, Error>`|
|Output |`Task<T, any Error>`|
|Use Case |Throwing asynchronous operation e.g. API call|

<details><summary><code>📖 Example</code></summary>
Expand Down Expand Up @@ -519,7 +519,7 @@ struct MoviesView: View {
| |Description|
|:----------|:----------|
|Summary |Provides a `AsyncPhase` value that represents asynchronous, sequential elements of the given `AsyncSequence`.|
|Output |`AsyncPhase<T, Error>`|
|Output |`AsyncPhase<T, any Error>`|
|Use Case |Handle multiple asynchronous values e.g. web-sockets|

<details><summary><code>📖 Example</code></summary>
Expand Down Expand Up @@ -744,7 +744,7 @@ struct FetchWeatherAtom: ThrowingTaskAtom, Hashable {

struct WeatherReportView: View {
@Watch(FetchWeatherAtom().phase)
var weatherPhase // : AsyncPhase<Weather, Error>
var weatherPhase // : AsyncPhase<Weather, any Error>

var body: some View {
switch weatherPhase {
Expand Down Expand Up @@ -833,11 +833,11 @@ private struct FetchMoviesTaskAtom: ThrowingTaskAtom, Hashable {
}

struct FetchMoviesPhaseAtom: ValueAtom, Refreshable, Hashable {
func value(context: Context) -> AsyncPhase<[Movies], Error> {
func value(context: Context) -> AsyncPhase<[Movies], any Error> {
context.watch(FetchMoviesTaskAtom().phase)
}

func refresh(context: CurrentContext) async -> AsyncPhase<[Movies], Error> {
func refresh(context: CurrentContext) async -> AsyncPhase<[Movies], any Error> {
await context.refresh(FetchMoviesTaskAtom().phase)
}

Expand Down Expand Up @@ -1093,7 +1093,7 @@ struct BooksView: View {

var body: some View {
// watch
let booksTask = context.watch(FetchBooksAtom()) // Task<[Book], Error>
let booksTask = context.watch(FetchBooksAtom()) // Task<[Book], any Error>
// binding
let searchQuery = context.binding(SearchQueryAtom()) // Binding<String>

Expand Down Expand Up @@ -1159,7 +1159,7 @@ struct LocationManagerDelegateAtom: ValueAtom, Hashable {
}

struct LocationManagerAtom: ValueAtom, Hashable {
func value(context: Context) -> LocationManagerProtocol {
func value(context: Context) -> any LocationManagerProtocol {
let delegate = context.watch(LocationManagerDelegateAtom())
let manager = CLLocationManager()
manager.delegate = delegate
Expand Down Expand Up @@ -1194,7 +1194,7 @@ struct APIClient: APIClientProtocol { ... }
struct MockAPIClient: APIClientProtocol { ... }

struct APIClientAtom: ValueAtom, Hashable {
func value(context: Context) -> APIClientProtocol {
func value(context: Context) -> any APIClientProtocol {
APIClient()
}
}
Expand Down Expand Up @@ -1252,7 +1252,7 @@ Optionally, you can pass `suspending` content to be displayed until the task com
```swift
struct NewsView: View {
@Watch(LatestNewsAtom())
var newsTask: Task<News, Error>
var newsTask: Task<News, any Error>

var body: some View {
Suspense(newsTask) { news in
Expand Down Expand Up @@ -1486,7 +1486,7 @@ class MockAPIClient: APIClientProtocol {
}

struct APIClientAtom: ValueAtom, Hashable {
func value(context: Context) -> APIClientProtocol {
func value(context: Context) -> any APIClientProtocol {
APIClient()
}
}
Expand Down Expand Up @@ -1715,7 +1715,7 @@ class MessageLoader: ObservableObject {
let context: AtomContext

@Published
var phase = AsyncPhase<[Message], Error>.suspending
var phase = AsyncPhase<[Message], any Error>.suspending

init(context: AtomContext) {
self.context = context
Expand Down
Loading