[ADD] iOS 13 compatible AsyncImage implementation with cache

This commit is contained in:
Fabian Thies
2022-11-27 00:26:15 +01:00
parent 16a8bce102
commit ca7acc17da
8 changed files with 143 additions and 60 deletions

View File

@@ -7,6 +7,7 @@
//
import SwiftUI
import AsyncImage
struct AppIconView: View {
let iconUrl: URL?
@@ -16,7 +17,7 @@ struct AppIconView: View {
}
var body: some View {
if let iconUrl, #available(iOS 15.0, *) {
if let iconUrl {
AsyncImage(url: iconUrl) { image in
image
.resizable()
@@ -25,10 +26,6 @@ struct AppIconView: View {
}
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
} else {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.background(Color.secondary)
.frame(width: size, height: size)
}
}
}

View File

@@ -8,6 +8,7 @@
import SwiftUI
import GridStack
import AsyncImage
import AltStoreCore
struct AppDetailView: View {
@@ -39,7 +40,7 @@ struct AppDetailView: View {
}
var body: some View {
ObservableScrollView(scrollOffset: self.$scrollOffset) { proxy in
ObservableScrollView(scrollOffset: $scrollOffset) { proxy in
LazyVStack {
headerView
.frame(height: headerViewHeight)
@@ -86,29 +87,35 @@ struct AppDetailView: View {
}
var contentView: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 24) {
if let subtitle = storeApp.subtitle {
Text(subtitle)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.padding()
.padding(.horizontal)
}
if !storeApp.screenshotURLs.isEmpty {
screenshotsView
// Equatable: Only reload the view if the screenshots change.
// This prevents unnecessary redraws on scroll.
AppScreenshotsScrollView(urls: storeApp.screenshotURLs)
.equatable()
}
Text(storeApp.localizedDescription)
.lineLimit(6)
.padding()
.padding(.horizontal)
currentVersionView
.padding(.horizontal)
permissionsView
.padding(.horizontal)
// TODO: Add review view
// Only let users rate the app if it is currently installed!
}
.padding(.vertical)
.background(
RoundedRectangle(cornerRadius: contentCornerRadius)
.foregroundColor(Color(UIColor.systemBackground))
@@ -116,28 +123,6 @@ struct AppDetailView: View {
)
}
var screenshotsView: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(storeApp.screenshotURLs) { url in
if #available(iOS 15.0, *) {
AsyncImage(url: url) { image in
image.resizable()
} placeholder: {
Rectangle()
.foregroundColor(.secondary)
}
.aspectRatio(9/16, contentMode: .fit)
.cornerRadius(8)
}
}
}
.padding(.horizontal)
}
.frame(height: 400)
.shadow(radius: 12)
}
var currentVersionView: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .bottom) {
@@ -169,7 +154,6 @@ struct AppDetailView: View {
.foregroundColor(.secondary)
}
}
.padding()
}
var permissionsView: some View {
@@ -199,12 +183,5 @@ struct AppDetailView: View {
Spacer()
}
.padding()
}
}
//struct AppDetailView_Previews: PreviewProvider {
// static var previews: some View {
// AppDetailView()
// }
//}

View File

@@ -0,0 +1,49 @@
//
// AppScreenshotsScrollView.swift
// SideStore
//
// Created by Fabian Thies on 27.11.22.
// Copyright © 2022 SideStore. All rights reserved.
//
import SwiftUI
import AsyncImage
/// Horizontal ScrollView with an asynchronously loaded image for each screenshot URL
///
/// The struct inherits the `Equatable` protocol and implements the respective comparisation function to prevent the view from being constantly re-rendered when a `@State` change in the parent view occurs.
/// This way, the `AppScreenshotsScrollView` will only be reloaded when the parameters change.
struct AppScreenshotsScrollView: View {
let urls: [URL]
var aspectRatio: CGFloat = 9/16
var height: CGFloat = 400
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(urls) { url in
AsyncImage(url: url) { image in
image
.resizable()
} placeholder: {
Rectangle()
.foregroundColor(.secondary)
}
.aspectRatio(aspectRatio, contentMode: .fit)
.cornerRadius(8)
}
}
.padding(.horizontal)
}
.frame(height: height)
.shadow(radius: 12)
}
}
extension AppScreenshotsScrollView: Equatable {
/// Prevent re-rendering of the view if the parameters didn't change
static func == (lhs: AppScreenshotsScrollView, rhs: AppScreenshotsScrollView) -> Bool {
lhs.urls == rhs.urls && lhs.aspectRatio == rhs.aspectRatio && lhs.height == rhs.height
}
}

View File

@@ -7,6 +7,7 @@
//
import SwiftUI
import AsyncImage
import AltStoreCore
struct BrowseAppPreviewView: View {
@@ -23,16 +24,15 @@ struct BrowseAppPreviewView: View {
if !storeApp.screenshotURLs.isEmpty {
HStack {
ForEach(storeApp.screenshotURLs.prefix(2)) { url in
if #available(iOS 15.0, *) {
AsyncImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color(UIColor.secondarySystemBackground)
}
.cornerRadius(8)
AsyncImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color(UIColor.secondarySystemBackground)
.aspectRatio(9/16, contentMode: .fit)
}
.cornerRadius(8)
}
}
.frame(height: 300)

View File

@@ -7,6 +7,7 @@
//
import SwiftUI
import AsyncImage
import AltStoreCore
struct NewsItemView: View {
@@ -53,17 +54,15 @@ struct NewsItemView: View {
}
.padding(24)
if let imageUrl = newsItem.imageURL, #available(iOS 15.0, *) {
AsyncImage(
url: imageUrl,
content: { image in
if let image = image.image {
image
.resizable()
.aspectRatio(contentMode: .fit)
}
}
)
if let imageUrl = newsItem.imageURL {
AsyncImage(url: imageUrl) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color.secondary
.frame(maxWidth: .infinity, maxHeight: 100)
}
}
}
.frame(

View File

@@ -7,6 +7,7 @@
//
import SwiftUI
import AsyncImage
import AltStoreCore
import Intents
@@ -86,6 +87,9 @@ struct SettingsView: View {
Text("Switch to UIKit")
}
SwiftUI.Button(action: resetImageCache) {
Text("Reset Image Cache")
}
} header: {
Text("Debug")
}
@@ -137,6 +141,19 @@ struct SettingsView: View {
UIApplication.shared.keyWindow?.rootViewController = rootVC
}
func resetImageCache() {
do {
let url = try FileManager.default.url(
for: .cachesDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
try FileManager.default.removeItem(at: url.appendingPathComponent("com.zeu.cache", isDirectory: true))
} catch let error {
fatalError("\(error)")
}
}
}
struct SettingsView_Previews: PreviewProvider {
@@ -144,3 +161,5 @@ struct SettingsView_Previews: PreviewProvider {
SettingsView()
}
}