Let's say one has a placeholder image, which is loaded into an UIImageView. Then, an async operation eventually loads an image with a different size.
The issue I am observing is that I don't know how to make imageView to "wrap" the new image, according to its size, preserving the image original aspect ratio.
Note that when using contentMode=.scaleAspect*, the view scales the image to fit its view. In my case, I want the view to fit the image.
import Foundation
import UIKit
class ViewController: UIViewController {
let imageView = UIImageView()
override func loadView() {
let view = UIView()
view.addSubview(imageView)
imageView.contentMode = .scaleAspectFill
imageView.backgroundColor = .red
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
imageView.widthAnchor.constraint(equalToConstant: 100),
imageView.heightAnchor.constraint(equalToConstant: 100),
])
self.view = view
}
override func viewDidLoad() {
// imageView.image = // some placeholder image is shown; size is 100 x 100
// imageView.image = // network call is made which eventually loads a different image with dynamic size
}
}
// Present the view controller in the Live View window
import PlaygroundSupport
PlaygroundPage.current.liveView = ViewController()
CodePudding user response:
One approach is to create a NSLayoutConstraint
var/property to use as the image view's Height constraint. Then, when the new image comes in, de-activate the constraint, re-create it with the new aspect-ratio (multiplier), and re-activate it.
Here's a quick example...
It starts with an empty image view (red background), at 1:1 ratio.
Each time you tap (well, in playground, click), we'll use a new SF Symbol to simulate getting a new image from a server.
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let imageView = UIImageView()
// we'll update the image view height constraint when a new image comes in
var ivHeight: NSLayoutConstraint!
let sysNames: [String] = [
"ruler", // wide and short
"flashlight.off.fill", // tall and narrow
"folder", // square-ish
]
var idx: Int = -1
override func loadView() {
let view = UIView()
view.addSubview(imageView)
//imageView.contentMode = .scaleAspectFill
imageView.contentMode = .scaleToFill
imageView.backgroundColor = .red
imageView.translatesAutoresizingMaskIntoConstraints = false
ivHeight = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1.0)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
imageView.widthAnchor.constraint(equalToConstant: 100),
ivHeight,
])
self.view = view
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// to simulate getting an image from a server...
idx = 1
let sysName = sysNames[idx % sysNames.count]
let cfg = UIImage.SymbolConfiguration(pointSize: 120.0, weight: .bold, scale: .large)
guard let sysImg = UIImage(systemName: sysName, withConfiguration: cfg)?.withTintColor(.systemBlue, renderingMode: .alwaysOriginal) else {
print("failed to create image \(sysName)")
return
}
// set the image
imageView.image = sysImg
// get the size of the image
let sz = sysImg.size
// de-activate the image view height constraint
ivHeight.isActive = false
// set the new image view height constraint, using
// image size height / width
// as the multiplier
ivHeight = imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: sz.height / sz.width)
// re-activate the image view height constraint
ivHeight.isActive = true
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()
It will give you these results... the image view will always have a width of 100
, but the height will change to match the aspect ratio of the image: