I'm trying to add insets to the text inside the UILabel without subclassing it. Or even with UILabel subclass but without changing too much the code.
How can I do it?
class CustomCell: UICollectionViewCell {
var data:CustomData? {
didSet {
guard let data = data else { return }
//bg.image = data.image
bg.text = data.title
}
}
fileprivate let bg: UILabel = {
let iv = UILabel()
iv.layer.backgroundColor = UIColor.gray.cgColor
iv.textAlignment = .center
iv.numberOfLines = 0
iv.font = UIFont(name: "Helvetica", size: 40)
iv.adjustsFontSizeToFitWidth = true
iv.minimumScaleFactor = 0.5
iv.translatesAutoresizingMaskIntoConstraints = false
iv.layer.cornerRadius = 12
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(bg)
bg.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
bg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
bg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
bg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Use constant parameter
bg.topAnchor.constraint(equalTo: contentView.topAnchor,constant:30).isActive = true
for 30 pts inset do
NSLayoutConstraint.activate([
bg.topAnchor.constraint(equalTo: contentView.topAnchor,constant:30),
bg.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant:30),
bg.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant:-30),
bg.bottomAnchor.constraint(equalTo: contentView.bottomAnchor,constant:-30)
])
You can also set UIEdgeInsets
Your entire approach is wrong. If the goal is to show the label text centered in bg, then bg should not be a label; it should contain a label, centered.
This example uses no code at all; the outer view self-sizes to the label, and the label's constraints to the outer view provide the insets:
Related
I am building a custom longitude/latitude selector. The view looks like:
textfield ° textfield ′ textfield ″ N|S
The components are all in a stack view. I would like the width of the textfields to auto-fit the content. Here is the code that I am using (the actual layout code is not a lot. Most of it is boilerplate):
class DMSLongLatInputView : UIView {
private var degreeTextField: DMSLongLatTextField!
private var minuteTextField: DMSLongLatTextField!
private var secondTextField: DMSLongLatTextField!
var signSelector: UISegmentedControl!
let fontSize: CGFloat = 22
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
degreeTextField = DMSLongLatTextField()
minuteTextField = DMSLongLatTextField()
secondTextField = DMSLongLatTextField()
signSelector = UISegmentedControl(items: ["N", "S"])
signSelector.selectedSegmentIndex = 0
signSelector.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: fontSize)], for: .normal)
[degreeTextField, minuteTextField, secondTextField].forEach { (tf) in
tf?.font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
}
let degreeLabel = UILabel()
degreeLabel.text = "°"
let minuteLabel = UILabel()
minuteLabel.text = "′"
let secondLabel = UILabel()
secondLabel.text = "″"
[degreeLabel, minuteLabel, secondLabel].forEach {
l in l.font = UIFont.systemFont(ofSize: fontSize)
}
let stackView = UIStackView(arrangedSubviews:
[degreeTextField,
degreeLabel,
minuteTextField,
minuteLabel,
secondTextField,
secondLabel,
signSelector
])
stackView.arrangedSubviews.forEach { (v) in
// I was hoping that this would make the widths of the stack view's subviews automatically
// fit the content
v.setContentCompressionResistancePriority(.required, for: .horizontal)
v.setContentHuggingPriority(.required, for: .horizontal)
}
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
addSubview(stackView)
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
stackView.translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .clear
}
}
fileprivate class DMSLongLatTextField: UITextField, UITextFieldDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
backgroundColor = .tertiarySystemFill
placeholder = "00"
borderStyle = .none
textAlignment = .right
}
}
This produces:
The first textfield got stretched by a lot, even though I set the content hugging priority to .required. I would like the first textfield to be only as wide as two digits, as that is how wide its placeholder is.
I suspected that this is because I used a wrong distribution, but I tried all the other 4 distributions, but none of them got the result I wanted. For example, .equalSpacing with spacing = 0 gave this result (as seen from the UI hierarchy inspector in the debugger):
Clearly the spacing is not 0! I would like all the subviews to be as close together as they can be, and stay centre-aligned. How can I do that?
You're setting Leading and Trailing on the stack view... in other words, you're telling auto-layout to:
"Fill the width of the screen with the subviews."
Change your constraints to this:
stackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
//stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
//stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
That should horizontally center your stack view and its subviews.
I have UITextView that I want to anchor to the right of my UICollectionViewCell. I apply the following constraint: textView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
But it DOES NOT anchor all the way to the right. I then apply the following constraint but this time: textView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true and it DOES anchor all the way to the left. Why might this be?
https://imgur.com/a/uykWLw5
Here is my ChatMessageCell.
import UIKit
class ChatMessageCell: UICollectionViewCell {
let textView: UITextView = {
let tv = UITextView()
tv.text = "Some Text"
tv.font = UIFont.systemFont(ofSize: 16)
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(textView)
textView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
textView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
textView.widthAnchor.constraint(equalToConstant: 200).isActive = true
textView.heightAnchor.constraint(equalTo: self.heightAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Your textView would be left-aligned. If you want your text view to remain with a width of 200 and appear to sit on the right you need to add: textView.textAlignment = .right
Also, I belive it's best to use leading and trailing constraints
I am in the process of implementing a UILabel as a subview of a UITextField which will be shown right above the UITextField itself. The UITextField has a rounded border and what I would like to achieve is the UILabel to be shown over the border.
Everything currently works as expected, but the UILabel is drawn behind the border of the UITextField. I want it to go "over" (above) the border so the white backgroundColor would be shown above part of the border and make the text more easily readible.
var priceTextField: CustomTextField = {
let priceTextField = CustomTextField()
priceTextField.layer.cornerRadius = 10.0
priceTextField.layer.borderWidth = 1.0
priceTextField.layer.borderColor = UIColor.darkGray.cgColor
priceTextField.translatesAutoresizingMaskIntoConstraints = false
priceTextField.font = UIFont.systemFont(ofSize: 15)
priceTextField.textColor = .black
priceTextField.text = "0"
priceTextField.suffix = "EUR"
priceTextField.suffixTextColor = .darkGray
priceTextField.suffixSpacing = 2.0
priceTextField.textAlignment = .center
priceTextField.labelText = "Price"
return priceTextField
}()
In my CustomTextField class (subclass of UITextField):
public var labelText: String?
var topLabel: UILabel = {
let topLabel = UILabel()
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.textAlignment = .center
topLabel.font = UIFont.systemFont(ofSize: 12)
topLabel.textColor = .lightGray
topLabel.backgroundColor = .white
topLabel.numberOfLines = 1
return topLabel
}()
func setupLabel() {
self.addSubview(topLabel)
topLabel.centerYAnchor.constraint(equalTo: self.topAnchor).isActive = true
topLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20).isActive = true
topLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
topLabel.text = labelText
}
I call setupLabel() at the end of the draw(_ rect: CGRect) method of UITextField (because I work with this to show the EUR sign always behind the entered value).
I have tried to play around with bringSubviewToFront and changing the zPosition of the layer of the UILabel, without success.
It now looks like this:
How can I bring the text "above" the border on the top?
EDIT: Tried Sh_Khan's solution, but it's still hidden behind the border.
import Foundation
import UIKit
public class CustomTextView: UIView, UITextFieldDelegate {
public var labelText: String?
var customTextField: CustomTextField = {
let customTextField = CustomTextField()
customTextField.translatesAutoresizingMaskIntoConstraints = false
customTextField.font = UIFont.systemFont(ofSize: 15)
customTextField.textColor = .black
customTextField.textAlignment = .center
customTextField.text = "0"
customTextField.suffix = "EUR"
customTextField.suffixTextColor = .lightGray
customTextField.suffixSpacing = 2.0
return customTextField
}()
var topLabel: UILabel = {
let topLabel = UILabel()
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.font = UIFont.systemFont(ofSize: 12)
topLabel.textColor = .darkGray
topLabel.numberOfLines = 1
topLabel.backgroundColor = .red
topLabel.textAlignment = .center
return topLabel
}()
override public init(frame: CGRect) {
super.init(frame: frame)
setupBorders()
}
public override func layoutSubviews() {
setupViews()
}
func setupBorders() {
self.layer.cornerRadius = 10.0
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.borderWidth = 1.0
}
func setupViews() {
addSubview(topLabel)
// insertSubview(topLabel, aboveSubview: customTextField)
insertSubview(customTextField, belowSubview: topLabel)
customTextField.topAnchor.constraint(equalTo: topAnchor).isActive = true
customTextField.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
customTextField.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
customTextField.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
topLabel.centerYAnchor.constraint(equalTo: topAnchor).isActive = true
topLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
topLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
topLabel.text = labelText
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
}
You can try to organize it by creating a UIView subclass , so everything appear properly in it's order of adding
class CustomView: UIView {
var priceTextField: CustomTextField = {
let priceTextField = CustomTextField()
priceTextField.layer.cornerRadius = 10.0
priceTextField.layer.borderWidth = 1.0
priceTextField.layer.borderColor = UIColor.darkGray.cgColor
priceTextField.translatesAutoresizingMaskIntoConstraints = false
priceTextField.font = UIFont.systemFont(ofSize: 15)
priceTextField.textColor = .black
priceTextField.text = "0"
priceTextField.suffix = "EUR"
priceTextField.suffixTextColor = .darkGray
priceTextField.suffixSpacing = 2.0
priceTextField.textAlignment = .center
priceTextField.labelText = "Price"
return priceTextField
}()
var topLabel: UILabel = {
let topLabel = UILabel()
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.textAlignment = .center
topLabel.font = UIFont.systemFont(ofSize: 12)
topLabel.textColor = .lightGray
topLabel.backgroundColor = .white
topLabel.numberOfLines = 1
return topLabel
}()
var lableStr:String?
init(frame: CGRect,lblTex:String) {
super.init(frame: frame)
lableStr = lblTex
createSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
createSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createSubviews()
}
func createSubviews() {
// all the layout code from above
// add the textfield then the label and set constraints properly
}
}
According to the Apple specification: It is composited above the receiver’s contents and sublayers.
So, the border will always be above all subviews, even if one brings the subview to the front and so on.
So one needs to make a background view to fake the border.
similar to Stackoverflow Question
Example:
Here self is "TextField"
activeborderView is "UiView"
activeborderView.frame = CGRect.init(x: -1, y: -1, width: self.frame.size.width+2, height: self.frame.size.height+2)
activeborderView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(activeborderView)
activeborderView.topAnchor.constraint(equalTo: self.topAnchor, constant:-1).isActive = true // Place our label 10 pts above the text field
activeborderView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: -1).isActive=true
activeborderView.heightAnchor.constraint(equalToConstant: self.frame.size.height+2).isActive=true
activeborderView.widthAnchor.constraint(equalToConstant: self.frame.size.width+2).isActive=true
activeborderView.layer.borderWidth = 3
activeborderView.layer.borderColor = CustomColor.blue().cgColor
activeborderView.layer.cornerRadius = 5
activeborderView.backgroundColor = .white
self.sendSubviewToBack(activeborderView)
self.setNeedsDisplay()
I want to create custom UITextField with error label on bottom of it. I want the label to be multiline, I tried numberOfLines = 0. But it is not working.
Here is my snippet for the class
public class MyTextField: UITextField {
private let helperTextLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.regular)
label.textColor = helperTextColor
label.numberOfLines = 0
return label
}()
public override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(errorTextLabel)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addSubview(errorTextLabel)
}
public override func layoutSubviews() {
super.layoutSubviews()
errorTextLabel.frame = CGRect(x: bounds.minX, y: bounds.maxY - 20, width: bounds.width, height: 20)
}
public override var intrinsicContentSize: CGSize {
return CGSize(width: 240.0, height: 68.0)
}
public override func sizeThatFits(_ size: CGSize) -> CGSize {
return intrinsicContentSize
}
}
I think the root cause is because I set height to 20, but how can I set the height dynamically based on the errorTextLabel.text value?
You are giving your label a fixed size.
Not using AutoLayout and giving the textfield and label room to expand it's size when needed.
Personally, if creating this particular control, I would create a UIView with a textfield and a label inside a UIStackView. That way if the label is hidden when there is no error the stackview will automatically adjust the height for you. Then when you unhide it, the view will expand to fit both controls.
A basic example:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
class LabelledTextView: UIView {
private let label = UILabel()
private let textfield = UITextField()
private let stackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
stackView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
stackView.alignment = .leading
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.addArrangedSubview(textfield)
stackView.addArrangedSubview(label)
textfield.placeholder = "Please enter some text"
label.numberOfLines = 0
label.text = "Text did not pass validation, Text did not pass validation, Text did not pass validation, Text did not pass validation"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
let labelledTextView = LabelledTextView(frame: CGRect(x: 50, y: 300, width: 300, height: 60))
let vc = UIViewController()
vc.view.addSubview(labelledTextView)
labelledTextView.translatesAutoresizingMaskIntoConstraints = false
labelledTextView.topAnchor.constraint(equalTo: vc.view.topAnchor).isActive = true
labelledTextView.leftAnchor.constraint(equalTo: vc.view.leftAnchor).isActive = true
labelledTextView.widthAnchor.constraint(equalToConstant: 300).isActive = true
labelledTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60).isActive = true
PlaygroundPage.current.liveView = vc.view
You need to use sizeToFit():
errorTextLabel.sizeToFit()
I have a collectionviewCell with a rounded imageview which shows an icon.
Unfortunately the icons are too big. How can I fit them to fit into the rounded ImageView:
Code
func makeItCircle() {
self.imageView.layer.cornerRadius = self.imageView.frame.height/2
self.imageView.layer.masksToBounds = false
self.imageView.clipsToBounds = true
self.imageView.contentMode = .scaleAspectFit
}
Picture
You can wrap the image views inside container views (the cell views you already have should work) to add some padding: center the image view inside its container and constraint its width and height to about 0.75 of the container. Also you'll have to set the background and corner rounding on the container, not on the image view.
(100x100)
class CenterTab:UIView{
let container:UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = 100/2
v.layer.shadowColor = UIColor.systemPurple.cgColor
v.layer.shadowOpacity = 0.4
v.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
v.layer.shadowRadius = 7
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let tabImg:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFit
img.clipsToBounds = true
img.tintColor = .systemPurple
img.translatesAutoresizingMaskIntoConstraints = false
return img
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(tabImg)
self.addSubview(container)
}
override func layoutSubviews() {
super.layoutSubviews()
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: self.topAnchor),
container.leadingAnchor.constraint(equalTo: self.leadingAnchor),
container.trailingAnchor.constraint(equalTo: self.trailingAnchor),
container.bottomAnchor.constraint(equalTo: self.bottomAnchor),
tabImg.centerXAnchor.constraint(equalTo: container.centerXAnchor),
tabImg.centerYAnchor.constraint(equalTo: container.centerYAnchor),
tabImg.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.60),
tabImg.heightAnchor.constraint(equalTo: container.widthAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}}