I make a request to the server and in response I get an svg image. I tried to display it using AsyncImage(), but it didn't work. Is there any solution to this problem?
So I tried to do:
AsyncImage(url: URL(string: "https://s3-symbol-logo.tradingview.com/applovin--big.svg"))
But this led to the fact that just placeholder was shown:
EDIT:
I found a solution and posted another answer, please check it out in this forum
There is no solution for this, because .SVG images are not actually images,
they are actually files (SVG: Scalable Vector Graphics) and they are based on calculations and not actual pixels.
The best option for you is to tell your backend to convert all .SVG files into .PNG or .JPG files and then give you the images urls in that format.
Or another valid option is to download the image into your project assets and then give it the name of the URL and whenever you are provided with the url you just use it like this:
Image("https://s3-symbol-logo.tradingview.com/applovin--big.svg")
I have been trying to do the same thing as you are but I only found one solution: present the .SVG image in a webView. if you are intrested you can find it here (Presenting .SVG as a web View) but keep in mind it is not optimal
there are two ways to implement SVG images in your project one is via using a library on GitHub called SVGKit (this way is expensive using the ram and slows down the loading time a little), but the other way that was perfect is using:
two libraries:
1- SDWebImageSVGCoder
2- SDWebImageSwiftUI
after you add these libraries to your project through the Xcode package dependencies, you write this code to set it up and use it.
import SwiftUI
import SDWebImageSVGCoder
#main
struct Package_trialsApp: App {
init() {
setUpDependencies() // Initialize SVGCoder
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// Initialize SVGCoder
private extension Package_trialsApp {
func setUpDependencies() {
SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared)
}
}
after setting that up you can use it like this:
import SDWebImageSwiftUI
WebImage(url: "https://www.svgrepo.com/show/423378/car-service.svg", options: [], context: [.imageThumbnailPixelSize : CGSize.zero])
.placeholder {ProgressView()}
.resizable()
.frame(width: 300, height: 300)
Bonus:
you can also do something like this to make your code more efficient and more reusable:
import SwiftUI
import SDWebImageSwiftUI
struct ContentView: View {
let imageArray = [
URL(string: "https://www.svgrepo.com/show/423378/car-service.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svgfiles/androi.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"),
URL(string: "https://jpeg.org/images/jpeg-home.jpg")
]
var body: some View {
ScrollView {
Link(destination: URL(string: "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg")!, label: {
Text("Sample SVG image")
.foregroundColor(.orange)
})
ForEach(imageArray, id: \.self) { url in
if let bolean = url?.absoluteString.suffix(3).localizedCaseInsensitiveContains("svg") {
if bolean {
HStack {
WebImage(url: url, options: [], context: [.imageThumbnailPixelSize : CGSize.zero])
.placeholder {ProgressView()}
.resizable()
.frame(width: 300, height: 300)
}
} else {
AsyncImage(url: url, content: { phase in
switch phase {
case .empty:
ProgressView()
case .failure:
Color.red
case .success(let image):
image.resizable().frame(width: 100, height: 100)
#unknown default:
fatalError()
}
})
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Looks like it's your image link problem.
I tested two AsyncImage, my link worked, but your link did not.
Try checking whether your link is the right path for the image loading.
Code is below the image:
VStack {
//my link worked
AsyncImage(url: URL(string: "https://picsum.photos/200")) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.frame(width: 100, height: 100)
//your link did not work. result in loading.
AsyncImage(url: URL(string: "https://s3-symbol-logo.tradingview.com/applovin--big.svg")) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.frame(width: 100, height: 100)
}
Related
I'm learning how to make a webview from youtube, however i followed every step of their code, however my code got an error while their code did not. I can't run but they can. Error said "Cannot convert value of type 'WebView.Context' (aka 'UIViewRepresentableContext') to expected argument type 'URLRequest'"
Can someone point out the problem? Thank you.
import SwiftUI
import WebKit
import UIKit
import SwiftUI
struct ContentViewww: View {
var body: some View {
NavigationView {
NavigationLink {
WebView(url: URL(string: "https://www.youtube.com")!)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} label: {
Text("open website")
.padding(10)
.background(.black)
.foregroundColor(.white)
}
.navigationBarHidden(true)
}
}
}
struct WebView: UIViewRepresentable {
var url : URL
func makeUIView(context: Context) -> some UIView {
let web = WKWebView()
let request = URLRequest(url: url)
web.load(context)
return web
}
func updateUIView(_ uiView: UIViewType, context: Context) {
let web = WKWebView()
let request = URLRequest(url: url)
web.load(request)
}
}
You are supposed to load request not context.
func makeUIView(context: Context) -> some UIView {
let web = WKWebView()
let request = URLRequest(url: url)
web.load(request) //modified
return web
}
So I'm trying to create a custom image picker something like instagram but way more basic. This is how I created the screen using this.
struct NewPostScreen: View {
#StateObject var manager = SelectNewPostScreenManager()
let columns = [GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1), GridItem(.flexible(), spacing: 1)]
var body: some View {
ScrollView {
VStack(spacing: 1) {
Image(uiImage: manager.selectedPhoto?.uiImage ?? UIImage(named: "placeholder-image")!)
.resizable()
.scaledToFit()
.frame(width: 350, height: 350)
.id(1)
LazyVGrid(columns: columns, spacing: 1) {
ForEach(manager.allPhotos) { photo in
Image(uiImage: photo.uiImage)
.resizable()
.scaledToFill()
.frame(maxWidth: UIScreen.main.bounds.width/3, minHeight: UIScreen.main.bounds.width/3, maxHeight: UIScreen.main.bounds.width/3)
.clipped()
.onTapGesture {
manager.selectedPhoto = photo
}
}
}
}
}
}
}
The UI looks good and everything but sometimes when I click an image using the tapGesture it gives me an incorrect selectedPhoto for my manager. Here is how my manager looks and how I fetch the photos from the library.
class SelectNewPostScreenManager: ObservableObject {
#Environment(\.dismiss) var dismiss
#Published var selectedPhoto: Photo?
#Published var allPhotos: [Photo] = []
init() {
fetchPhotos()
}
private func assetsFetchOptions() -> PHFetchOptions {
let fetchOptions = PHFetchOptions()
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
return fetchOptions
}
func fetchPhotos() {
print("Fetching Photos")
let options = assetsFetchOptions()
let allAssets = PHAsset.fetchAssets(with: .image, options: options)
DispatchQueue.global(qos: . background).async {
allAssets.enumerateObjects { asset, count, _ in
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 250, height: 250)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, info in
guard let image = image else { return }
let photo = Photo(uiImage: image)
DispatchQueue.main.async {
self.allPhotos.append(photo)
}
}
}
}
}
}
This is how my photo object looks like as well.
struct Photo: Identifiable {
let id = UUID()
let uiImage: UIImage
}
I have no clue to why the tap gesture is not selecting the right item. Ive spent a couple of hours trying to figure out to why this is happening. I might just end up using the UIImagePickerController instead lol.
Anyways if someone can copy and paste this code into a new project of Xcode and run it on your actual device instead of the simulator. Let me know if its happening to you as well.
I was running it on an iPhone X.
The problem is that the image gesture are extending beyond your defined frame, I am sure there are many ways to fix this, but I solved it by adding the contentShape modifier
Please replace your image code with the following
Image(uiImage: photo.uiImage)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width/3, height: UIScreen.main.bounds.width/3)
.clipped()
.contentShape(Path(CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width/3, height: UIScreen.main.bounds.width/3)))
.onTapGesture {
manager.selectedPhoto = photo
}
contentShape define the hit area for the gesture
I have AsyncImage inside a TabView. When I do this the image never appears. I just see the progress bar.
#available(iOS 15.0, *)
struct TEST: View {
var body: some View {
VStack {
TabView {
AsyncImage(url: URL(string: "https://blckbirds.com/wp-content/uploads/2021/10/pexels-kammeran-gonzalezkeola-6128227-2.jpg"), scale: 2) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
ProgressView()
.progressViewStyle(.circular)
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .automatic))
}//V
}
}
try using a ZStack to wrap the AsyncImage, like this, works for me:
struct TEST: View {
var body: some View {
VStack {
TabView {
ZStack { // <--- here
AsyncImage(url: URL(string: "https://blckbirds.com/wp-content/uploads/2021/10/pexels-kammeran-gonzalezkeola-6128227-2.jpg"), scale: 2) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: { ProgressView().progressViewStyle(.circular) }
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .automatic))
}
}
}
I am new to SwiftUI. I copied the #workingdog support's code and it worked well. But I think there is some glitch with the preview and I am not sure this an unknown issue or not.
With the original code, I can only see the loading view, not matter I close and re-open the preview window. Then I added a frame for the image view. I can see the image being properly loaded by changing frame size to something else then change it back, e.g. 300 -> 350 -> 300.
struct ExampleImgItem: View {
var body: some View {
`workingdog support`'s code
}
struct ExampleImgItem_Previews: PreviewProvider {
static var previews: some View {
ExampleImgItem()
.frame(width: 300, height: 300, alignment: .leading)
}
}
P.S. I am using Xcode 13.3.1,
Got the same issue. My solution is to add
.aspectRatio(CGSize(width: 6, height: 9), contentMode: .fill)
.scaledToFit()
to TabView.
I would like to use .redacted on the entire view until the image is successfully loaded into AsyncImage.. Currently I cannot find a way to complete this.. My last attempt was this..
struct MyView: View {
var body: some View {
VStack {
//Other Views
AsyncImage(url: URL(string: "https://cdn2.thecatapi.com/images/7CGV6WVXq.jpg")!) { phase in
if let image = phase.image {
image
//Some image modifiers
self.imageLoaded = true // Of course this won't work in this closure but I cannot think of where else to put it.
}
}
}.redacted(reason: imageLoaded ? .placeholder : []) // <-- How to redact programmatically?
}
}
The closest solution is to use onDisappear modifier which is triggered when the view disappears (which mean the image had loaded
VStack {
//Other Views
AsyncImage(url: URL(string: "https://cdn2.thecatapi.com/images/7CGV6WVXq.jpg")!) { phase in
if let image = phase.image {
image
} else {
// When the image is loading, or has failed to load
// You can use any view (example: a loading view or placeholder)
RoundedRectangle(cornerRadius: 10)
.onDisappear {
imageLoaded = true
}
}
}
.frame(width: 300, height: 300)
}
.redacted(reason: imageLoaded ? [] : .placeholder)
I've been trying to make a List of buttons, and when it gets clicked, it calls some code, I also need some 'thumbnails' to go along with every button, so I made a HStack for each button and put it all on a List, now the thumbnail doesn't render, BTW, it renders system images and local assets just fine, it's just the images grabbed from the network
you can just copy-paste this into ContentView.swift to reproduce it
import SwiftUI
func artwork() -> Image
{
let art = "https://via.placeholder.com/150"
if let url = URL(string: art), let data = try? Data(contentsOf: url), let image = UIImage(data: data)
{
return Image(uiImage: image)
}
else
{
return Image(systemName: "book")
}
}
struct ContentView: View
{
var body: some View
{
List
{
Button(action: {...my code...})
{
HStack
{
artwork()
}
... some other info
}
}
}
}
This issue is similar to Avoid button styling its content in SwiftUI
You just have to add .renderingMode(.original) to your Image like so:
func artwork() -> Image
{
let art = "https://via.placeholder.com/150"
if let url = URL(string: art), let data = try? Data(contentsOf: url), let image = UIImage(data: data)
{
return Image(uiImage: image)
}
else
{
return Image(systemName: "book")
}
}
struct ContentView: View
{
var body: some View
{
List
{
Button(action: {})
{
HStack
{
artwork().renderingMode(.original)
}
}
}
}
}