I try to include a pdf in my SwiftUI enabled app using Xcode 11.4 and iOS 13.4. However, when I resize the pdf, it gets crips edges. I have included two versions of the pdf: One large pdf (icon.pdf
) and one small pdf (icon_small.pdf
). When I resize icon.pdf
it gets start edges, while icon_small.pdf
gets smooth edges. The issue applies to all other pdfs I have tried as well.
This is my code:
struct ContentView: View {
var body: some View {
VStack {
Spacer()
Text("icon.pdf:")
Image("icon")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width: 27.0, height: 27.0)
Spacer()
Text("icon_small.pdf:")
Image("icon_small")
Spacer()
}
}
}
Both icon.pdf
and icon_small.pdf
have the following asset settings:
The pdfs are available here:
PDF vectors needs to be programmatically resized via UIGraphicsBeginImageContextWithOptions
so that they are not shown blurred when you scale them up (or down). There is no need to have multiple PDFs with different resolution to accomplish this.
Unfortunately this is not done automatically by UIKit or SwiftUI. Here is an example where a 24x24 PDF vector is tinted and resized to 200x200.
Image(uiImage: UIImage(named: "heart")!.tinted(withColor: .blue,
biggerSize: CGSize(width: 200, height: 200)))
.resizable()
.frame(width: 200, height: 200,
alignment: .center)
extension UIImage {
/// Uses compositor blending to apply color to an image. When an image is too small it will be shown
/// blurred. So you have to provide a size property to get a good resolution image
public func tinted(withColor: UIColor?, biggerSize: CGSize = .zero) -> UIImage {
guard let withColor = withColor else { return self }
let size = biggerSize == .zero ? self.size : biggerSize
let img2 = UIImage.createWithColor(size, color: withColor)
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
let renderer = UIGraphicsImageRenderer(size: size)
let result = renderer.image { _ in
img2.draw(in: rect, blendMode: .normal, alpha: 1)
draw(in: rect, blendMode: .destinationIn, alpha: 1)
}
return result
}
public static func createWithColor(_ size: CGSize, color: UIColor) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
let rect = CGRect(size: size)
color.setFill()
context!.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
I did a side by side comparison for both vector images using the ones you provided:
- http://simensolbakken.com/public/stackoverflow/icon.pdf
- http://simensolbakken.com/public/stackoverflow/icon_small.pdf
At first, I used SwiftUI
's inbuilt Image
and as mentioned, both performed badly at their extreme ends:
At first I thought it might be your pdf vectors so I used ones that I know have worked well in my previous projects, but I got the same issues.
Thinking it to be a UIImage
issue, I used SwiftUI
s Image(uiImage:)
but same problem.
Last guess was the image container, and knowing that UIImageView
has handled vector images well, getting UIViewRepresentable
to wrap the UIImageView
seems to solve this issue. And for now it looks like a possible workaround.
struct MyImageView: UIViewRepresentable {
var name: String
var contentMode: UIView.ContentMode = .scaleAspectFit
var tintColor: UIColor = .black
func makeUIView(context: Context) -> UIImageView {
let imageView = UIImageView()
imageView.setContentCompressionResistancePriority(.fittingSizeLevel,
for: .vertical)
return imageView
}
func updateUIView(_ uiView: UIImageView, context: Context) {
uiView.contentMode = contentMode
uiView.tintColor = tintColor
if let image = UIImage(named: name) {
uiView.image = image
}
}
}
This loses some SwiftUI
Image
modifiers (you still have normal View
modifiers) but you can always pass in some parameters such as contentMode
and tintColor
as shown above. Add more if needed and handle accordingly.
struct ContentView: View {
var body: some View {
VStack {
MyImageView(name: "icon", //REQUIRED
contentMode: .scaleAspectFit, //OPTIONAL
tintColor: .black /*OPTIONAL*/)
.frame(width: 27, height: 27)
MyImageView(name: "icon_small", //REQUIRED
contentMode: .scaleAspectFit, //OPTIONAL
tintColor: .black /*OPTIONAL*/)
.frame(width: 27, height: 27)
}
}
}
Now this is all speculation but it looks as though SwiftUI
treats vector images as a PNG
.
The following example is a simple side by side comparison of the small and large vector images rendered in UIKit
's UIImageView
and SwiftUI
's Image
.
struct ContentView: View {
let (largeImage, smallImage) = ("icon", "icon_small")
let range = stride(from: 20, to: 320, by: 40).map { CGFloat($0) }
var body: some View {
List(range, id: \.self) { (side) in
ScrollView(.horizontal) {
VStack(alignment: .leading) {
Text(String(format: "%gx%g", side, side))
HStack {
VStack {
Text("UIKit")
MyImageView(name: self.smallImage)
.frame(width: side, height: side)
MyImageView(name: self.largeImage)
.frame(width: side, height: side)
}
VStack {
Text("SwiftUI")
Image(self.smallImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: side)
Image(self.largeImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: side)
}
}
}
}
}
}
}
UIImageView
SwiftUI
Image
UIImageView
SwiftUI
Image
UIKit
's UIImageView
has consistent performace while SwiftUI
's Image
is having trouble.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With