Swift中如何获取视图及标签实际高度以修复UIScrollView滚动问题?
解决UIScrollView无法滚到底部&Label实际高度获取问题
当前方案的问题分析
手动累加子视图高度的错误
直接遍历contentview.subviews累加frame.height,既没考虑子视图之间的间距,也没注意到Label的frame还没更新到实际文本高度(布局流程中viewDidLayoutSubviews调用时,Label可能还没完成自适应布局)。Label高度计算方法的错误
你写的getLableHeightRuntime里把宽度设为0,这会导致文本无限换行,计算出的高度完全不符合实际显示宽度下的高度;而且后续把计算出的高度重复加到原有contentHeight里,等于Label高度被计算了两次,直接导致scrollView.contentSize过大,出现滚动过度、按钮点击区域偏移的问题。
推荐解决方案:用自动布局让ScrollView自动计算ContentSize(官方推荐)
这是最可靠的方式,彻底避免手动计算高度的各种坑:
步骤1:给ScrollView和ContentView设置正确约束
override func viewDidLoad() { super.viewDidLoad() // 关闭自动转译约束(代码创建视图时需要) scrollView.translatesAutoresizingMaskIntoConstraints = false contentview.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ // ScrollView和父视图四边对齐 scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), scrollView.topAnchor.constraint(equalTo: view.topAnchor), scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), // ContentView和ScrollView的内容引导区四边对齐 contentview.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), contentview.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), contentview.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), contentview.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), // 确保ContentView宽度和ScrollView一致,避免横向滚动 contentview.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor) ]) }
步骤2:给Label和其他控件设置完整约束
关键要给Label打开多行支持,并设置完整的上下左右约束,让AutoLayout自动计算Label高度:
// 以lblLocations为例 lblLocations.translatesAutoresizingMaskIntoConstraints = false lblLocations.numberOfLines = 0 // 允许文本自动换行 lblLocations.lineBreakMode = .byWordWrapping // 可选,按单词换行 NSLayoutConstraint.activate([ // Label和ContentView的左右边距 lblLocations.leadingAnchor.constraint(equalTo: contentview.leadingAnchor, constant: 16), lblLocations.trailingAnchor.constraint(equalTo: contentview.trailingAnchor, constant: -16), // Label和上方视图的间距(比如ContentView顶部) lblLocations.topAnchor.constraint(equalTo: contentview.topAnchor, constant: 16), // 给下一个控件(比如lblTeachers)设置和当前Label的间距 lblTeachers.topAnchor.constraint(equalTo: lblLocations.bottomAnchor, constant: 12), lblTeachers.leadingAnchor.constraint(equalTo: contentview.leadingAnchor, constant: 16), lblTeachers.trailingAnchor.constraint(equalTo: contentview.trailingAnchor, constant: -16), // 给最后一个控件设置和ContentView底部的间距,确保ContentView高度能被正确计算 someButton.bottomAnchor.constraint(equalTo: contentview.bottomAnchor, constant: -16) ])
效果
当你更新Label文本、或者点击按钮改变视图时,AutoLayout会自动调整Label高度、ContentView高度,ScrollView的contentSize也会自动更新,完全不需要手动计算,滚动和点击都会正常工作。
兼容旧项目:手动计算Label高度的正确方式
如果必须手动计算,先修复高度计算方法,再正确计算ContentView高度:
修复Label高度计算方法
func getLabelActualHeight(for label: UILabel) -> CGFloat { guard let text = label.text, let font = label.font else { return 0 } // 使用Label的实际显示宽度(如果Label有内边距,需要减去左右内边距) let availableWidth = label.bounds.width - label.contentEdgeInsets.left - label.contentEdgeInsets.right let constraintRect = CGSize(width: availableWidth, height: .greatestFiniteMagnitude) let boundingBox = text.boundingRect( with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: font], context: nil ) // 加上Label的内边距 return ceil(boundingBox.height) + label.contentEdgeInsets.top + label.contentEdgeInsets.bottom }
正确更新ScrollView ContentSize
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() // 先让所有子视图完成布局 contentview.layoutIfNeeded() // 更新Label的实际高度 let locationsHeight = getLabelActualHeight(for: lblLocations) lblLocations.frame.size.height = locationsHeight let teachersHeight = getLabelActualHeight(for: lblTeachers) lblTeachers.frame.size.height = teachersHeight // 计算ContentView的总高度:取所有子视图的最大底部坐标(避免累加间距错误) let maxBottom = contentview.subviews.map { $0.frame.origin.y + $0.frame.height }.max() ?? 0 // 加上ContentView的内边距 let totalContentHeight = maxBottom + contentview.layoutMargins.top + contentview.layoutMargins.bottom scrollView.contentSize = CGSize(width: scrollView.frame.width, height: totalContentHeight) }
注意点
- 当点击按钮改变视图后,需要调用
contentview.layoutIfNeeded()触发布局更新,再重新计算contentSize。 - 不要重复累加Label高度,确保每个子视图的高度只计算一次。
内容的提问来源于stack exchange,提问作者Nienke
相关产品推荐
相关产品推荐

