What I am trying to achieve (and I am not 100% sure how to do it or how to explain it properly) is described in the below screenshot.
I have added allowsDefaultTighteningForTruncation = true
and lineBreakMode = .byClipping
to my label, but it now displays the beginning of the word and I need to display the end of the word, any ideas on how to achieve that? or any ideas what to look for in apple docs? I've read everything I could think of so far.
To get that result, you need to embed the label in a UIView
and constrain the label's Trailing but not Leading.
Make sure the "holder" view has Clips To Bounds set to true.
As the label grows in width, it will extend past the leading edge of the holder view.
Here's a quick example:
class ViewController: UIViewController {
let theLabel = UILabel()
let holderView = UIView()
let strs: [String] = [
"Misinterpret",
"Misinterpreted",
"Misinterpretation",
]
var idx = 0
override func viewDidLoad() {
super.viewDidLoad()
theLabel.translatesAutoresizingMaskIntoConstraints = false
holderView.translatesAutoresizingMaskIntoConstraints = false
holderView.backgroundColor = .systemBlue
theLabel.backgroundColor = .yellow
theLabel.font = .systemFont(ofSize: 30.0)
holderView.addSubview(theLabel)
view.addSubview(holderView)
NSLayoutConstraint.activate([
holderView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
holderView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
holderView.widthAnchor.constraint(equalToConstant: 200.0),
holderView.heightAnchor.constraint(equalTo: theLabel.heightAnchor, constant: 8.0),
theLabel.centerYAnchor.constraint(equalTo: holderView.centerYAnchor),
theLabel.trailingAnchor.constraint(equalTo: holderView.trailingAnchor, constant: -8.0),
])
// clip theLabel when it gets too wide
holderView.clipsToBounds = true
theLabel.text = strs[idx]
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
idx += 1
theLabel.text = strs[idx % strs.count]
}
}
Output:
The "type ahead" suggestion bar probably also uses a gradient mask so the text does not look so abruptly clipped... but that's another question.
Edit - here's a more complete example.
As you enter text, the labels will update.
The label in the gray box will be centered horizontally, until it is too wide to fit, at which point it will stay "right-aligned." It will also have a slight gradient mask at the left edge so it is not cut off abruptly.
class ViewController: UIViewController {
let textField = UITextField()
let theClippedLabel = UILabel()
let holderView = UIView()
// plain label showing the actual size
let theActualLabel = UILabel()
let leftEdgeFadeMask = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
[textField, theClippedLabel, holderView, theActualLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
holderView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
theClippedLabel.backgroundColor = .clear
theActualLabel.backgroundColor = .green
textField.borderStyle = .roundedRect
textField.placeholder = "Type here..."
textField.addTarget(self, action: #selector(didEdit(_:)), for: .editingChanged)
theClippedLabel.font = .systemFont(ofSize: 30.0)
theActualLabel.font = theClippedLabel.font
holderView.addSubview(theClippedLabel)
view.addSubview(holderView)
view.addSubview(theActualLabel)
view.addSubview(textField)
// center label horizontally, unless it is wider than holderView (minus Left/Right "padding")
let cx = theClippedLabel.centerXAnchor.constraint(equalTo: holderView.centerXAnchor)
cx.priority = .defaultHigh
NSLayoutConstraint.activate([
// center a 200-pt wide "holder" view
holderView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
holderView.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 20.0),
holderView.widthAnchor.constraint(equalToConstant: 200.0),
// holderView height is 16-pts taller than the label height (8-pts Top / Bottom "padding")
holderView.heightAnchor.constraint(equalTo: theClippedLabel.heightAnchor, constant: 16.0),
// center the label vertically
theClippedLabel.centerYAnchor.constraint(equalTo: holderView.centerYAnchor),
// keep the label's Trailing edge at least 8-pts from the holderView's Trailing edge
theClippedLabel.trailingAnchor.constraint(lessThanOrEqualTo: holderView.trailingAnchor, constant: -8.0),
// activate cx constraint
cx,
theActualLabel.topAnchor.constraint(equalTo: holderView.bottomAnchor, constant: 4.0),
theActualLabel.centerXAnchor.constraint(equalTo: holderView.centerXAnchor),
textField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12.0),
textField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12.0),
textField.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12.0),
])
// clip theLabel when it gets too wide
holderView.clipsToBounds = true
// gradient mask for left-edge of label
leftEdgeFadeMask.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
leftEdgeFadeMask.startPoint = CGPoint(x: 0.0, y: 0.0)
leftEdgeFadeMask.endPoint = CGPoint(x: 1.0, y: 0.0)
leftEdgeFadeMask.locations = [0.0, 0.1]
theClippedLabel.layer.mask = leftEdgeFadeMask
// so we have something to see when we start
theClippedLabel.text = " "
theActualLabel.text = theClippedLabel.text
}
@objc func didEdit(_ sender: Any) {
// if the textField is empty, use a space character so
// the labels don't disappear
var str = " "
if let s = textField.text, !s.isEmpty {
str = s
}
theClippedLabel.text = str
theActualLabel.text = str
updateMask()
}
func updateMask() -> Void {
// update label frame
theClippedLabel.sizeToFit()
// we want the gradient mask to start at the leading edge
// of the holder view, with
// 4-pts Left and 8-pts Right "padding"
var r = holderView.bounds
let targetW = r.width - 12
r.size.width -= 12
r.size.height -= 16
r.origin.x = theClippedLabel.bounds.width - targetW
// disable built-in layer animations
CATransaction.begin()
CATransaction.setDisableActions(true)
leftEdgeFadeMask.frame = r
CATransaction.commit()
}
}
Example result:
Note that this is example code only. In practical use, we'd want to build this as a custom view with all of the sizing and gradient mask logic self-contained.