iOS 11中UIBarButtonItem调用setTitleTextAttributes无效,如何动态修改全局字体?
我之前也踩过iOS 11里UIBarButtonItem外观代理设置不生效的坑,结合你动态修改字体的需求,整理了一套可行的解决方案:
一、先搞定iOS 11外观代理的生效问题
iOS 11里直接在didFinishLaunchingWithOptions里设置外观代理,有时候会因为视图布局时机问题导致不生效。建议把外观设置代码移到applicationDidBecomeActive中,或者设置后强制刷新导航栏:
func applicationDidBecomeActive(_ application: UIApplication) { // 先从UserDefaults读取上次存储的字体,没有则用默认自定义字体 let defaultFont = UIFont(name: "YourDefaultCustomFont", size: 16)! let savedFontName = UserDefaults.standard.string(forKey: "SelectedBarButtonFont") ?? "YourDefaultCustomFont" let targetFont = UIFont(name: savedFontName, size: 16) ?? defaultFont UIBarButtonItem.appearance().setTitleTextAttributes([.font: targetFont], for: .normal) // 强制刷新所有导航栏的布局,确保字体生效 UINavigationBar.appearance().layoutIfNeeded() }
二、动态修改字体的完整流程
1. 存储用户选择的字体到UserDefaults
当用户选好字体后,把字体名称存起来(字体对象不能直接存,所以存名称最稳妥):
// 假设用户选择的字体名称是"UserSelectedFont" UserDefaults.standard.set("UserSelectedFont", forKey: "SelectedBarButtonFont") // iOS10+自动同步,手动调用更保险 UserDefaults.standard.synchronize()
2. 发送本地通知通知所有控制器更新
先定义一个专属的通知名称,方便所有需要的控制器监听:
extension Notification.Name { static let barButtonFontUpdated = Notification.Name("BarButtonFontUpdated") } // 存储字体后立刻发送通知 NotificationCenter.default.post(name: .barButtonFontUpdated, object: nil)
3. 让控制器监听通知并更新字体
建议写一个基类导航控制器,让所有需要的控制器继承它,统一处理字体更新:
class BaseNavController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() // 添加通知监听 NotificationCenter.default.addObserver(self, selector: #selector(updateBarButtonFonts), name: .barButtonFontUpdated, object: nil) // 初始化时先加载一次存储的字体 updateBarButtonFonts() } @objc private func updateBarButtonFonts() { let defaultFont = UIFont(name: "YourDefaultCustomFont", size: 16)! guard let savedFontName = UserDefaults.standard.string(forKey: "SelectedBarButtonFont"), let targetFont = UIFont(name: savedFontName, size: 16) else { // 没有存储的字体时用默认值 resetToDefaultFont(defaultFont) return } // 更新全局外观代理 UIBarButtonItem.appearance().setTitleTextAttributes([.font: targetFont], for: .normal) // 刷新当前导航栏的布局 navigationBar.layoutIfNeeded() // 遍历当前所有子控制器,直接更新它们的导航栏按钮(避免缓存不刷新) viewControllers.forEach { vc in vc.navigationItem.leftBarButtonItems?.forEach { $0.setTitleTextAttributes([.font: targetFont], for: .normal) } vc.navigationItem.rightBarButtonItems?.forEach { $0.setTitleTextAttributes([.font: targetFont], for: .normal) } } } private func resetToDefaultFont(_ font: UIFont) { UIBarButtonItem.appearance().setTitleTextAttributes([.font: font], for: .normal) navigationBar.layoutIfNeeded() viewControllers.forEach { vc in vc.navigationItem.leftBarButtonItems?.forEach { $0.setTitleTextAttributes([.font: font], for: .normal) } vc.navigationItem.rightBarButtonItems?.forEach { $0.setTitleTextAttributes([.font: font], for: .normal) } } } deinit { // 销毁时移除监听,防止内存泄漏 NotificationCenter.default.removeObserver(self) } }
4. 极端情况的兜底方案
如果外观代理还是不生效,可以在创建UIBarButtonItem时直接设置字体:
// 在控制器里创建按钮时 let targetFont = UIFont(name: "UserSelectedFont", size: 16)! let customBarButton = UIBarButtonItem(title: "操作", style: .plain, target: self, action: #selector(handleTap)) customBarButton.setTitleTextAttributes([.font: targetFont], for: .normal) navigationItem.rightBarButtonItem = customBarButton
三、关键注意事项
- iOS 11里
UIBarButtonItem的外观设置依赖视图布局时机,layoutIfNeeded()是强制刷新的关键 - 通知触发后,既要更新全局外观代理,也要直接遍历现有控制器的按钮设置属性,避免系统缓存导致的不刷新
- 一定要在控制器销毁时移除通知监听,防止内存泄漏
内容的提问来源于stack exchange,提问作者Ahmed Khedr
相关产品推荐
相关产品推荐

