Skip to content

Commit

Permalink
Fix .svg CIS2Token images loading (#397)
Browse files Browse the repository at this point in the history
## Purpose

Due to loading of `.svg` CIS2Token image cause memory leak and fast app
memory our crash, wee need to separate logic for regular .png/.jpeg
images type and .svg

## Changes
Add fallback for token preview load logic, in case when token image is
`.svg` format
  • Loading branch information
ramakser committed Apr 9, 2024
1 parent 88ec97a commit bbadf02
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 170 deletions.
12 changes: 11 additions & 1 deletion ConcordiumWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@
080F53A42BA9E38800BADD38 /* SearchTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */; };
080F53A52BA9E38800BADD38 /* SearchTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */; };
080F53A62BA9E38800BADD38 /* SearchTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */; };
08C169E02BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; };
08C169E12BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; };
08C169E22BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; };
08C169E32BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; };
08F7ABD32B962CCA008588D2 /* CIS2TokenSelectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */; };
08F7ABD42B962CCA008588D2 /* CIS2TokenSelectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */; };
08F7ABD52B962CCA008588D2 /* CIS2TokenSelectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */; };
Expand Down Expand Up @@ -2953,6 +2957,7 @@
07E6E27527B558A30083A852 /* intro_flow_style.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = intro_flow_style.css; sourceTree = "<group>"; };
07E8573326F1D24F001FB2D2 /* RealmHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmHelper.swift; sourceTree = "<group>"; };
080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTokenViewModel.swift; sourceTree = "<group>"; };
08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIS2TokenImagePreview.swift; sourceTree = "<group>"; };
08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIS2TokenSelectViewModel.swift; sourceTree = "<group>"; };
17A41E4A266789F10079BD3A /* StagingNet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StagingNet.entitlements; sourceTree = "<group>"; };
17A41E4B26678FF80079BD3A /* Concordium ID.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Concordium ID.entitlements"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3722,6 +3727,7 @@
0154A6772A8CEA0500C33954 /* CIS2TokenSelectView.swift */,
08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */,
080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */,
08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */,
);
path = TokenSelectionView;
sourceTree = "<group>";
Expand Down Expand Up @@ -6878,6 +6884,7 @@
89B34A0C2892ADAE0059891C /* RecoveryPhraseRecoverIntroPresenter.swift in Sources */,
075A4BBE2719A04F00066AF1 /* CopyReferenceInfoWidgetViewController.swift in Sources */,
8992BA22281FFDDB005279C9 /* BakerPoolMenuPresenter.swift in Sources */,
08C169E02BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */,
89B34A022892AACE0059891C /* RecoveryPhraseRecoverIntroView.swift in Sources */,
017A5F242A49B97500E80B56 /* Schema.swift in Sources */,
FA01CA22291C5D3100BD8738 /* CGFloat+Helper.swift in Sources */,
Expand Down Expand Up @@ -7249,6 +7256,7 @@
FA01C9D4291C5D3100BD8738 /* UIScreen+Dimensions.swift in Sources */,
FA01C9E0291C5D3100BD8738 /* UITextField+Helper.swift in Sources */,
89D46873289D21D2007D3FC8 /* SeedIdentityStatusView.swift in Sources */,
08C169E22BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */,
7F85B774246A9A7C00ED09B8 /* AccountDetailsViewController.swift in Sources */,
7F168A0E24812B7100DE429E /* SendFundConfirmationViewController.swift in Sources */,
01A3D11A2B19CF7E004FB79D /* SendFundsAmount.swift in Sources */,
Expand Down Expand Up @@ -7843,6 +7851,7 @@
7F85B86F246A9AB600ED09B8 /* ShowAlertProtocol.swift in Sources */,
FA01C9D5291C5D3100BD8738 /* UIScreen+Dimensions.swift in Sources */,
FA01C9E1291C5D3100BD8738 /* UITextField+Helper.swift in Sources */,
08C169E32BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */,
89D46874289D21D2007D3FC8 /* SeedIdentityStatusView.swift in Sources */,
7F168A0F24812B7100DE429E /* SendFundConfirmationViewController.swift in Sources */,
01A3D11B2B19CF7E004FB79D /* SendFundsAmount.swift in Sources */,
Expand Down Expand Up @@ -8589,6 +8598,7 @@
19C2EE4F9BADE3BCC1AF1358 /* SelectRecipientViewController.swift in Sources */,
079F310326EB5A2D00ED96D3 /* PermissionHelper.swift in Sources */,
19C2ED4B9B793A090B70CF7F /* AddRecipientPresenter.swift in Sources */,
08C169E12BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */,
07519F6D26FB23DE00F4E6E3 /* AddMemoViewController.swift in Sources */,
01A3D11E2B19CF90004FB79D /* FungibleToken.swift in Sources */,
52CE0C7A250FFEC70018B5D1 /* Encrypted.swift in Sources */,
Expand Down Expand Up @@ -9342,7 +9352,7 @@
repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
minimumVersion = 3.0.0;
};
};
01BCA9582AB9CF62006D1F0C /* XCRemoteSwiftPackageReference "BigInt" */ = {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ struct TokenDetailsView: View {
}
HStack(alignment: .center) {
let size: CGFloat = token.display == nil ? 100 : 300
WebImage(url: token.display ?? token.thumbnail)
.resizable()
.placeholder(Image(systemName: "photo"))
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: size, height: size, alignment: .center)
WebImage(url: token.display ?? token.thumbnail) { image in
image.resizable()
} placeholder: {
Image(systemName: "photo")
}
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: size, height: size, alignment: .center)
}
.frame(maxWidth: .infinity)
Group {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// CryptoImage.swift
// ConcordiumWallet
//
// Created by Maksym Rachytskyy on 08.04.2024.
// Copyright © 2024 concordium. All rights reserved.
//

import SwiftUI
import SDWebImageSwiftUI
import SDWebImageSVGCoder

struct CIS2TokenImagePreview: View {
enum Size {
case small
case medium
case custom(width: CGFloat, height: CGFloat)

var size: CGSize {
switch self {
case .small: return .init(width: 20, height: 20)
case .medium: return .init(width: 45, height: 45)
case let .custom(width, height): return .init(width: width, height: height)
}
}
}

let url: URL?
let size: CIS2TokenImagePreview.Size

var body: some View {
Group {
if let url = url, (url.absoluteString.range(of: ".svg", options: .caseInsensitive) != nil) {
WebImage(
url: url,
context: [.imageCoder: CustomSVGDecoder(fallbackDecoder: SDImageSVGCoder.shared)]
)
.resizable()
.indicator(.activity)

} else {
AsyncImage(url: url, scale: 1.0) { image in
image
.resizable()
} placeholder: {
Color.gray.opacity(0.4).clipShape(Circle())
}
}
}
.clipShape(Circle())
.transition(.fade(duration: 0.5))
.aspectRatio(contentMode: .fill)
.frame(width: size.size.width, height: size.size.height)
}
}

/// https://stackoverflow.com/questions/74427783/download-svg-image-in-ios-swift
private class CustomSVGDecoder: NSObject, SDImageCoder {

let fallbackDecoder: SDImageCoder?

init(fallbackDecoder: SDImageCoder?) {
self.fallbackDecoder = fallbackDecoder
}

static var regex: NSRegularExpression = {
let pattern = "<image.*xlink:href=\"data:image\\/(png|jpg);base64,(.*)\"\\/>"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
return regex
}()

func canDecode(from data: Data?) -> Bool {
guard let data = data, let string = String(data: data, encoding: .utf8) else { return false }
guard Self.regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) == nil else {
return true //It self should decode
}
guard let fallbackDecoder = fallbackDecoder else {
return false
}
return fallbackDecoder.canDecode(from: data)
}

func decodedImage(with data: Data?, options: [SDImageCoderOption : Any]? = nil) -> UIImage? {
guard let data = data,
let string = String(data: data, encoding: .utf8) else { return nil }
guard let match = Self.regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) else {
return fallbackDecoder?.decodedImage(with: data, options: options)
}
guard let rawBase64DataRange = Range(match.range(at: 2), in: string) else {
return fallbackDecoder?.decodedImage(with: data, options: options)
}
let rawBase64Data = String(string[rawBase64DataRange])
guard let imageData = Data(base64Encoded: Data(rawBase64Data.utf8), options: .ignoreUnknownCharacters) else {
return fallbackDecoder?.decodedImage(with: data, options: options)
}
return UIImage(data: imageData)
}

//You might need to implement these methods, I didn't check their meaning yet
func canEncode(to format: SDImageFormat) -> Bool {
return true
}

func encodedData(with image: UIImage?, format: SDImageFormat, options: [SDImageCoderOption : Any]? = nil) -> Data? {
return nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,7 @@ struct CIS2TokenSelectView: View {

func CIS2TokenView(model: CIS2TokenSelectionRepresentable) -> some View {
HStack {
WebImage(url: model.thumbnail ?? model.display)
.resizable()
.placeholder(Image(systemName: "photo"))
.indicator(.activity)
.scaledToFit()
.frame(width: 45, height: 45, alignment: .center)
CIS2TokenImagePreview(url: model.thumbnail ?? model.display, size: .medium)

VStack(alignment: .leading) {
Text(model.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ struct SendFundTokenSelection: View {
didSelectToken(token)
} label: {
HStack {
WebImage(url: token.thumbnail)
.resizable()
.placeholder(Image(systemName: "photo"))
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: 48, height: 48, alignment: .center)
WebImage(url: token.thumbnail) { image in
image.resizable()
} placeholder: {
Image(systemName: "photo")
}
.indicator(.activity)
.transition(.fade(duration: 0.5))
.scaledToFit()
.frame(width: 48, height: 48, alignment: .center)
VStack(alignment: .leading) {
Text(token.name)
.multilineTextAlignment(.leading)
Expand Down

0 comments on commit bbadf02

Please sign in to comment.