I have a string : 12345678901234567890123456789012345678901234567890(...)
the default label will be layouting from left to right.
and I want to display this label with this kind of layout (and the will be no ellipsis):
the text layout begin not in the left , but starts in the middle
then the layout continue to the right
fill the left at last
How to do this
You can do this a few different ways...
UILabel
as subviewsCATextLayer
Here's an example using CATextLayer
. Based on your descriptions:
I'm calling the custom view subclass RightLeftLabelView
:
class RightLeftLabelView: UIView {
public var text: String = ""
{
didSet {
setNeedsLayout()
invalidateIntrinsicContentSize()
}
}
public var font: UIFont = .systemFont(ofSize: 17.0)
{
didSet {
setNeedsLayout()
invalidateIntrinsicContentSize()
}
}
public var textColor: UIColor = .black
{
didSet {
setNeedsLayout()
}
}
private let leftTL = CATextLayer()
private let rightTL = CATextLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
clipsToBounds = true
[leftTL, rightTL].forEach { tl in
tl.contentsScale = UIScreen.main.scale
layer.addSublayer(tl)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// get the size of the text, limited to a single line
let sz = font.sizeOfString(string: text, constrainedToWidth: .greatestFiniteMagnitude)
var r = CGRect(origin: .zero, size: CGSize(width: ceil(sz.width), height: ceil(sz.height)))
// start right text layer at horizontal center
r.origin.x = bounds.midX
rightTL.frame = r //.offsetBy(dx: r.width * 0.5, dy: 0.0)
// end left text layer at horizontal center
r.origin.x -= r.width
leftTL.frame = r //.offsetBy(dx: -r.width * 0.5, dy: 0.0)
[leftTL, rightTL].forEach { tl in
tl.string = text
tl.font = font
tl.fontSize = font.pointSize
tl.foregroundColor = textColor.cgColor
}
}
override var intrinsicContentSize: CGSize {
return font.sizeOfString(string: text, constrainedToWidth: .greatestFiniteMagnitude)
}
}
It uses this UIFont
extension to get the size of the text:
extension UIFont {
func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {
let attributes = [NSAttributedString.Key.font:self]
let attString = NSAttributedString(string: string,attributes: attributes)
let framesetter = CTFramesetterCreateWithAttributedString(attString)
return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0,length: 0), nil, CGSize(width: width, height: .greatestFiniteMagnitude), nil)
}
}
and an example controller:
class RightLeftLabelVC: UIViewController {
let sampleStrings: [String] = [
"0123456789",
"This is a good test of custom wrapping.",
"This is a good test of custom wrapping when the text is too long to fit.",
]
var sampleIDX: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
let stack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.alignment = .center
v.spacing = 2
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
view.addSubview(stack)
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// center the stack view
stack.centerYAnchor.constraint(equalTo: safeG.centerYAnchor),
// full width
stack.leadingAnchor.constraint(equalTo: safeG.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: safeG.trailingAnchor),
])
let myTestViewA = RightLeftLabelView()
let myTestViewB = RightLeftLabelView()
let myTestViewC = RightLeftLabelView()
let actualA = UILabel()
let actualB = UILabel()
let actualC = UILabel()
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualA)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewA)
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualB)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewB)
stack.addArrangedSubview(infoLabel("UILabel"))
stack.addArrangedSubview(actualC)
stack.addArrangedSubview(infoLabel("Custom View"))
stack.addArrangedSubview(myTestViewC)
// some vertical spacing
stack.setCustomSpacing(32.0, after: myTestViewA)
stack.setCustomSpacing(32.0, after: myTestViewB)
stack.setCustomSpacing(32.0, after: myTestViewC)
// for convenience
let rlViews: [RightLeftLabelView] = [
myTestViewA, myTestViewB, myTestViewC
]
let labels: [UILabel] = [
actualA, actualB, actualC
]
let strings: [String] = [
"0123456789",
"This is a good test of custom wrapping.",
"This is an example test of custom wrapping when the text is too long to fit.",
]
// set various properties
var i: Int = 0
for (v, l) in zip(rlViews, labels) {
v.backgroundColor = .cyan
v.text = strings[i]
l.backgroundColor = .green
l.text = strings[i]
l.numberOfLines = 0
i += 1
}
}
func infoLabel(_ s: String) -> UILabel {
let v = UILabel()
v.text = s
v.font = .italicSystemFont(ofSize: 14.0)
return v
}
}
The result looks like this: