如何在Xcode 16中动态调整UITextView的字体大小?
嘿,这个需求我太懂了——医疗类APP既要保证合规文本完整展示,又要在不同尺寸的iPhone上看起来舒服,确实挺折腾的。从你提供的截图就能看出核心问题:
- iPhone SE3 上文本需要缩小才能避免滚动
- iPhone 16 Pro Max 上有大量留白,需要放大字号填充空间


我帮你梳理几个适配Xcode 16和最新Swift语法的实用方案,完美解决你的问题:
方案一:动态计算字号,让文本刚好适配TextView高度
这个思路是先设置基础字号,然后通过计算文本高度逐步调整(缩小或放大),直到文本刚好填满TextView的可用空间,同时限制最大/最小字号避免字体极端化。
首先,我们需要一个辅助方法来计算富文本在特定字号下的高度(保留你设置的不同字体样式,只修改字号):
func calculateTextHeight(for attributedText: NSAttributedString, in textView: UITextView, fontSize: CGFloat) -> CGFloat { // 复制原富文本,批量修改所有字体的字号 let mutableText = NSMutableAttributedString(attributedString: attributedText) mutableText.enumerateAttribute(.font, in: NSRange(location: 0, length: mutableText.length)) { value, range, _ in if let font = value as? UIFont { let scaledFont = font.withSize(fontSize) mutableText.addAttribute(.font, value: scaledFont, range: range) } } // 计算文本在TextView宽度下的高度 let availableWidth = textView.bounds.width - textView.textContainerInset.left - textView.textContainerInset.right let textRect = mutableText.boundingRect( with: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil ) return textRect.height }
然后在你的ViewController中,设置完富文本后添加这段调整逻辑:
// 你的原有代码:设置consentTextView属性和contentString... consentTextView.attributedText = contentString // 开始动态调整字号 let minFontSize: CGFloat = 12 // 最小字号,避免过小无法阅读 let maxFontSize: CGFloat = 18 // 最大字号,避免过大溢出 var targetFontSize: CGFloat = 15 // 初始基准字号 // 获取TextView的可用高度(去掉内边距) let availableHeight = consentTextView.bounds.height - consentTextView.textContainerInset.top - consentTextView.textContainerInset.bottom // 迭代调整字号,直到文本刚好适配 while true { let currentTextHeight = calculateTextHeight(for: contentString, in: consentTextView, fontSize: targetFontSize) if currentTextHeight <= availableHeight && targetFontSize < maxFontSize { // 文本能放下,尝试放大字号 targetFontSize += 0.5 } else if currentTextHeight > availableHeight && targetFontSize > minFontSize { // 文本放不下,缩小字号 targetFontSize -= 0.5 } else { break // 找到最优字号,退出循环 } } // 将最优字号应用到富文本 let finalAttributedText = NSMutableAttributedString(attributedString: contentString) finalAttributedText.enumerateAttribute(.font, in: NSRange(location: 0, length: finalAttributedText.length)) { value, range, _ in if let font = value as? UIFont { let scaledFont = font.withSize(targetFontSize) finalAttributedText.addAttribute(.font, value: scaledFont, range: range) } } consentTextView.attributedText = finalAttributedText consentTextView.isScrollEnabled = false // 文本已适配,关闭滚动
方案二:自定义UITextView子类,封装自动缩放逻辑
如果你的APP多个页面需要这个功能,自定义子类会更便于复用,而且能自动响应视图布局变化(比如旋转屏幕):
class AutoScalingTextView: UITextView { // 可配置的字号范围 var minFontSize: CGFloat = 12 var maxFontSize: CGFloat = 18 override func layoutSubviews() { super.layoutSubviews() adjustFontSizeToFit() } private func adjustFontSizeToFit() { guard let attributedText = attributedText else { return } let availableHeight = bounds.height - textContainerInset.top - textContainerInset.bottom // 用二分法查找最优字号,比循环更高效 var low = minFontSize var high = maxFontSize var bestSize = minFontSize for _ in 0..<10 { // 迭代10次足够达到精确效果 let midSize = (low + high) / 2 let textHeight = calculateTextHeight(for: attributedText, fontSize: midSize) if textHeight <= availableHeight { bestSize = midSize low = midSize } else { high = midSize } } // 应用最优字号 applyFontSize(bestSize) // 极端情况下如果还是放不下,自动开启滚动 isScrollEnabled = calculateTextHeight(for: attributedText, fontSize: bestSize) > availableHeight } private func calculateTextHeight(for attributedText: NSAttributedString, fontSize: CGFloat) -> CGFloat { let mutableText = NSMutableAttributedString(attributedString: attributedText) mutableText.enumerateAttribute(.font, in: NSRange(location: 0, length: mutableText.length)) { value, range, _ in if let font = value as? UIFont { let scaledFont = font.withSize(fontSize) mutableText.addAttribute(.font, value: scaledFont, range: range) } } let availableWidth = bounds.width - textContainerInset.left - textContainerInset.right let textRect = mutableText.boundingRect( with: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil ) return textRect.height } private func applyFontSize(_ size: CGFloat) { guard let attributedText = attributedText else { return } let mutableText = NSMutableAttributedString(attributedString: attributedText) mutableText.enumerateAttribute(.font, in: NSRange(location: 0, length: mutableText.length)) { value, range, _ in if let font = value as? UIFont { let scaledFont = font.withSize(size) mutableText.addAttribute(.font, value: scaledFont, range: range) } } self.attributedText = mutableText } }
使用时只需将原有的UITextView替换为AutoScalingTextView,并设置字号范围:
let consentTextView = AutoScalingTextView() consentTextView.minFontSize = 12 consentTextView.maxFontSize = 18 // 其他原有设置保持不变...
关于UILabel vs UITextView的选择
你说得很对,UITextView更适合展示大段文本——尤其是医疗类场景,用户可能需要选中复制文本(即使你禁用了编辑,TextView默认支持选中)。UILabel虽然也能做自动缩放,但处理复杂富文本样式时,TextView的兼容性和扩展性更好;而且如果未来遇到极端小屏(比如旧款SE),TextView可以无缝开启滚动作为 fallback,体验更流畅。
最后提醒一句:方案一需要在viewDidLayoutSubviews中手动调用调整逻辑,因为视图的实际 bounds 要在布局完成后才会确定,避免初始计算错误;方案二已经在layoutSubviews中自动处理了这个问题。
备注:内容来源于stack exchange,提问作者Infantools

