Last active
May 28, 2022 13:11
-
-
Save leovido/3780c94ff23ba7bb965207d00aa03f6e to your computer and use it in GitHub Desktop.
This file contains hidden or 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
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