iOS - Constraint to UIImage with centrally aligned UILabel's text - swift

I have UITableViewCell that has a UIlabel aligned center I'm setting image in default imageView property of the UITableViewCell but since text is aligned center there is a gap between text and the image.
I want image then little space then text all center to UITableViewCell I have tried following code,
cell.imageView?.image = image
cell.imageView?.translatesAutoresizingMaskIntoConstraints = false
cell.imageView?.centerYAnchor.constraint(equalTo: label.centerYAnchor).isActive = true
let rect: CGRect = label.textRect(forBounds: label.bounds, limitedToNumberOfLines: 1)
cell.imageView?.leadingAnchor.constraint(equalTo: label.leadingAnchor, constant: rect.origin.x - padding).isActive = true
That works for me but when I switch device from iPhone 11 Max Pro to iPhone 8 image overlaps the text because label.textRect always brings the same text irrespective of screen size
I have also tried using range of the first later and using it's rect but same problem of not being changed per screen size.
Can this be achieved without putting custom UIImageView in UITableViewCell?

You could use a stackView that you center inside your cell and add your imageView and your label as arranged subViews. Note that you would need to create a custom cell.
Create your stackView:
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
stackView.spacing = 10 // You can set the spacing accordingly
return stackView
}()
Layout as follows:
contentView.addSubview(stackView)
// Swap these two lines if instead you want label then image
stackView.addArrangedSubview(image)
stackView.addArrangedSubview(label)
// StackView
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor).isActive = true

Related

AutoLayout doesn't work for custom UIView [duplicate]

Im working on a chat app, so the height of the rows varies. I am using separate cells for plain txt msg and msg with image (and maybe text). I allow the user to select an image from the phone. I display that image in a separate VC where he can enter text if he chooses and send it. I have a model for the msg which means I do conversion between base64 and image formats. I have tried to simplify to the max my cell class to understand the following problem: the image inside the image view appears zoomed beyond what the normal phone zoom would allow; and the height of the cell is immense. On the cell class that I need to use I have more items and constraints but the basic logic of interest here is below:
fileprivate func configureMsgsTable() {
tableView.contentInsetAdjustmentBehavior = .automatic
tableView.backgroundColor = .clear
tableView.keyboardDismissMode = .interactive
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
tableView.sectionFooterHeight = 0.0
}
these are the functions I use for encoding/decoding:
fileprivate func convertImageToBase64String (img: UIImage) -> String {
let imageData:NSData = img.jpegData(compressionQuality: 0.50)! as NSData
let imgString = imageData.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters)
return imgString
}
fileprivate func convertBase64StringToImage (imageBase64String:String) -> UIImage {
let imageData = Data.init(base64Encoded: imageBase64String, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)
let image = UIImage(data: imageData!)
return image!
}
and this is the cell class.
class MsgWithImg: UITableViewCell {
//MARK: - Observer.
internal var valuesToDisplay: NewProjectGrpMsgModel! {
didSet {
imgView.image = convertBase64StringToImage(imageBase64String: valuesToDisplay.msgAttachment)
}
}
//MARK: - Properties.
fileprivate let imgView: UIImageView = {
let imgView = UIImageView(frame: .zero)
imgView.clipsToBounds = true
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFill
imgView.layer.cornerRadius = 0
return imgView
}()
//MARK: - Init.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
clipsToBounds = true
selectionStyle = .none
setupViews()
}
required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}
fileprivate func setupViews() {
contentView.addSubview(imgView)
let imgViewConstraints = [
imgView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 3),
imgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -3),
imgView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -45)
]
NSLayoutConstraint.activate(imgViewConstraints)
}
}
I spent some time thinking that this is an auto layout problem; or the fact that the table row height is automatic. that's why I built this test cell class with only the image view. but I think this problem is of a different nature. I did read quite a few answers to what I could find relevant on this website but I cannot determine what the problem is and the console does not print out anything.
The problem is that if you don't give a UIImageView both a width and a height, its intrinsicContentSize becomes the size of the image assigned to it.
With your code as-is, you've given the image view a width by constraining its Leading and Trailing anchors, but you haven't given it a height -- either by itself or by the cell's height (since you want auto-sizing cells).
So, if we use these four images:
The resulting table view looks like this:
And here's what's happening on an iPhone 13 (note: all sizes are rounded)...
For the 100x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 690
For the 100x300 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (300-pts), setting the image view frame 300-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 1035
For the 600x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 600 x 200
For the 800x600 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (600-pts), setting the image view frame 600-pts tall
image view is set to .scaleAspectFill, so the scaled size is 800 x 600
It may be clearer if we set the image view to .scaleAspectFit (with a red background so we can see the frame):
As a general rule, it is common to give the image view a fixed size (or proportional size), and use .scaleAspectFit to show the complete images. Or, also common, to use a pre-processor to generate "thumbnail" sized images for the table view cells.

How do I make a Swift UIStackView size dynamically based on content?

I have added the following UIStackView to my ViewController. As the views change the values of textOneLabel and textTwoLabel change.
With the following code the initial StackView is centered and the portions filled proportionally. However with subsequent text combinations the bounds of the StackView don't change, leaving the content off center. How can I change the StackView properties so it will adapt to the content and always stay centered?
headerStackView.axis = .horizontal
headerStackView.distribution = .fillProportionally
headerStackView.spacing = 8
headerStackView.layer.borderWidth = 1
headerStackView.layer.borderColor = UIColor.red.cgColor
headerStackView.translatesAutoresizingMaskIntoConstraints = false
headerStackView.topAnchor.constraint(equalTo: timerHeader.topAnchor, constant: 4).isActive = true
headerStackView.centerXAnchor.constraint(equalTo: timerHeader.centerXAnchor).isActive = true
headerStackView.addArrangedSubview(textOneLabel)
headerStackView.addArrangedSubview(textTwoLabel)
First, forget you ever heard of .fillProportionally...
it doesn't do what you think it does
you'll encounter very unexpected layout issues if your stack view has spacing greater than Zero
if your stack view has no width (neither width anchor nor leading/trailing anchors), .fillProportionally doesn't do anything
So, change your .distribution to .fill.
Add these lines to control what auto-layout does with your labels:
textOneLabel.setContentHuggingPriority(.required, for: .horizontal)
textOneLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
textTwoLabel.setContentHuggingPriority(.required, for: .horizontal)
textTwoLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
Now, your stackView FRAME will remain centered.

Snapkit centerY constraint centers item above the center Y axis

I'm trying to make a custom UICollectionView cell class. The cell consists of a content view and a label. I want the label to be in the center of the view, horizontally and vertically, but instead the label is placed above the content view's center y axis.
I've made sure that the constraints are set, no other constraints are being set, and that the issue affects all views in the content view (I added another view and set its center Y axis as a test, and that also didn't work). I also set the content view and the label's background colors to be contrasting, and have confirmed that the label is not lying on the content view's center y anchor.
Here is how I set the consraints:
label.snp.makeConstraints{make in
make.centerX.centerY.equalToSuperview()
}
Here is what I get instead. Clearly the label is not centered vertically. You can see the blue UIView, which I added as a test, is also not centered vertically.
I used to add my constraints programmatically in this way
self.view.addSubview(image)
image.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
image.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
image.heightAnchor.constraint(equalToConstant: 30).isActive = true
image.widthAnchor.constraint(equalToConstant: 30).isActive = true
and my image is declarated in this way
let image: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage(named: "ico_return")
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
Hope it helps
Can you try Following Code.
class FilterCollectionViewCell: UICollectionViewCell {
let labelTemp = UILabel()
override func awakeFromNib() {
labelTemp.backgroundColor = .white
labelTemp.textColor = .black
labelTemp.text = "testing"
self.contentView.addSubview(labelTemp)
labelTemp.snp.makeConstraints { (make) in
make.centerX.centerY.equalTo(self.contentView)
}
}
}
Fast and easy:
myLabel.snp.makeConstraints { (make) in
make.center.equalTo(self.topView.snp.center)
}

swift: adding uistackView with UITextField of same height

I have a vertical StackView with Distribution "Equal Spacing"
containing horizontal StackViews each containing a Switch and a label except the last one, which contains a Switch and a TextField.
Now, when the Switch of that last one is turned on, I want to add an additional horizontal StackView to the vertical one which looks like the last one:
#IBAction func onValueChange(_ sender: UISwitch) {
let parent = sender.superView as! UIStackView
let pparent = parent.superView as! UIStackView
let textField = parent.arragedSubviews[1] as TextField
let row = UIStackView
row.axis = UILayoutConstraintAxis.horizontal
row.spacing = 30
let swtch = UISwitch()
swtch.isOn = false
let input = UITextField()
input.borderStyle = textField.borderStyle
row.addArrangedSubView(swtch)
row.addArrangedSubView(input)
pparent.addArrangedSubView(row)
}
it works quite well, except the new row is smaller in height and therefore the textfield is also smaller in height, which looks stupid. I didn't put any restraints on the horizontal StackViews. What should I do?
Use .fillEqually instead of .equalSpacing to make sure that the rows in the stackView would equal in height.

Reduce space around a textfield using auto layout

I'm currently using a stackView that contains a few vertically aligned elements. I'm doing everything in code.
I've marked every stack view's subview. Here's a screenshot:
This is the code for the stackview:
func setUpVerticalLayoutStackView() {
stackViewForVerticalLayout = UIStackView(arrangedSubviews: [viewTitleButton, stackViewForTheTwoPersons, owedButton, amountTextField, doneButton])
stackViewForVerticalLayout.axis = .vertical
stackViewForVerticalLayout.distribution = .equalCentering
stackViewForVerticalLayout.alignment = .center
stackViewForVerticalLayout.spacing = 20
stackViewForVerticalLayout.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackViewForVerticalLayout)
}
My question is: why does the textfield occupy so much space? Can I reduce it somehow?
Don't mind the "r" at the center of the view, it's just something I mistakenly added in an image editor.
Change stackViewForVerticalLayout.distribution to .equalSpacing or .fillEqually depending on what you want. This will reduce this size of the field