I'm trying to add an SVG image to my SwiftUI application and the edges around the image are jagged. How can I fix this?
I have verified that the actual SVG image is not jagged. I also tried adding .interpolation(.none) with no success. As well as enabling Preserve Vector Data, which didn't work either.
It almost looks like it's not treating the image as an SVG, because changing the foreground color property doesn't have any impact.
HStack {
if let satelliteImage = UIImage(named: "satellite-dish-solid") {
Image(uiImage: satelliteImage)
.resizable()
.frame(width: 45, height: 45)
.foregroundColor(.primary)
}
Text("Satellite")
}
Related
My app involves displaying <100 image thumbnails and for some reason my iPad Pro 2018 is struggling to scroll through the images smoothly. I recreated a simplified example below. The image is 200px square.
Replacing the images with colored rectangles eliminates the lag. Removing the shadow also removes the lag. I think rendering 50 images with a shadow should be within my device's capabilities, but let me know if anyone disagrees.
struct ContentView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false, content: {
let gridLayout = [(GridItem(.adaptive(minimum: 160)))]
LazyVGrid(columns: gridLayout, spacing: 8) {
ForEach(0..<50) { index in
Image("cookie_200")
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.padding(8)
.shadow(radius: 4)
}
}
})
}
}
Question
Is there a less performance-intensive way to show these thumbnails?
Screen capture with shadow (laggy scroll):
https://share.icloud.com/photos/04eFNISH1khfkFqgAmGfGJJZw
Screen capture without shadow (smooth):
https://share.icloud.com/photos/02etl7kVG30Cnc6cr_dwSJK-Q
Image:
Shadows and transparency make the runtime do a lot of work. Hence the lag.
Question Is there a less performance-intensive way to show these thumbnails?
Yes. Instead of making the runtime draw the shadows, you draw the shadows. In particular you make an image consisting of the thumbnail and the shadow, on an opaque background the same color as your view background. Now scrolling is perfectly performant.
Consider the following example:
struct ContentView: View {
#State var showSplash: Bool = true
#Namespace var animationNamespace
var body: some View {
ZStack {
if showSplash {
GeometryReader { geometry in
AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
image.resizable()
.scaledToFill()
.matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
.transition(.move(edge: .bottom))
.frame(width: geometry.size.width)
.transition(.move(edge: .bottom))
.edgesIgnoringSafeArea(.all)
.clipped()
} placeholder: {
Color.gray
}
}
.onTapGesture {
toggleSplashScreen(false)
}
} else {
ScrollView {
GeometryReader { geometry in
AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
image
image
.resizable()
.scaledToFill()
.matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
.transition(.move(edge: .bottom))
} placeholder: {
Color.gray
}
.frame(width: geometry.size.width, height: 400)
.clipped()
}
.edgesIgnoringSafeArea(.all)
.onTapGesture {
toggleSplashScreen(true)
}
}
}
}
}
}
With a helper method here:
private extension ContentView {
func toggleSplashScreen(_ toggle: Bool) {
withAnimation(.spring(response: 0.85, dampingFraction: 0.95)) {
showSplash = toggle
}
}
}
This produces:
I noticed two things here that I would like to fix
The flashing white effect when transitioning between the two states.
I noticed since we are using AsyncImage, when showSplash changes the AsyncImages only sometimes hits the placeholder block. As a result, the transition becomes really choppy. I tested this with a static image from the assets file and the transition then became smooth. I also tried creating a Caching mechanism on the AsyncImage but still had issues with it hitting placeholder block sometimes.
Would love to hear any ideas :) Thanks!
There are a couple of things that I think you could do to improve this.
First, You are fighting a little bit against the way SwiftUI maintains a view's identity. One of the ways that SwiftUI determines when it can reuse an existing structure as opposed to recreating a structure, is by it's location in the view hierarchy. So when you toggle your structure you go from:
GeometryReader
AsyncImage
to
ScrollView
GeometryReader
AsyncImage
As a result, the system thinks these are two AsyncImage views and so it's rebuilding the view (and reloading the image) every time. I think that's where your white flashes come from since you're seeing your gray placeholder in the middle of your animation. If you could leave the scroll view in place, possibly disabling scrolling when it's not needed (if that's possible) then the OS could maintain the identity of the AsyncImage. (see https://developer.apple.com/videos/play/wwdc2021/10022/)
That leads to the second area of investigation for you. AsyncImage is wonderful in the convenience it gives you in loading content from the network. Unfortunately it doesn't make that communication faster. Your goal should be to have AsyncImage go to the network as few times as possible.
Right now, your resizing strategy focuses on resizing the image. That means that for every transition you're "hitting the network" (read putting your code on the slow, dusty, dirt road path). Instead of resizing the image, you should just load the image once (the slow part) and resize the view that is displaying it. The general idea would be to let AsyncImage load the image, then control how the image is animated by animating the frame of the view.
This is where I get less helpful. I don't know enough about AsyncImage to know if it's capable of implementing that strategy. It seems that it should be... but I don't know that it is. You might have to resort to downloading and storing the image as state separately from the view that presents it.
So my advice is to limit the number of times AsyncImage has to reload the network data. That involves helping SwiftUI maintain the identity of the AsyncImage so it doesn't have to reload each time the view is created. And, try to implement your animations and scaling on the view, not the image, because rescaling the image also requires a network reload.
I've been wondering if there is any way to customize the preview image of the view that's being dragged when using onDrag?
As you might see, the preview image is by default a slightly bigger opacity image of the view.
From what I have found, a preview image is generated at the very beginning of the dragging process. But I couldn't find a way to change it.
What I mean by customizing is to have some custom image or a preview image of a custom view. (Both without the default opacity)
Does anyone have an idea?
I have tried to use previewImageHandler and in general, read a lot about the NSItemProvider. But for me, it seems like this is something that is not possible for SwiftUI yet?
With UIKit one could have just customized the UIDragItem - something like that using previewProvider: Here
Here is my demo code:
struct ContentView: View {
var body: some View {
DraggedView()
.onDrag({ NSItemProvider() })
}
private struct DraggedView: View {
var body: some View {
RoundedRectangle(cornerRadius: 20)
.frame(width: 120, height: 160)
.foregroundColor(.green)
}
}
}
I will use this for drag and drop within a LazyVGrid, so custom gestures are unfortunately no option.
One second idea I had would be to have a gesture simultaneously that first changes the item to be dragged to something else and then onDrag starts and returns the NSItemProvider with the preview image which would be then the one I would want. But I couldn't have those two gestures go at the same time, you would have to dismiss one first in order to start the second.
Thank you!
iOS 15 adds an API to do this - you can specify the View to use for the preview. onDrag(_:preview:)
RoundedRectangle(cornerRadius: 20)
.frame(width: 120, height: 160)
.foregroundColor(.green)
.onDrag {
NSItemProvider()
} preview: {
RoundedRectangle(cornerRadius: 18)
.frame(width: 100, height: 140)
.foregroundColor(.green)
}
I want to develop a simple full screen image view in SwiftUI, the image should fit in side the screen, also I want to fetch the image from a CDN so in the request I need to provide the size of the required image.
All the example of SwiffUI I see on the net are some like this.
var body: some View {
Image("my-image")
.frame(width: 375.0, height: 812.0)
}
So I have two question.
How do I fetch image from URL/link
How do I get the size of the image to request, as it will be different for different iOS devices
How do I fetch image from URL/link?
SwiftUI's Image component doesn't provide a native way to load image from an URL, suggest you use one of the open source libraries instead of implementing complex image loading logic, I recommend using SwiftUI version popular open source library SDWebImage, it is called SDWebImageSwiftUI
Here is an example to load an image from image URL
struct ContentView: View {
var body: some View {
WebImage(url:URL(string: "https://<replace with image URL/link>"))
.resizable()
.placeholder {
Rectangle().foregroundColor(.gray)
}
.indicator(.activity)
.scaledToFit()
.frame(width: 375.0, height:812.0)
}
}
How do I get the size of the image to request, as it will be different for different iOS devices?
To get the run-time size of a view, SwiftUI provides a container view called GeometryReader. Whichever component's size you need you have to embed that component(s) inside a GeometryReader container, this container give you access to the run-time size of itself.
Here is complete code to get the run-time size of an image, make a request to image CDN and load the image into full screen view. Here I am using image from imageOpt image CDN you can use any image CDN.
struct ContentView: View {
// Use the URL of imageSet obtained from image CDN
let imageURL = "https://djy8xy980kp8s.cloudfront.net/2e0d6k-p25/q"
var body: some View {
/* Embed the WebImage component inside a GoemetryReader container
* this will give you access to run-time size of the container */
GeometryReader { geo in
WebImage(url:
/* Get the final URL with width & height parameters,
* since you want to fit the image inside full screen
* set crop to false */
imageOptClient.constructURL(imageURL: self.imageURL,
imageSize: CGSize(
// goe.size will give the runtime size of the container
width: geo.size.width,
height: geo.size.height),
crop: false))
.resizable()
.placeholder {
Rectangle().foregroundColor(.gray)
}
.indicator(.activity) // Activity Indicator
.scaledToFit()
.frame(width: geo.size.width, height: geo.size.height,
alignment: .center)
}
}
}
disclaimer: I work for imageOpt
So I'm just trying to build a list that look like below, however, the SwiftUI Button drive me crazy and I wanna make corner radius 10, but the corner actually always disappears.
This is the row I want, notice that the 'Follow' button has cornerRadius and a proper height
However after I searched tons of damn answer, what I can only got is this, the corner even disappears!!!:
Button(action: {
}) {
if person.isInKnock {
Text("Follow").font(.system(size: 14)).foregroundColor(Color(ColorUtils.hexStringToUIColor(hex: Constants.THEME.THEME_COLOR))).padding()
}
else {
Text("Invite to Knock").font(.system(size: 14)).foregroundColor(Color(ColorUtils.hexStringToUIColor(hex: Constants.THEME.THEME_COLOR))).padding()
}
}.frame(height: CGFloat(30)).border(Color.gray, width: CGFloat(1)).cornerRadius(CGFloat(10))
Apple has been switching up the modifier functions constantly, so it's annoying to keep up with all the changes. Here's a working solution I've found:
Button(action: {}) {
Text("Follow")
.foregroundColor(Color(.systemTeal))
.bold()
.padding([.leading, .trailing])
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray)
.foregroundColor(.clear)
)
}
This produces the following image:
The key is overlaying a RoundedRectangle and changing the stroke and corner radius. .stroke() changes the shape itself, while .border() changes the view of the rectangle. Hope this helps!