我有让用户更改
Font.Design
的逻辑,这对于 SwiftUI 视图以及导航栏(下面包含的逻辑)都适用。但它不会重新加载或重新应用到现有的 TabView
工具栏,需要重新加载。实时更新视图的最佳方式是什么?
@MainActor
private func configureTabBars(with fontDesign: Font.Design) {
UINavigationBar.appearance().largeTitleTextAttributes = [
.font : UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .caption1).withDesign(fontDesign.systemDesign)!, size: 0)
]
UITabBarItem.appearance().setTitleTextAttributes([
.font : UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .caption1).withDesign(fontDesign.systemDesign)!, size: 0)
], for: [])
}
private extension Font.Design {
var systemDesign: UIFontDescriptor.SystemDesign {
switch self {
case .serif: .serif
case .rounded: .rounded
case .monospaced: .monospaced
default: .default
}
}
}
与这个问题类似,
appearance()
不起作用的原因是它不会影响已经添加到窗口视图层次结构中的视图。
与链接的问题类似,这可以通过使用
UIViewControllerRepresentable
并从我们控制的视图控制器中找到 UINavigationController
和 UITabBarItem
来完成。
struct BarAppearances: UIViewControllerRepresentable {
let fontDesign: Font.Design
// instead of passing a Font.Design through the initialiser like above,
// also consider injecting it into the environment, so you can read it directly like
// @Environment(\.fontDesign) var fontDesign
class VC: UIViewController {
var fontDesign: Font.Design = .default
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateBarAppearances()
}
func updateBarAppearances() {
let font = UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .caption1).withDesign(fontDesign.systemDesign)!, size: 0)
// here I am using the new UIBarAppearance APIs, but the old 'largeTitleTextAttributes' should work too
let navAppearance = UINavigationBarAppearance()
navAppearance.configureWithOpaqueBackground()
navAppearance.largeTitleTextAttributes = [.font: font]
findImmediateChildOfTabVC()?.tabBarItem.setTitleTextAttributes([.font: font], for: [])
navigationController?.navigationBar.standardAppearance = navAppearance
navigationController?.navigationBar.scrollEdgeAppearance = navAppearance
navigationController?.navigationBar.compactAppearance = navAppearance
navigationController?.navigationBar.compactScrollEdgeAppearance = navAppearance
}
// the UITabBarItem we want to modify is the immediate child of the UITabBarController
// changing the tabBarItems of other VCs won't have an effect
func findImmediateChildOfTabVC() -> UIViewController? {
var vc: UIViewController? = self
while vc != nil && !(vc?.parent is UITabBarController) {
vc = vc?.parent
}
return vc
}
}
func makeUIViewController(context: Context) -> VC {
VC()
}
func updateUIViewController(_ uiViewController: VC, context: Context) {
uiViewController.fontDesign = fontDesign
uiViewController.updateBarAppearances()
}
}
要使用它,请将其作为根视图的
background
放在 NavigationStack
中:
struct ContentView: View {
@State var id = UUID()
@State var design = Font.Design.default
var body: some View {
TabView {
NavigationStack {
VStack {
Button("Set to monospace") {
design = .monospaced
}
Button("Set to rounded") {
design = .rounded
}
}
.navigationTitle("Some Nav Title")
.background { BarAppearances(fontDesign: design) }
}
.tabItem {
Text("Some Tab Title")
}
}
}
}
解决此问题的另一种方法是通过更改
id
的 TabView
来简单地重新创建整个视图层次结构。
这是一个非常简单的例子
@State var id = UUID()
var body: some View {
TabView {
NavigationStack {
VStack {
Button("Set to monospace") {
configureTabBars(with: .monospaced)
id = UUID()
}
Button("Set to rounded") {
configureTabBars(with: .rounded)
id = UUID()
}
}
.navigationTitle("Some Nav Title")
}
.tabItem {
Text("Some Tab Title")
}
}
.id(id)
}
这样做的问题是各个选项卡的所有
@State
都会丢失。不过,由于这改变了应用程序的主题,作为用户,我个人并不介意。