如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

huangapple go评论57阅读模式
英文:

How to obtain an approximately square multiline UILabel using Autolayout

问题

我将一个多行的UILabel放入一个弹出窗口中。文本的长度是可变的。我真正想做的是获得一个大致是正方形的尺寸,这样在显示时会看起来很好。

如果我创建一个将标签宽度设置为等于高度的约束,我会得到一个与高度匹配的单行标签。

有一个(我认为很糟糕的)解决方案:获取单行标签的宽度,然后在循环中将宽度约束设置为原始宽度的逐渐减小的分数,然后布局,直到标签的边界大致符合所需的尺寸。

毫无疑问,应该有一个更好的解决方案。

英文:

I'm putting a multiline UILabel into a popup. The text has a variable length. What I'd really like to do is get a size that is approximately square—so that when displayed it has a nice appearance.

If I create a constraint that sets the label width to equal the height, I get a one line label with a matching height.

There is a (IMHO a terrible) solution: get the width of the one line label, then in a loop set a width constraint to an ever decreasing a fraction of the original width, layout, then stop when the label bound's is more or less what's desired.

Surely there is a better solution.

答案1

得分: 1

以下是翻译好的部分:

我认为没有办法在不遍历尺寸的情况下完成这个任务。

我无法完全理解你在答案中的代码中在做什么——无法让它产生预期的结果。然而...

首先,我们可以利用systemLayoutSizeFittingApple文档)而不是设置约束并强制进行自动布局传递。例如:

let v = UILabel()
v.numberOfLines = 0
v.font = .systemFont(ofSize: 16.0, weight: .regular)

// 设置标签的文本
v.text = "示例字符串:快速的红狐跳过懒惰的棕色狗。"

// 获取最宽的尺寸
let sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))

print("尺寸:", sz)

// 输出 "尺寸:(497.0, 19.333333333333332)"

这为我们提供了字符串作为单行的宽度和高度。不过,如果字符串包含嵌入的换行字符:

"示例字符串:\n\n快速的红狐跳过懒惰的棕色狗。"

它的“原生”布局将是:

示例字符串:

快速的红狐跳过懒惰的棕色狗。

所以同样的代码行:

let sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))

输出的是 "尺寸:(355.6666666666667, 57.333333333333336)",这确实是我们想要的。

所以,让我们将width更改为非约束宽度的一半(我们将删除换行字符):

v.text = "示例字符串:快速的红狐跳过懒惰的棕色狗。"

var sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))
print("尺寸:", sz)

sz = v.systemLayoutSizeFitting(.init(width: sz.width * 0.5, height: 1.0))
print("尺寸:", sz)

// 输出 "尺寸:(497.0, 19.333333333333332)"
// 输出 "尺寸:(244.66666666666666, 57.333333333333336)"

现在,比率可以表示为带有 width / height 的小数值。

例如,5:35.0 / 3.0 = 1.666...

通过上面两个得到的尺寸,我们得到了:

尺寸:(497.0, 19.333333333333332)
比率:497.0 / 19.333 = 25.70689655172414
又称:5.0 : 0.195

尺寸:(244.66666666666666, 57.333333333333336)
比率:244.666 / 57.333 = 4.267441860465116
又称:5.0 : 1.172

如果我们想找到给定宽高比的“最佳”尺寸,我们可以循环遍历,每次将宽度减半并比较结果:

// 我们想要一个5:3的结果
let targetWidth: CGFloat = 5.0
let targetHeight: CGFloat = 3.0
let targetRatio: CGFloat = targetWidth / targetHeight

print("目标比率:", targetRatio)
print("又称:", String(format: "%0.1f : %0.3f", targetWidth, targetHeight))

var sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))

while (sz.width / sz.height) > targetRatio {

    print()
    print("新尺寸:", sz)
    print("新比率:", sz.width / sz.height)
    print("又称:", String(format: "%0.1f : %0.3f", targetWidth, sz.height / (sz.width / targetWidth)))

    // 将宽度减半
    // 获取新的受宽度约束的尺寸
    sz = v.systemLayoutSizeFitting(.init(width: sz.width * 0.5, height: .greatestFiniteMagnitude))

}

print()
print("现在的比率小于目标比率")
print("新尺寸:", sz)
print("新比率:", sz.width / sz.height)
print("又称:", String(format: "%0.1f : %0.3f", targetWidth, sz.height / (sz.width / targetWidth)))

控制台输出将是:

目标比率:1.6666666666666667
又称:5.0 : 3.000

新尺寸:(497.0, 19.333333333333332)
新比率:25.70689655172414
又称:5.0 : 0.195

新尺寸:(244.66666666666666, 57.333333333333336)
新比率:4.267441860465116
又称:5.0 : 1.172

我们现在有一个比目标比率小的比率
新尺寸:(115.0, 95.66666666666667)
新比率:1.2020905923344947
又称:5.0 : 4.159

这看起来像这样的可视化效果:

(图片链接已省略)

最终的比率 5.0 : 4.159 并不非常接近 5 : 3,而且与之前的 5.0 : 1.172 比率之间存在很大的差距... 我们可以用这个尺寸更接近目标:

(图片链接已省略)

我们可以简单地“蛮力”来解决它

英文:

I don't think there is any way to do this without looping through sizes.

I can't quite work out what you're doing in the code in your answer -- couldn't get it to produce the expected results. However...

First, instead of setting constraints and forcing auto-layout passes, we can take advantage of systemLayoutSizeFitting (Apple Docs). For example:

	let v = UILabel()
	v.numberOfLines = 0
	v.font = .systemFont(ofSize: 16.0, weight: .regular)
	
	// set the label's text
	v.text = "An Example String: The quick red fox jumps over the lazy brown dog."
	
	// get the widest size
	let sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))

	print("Size:", sz)
	
	// prints "Size: (497.0, 19.333333333333332)"

That gives us the width and height of the string as a single line. Well, not really... if the string has embedded newline characters:

"An Example String:\n\nThe quick red fox jumps over the lazy brown dog."

Its "native" layout will be:

An Example String:

The quick red fox jumps over the lazy brown dog.

So that same line of code:

	let sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))

    // prints "Size: (355.6666666666667, 57.333333333333336)"

Which is what we want.

So, let's change the width to one-half of the non-constrained width (we'll remove the newline chars):

	v.text = "An Example String: The quick red fox jumps over the lazy brown dog."
	
	var sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))
	print("Size:", sz)
	
	sz = v.systemLayoutSizeFitting(.init(width: sz.width * 0.5, height: 1.0))
	print("Size:", sz)

	// prints "Size: (497.0, 19.333333333333332)"
	// prints "Size: (244.66666666666666, 57.333333333333336)"

Now, a ratio can be expressed as a decimal value with width / height.

For example, 5:3 is 5.0 / 3.0 = 1.666...

With the above two resulting sizes, we get:

Size: (497.0, 19.333333333333332)
Ratio: 497.0 / 19.333 = 25.70689655172414
AKA:   5.0 : 0.195

Size: (244.66666666666666, 57.333333333333336)
Ratio: 244.666 / 57.333 = 4.267441860465116
AKA:   5.0 : 1.172

If we want to find the "optimal" dimensions for a given aspect ratio, we can loop through, dividing the width in half each time, and compare the result:

	// we want a 5:3 result
	let targetWidth: CGFloat = 5.0
	let targetHeight: CGFloat = 3.0
	let targetRatio: CGFloat = targetWidth / targetHeight
	
	print("Target Ratio:", targetRatio)
	print("AKA:         ", String(format: "%0.1f : %0.3f", targetWidth, targetHeight))

	var sz = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: 1.0))

	while (sz.width / sz.height) > targetRatio {

		print()
		print("New Size:", sz)
		print("New Ratio:", sz.width / sz.height)
		print("AKA:      ", String(format: "%0.1f : %0.3f", targetWidth, sz.height / (sz.width / targetWidth)))

		// cut the Width in half
		// get the new Size contstrained to Width
		sz = v.systemLayoutSizeFitting(.init(width: sz.width * 0.5, height: .greatestFiniteMagnitude))
		
	}

	print()
	print("We now have a smaller ratio than targetRatio")
	print("New Size:", sz)
	print("New Ratio:", sz.width / sz.height)
	print("AKA:      ", String(format: "%0.1f : %0.3f", targetWidth, sz.height / (sz.width / targetWidth)))

and the console output will be:

Target Ratio: 1.6666666666666667
AKA:          5.0 : 3.000

New Size: (497.0, 19.333333333333332)
New Ratio: 25.70689655172414
AKA:       5.0 : 0.195

New Size: (244.66666666666666, 57.333333333333336)
New Ratio: 4.267441860465116
AKA:       5.0 : 1.172

We now have a smaller ratio than targetRatio
New Size: (115.0, 95.66666666666667)
New Ratio: 1.2020905923344947
AKA:       5.0 : 4.159

The visualization of that looks like this:

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

The final ratio of 5.0 : 4.159 isn't very close to 5 : 3 though, and there's a big gap between that and the previous 5.0 : 1.172 ratio... and we can get much closer with this size:

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

We could simply "brute force" it, adding another loop increasing the width by 1.0 each time, until we get closer to the target ratio. But, that would be inefficient... and potentially a lot of loops.

So one approach would be to evaluate the size/ratio changes using effectively a binary search pattern, increasing or decreasing the width by one-half of the previous gap, until we find our "optimal" size.

Here's an example function:

func sizeForOptimalDimension(label: UILabel, targetWidth: CGFloat, targetHeight: CGFloat) -> (CGSize, CGSize)? {
	
	guard let str = label.text, !str.isEmpty else { return nil }
	
	let targetRatio: CGFloat = targetWidth / targetHeight
	
	var curWidth: CGFloat = 0
	var curSize: CGSize = .zero
	var lastSize: CGSize = .zero
	var prevDiff: CGFloat = 0
	var wInc: CGFloat = 0
	
	// create a new label for calculations
	let v = UILabel()
	v.numberOfLines = 0
	v.font = label.font
	
	// set the label's text
	v.text = str
	
	// get the widest size (single-line height)
	curSize = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: font.lineHeight + 1.0))
	
	curWidth = curSize.width
	
	// if currentRatio is less than targetRatio to begin with,
	// for example, a string with embedded newlines
	// such as: ""One\nTwo\nThree"
	//	-------
	//	One
	//	Two
	//	Three
	//	Four
	//	Five
	//	-------
	// we can't do anything to it (and we'd get an infinite loop)
	if (curSize.width / curSize.height) < targetRatio {
		lastSize = curSize
	} else {
		
		prevDiff = curWidth
		
		// while the calculated Ratio is Greater-Than the targetRatio
		while (curSize.width / curSize.height) > targetRatio {
			
			// cut the Width in half
			curWidth *= 0.5
			// get the new Size contstrained to Width
			curSize = v.systemLayoutSizeFitting(.init(width: curWidth, height: .greatestFiniteMagnitude))
			
			prevDiff -= curWidth
			
		}
		
		// we now have a smaller ratio than targetRatio
		lastSize = curSize
		
		curWidth = curSize.width
		
		// we'll start incrementing by one-half of the last difference
		wInc = floor(prevDiff * 0.5)
		
		// this should always be true
		//	but sanity check to avoid an infinite loop
		if (curSize.width / curSize.height) < targetRatio {
			
			// while the calculated Ratio is Less-Than the targetRatio
			while (curSize.width / curSize.height) < targetRatio {
				
				// save the current size
				lastSize = curSize
				
				// increment Width
				curWidth += wInc
				
				// get the new Size contstrained to Width
				curSize = v.systemLayoutSizeFitting(.init(width: curWidth, height: .greatestFiniteMagnitude))
				
				// if the new ratio is again Greater than the targetRatio
				//	AND the increment is Greater than 1
				if (curSize.width / curSize.height) > targetRatio && wInc > 1.0 {
					//	reset the current width
					//	reset the current size
					//	cut the increment in half
					curWidth -= wInc
					curSize = lastSize
					wInc = floor(wInc * 0.5)
				}
				
			}
			
		}
		
	}
	
	return (lastSize, curSize)
	
}

You'll notice that function returns two sizes -- the closest size smaller and the closest size larger than the target ratio. This will be most noticeable with shorter strings. For example:

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

Neither resulting ratio -- 5.0:5.059 or 5.0:1.917 -- is particularly close to 5.0:3.0. So, depending on your overall layout goal, you could choose "closest to target ratio" or "prefer wider" for example.

Here is a complete view controller class to try out:

class ViewController: UIViewController {
// target ratio
let targetWidth: CGFloat = 5.0
let targetHeight: CGFloat = 3.0
let font: UIFont = .systemFont(ofSize: 16.0, weight: .regular)
let labelA: UILabel = UILabel()
let labelB: UILabel = UILabel()
let labelC: UILabel = UILabel()
var labelAWidth: NSLayoutConstraint!
var labelBWidth: NSLayoutConstraint!
var labelCWidth: NSLayoutConstraint!
let infoA: UILabel = UILabel()
let infoB: UILabel = UILabel()
let infoC: UILabel = UILabel()
var samples: [String] = [
"An Example String: The quick red fox jumps over the lazy brown dog.",
"Short string to use in testing.",
"UILabel: A label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label.",
"UIButton: Displays a plain styled button that can have a title, subtitle, image, and other appearance properties.",
"UIViewController: Provides view-management functionality for toolbars, navigation bars, and application views. The UIViewController class also supports modal views and rotating views when device orientation changes.",
"Linefeeds:\n\nNote that this also works with strings that contain embedded linefeed characters.",
// this will break UICollectionViewFlowLayoutInvalidationContext onto multiple lines
"But, if we have a very long word, such as UICollectionViewFlowLayoutInvalidationContext, word-wrapping is an issue.",
// this will be tall and very narrow, so we can't do anything with it
"And\nwe\ncannot\ndo\nanything\nwith\nthis.",
]
override func viewDidLoad() {
super.viewDidLoad()
[infoA, infoB, infoC].forEach { v in
v.numberOfLines = 0
v.textAlignment = .center
v.font = .systemFont(ofSize: 14.0, weight: .light)
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
[labelA, labelB, labelC].forEach { v in
v.numberOfLines = 0
v.font = font
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
labelA.backgroundColor = .cyan
labelB.backgroundColor = .green
labelC.backgroundColor = .yellow
// these will be set later
labelAWidth = labelA.widthAnchor.constraint(equalToConstant: 100.0)
labelBWidth = labelB.widthAnchor.constraint(equalToConstant: 100.0)
labelCWidth = labelC.widthAnchor.constraint(equalToConstant: 100.0)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
infoA.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
infoA.centerXAnchor.constraint(equalTo: g.centerXAnchor),
labelA.topAnchor.constraint(equalTo: infoA.bottomAnchor, constant: 2.0),
labelA.centerXAnchor.constraint(equalTo: g.centerXAnchor),
infoB.topAnchor.constraint(equalTo: labelA.bottomAnchor, constant: 16.0),
infoB.centerXAnchor.constraint(equalTo: g.centerXAnchor),
labelB.topAnchor.constraint(equalTo: infoB.bottomAnchor, constant: 2.0),
labelB.centerXAnchor.constraint(equalTo: g.centerXAnchor),
infoC.topAnchor.constraint(equalTo: labelB.bottomAnchor, constant: 16.0),
infoC.centerXAnchor.constraint(equalTo: g.centerXAnchor),
labelC.topAnchor.constraint(equalTo: infoC.bottomAnchor, constant: 2.0),
labelC.centerXAnchor.constraint(equalTo: g.centerXAnchor),
labelAWidth, labelBWidth, labelCWidth,
])
nextString()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
nextString()
}
func nextString() {
// cycle to the next sample string
let str = samples.removeFirst()
samples.append(str)
[labelA, labelB, labelC].forEach { v in
v.text = str
}
updateLabels()
}
func updateLabels() {
var lastSize: CGSize = .zero
var curSize: CGSize = .zero
(lastSize, curSize) = sizeForOptimalDimension(label: labelA, targetWidth: targetWidth, targetHeight: targetHeight) ?? (.zero, .zero)
// get the ratios of the last smaller and last larger calculated sizes
let lastRatio: CGFloat = lastSize.width / lastSize.height
let curRatio: CGFloat = curSize.width / curSize.height
let targetRatio: CGFloat = targetWidth / targetHeight
let lastDiff: CGFloat = abs(targetRatio - lastRatio)
let curDiff: CGFloat = abs(targetRatio - curRatio)
// which new ratio is closest to targetRatio?
let finalSize: CGSize = curDiff < lastDiff ? curSize : lastSize
var w: CGFloat = 0
var h: CGFloat = 0
var r: CGFloat = 0
w = lastSize.width
h = lastSize.height
r = h / (w / targetWidth)
let lastR: String = String(format: "%0.1f:%0.3f", targetWidth, r)
w = curSize.width
h = curSize.height
r = h / (w / targetWidth)
let curR: String = String(format: "%0.1f:%0.3f", targetWidth, r)
w = finalSize.width
h = finalSize.height
r = h / (w / targetWidth)
let finalR: String = String(format: "%0.1f:%0.3f", targetWidth, r)
// update the width constraints
labelAWidth.constant = lastSize.width
labelBWidth.constant = curSize.width
labelCWidth.constant = finalSize.width
let infoStrings: [String] = [
String(format: "%0.3f x %0.3f = Ratio \(lastR)", lastSize.width, lastSize.height),
String(format: "%0.3f x %0.3f = Ratio \(curR)", curSize.width, curSize.height),
String(format: "Closest to %0.1f x %0.1f\n%0.3f x %0.3f = Ratio \(finalR)", targetWidth, targetHeight, curSize.width, curSize.height),
]
for (s, v) in zip(infoStrings, [infoA, infoB, infoC]) {
v.text = s
}
}
func sizeForOptimalDimension(label: UILabel, targetWidth: CGFloat, targetHeight: CGFloat) -> (CGSize, CGSize)? {
guard let str = label.text, !str.isEmpty else { return nil }
let targetRatio: CGFloat = targetWidth / targetHeight
var curWidth: CGFloat = 0
var curSize: CGSize = .zero
var lastSize: CGSize = .zero
var prevDiff: CGFloat = 0
var wInc: CGFloat = 0
// create a new label for calculations
let v = UILabel()
v.numberOfLines = 0
v.font = label.font
// set the label's text
v.text = str
// get the widest size (single-line height)
curSize = v.systemLayoutSizeFitting(.init(width: .greatestFiniteMagnitude, height: font.lineHeight + 1.0))
curWidth = curSize.width
// if currentRatio is less than targetRatio to begin with,
// for example, a string with embedded newlines
// such as: ""One\nTwo\nThree"
//	-------
//	One
//	Two
//	Three
//	Four
//	Five
//	-------
// we can't do anything to it (and we'd get an infinite loop)
if (curSize.width / curSize.height) < targetRatio {
lastSize = curSize
} else {
prevDiff = curWidth
// while the calculated Ratio is Greater-Than the targetRatio
while (curSize.width / curSize.height) > targetRatio {
// cut the Width in half
curWidth *= 0.5
// get the new Size contstrained to Width
curSize = v.systemLayoutSizeFitting(.init(width: curWidth, height: .greatestFiniteMagnitude))
prevDiff -= curWidth
}
// we now have a smaller ratio than targetRatio
lastSize = curSize
curWidth = curSize.width
// we'll start incrementing by one-half of the last difference
wInc = floor(prevDiff * 0.5)
// this should always be true
//	but sanity check to avoid an infinite loop
if (curSize.width / curSize.height) < targetRatio {
// while the calculated Ratio is Less-Than the targetRatio
while (curSize.width / curSize.height) < targetRatio {
// save the current size
lastSize = curSize
// increment Width
curWidth += wInc
// get the new Size contstrained to Width
curSize = v.systemLayoutSizeFitting(.init(width: curWidth, height: .greatestFiniteMagnitude))
// if the new ratio is again Greater than the targetRatio
//	AND the increment is Greater than 1
if (curSize.width / curSize.height) > targetRatio && wInc > 1.0 {
//	reset the current width
//	reset the current size
//	cut the increment in half
curWidth -= wInc
curSize = lastSize
wInc = floor(wInc * 0.5)
}
}
}
}
return (lastSize, curSize)
}
}

Tapping anywhere will cycle through the sample strings array:

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

There will be some "edge cases" that can cause issues... One is when you have a very long word - such as UICollectionViewFlowLayoutInvalidationContext - that can result in quirky word-wrapping:

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

The other case is when the "native" layout cannot be modified to fit the target ratio - such as this string: "And\nwe\ncannot\ndo\nanything\nwith\nthis." which gives us this result:

如何使用自动布局(Autolayout)获得一个近似正方形的多行UILabel。

This is just one possible approach - but maybe it can help you out. Please keep in mind: this is EXAMPLE CODE ONLY!!!. I didn't spend much time on it... the "fit algorithm" could likely be improved, and I'm sure there are some edge cases that would need some additional error handling.

答案2

得分: 0

以下是您的代码的翻译:

对于任何感兴趣的人,这是我的当前解决方案:

let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "...您的文本..."
label.textAlignment = .left
label.numberOfLines = 0
label.adjustsFontForContentSizeCategory = false
label.adjustsFontSizeToFitWidth = false
label.lineBreakMode = .byWordWrapping

// 要动态调整大小,标签必须安装在具有窗口属性的某个视图中
view.addSubview(label)
constrainToOptimalDimension(label: label)
label.removeFromSuperview()

private func constrainToOptimalDimension(label: UILabel) {
    // 标签现在有一个强制横向图片框架方向的宽度约束

    private func constrainToOptimalDimension(label: UILabel) {
        label.sizeToFit()
        let oneLineWidth = Int(label.bounds.size.width)

        var optimalWidth = oneLineWidth
        for i in 2..<10 {
            label.snp.remakeConstraints { make in
                make.width.equalTo(oneLineWidth/i) // .multipliedBy(0.66)
            }
            label.setNeedsLayout()
            label.layoutIfNeeded()
            // 我的目标 - 图片框宽度为5,高度为3
            if label.bounds.size.height * 5 > label.bounds.size.width * 3 {
                optimalWidth = oneLineWidth/(i - 1)
                break
            }
        }
        // 使用SnapKit,但您可以使用任何您喜欢的约束引擎
        label.snp.remakeConstraints { make in
            make.width.equalTo(optimalWidth) // .multipliedBy(0.66)
        }
        label.setNeedsLayout()
        return
    }
}

请注意,代码中的HTML实体(如&quot;&lt;&gt;)已经被正确翻译为相应的字符。

英文:

For anyone interested, here is my current solution:

let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = &quot;... your text...&quot;
label.textAlignment = .left
label.numberOfLines = 0
label.adjustsFontForContentSizeCategory = false
label.adjustsFontSizeToFitWidth = false
label.lineBreakMode = .byWordWrapping
// to dynamically resize, the label must be installed in some view that has a window property
view.addSubview(label)
constrainToOptimalDimension(label: label)
label.removeFromSuperview()   private func constrainToOptimalDimension(label: UILabel) {
// label now has a width constraint that forces a landscape picture frame orientation
private func constrainToOptimalDimension(label: UILabel) {
label.sizeToFit()
let oneLineWidth = Int(label.bounds.size.width)
var optimalWidth = oneLineWidth
for i in 2..&lt;10 {
label.snp.remakeConstraints { make in
make.width.equalTo(oneLineWidth/i) // .multipliedBy(0.66)
}
label.setNeedsLayout()
label.layoutIfNeeded()
// my goal - a picture frame width of 5 height 3
if label.bounds.size.height * 5 &gt; label.bounds.size.width * 3 {
optimalWidth = oneLineWidth/(i - 1)
break
}
}
// using SnapKit, but you can use any contraint engine you want
label.snp.remakeConstraints { make in
make.width.equalTo(optimalWidth) // .multipliedBy(0.66)
}
label.setNeedsLayout()
return
}

huangapple
  • 本文由 发表于 2023年6月12日 23:53:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76458325.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定