I understand that it is impossible to use the .scaledToFit() modifier on an image with a frame of 120 and 430, with a frame modifier like .frame(height: 100, width: 100) and expect the image to somehow keep the aspect ratio and fit the frame of width 100 and heigh 100.
So, it seems like the only way around this would be to force the user to crop their image into a square when uploading it with the image picker. Is this possible in SwiftUI/is there something I'm missing here?
Thanks
You can use aspectRatio(contentMode:):
Image("imageName")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
If I understand your problem correctly, you want to put non-square image (120x430) in a square box (100x100). To do that you can scale image preserving it's aspect ratio. Image will be scaled to maximum height or width (depending on target width and height).
Check out this article: https://www.robertpieta.com/resize-uiimage-no-stretching-swift/
Here is copy and paste solution:
extension UIImage {
func scalePreservingAspectRatio(width: Int, height: Int) -> UIImage {
let widthRatio = CGFloat(width) / size.width
let heightRatio = CGFloat(height) / size.height
let scaleFactor = min(widthRatio, heightRatio)
let scaledImageSize = CGSize(
width: size.width * scaleFactor,
height: size.height * scaleFactor
)
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let renderer = UIGraphicsImageRenderer(
size: scaledImageSize,
format: format
)
let scaledImage = renderer.image { _ in
self.draw(in: CGRect(
origin: .zero,
size: scaledImageSize
))
}
return scaledImage
}
}
Related
I just want to simply adjust the image size of image to the parent's view size.
Images in swiftUI are miss behaved children that simply will not adjust to their parent...
I wanna be able to call ImageCard("image").frame(decide the size of the image)
struct ImageCard: View {
let backgoundImage: String?
var body: some View {
ZStack{
Image(backgoundImage!)
.resizable() // for resizing
.scaledToFit() // for filling image on ImageView
.cornerRadius(5)
.shadow(color: .gray, radius: 6, x: 0, y: 3)
}
}
}
If I understood correctly the intention was to fill proportionally, so
ZStack{
Image(backgoundImage!)
.resizable() // for resizing
.scaledToFill() // << here !! // for filling image on ImageView
but in that case it can spread out of bounds, so it needs to be clipped in place of .frame applied, so either
ImageCard("image")
.frame(decide the size of the image)
.clipped() // << here !!
or, better, as already described inject dimension inside card and apply it there, like
Image(backgoundImage!)
.resizable() // for resizing
.scaledToFill() // << here !!
.frame(decide the size of the image)
.clipped() // << here !!
.cornerRadius(5)
.shadow(color: .gray, radius: 6, x: 0, y: 3)
}
I wrote a package just for jmages, You give it the max height & width without needing the Image's exact dimensions & it'll maximize the size without being stretched: https://github.com/NoeOnJupiter/SImages
Usage:
DownsampledImage(.wrapped(UIImage(named: backgroundImage)))
.resizable(.wrapped(true))
.frame(width: width, height: height)
Plus this will downsample your image to the size it's displayed in, your memory usage will be much lower since it depends on the resolution of the Image.
Note: If you wanna bound the image to the whole View, use UIScreen.main.bounds for the frame.
You can simply include two variables for width and height to make your ImageCard() become adjustable at any time since you mentioned that you wanted:
I wanna be able to call ImageCard("image").frame(decide the size of the image)
You don't need ZStack or scaleToFit() because you wanted to decide the size whenever the ImageCard() is called. Code is below the image.
struct DemoView: View {
var body: some View {
ImageCard(backgroundImage: "Swift", width: 300, height: 500)
}
}
struct ImageCard: View {
let backgroundImage: String?
let width: CGFloat
let height: CGFloat
var body: some View {
Image(backgroundImage ?? "")
.resizable()
.frame(width: width, height: height)
}
}
I have been trying to align a view to a background image, but I haven't been able to find a solution that works for all devices. I am targeting iPhones in landscape orientation.
In this example I want to make the red rectangle align with the iMac screen. This code gets pretty close, by using an offset. It looks good in the preview canvas, but doesn't align in the Simulator or on a device.
I tried using .position(x:y:), but that was even more messy.
I found that if I crop the background so the target region is exactly centered, then it is possible, but I really hope that's not the only solution.
struct GeometryView: View {
let backgroundImageSize = CGSize(width: 1500, height: 694)
let frameSize = CGSize(width: 535, height: 304)
var body: some View {
GeometryReader { geometry in
let widthScale = geometry.size.width / backgroundImageSize.width
let heightScale = geometry.size.height / backgroundImageSize.height
let scale = widthScale > heightScale ? widthScale : heightScale
let frame = CGSize(width: frameSize.width * scale,
height: frameSize.height * scale)
ZStack {
Rectangle()
.frame(width: frame.width, height: frame.height)
.foregroundColor(.red).opacity(0.5)
.offset(x: 5, y: -8)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.background(
Image("imac-on-desk")
.resizable()
.scaledToFill()
.ignoresSafeArea())
}
}
}
background image
this would work, but only on an iPhone 12. If you use .scaledToFill on the image the different display aspect ratios of phones will lead to different offsets. You could at least crop the background image , so the white screen is exactly in the center of the image.
var body: some View {
GeometryReader { geometry in
ZStack {
Image("background")
.resizable()
.scaledToFill()
.ignoresSafeArea()
Rectangle()
.foregroundColor(.red).opacity(0.5)
.frame(width: geometry.size.width / 2.45,
height: geometry.size.height / 2.1)
.offset(x: -geometry.size.width * 0.025, y: 0)
}
}
}
I'd like to do what this guy was trying to do but in SwiftUI. How can I convert the code in the answers to apply it to SwiftUI?
This is what I have tried so far:
struct ImageMerger {
func merge(_ bottomImageName: String, with topImageName: String) -> UIImage {
let bottomImage = UIImage(named: bottomImageName)
let topImage = UIImage(named: topImageName)
let size = CGSize(width: 375, height: 245)
UIGraphicsBeginImageContext(size)
let areaSize = CGRect(x: 0, y: 0, width: size.width, height: size.height)
bottomImage!.draw(in: areaSize)
topImage!.draw(in: areaSize, blendMode: .normal, alpha: 1)
let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}}
And to apply it to my view I did the following:
struct Test: View {
var imageMerger = ImageMerger()
var body: some View {
Image(uiImage: imageMerger.merge("Consulta-Dep", with: "CellBackground"))
}}
I switched the bottom image with the top image so you could see that the images aren't changing sizes. This is what it looks like in the Preview:
Ok, so this isn't exactly answering my question, but since one of the pictures is a blank shape, this is what ended up working for me:
Image("topImageName")
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width * 0.8)
.cornerRadius(15)
.overlay(RoundedRectangle(cornerRadius: 15)
.stroke(Color.white, lineWidth: 0))
.shadow(radius: 10)
The important part is using a cornerRadius modifier (which clips the view) and then overlay a stroke with no width.
I actually got the idea from this post.
I hope someone else can benefit from this answer!
In the following code, I did some experiments and please note that I have made some custom extensions for gradients (not shown here), but they are not important, you can ignore them.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
// body
var body: some View {
// background image
let image = Image("some_image"))
.resizable()
.scaledToFit()
.frame(maxWidth: 300)
// image mask
let imageMask = Image(systemName: "heart.fill")
.resizable().scaledToFit().frame(width: 100).opacity(0.8)
// text mask
let textMask = Text("SwiftUI is Awesome!")
.font(.title).fontWeight(.bold)
return HStack {
VStack {
// my custom extension (not important)
Gradient.vertical (.red , .orange)
Gradient.horizontal(.green, .blue )
Gradient.diagonal (.pink , .purple)
image
.overlay(imageMask.border(Color.blue), alignment: .topLeading)
.border(Color.green)
image.mask(imageMask)
}.border(Color.blue)
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
and the result is:
My question is: why does the image keep its aspect ratio, but not its frame (the green border)?
scaledToFit() is forcing the image to maintain its aspect ratio while fitting inside the parent view, which in its case is the frame.
However, the frame will try to extend as much as you allow it, i.e. 300, which is more than the width of the scaled down image.
You can observe this even further by setting a maxHeight: 100
I would like to add image and texts (under each image view) in ScrollView.
I add ScrollView firstly and add UIImage View. But, I add two images in image array and it only appears one image when I run the app. Here is my code;
#IBOutlet weak var imgView: UIImageView!
var imgArray = [#imageLiteral(resourceName: "home_image"),#imageLiteral(resourceName: "provider_image")]
and in ViewDidLoad() function,
for index in 0 ..< imgArray.count
{
let imgView = UIImageView()
debugPrint(index)
imgView.image = imgArray[index]
}
I also want to add each image caption under each image. How to do it? Why only one image appear? :(
Try this:
let width = customScroll.frame.size.width
for item in 1..<4{
image = UIImageView(frame: CGRect(x: (CGFloat(item) * width - width), y: 10, width: width, height: 250))
image.image = UIImage.init(named: "img"+String(item))
customScroll.addSubview(image)
print(image.frame)
}
customScroll.contentSize = CGSize(width: view.frame.size.width * 4, height: 300)