I finally found a way to trigger the TabView reload not from user tab button touches, with regular @Published properties.
Here is example based on an app with a settings panel allowing to change app skin, including the TabBar background color.
1) The Main struct embed the ContentView that embed the TabView
• SettingsModel embed the properties that share a) the skin values b) a flag to trigger the reload
• ContentView owns one param: skinChanged
@main
struct MyApp: App {
@ObservedObject private var settings = SettingsModel.shared
var body: some Scene {
WindowGroup {
ContentView(skinChanged: settings.skinChanged)
}
}
}
.
2) The ContentView struct embed the TabView
• init(skinChanged: Bool) allows to custom TabBar appearance that
will reload if settings.skinChanged changes of value
• init / if !skinChanged { } applies TabBar appearance only if needed
• body / if !self.settings.skinChanged { } allows to create a change in
the view alternating from TabView to a Rectangle (as a background)
struct ContentView: View {
@ObservedObject private var settings = SettingsModel.shared
init(skinChanged: Bool) {
if !skinChanged {
let tabBarAppearance = UITabBar.appearance()
tabBarAppearance.backgroundColor = UIColor(settings.skin.tabBarBg)
tabBarAppearance.unselectedItemTintColor = UIColor(settings.skin.tabBarUnselected)
}
}
var body: some View {
if !self.settings.skinChanged {
TabView(selection: $someStateProp) {
...
}
} else {
Rectangle()
.fill(settings.skin.tabBarBg)
}
}
}
.
3) The SettingModel struct publishes properties that trigger skin changes and skin values
• In order to create a change in ContentView(skinChanged:) with
settings.skinChanged, the value must pass from false to true then
back to false
• To let the UI applies each changes the 2 changes must be
parallelized with 2 synchronized tasks
class SettingsModel: ObservableObject {
static public let shared = SettingsModel()
...
@Published var skin = Skin(style: .modern)
@Published var skinId = 0 {
didSet {
...
self.skin = Skin(style: Skin.Style.allCases[self.skinId])
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.skinChanged = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.skinChanged = false
}
}
}
@Published var skinChanged = false
...
}