iOS折叠导航栏修改UI元素后意外恢复大标题问题求助
问题:ScrollView滚动折叠导航栏后,修改UI元素导致导航栏恢复大标题
我做了个Demo:滚动ScrollView时导航栏标题能正常折叠,但点击底部按钮修改图标(或者修改其他UI元素的可见性,比如设置button.isHidden = true)时,导航栏会意外变回大标题,这不是预期行为。哪怕在主线程执行UI修改,问题依然存在。
复现代码
import UIKit class ViewController: UIViewController { var buttonActivated = false let scrollView: UIScrollView = { let view = UIScrollView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .clear return view }() let cardView1 : UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .red view.layer.cornerRadius = 12.5 view.layer.shadowOffset = CGSize(width: 0, height: 5) view.layer.shadowRadius = 5 view.layer.shadowOpacity = 0.3 return view }() let cardView2 : UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .red view.layer.cornerRadius = 12.5 view.layer.shadowOffset = CGSize(width: 0, height: 5) view.layer.shadowRadius = 5 view.layer.shadowOpacity = 0.3 return view }() let cardView3 : UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .red view.layer.cornerRadius = 12.5 view.layer.shadowOffset = CGSize(width: 0, height: 5) view.layer.shadowRadius = 5 view.layer.shadowOpacity = 0.3 return view }() let cardView4 : UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .red view.layer.cornerRadius = 12.5 view.layer.shadowOffset = CGSize(width: 0, height: 5) view.layer.shadowRadius = 5 view.layer.shadowOpacity = 0.3 return view }() let button: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("Test Button ", for: .normal) button.setTitleColor(UIColor.label, for: .normal) button.setImage(UIImage(systemName: "chevron.down"), for: .normal) button.tintColor = UIColor.label button.titleLabel?.font = .systemFont(ofSize: 12, weight: .regular) button.contentHorizontalAlignment = .center button.semanticContentAttribute = .forceRightToLeft button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) return button }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(scrollView) scrollView.addSubview(cardView1) scrollView.addSubview(cardView2) scrollView.addSubview(cardView3) scrollView.addSubview(cardView4) scrollView.addSubview(button) NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: view.topAnchor), scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), cardView1.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 20), cardView1.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20), cardView1.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20), cardView1.heightAnchor.constraint(equalToConstant: 300), cardView2.topAnchor.constraint(equalTo: cardView1.bottomAnchor, constant: 20), cardView2.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20), cardView2.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20), cardView2.heightAnchor.constraint(equalToConstant: 300), cardView3.topAnchor.constraint(equalTo: cardView2.bottomAnchor, constant: 20), cardView3.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20), cardView3.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20), cardView3.heightAnchor.constraint(equalToConstant: 300), cardView4.topAnchor.constraint(equalTo: cardView3.bottomAnchor, constant: 20), cardView4.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20), cardView4.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20), cardView4.heightAnchor.constraint(equalToConstant: 300), button.topAnchor.constraint(equalTo: cardView4.bottomAnchor, constant: 20), button.leadingAnchor.constraint(equalTo: scrollView.readableContentGuide.leadingAnchor, constant: 20), button.trailingAnchor.constraint(equalTo: scrollView.readableContentGuide.trailingAnchor, constant: -20), button.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -20), ]) } @objc private func buttonPressed() { if !buttonActivated { button.setImage(UIImage(systemName: "chevron.up"), for: .normal) } else { button.setImage(UIImage(systemName: "chevron.down"), for: .normal) } buttonActivated = !buttonActivated } }
问题演示

补充说明
即使在主线程执行UI修改,问题仍存在,示例代码:
DispatchQueue.main.async { self.button.setImage(UIImage(systemName: "chevron.down"), for: .normal) }
解决方案
这个问题是UIKit在部分UI更新操作时,会自动重置导航栏与ScrollView的滚动联动状态,以下是几种可行的解决方法:
方法1:手动保存并恢复导航栏状态
在修改UI前记录当前导航栏的折叠状态,修改完成后强制恢复:
@objc private func buttonPressed() { // 记录当前导航栏是否处于折叠状态(折叠后高度为44,大标题状态为96) let isCollapsed = navigationController?.navigationBar.frame.height == 44 // 执行UI修改 button.setImage(UIImage(systemName: buttonActivated ? "chevron.down" : "chevron.up"), for: .normal) buttonActivated.toggle() // 恢复折叠状态 if isCollapsed, let nav = navigationController { nav.navigationBar.sizeToFit() // 保持ScrollView当前偏移,避免导航栏跳动 scrollView.contentOffset = scrollView.contentOffset } }
方法2:手动控制导航栏折叠逻辑
通过UINavigationControllerDelegate和UIScrollViewDelegate接管导航栏的折叠控制,避免UIKit自动重置:
- 设置代理并初始化:
override func viewDidLoad() { super.viewDidLoad() navigationController?.delegate = self scrollView.delegate = self // ... 其他原有代码 }
- 实现代理方法:
extension ViewController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { // 启用大标题模式,由ScrollView滚动控制折叠 navigationController.navigationBar.prefersLargeTitles = true } } extension ViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // 根据滚动偏移量判断是否折叠导航栏(阈值可自行调整) let shouldCollapse = scrollView.contentOffset.y > 100 navigationController?.navigationBar.prefersLargeTitles = !shouldCollapse } }
方法3:优化UI更新方式(iOS 15+)
使用UIButton.Configuration修改按钮图标,减少视图层级的布局重绘触发:
// 初始化按钮时改用Configuration let button: UIButton = { var config = UIButton.Configuration.plain() config.title = "Test Button " config.titleColor = .label config.image = UIImage(systemName: "chevron.down") config.baseForegroundColor = .label config.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in var outgoing = incoming outgoing.font = .systemFont(ofSize: 12, weight: .regular) return outgoing } config.contentHorizontalAlignment = .center config.semanticContentAttribute = .forceRightToLeft let button = UIButton(configuration: config) button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) return button }() // 修改按钮图标时直接更新configuration @objc private func buttonPressed() { button.configuration?.image = UIImage(systemName: buttonActivated ? "chevron.down" : "chevron.up") buttonActivated.toggle() }
内容的提问来源于stack exchange,提问作者vaely
相关产品推荐
相关产品推荐

