Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Create a cache system for images #573

Merged
59 changes: 59 additions & 0 deletions app-ios/Sources/CommonComponents/Timetable/CircularUserIcon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Foundation
import SwiftUI

private actor SpeakerIconInMemoryCache {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be appropriate to rename this actor as well as circular icon view.
What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry...
I forgot rename this actor.

I fixed it.
Please check again 🙏🏻

static let shared = SpeakerIconInMemoryCache()
private init() {}

private var cache: [String: Data] = [:]

func data(urlString: String) -> Data? {
return cache[urlString]
}

func set(data: Data, urlString: String) {
cache[urlString] = data
}
}

public struct CircularUserIcon: View {
let urlString: String
@State private var iconData: Data?

public init(urlString: String) {
self.urlString = urlString
}

public var body: some View {
Group {
if let data = iconData,
let uiImage = UIImage(data: data) {
Image(uiImage: uiImage)
.resizable()
} else {
Circle().stroke(Color.gray)
}
}
.clipShape(Circle())
.task {
if let data = await SpeakerIconInMemoryCache.shared.data(urlString: urlString) {
iconData = data
return
}

guard let url = URL(string: urlString) else {
return
}
let urlRequest = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy)
if let (data, _) = try? await URLSession.shared.data(for: urlRequest) {

Check warning on line 48 in app-ios/Sources/CommonComponents/Timetable/CircularUserIcon.swift

View workflow job for this annotation

GitHub Actions / build-test-ios

passing argument of non-sendable type '(any URLSessionTaskDelegate)?' outside of main actor-isolated context may introduce data races
iconData = data
await SpeakerIconInMemoryCache.shared.set(data: data, urlString: urlString)
}
}
}
}

#Preview {
CircularUserIcon(urlString: "https://avatars.githubusercontent.com/u/10727543?s=96&v=4")
.frame(width: 32, height: 32)
}
14 changes: 2 additions & 12 deletions app-ios/Sources/CommonComponents/Timetable/TimetableCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,8 @@ public struct TimetableCard: View {

ForEach(timetableItem.speakers, id: \.id) { speaker in
HStack(spacing: 8) {
Group {
if let url = URL(string: speaker.iconUrl) {
AsyncImage(url: url) {
$0.image?.resizable()
}
} else {
Circle().stroke(Color.gray)
}
}
.frame(width: 32, height: 32)
.clipShape(Circle())

CircularUserIcon(urlString: speaker.iconUrl)
.frame(width: 32, height: 32)
Text(speaker.name)
.textStyle(.titleSmall)
.foregroundStyle(AssetColors.Surface.onSurfaceVariant.swiftUIColor)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import SwiftUI
import Theme
import Model
import CommonComponents

struct ContributorListItemView: View {
let contributor: Contributor
Expand All @@ -13,11 +14,8 @@ struct ContributorListItemView: View {
}
} label: {
HStack(alignment: .center, spacing: 12) {
AsyncImage(url: contributor.iconUrl) {
$0.image?.resizable()
}
.frame(width: 52, height: 52)
.clipShape(Circle())
CircularUserIcon(urlString: contributor.iconUrl.absoluteString)
.frame(width: 52, height: 52)

Text(contributor.userName)
.textStyle(.bodyLarge)
Expand Down
18 changes: 8 additions & 10 deletions app-ios/Sources/StaffFeature/StaffLabel.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import SwiftUI
import Theme
import CommonComponents

struct StaffLabel: View {
let name: String
let icon: URL

var body: some View {
HStack(alignment: .center, spacing: 12) {
AsyncImage(url: icon) {
$0.image?.resizable()
}
.frame(width: 52, height: 52)
.clipShape(Circle())
.overlay(
Circle()
.stroke(AssetColors.Outline.outline.swiftUIColor, lineWidth: 1)
)
CircularUserIcon(urlString: icon.absoluteString)
.frame(width: 52, height: 52)
.overlay(
Circle()
.stroke(AssetColors.Outline.outline.swiftUIColor, lineWidth: 1)
)

Text(name)
.textStyle(.bodyLarge)
Expand All @@ -27,5 +25,5 @@ struct StaffLabel: View {
}

#Preview {
StaffLabel(name: "hoge", icon: .init(string: "")!)
StaffLabel(name: "hoge", icon: .init(string: "https://avatars.githubusercontent.com/u/10727543?s=156&v=4")!)
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,8 @@ public struct TimetableDetailView: View {

ForEach(store.timetableItem.speakers, id: \.id) { speaker in
HStack(spacing: 12) {
if let url = URL(string: speaker.iconUrl) {
AsyncImage(url: url) {
$0.image?.resizable()
}
CircularUserIcon(urlString: speaker.iconUrl)
.frame(width: 52, height: 52)
.clipShape(Circle())
}

VStack(alignment: .leading, spacing: 8) {
Text(speaker.name)
Expand Down
Loading