Skip to content

Commit

Permalink
better asyc photo loading
Browse files Browse the repository at this point in the history
  • Loading branch information
VPhung24 committed May 25, 2023
1 parent fa2c8a5 commit 70bc41a
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 11 deletions.
9 changes: 7 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0"),
.package(url: "https://github.com/realm/SwiftLint.git", from: "0.51.0")
.package(url: "https://github.com/realm/SwiftLint.git", from: "0.51.0"),
.package(url: "https://github.com/kean/Nuke.git", from: "12.1.0"),
.package(url: "https://github.com/kaishin/Gifu.git", from: "3.4.0")
],
targets: [
.target(
name: "SnowballSwiftKit",
dependencies: [
"Alamofire",
"SnowballAssetKit"
"SnowballAssetKit",
"Nuke",
.product(name: "NukeUI", package: "Nuke"),
"Gifu"
],
plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@
"version" : "1.7.1"
}
},
{
"identity" : "gifu",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kaishin/Gifu.git",
"state" : {
"revision" : "82da0086dea14ca9afc9801234ad8dc4cd9e2738",
"version" : "3.4.1"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke.git",
"state" : {
"revision" : "f4d9b95788679d0654c032961f73e7e9c16ca6b4",
"version" : "12.1.0"
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
Expand Down
85 changes: 85 additions & 0 deletions Sources/SnowballSwiftKit/Views/GIFImage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// GIFImage.swift
//
//
// Created by Vivian Phung on 5/25/23.
//

import SwiftUI
import Gifu

public struct GIFImage: View {
private let source: GIFSource
private var loopCount = 0
private var isResizable = false

/// Initializes the view with the given GIF image data.
public init(data: Data) {
self.source = .data(data)
}

/// Initialzies the view with the given GIF image url.
public init(url: URL) {
self.source = .url(url)
}

/// Initialzies the view with the given GIF image name.
public init(imageName: String) {
self.source = .imageName(imageName)
}

/// Sets the desired number of loops. By default, the number of loops infinite.
public func loopCount(_ value: Int) -> GIFImage {
var copy = self
copy.loopCount = value
return copy
}

/// Sets an image to fit its space.
public func resizable() -> GIFImage {
var copy = self
copy.isResizable = true
return copy
}

public var body: some View {
_GIFImage(source: source, loopCount: loopCount, isResizable: isResizable)
}
}

@available(iOS 13, tvOS 13, *)
private struct _GIFImage: UIViewRepresentable {
let source: GIFSource
let loopCount: Int
let isResizable: Bool

func makeUIView(context: Context) -> GIFImageView {
let imageView = GIFImageView()
if isResizable {
imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
}
return imageView
}

func updateUIView(_ imageView: GIFImageView, context: Context) {
switch source {
case .data(let data):
imageView.animate(withGIFData: data, loopCount: loopCount)
case .url(let url):
imageView.animate(withGIFURL: url, loopCount: loopCount)
case .imageName(let imageName):
imageView.animate(withGIFNamed: imageName, loopCount: loopCount)
}
}

static func dismantleUIView(_ imageView: GIFImageView, coordinator: ()) {
imageView.prepareForReuse()
}
}

private enum GIFSource {
case data(Data)
case url(URL)
case imageName(String)
}
42 changes: 39 additions & 3 deletions Sources/SnowballSwiftKit/Views/SnowballNFTGridView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
// Created by Vivian Phung on 5/1/23.
//

import Foundation
import SwiftUI
import Nuke
import NukeUI

// todo: snowball settings
public struct SnowballNFTGridView: View {
@State private var listId = UUID()
@StateObject private var viewModel = AlchemyNFTViewModel()
@State var ethAddress: String
var ethAddress: String

// todo: snowball settings with api key
var alchemyKey: String
Expand All @@ -25,14 +27,22 @@ public struct SnowballNFTGridView: View {
self.ethAddress = ethAddress
self.alchemyKey = alchemyKey
}

private let pipeline = ImagePipeline {
$0.dataLoader = {
let config = URLSessionConfiguration.default
config.urlCache = nil
return DataLoader(configuration: config)
}()
}

public var body: some View {
ScrollView {
VStack {
LazyVGrid(columns: gridLayout, spacing: 10) {
ForEach(viewModel.nfts) { nft in
VStack(alignment: .leading) {
AsyncImage(url: URL(string: nft.media.first?.thumbnail ?? "https://en.wikipedia.org/wiki/File:Lynx_kitten.jpg"))
makeImage(url: URL(string: nft.media.first?.thumbnail ?? "https://en.wikipedia.org/wiki/File:Lynx_kitten.jpg")!)
Text(nft.title)
.font(.caption)
.lineLimit(1)
Expand All @@ -45,10 +55,36 @@ public struct SnowballNFTGridView: View {
}
}
.padding(10)
.navigationBarItems(trailing: Button(action: {
ImagePipeline.shared.cache.removeAll()
self.listId = UUID()
}, label: {
Image(systemName: "arrow.clockwise")
}))
.listStyle(.plain)
.onAppear {
viewModel.fetchNFTs(forAddress: ethAddress, key: alchemyKey)
}
}
.onAppear {
viewModel.fetchNFTs(forAddress: self.ethAddress, key: alchemyKey)
}
}
}

// This is where the image view is created.
func makeImage(url: URL) -> some View {
LazyImage(url: url) { state in
if let container = state.imageContainer, container.type == .gif, let data = container.data {
GIFImage(data: data)
} else if let image = state.image {
image
.resizable()
.scaledToFit()
} else {
Color.gray.opacity(0.2) // Placeholder
}
}
.pipeline(pipeline)
}
}
51 changes: 45 additions & 6 deletions Sources/SnowballSwiftKit/Views/SnowballNFTListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
//

import SwiftUI
import Nuke
import NukeUI

// todo: snowball settings
public struct SnowballNFTListView: View {
@StateObject private var viewModel = AlchemyNFTViewModel()
@State private var listId = UUID()
let ethAddress: String

// todo: snowball settings with api key
Expand All @@ -20,20 +23,56 @@ public struct SnowballNFTListView: View {
self.alchemyKey = alchemyKey
}

private let pipeline = ImagePipeline {
$0.dataLoader = {
let config = URLSessionConfiguration.default
config.urlCache = nil
return DataLoader(configuration: config)
}()
}

public var body: some View {
List(viewModel.nfts) { nft in
HStack {
AsyncImage(url: URL(string: nft.media.first?.thumbnail ?? "https://en.wikipedia.org/wiki/File:Lynx_kitten.jpg")!)

let view = VStack {
VStack {
makeImage(url: URL(string: nft.media.first?.thumbnail ?? "https://en.wikipedia.org/wiki/File:Lynx_kitten.jpg")!)
Text(nft.title)
.font(.caption)
.lineLimit(1)
.padding(.top, 5)
}
}.padding()
}.listRowInsets(EdgeInsets(.zero))
if #available(iOS 15, *) {
view.listRowSeparator(.hidden)
} else {
view
}
}

.id(listId)
.navigationBarItems(trailing: Button(action: {
ImagePipeline.shared.cache.removeAll()
self.listId = UUID()
}, label: {
Image(systemName: "arrow.clockwise")
}))
.listStyle(.plain)
.onAppear {
viewModel.fetchNFTs(forAddress: ethAddress, key: alchemyKey)
}
}

// This is where the image view is created.
func makeImage(url: URL) -> some View {
LazyImage(url: url) { state in
if let container = state.imageContainer, container.type == .gif, let data = container.data {
GIFImage(data: data)
} else if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fit)
} else {
Color.gray.opacity(0.2) // Placeholder
}
}
.pipeline(pipeline)
}
}

0 comments on commit 70bc41a

Please sign in to comment.