UIProgressView从后台返回后首次丢失Frame问题求助
问题描述
我实现了一个类似页面控制器的控件,包含UIProgressView和3个点视图,每个页面对应一段视频,UIProgressView用于展示视频播放进度。由于需要让UIProgressView与点视图交换位置(例如当前视频结束、下一个视频开始时交换),我没有使用左右锚点约束,而是直接修改frame来实现位置切换。
当前遇到的问题:将APP切换到后台再返回后,UIProgressView丢失原有frame,其.frame.minX变为0,锚定到左侧,且仅首次从后台返回时会出现该问题。
已尝试的解决方案
- 后台切换前保存progressView的frame,返回前台时恢复并调用
setNeedsDisplay() - 后台切换前为
leftAnchor添加带constant值的约束,返回前台后移除该约束 - 结合上述两种方案
当前代码
func appWillMoveInBackground() { progressBarXConstraint = progressBar.leftAnchor.constraint(equalTo: self.leftAnchor, constant: progressBar.frame.minX) progressBarXConstraint?.isActive = true progressBarFrame = progressBar.frame } func updateProgressWidth() { progressBarXConstraint?.isActive = false // 这里用约束是因为宽高也会失效,只有约束能解决 progressBar.widthAnchor.constraint(equalToConstant: 32).isActive = true progressBar.heightAnchor.constraint(equalToConstant: 6).isActive = true progressBar.frame = progressBarFrame progressBar.setNeedsDisplay() }
补充说明
由于滚动时需要同步移动UIProgressView和对应点视图的位置(支持半页滚动,可随时返回初始位置),因此无法使用约束布局,相关代码如下:
func scrollViewDidScroll(_ scrollView: UIScrollView) { // 计算相关变量 // 调用修改frame的方法 pageControl.moveBetweenPages(atPercentValue: distInPercents - 100) } // moveBetweenPages()方法的部分代码 progressBar.frame = CGRect(x: progStartX + progAddDist, y: 0, width: SizeConstants.progressBarWidth.value, height: SizeConstants.height.value) let dotStartX: CGFloat = SizeConstants.progressBarWidth.value + SizeConstants.itemsSpacing.value + (CGFloat(currentPageNum) * dotSectionSize) dots[currentPageNum].view.frame = CGRect(x: dotStartX - dotAddDist, y: 0, width: SizeConstants.dotWidth.value, height: SizeConstants.height.value)
后台切换前后UI状态

解决方案
核心问题分析
iOS在APP进入后台时会自动对视图进行布局快照与约束重置,首次返回前台时,AutoLayout会优先应用已存在的约束规则(哪怕已标记为inactive),直接覆盖你手动修改的frame。此外,动态创建宽高约束会引发约束冲突,进一步导致布局异常。
具体修复步骤
提前配置基础约束,避免动态创建
在progressBar初始化阶段(如viewDidLoad)固定宽高约束并保持激活,同时设置一个可修改的左侧约束控制位置:// 初始化progressBar时调用 progressBar.translatesAutoresizingMaskIntoConstraints = false progressBarLeftConstraint = progressBar.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0) NSLayoutConstraint.activate([ progressBarLeftConstraint!, progressBar.widthAnchor.constraint(equalToConstant: 32), progressBar.heightAnchor.constraint(equalToConstant: 6), progressBar.topAnchor.constraint(equalTo: self.topAnchor), progressBar.bottomAnchor.constraint(equalTo: self.bottomAnchor) ])后台切换时保存位置,前台恢复时更新约束
统一通过约束控制位置,避免frame与约束混用:var progressBarLeftConstraint: NSLayoutConstraint? var savedLeftConstant: CGFloat = 0 func appWillMoveInBackground() { // 保存当前左侧约束的constant值 savedLeftConstant = progressBarLeftConstraint?.constant ?? progressBar.frame.minX } func appWillEnterForeground() { // 更新约束constant,让AutoLayout自动处理布局 progressBarLeftConstraint?.constant = savedLeftConstant self.layoutIfNeeded() }滚动时修改约束constant而非frame
将滚动时修改frame的逻辑替换为更新约束constant,确保布局逻辑统一:// 修改moveBetweenPages()中的代码 progressBarLeftConstraint?.constant = progStartX + progAddDist // 点视图若有同样问题,可采用相同约束控制方式,或确保其translatesAutoresizingMaskIntoConstraints保持默认的true
方案有效性说明
- 避免了frame与约束的冲突,让AutoLayout全程主导布局逻辑
- 固定宽高约束消除了动态创建约束引发的冲突问题
- 后台保存/前台恢复约束constant的方式,适配iOS的后台布局重置机制
内容的提问来源于stack exchange,提问作者Koder 228
相关产品推荐
相关产品推荐

