You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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自动重置:

  1. 设置代理并初始化:
override func viewDidLoad() {
    super.viewDidLoad()
    navigationController?.delegate = self
    scrollView.delegate = self
    // ... 其他原有代码
}
  1. 实现代理方法:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 22:01:16