Skip to content

Instantly share code, notes, and snippets.

@leovido
Last active May 28, 2022 13:11
Show Gist options
  • Save leovido/3780c94ff23ba7bb965207d00aa03f6e to your computer and use it in GitHub Desktop.
Save leovido/3780c94ff23ba7bb965207d00aa03f6e to your computer and use it in GitHub Desktop.
public enum ImageLoaderError: Error, Equatable {
case invalidData
case imageLoadingFailed
case message(String)
}
public struct ImageState: Equatable {
public var image: UIImage?
}
public enum ImageLoaderAction: Equatable {
case downloadImage(url: URL?)
case imageLoadingFailed
case imageLoaded(Result<UIImage, ImageLoaderError>)
}
public struct ImageLoaderEnvironment {
public var fetchImage: (URL?) -> Effect<UIImage, ImageLoaderError>
public init(fetchImage: @escaping (URL?) -> Effect<UIImage, ImageLoaderError>) {
self.fetchImage = fetchImage
}
}
extension ImageLoaderEnvironment {
public static var live: ImageLoaderEnvironment = ImageLoaderEnvironment { url in
guard let url = url else {
return Effect(error: ImageLoaderError.imageLoadingFailed)
}
return URLSession.shared.dataTaskPublisher(for: url)
.receive(on: RunLoop.main)
.map(\.data)
.tryMap({ data -> UIImage in
guard let newImage = UIImage(data: data) else {
throw ImageLoaderError.invalidData
}
return newImage
})
.mapError({ ImageLoaderError.message($0.localizedDescription) })
.eraseToEffect()
}
public static var mock: ImageLoaderEnvironment = ImageLoaderEnvironment { url in
guard let url = url,
let localImage: UIImage = .init(named: "demo", in: .module, with: nil)
else {
return Effect(error: ImageLoaderError.imageLoadingFailed)
}
return Effect(value: localImage)
}
}
struct ImageView: View {
let store: Store<ImageState, ImageLoaderAction> = .init(
initialState: ImageState(),
reducer: imageLoaderReducer.debug(),
environment: ImageLoaderEnvironment.mock
)
var body: some View {
WithViewStore(store) { viewStore in
Image(uiImage: viewStore.image ?? UIImage(systemName: "person")!)
.onAppear() {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
viewStore.send(.downloadImage(url: URL(string: "https://example.com")))
}
}
}
}
}
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
ImageView()
}
}
public let imageLoaderReducer: Reducer<ImageState, ImageLoaderAction, ImageLoaderEnvironment> =
.init { state, action, environment in
switch action {
case let .downloadImage(url):
return environment.fetchImage(url)
.catchToEffect()
.map(ImageLoaderAction.imageLoaded)
case .imageLoadingFailed:
return .none
case .imageLoaded(.success(let image)):
state.image = image
return .none
case .imageLoaded(.failure(let error)):
state.image = UIImage(systemName: "exclamationmark.triangle.fill")
return .none
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment