Last active
July 6, 2023 09:42
-
-
Save shayanbo/46e08a08aafe2bddcba2d7c48020e33e to your computer and use it in GitHub Desktop.
Enhanced AsyncImage Sample
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
import SwiftUI | |
import CommonCrypto | |
struct AsyncImage<Content> : View where Content: View { | |
enum Error : Swift.Error { | |
case invalidData | |
} | |
private let url: URL | |
@State | |
private var phase: AsyncImagePhase = .empty | |
@ViewBuilder | |
private let content: (AsyncImagePhase) -> Content | |
init(url: URL) where Content == Image { | |
self.url = url | |
self.content = { phase in | |
phase.image ?? Image("") | |
} | |
} | |
init<I, P>(url: URL, content: @escaping (Image)->I, placeholder: @escaping () -> P) where Content == Group<_ConditionalContent<I, P>>, I : View, P : View { | |
self.url = url | |
self.content = { phase in | |
Group { | |
if let image = phase.image { | |
content(image) | |
} else { | |
placeholder() | |
} | |
} | |
} | |
} | |
init(url: URL, @ViewBuilder content: @escaping (AsyncImagePhase) -> Content) { | |
self.url = url | |
self.content = content | |
} | |
var body: some View { | |
content(phase).task { | |
/// try cache | |
let cachedName = url.absoluteString.md5 | |
let cacheURL = URL.cachesDirectory.appending(path: cachedName) | |
var cachePath = cacheURL.absoluteString | |
cachePath.replace("file://", with: "") | |
if FileManager.default.fileExists(atPath: cachePath) { | |
if let image = try? UIImage(data: Data(contentsOf: cacheURL)) { | |
self.phase = .success(Image(uiImage: image)) | |
return | |
} | |
} | |
/// load from remote | |
do { | |
let result = try await URLSession.shared.data(from: url) | |
if let image = UIImage(data: result.0) { | |
/// disk cache | |
let cachedName = url.absoluteString.md5 | |
let cachePath = URL.cachesDirectory.appending(path: cachedName) | |
try? result.0.write(to: cachePath) | |
self.phase = .success(Image(uiImage: image)) | |
} else { | |
self.phase = .failure(Error.invalidData) | |
} | |
} catch (let error) { | |
self.phase = .failure(error) | |
} | |
} | |
} | |
} | |
extension String { | |
var md5: String { | |
guard let data = data(using: .utf8) else { | |
return self | |
} | |
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) | |
_ = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in | |
return CC_MD5(bytes.baseAddress, CC_LONG(data.count), &digest) | |
} | |
return digest.map { String(format: "%02x", $0) }.joined() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment