Uiimageview .Scaleaspectfit and Autolayout Not Working Programmatically from Swift

UIImageView .scaleAspectFit and autolayout not working programmatically from Swift

In your console log, these two lines tell you what's happening:

(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you
don't understand, refer to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)

.

NSAutoresizingMaskLayoutConstraint:0x17009cc50 h=--& v=--&
UIImageView:0x101432d20.midY == 877.5 (active)

Adding this line to your code will fix things:

swiftImageView.translatesAutoresizingMaskIntoConstraints = false

Setting content mode in UIImageView no longer works in iOS 11

Works for me such way (using additional UIView)

        let titleView = UIView(frame: CGRect(x: 0, y: 0, width: 120, height: 40))
let image = UIImageView(image: UIImage(named: "img-logo"))
image.contentMode = .scaleAspectFit
image.frame = titleView.bounds
titleView.addSubview(image)
viewController.navigationItem.titleView = titleView

iOS: Programmatically align UIImageView to bottom using anchors

I was able to find a solution using the comments above and further experiments, by computing and setting the height anchor from the image ratio.

// Compute image ratio
let ratio = imgView.intrinsicContentSize.height / imgView.intrinsicContentSize.width

// Set height anchor as a computed value of the (auto-scaled) width, and the image ratio
NSLayoutConstraint.activate([
// ...other contraints go here still...
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: ratio)
])

Remove extra space from UIImageView

If you want your image view to have a max size of 100 x 30, you need to create Width and Height constraints in your cell class:

var cWidth: NSLayoutConstraint!
var cHeight: NSLayoutConstraint!

then initialize them in your cell's init:

// initialize image view Width and Height constraints
cWidth = assetImageView.widthAnchor.constraint(equalToConstant: 0)
cHeight = assetImageView.heightAnchor.constraint(equalToConstant: 0)

NSLayoutConstraint.activate([

// label constraints...

assetImageView.topAnchor.constraint(equalTo: typeOrderLabel.bottomAnchor, constant: 15.0),
assetImageView.leadingAnchor.constraint(equalTo: typeOrderLabel.leadingAnchor),

// activate image view Width and Height constraints
cWidth,
cHeight,

])

Then, when you set your image in cellForRowAt, calculate the size based on the size of the image and update the Width and Height constraint constants:

assetImageView.image = img

if img.size.height <= maxHeight && img.size.width <= maxWidth {

// image height and width are smaller than max Height and Width
// so use actual size
cWidth.constant = img.size.width
cHeight.constant = img.size.height

} else {

// use standard Aspect Fit calculation
var f = maxWidth / img.size.width
var w = img.size.width * f
var h = img.size.height * f
if h > maxHeight {
f = maxHeight / img.size.height
h = img.size.height * f
w = img.size.width * f
}

// update constraints
cWidth.constant = w
cHeight.constant = h

}

Here is a complete example:

struct EduardoStruct {
var typeOrder: String = ""
var other: String = ""
var asset: String = ""
}

class EduardoCell: UITableViewCell {

let typeOrderLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.font = .systemFont(ofSize: 14, weight: .bold)
v.textColor = .systemGreen
// if we want to see the label frame
//v.backgroundColor = .yellow
return v
}()
let assetImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleToFill
imageView.backgroundColor = .cyan
return imageView
}()
let otherLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.font = .systemFont(ofSize: 13, weight: .thin)
v.textColor = .darkGray
// if we want to see the label frame
//v.backgroundColor = .yellow
return v
}()

var cWidth: NSLayoutConstraint!
var cHeight: NSLayoutConstraint!

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// add the subviews
contentView.addSubview(typeOrderLabel)
contentView.addSubview(assetImageView)
contentView.addSubview(otherLabel)

// initialize image view Width and Height constraints
cWidth = assetImageView.widthAnchor.constraint(equalToConstant: 0)
cHeight = assetImageView.heightAnchor.constraint(equalToConstant: 0)

let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([

typeOrderLabel.topAnchor.constraint(equalTo: g.topAnchor),
typeOrderLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
typeOrderLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),

assetImageView.topAnchor.constraint(equalTo: typeOrderLabel.bottomAnchor, constant: 15.0),
assetImageView.leadingAnchor.constraint(equalTo: typeOrderLabel.leadingAnchor),

otherLabel.topAnchor.constraint(equalTo: assetImageView.bottomAnchor, constant: 10.0),
otherLabel.leadingAnchor.constraint(equalTo: typeOrderLabel.leadingAnchor),
otherLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
otherLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),

// activate image view Width and Height constraints
cWidth,
cHeight,

])
}

func fillData(_ st: EduardoStruct) {
typeOrderLabel.text = st.typeOrder
otherLabel.text = st.other

// image max Width and Height
let maxWidth: CGFloat = 100
let maxHeight: CGFloat = 30

guard let img = UIImage(named: st.asset) else {
// if we can't load the image, set the image view
// to maxWidth x maxHeight
cWidth.constant = maxWidth
cHeight.constant = maxHeight
assetImageView.image = nil
return
}

assetImageView.image = img

if img.size.height <= maxHeight && img.size.width <= maxWidth {

// image height and width are smaller than max Height and Width
// so use actual size
cWidth.constant = img.size.width
cHeight.constant = img.size.height

} else {

// use standard Aspect Fit calculation
var f = maxWidth / img.size.width
var w = img.size.width * f
var h = img.size.height * f
if h > maxHeight {
f = maxHeight / img.size.height
h = img.size.height * f
w = img.size.width * f
}

// update constraints
cWidth.constant = w
cHeight.constant = h

}
}
}

class EduardoExampleTableViewController: UITableViewController {

var myData: [EduardoStruct] = []

override func viewDidLoad() {
super.viewDidLoad()

let imageNames: [String] = [
"img100x30", "img80x20", "img120x50", "img80x80", "img150x30", "img120x120",
]

for (i, str) in imageNames.enumerated() {
var st: EduardoStruct = EduardoStruct()
st.typeOrder = "TYPE \(i)"
st.other = "OTHER \(i)"
st.asset = str
myData.append(st)
}

tableView.register(EduardoCell.self, forCellReuseIdentifier: "cell")
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! EduardoCell
cell.fillData(myData[indexPath.row])
return cell
}

}

With that code, using these "asset images":

Sample Image Sample Image Sample Image Sample Image Sample Image Sample Image

We get this result:

Sample Image

As you see, the images are all "aspect fit" scaled to a max size of 100 x 30, with no "extra space."


Edit

After clarification from the OP, the images will be 160 x 55 pixels, and the goal is to:

  • extract only the "useful" part of the image (the non-alpha portion)
  • display that portion at a max size of 100 x 30 while maintaining aspect ratio.

So, with a new set of images, each being 160 x 55 with transparent "backgrounds" (download these images to see):

Sample Image Sample Image Sample Image Sample Image Sample Image

We can use this UIImage extension to "clip out" the non-transparent portion:

extension UIImage {

func clipAlpha(_ tolerancePercent: Double) -> UIImage {

guard let imageRef = self.cgImage else {
return self
}

let columns = imageRef.width
let rows = imageRef.height

let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * columns
let bitmapByteCount = bytesPerRow * rows

// allocate memory
let rawData = UnsafeMutablePointer<UInt8>.allocate(capacity: bitmapByteCount)
// initialize buffer to Zeroes
rawData.initialize(repeating: 0, count: bitmapByteCount)
defer {
rawData.deallocate()
}

guard let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear) else {
return self
}

guard let context = CGContext(
data: rawData,
width: columns,
height: rows,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
| CGBitmapInfo.byteOrder32Big.rawValue
) else {
return self
}

var l: Int = -1
var r: Int = -1
var t: Int = -1
var b: Int = -1

// for debugging...
// used to count the number of iterations needed
// to find the non-alpha bounding box
//var c: Int = 0

var colOffset: Int = 0
var rowOffset: Int = 0

// Draw source image on created context.
let rc = CGRect(x: 0, y: 0, width: columns, height: rows)
context.draw(imageRef, in: rc)

let tolerance: Int = Int(255.0 * tolerancePercent)

// find the left-most non-alpha pixel
for col in 0..<columns {
colOffset = col * bytesPerPixel
for row in 0..<rows {
// debugging
//c += 1
rowOffset = row * bytesPerRow
// Get alpha of current pixel
let alpha = CGFloat(rawData[colOffset + rowOffset + 3])
if alpha > CGFloat(tolerance) {
l = col
break
}
}
if l > -1 {
break
}
}

// find the right-most non-alpha pixel
for col in stride(from: columns - 1, to: l, by: -1) {
colOffset = col * bytesPerPixel
for row in 0..<rows {
// debugging
//c += 1
rowOffset = row * bytesPerRow
// Get alpha of current pixel
let alpha = CGFloat(rawData[colOffset + rowOffset + 3])
if alpha > CGFloat(tolerance) {
r = col
break
}
}
if r > -1 {
break
}
}

// find the top-most non-alpha pixel
for row in 0..<rows {
rowOffset = row * bytesPerRow
for col in l..<r {
// debugging
//c += 1
colOffset = col * bytesPerPixel
// Get alpha of current pixel
let alpha = CGFloat(rawData[colOffset + rowOffset + 3])
if alpha > CGFloat(tolerance) {
t = row
break
}
}
if t > -1 {
break
}
}

// find the bottom-most non-alpha pixel
for row in stride(from: rows - 1, to: t, by: -1) {
rowOffset = row * bytesPerRow
for col in l..<r {
// debugging
//c += 1
colOffset = col * bytesPerPixel
// Get alpha of current pixel
let alpha = CGFloat(rawData[colOffset + rowOffset + 3])
if alpha > CGFloat(tolerance) {
b = row
break
}
}
if b > -1 {
break
}
}

// debugging
//print(c, l, t, r, b)

// define a rectangle for the non-alpha pixels
let targetRect = CGRect(x: l, y: t, width: r - l + 1, height: b - t + 1)
let size = targetRect.size
let renderer = UIGraphicsImageRenderer(size: size)

let renderedImage = renderer.image { _ in
// render the non-alpha portion
self.draw(at: CGPoint(x: -targetRect.origin.x, y: -targetRect.origin.y))
}

return renderedImage

}

}

Then, instead of using the image directly, we'll first "clip" it and then apply the previous aspect-sizing code.

Here is the complete example (only slightly modified from the original posting):

struct EduardoStruct {
var typeOrder: String = ""
var other: String = ""
var asset: String = ""
}

class EduardoCell: UITableViewCell {

let typeOrderLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.font = .systemFont(ofSize: 14, weight: .bold)
v.textColor = .systemGreen
// if we want to see the label frame
//v.backgroundColor = .yellow
return v
}()
let assetImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleToFill
imageView.backgroundColor = .cyan
return imageView
}()
let otherLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.font = .systemFont(ofSize: 13, weight: .thin)
v.textColor = .darkGray
// if we want to see the label frame
//v.backgroundColor = .yellow
return v
}()

var cWidth: NSLayoutConstraint!
var cHeight: NSLayoutConstraint!

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// add the subviews
contentView.addSubview(typeOrderLabel)
contentView.addSubview(assetImageView)
contentView.addSubview(otherLabel)

// initialize image view Width and Height constraints
cWidth = assetImageView.widthAnchor.constraint(equalToConstant: 0)
cHeight = assetImageView.heightAnchor.constraint(equalToConstant: 0)

let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([

typeOrderLabel.topAnchor.constraint(equalTo: g.topAnchor),
typeOrderLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
typeOrderLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),

assetImageView.topAnchor.constraint(equalTo: typeOrderLabel.bottomAnchor, constant: 15.0),
assetImageView.leadingAnchor.constraint(equalTo: typeOrderLabel.leadingAnchor),

otherLabel.topAnchor.constraint(equalTo: assetImageView.bottomAnchor, constant: 10.0),
otherLabel.leadingAnchor.constraint(equalTo: typeOrderLabel.leadingAnchor),
otherLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
otherLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),

// activate image view Width and Height constraints
cWidth,
cHeight,

])
}

func fillData(_ st: EduardoStruct, showBKG: Bool) {
typeOrderLabel.text = st.typeOrder
otherLabel.text = st.other

// image max Width and Height
let maxWidth: CGFloat = 100
let maxHeight: CGFloat = 30

assetImageView.backgroundColor = showBKG ? .cyan : .clear

guard let origImg = UIImage(named: st.asset) else {
// if we can't load the image, set the image view
// to maxWidth x maxHeight
cWidth.constant = maxWidth
cHeight.constant = maxHeight
assetImageView.image = nil
return
}

let img = origImg.clipAlpha(0.0)

assetImageView.image = img

if img.size.height <= maxHeight && img.size.width <= maxWidth {

// image height and width are smaller than max Height and Width
// so use actual size
cWidth.constant = img.size.width
cHeight.constant = img.size.height

} else {

// use standard Aspect Fit calculation
var f = maxWidth / img.size.width
var w = img.size.width * f
var h = img.size.height * f
if h > maxHeight {
f = maxHeight / img.size.height
h = img.size.height * f
w = img.size.width * f
}

// update constraints
cWidth.constant = w
cHeight.constant = h

}
}
}

class TestSizingCellTableViewController: UITableViewController {

var myData: [EduardoStruct] = []

override func viewDidLoad() {
super.viewDidLoad()

let imageNames: [String] = [
"img1", "img2", "img3", "img4", "img5",
]

for (i, str) in imageNames.enumerated() {
var st: EduardoStruct = EduardoStruct()
st.typeOrder = "TYPE \(i)"
st.other = "OTHER \(i)"
st.asset = str
myData.append(st)
}

tableView.register(EduardoCell.self, forCellReuseIdentifier: "cell")
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! EduardoCell

// set cell's imageView background to cyan or clear
let showBKG = false

cell.fillData(myData[indexPath.row], showBKG: showBKG)

return cell
}

}

And the output - first with the imageView background set to .cyan (so we can see the actual frames):

Sample Image

and with the imageView background set to .clear:

Sample Image

Using auto layout with UIImageView doesn't preserve frame size

You should not set any frames when using auto layout. You need to add two more constraints for the width and height of the image view.

let imageView = UIImageView()
// your other code here

imageView.addConstraint(NSLayoutConstraint(item: imageView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 100))

imageView.addConstraint(NSLayoutConstraint(item: imageView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 100))


Related Topics



Leave a reply



Submit