Is it possible to add a view modifier inside .onChange
?
Simplified example:
content
.onChange(of: publishedValue) {
content.foregroundColor(.red)
}
I have a theme that when changed needs to change the status bar color. I have a view modifier created for that ( https://barstool.engineering/set-the-ios-status-bar-style-in-swiftui-using-a-custom-view-modifier/ ). The modifier works fine, but I need to update it as the publishedValue
changes.
Actual minimal example:
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: TestViewModel
var body: some View {
ZStack {
Rectangle().foregroundColor(.mint)
VStack(alignment: .center, spacing: 25) {
Text("Test text \(viewModel.publishedValue)")
.onChange(of: viewModel.publishedValue) { newValue in
// Change status bar color
if viewModel.publishedValue % 2 == 0 {
self.body.statusBarStyle(.lightContent)
} else {
self.body.statusBarStyle(.darkContent)
}
}
Button("Increment") {
viewModel.publishedValue = 1
}
}
}
.ignoresSafeArea()
.statusBarStyle(.lightContent)
}
}
class TestViewModel: ObservableObject {
@Published var publishedValue: Int
init(publishedValue: Int) {
self.publishedValue = publishedValue
}
}
extension View {
/// Overrides the default status bar style with the given `UIStatusBarStyle`.
///
/// - Parameters:
/// - style: The `UIStatusBarStyle` to be used.
func statusBarStyle(_ style: UIStatusBarStyle) -> some View {
return self.background(HostingWindowFinder(callback: { window in
guard let rootViewController = window?.rootViewController else { return }
let hostingController = HostingViewController(rootViewController: rootViewController, style: style)
window?.rootViewController = hostingController
}))
}
}
fileprivate class HostingViewController: UIViewController {
private var rootViewController: UIViewController?
private var style: UIStatusBarStyle = .default
init(rootViewController: UIViewController, style: UIStatusBarStyle) {
self.rootViewController = rootViewController
self.style = style
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let child = rootViewController else { return }
addChild(child)
view.addSubview(child.view)
child.didMove(toParent: self)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return style
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
setNeedsStatusBarAppearanceUpdate()
}
}
fileprivate struct HostingWindowFinder: UIViewRepresentable {
var callback: (UIWindow?) -> ()
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async { [weak view] in
self.callback(view?.window)
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
// ...
}
}
GitHub repo for the example project: https://github.com/Iikeli/view-modifier-test
CodePudding user response:
You are way overcomplicating this. Since your viewModel
is @ObservedObject
and the publishedValue
is Published
, the body of your View
will be recalculated automatically every time publishedValue
is updated. There's no need for a manual onChange
.
You can simply move the logic into the input argument of statusBarStyle
.
var body: some View {
ZStack {
Rectangle().foregroundColor(.mint)
VStack(alignment: .center, spacing: 25) {
Text("Test text \(viewModel.publishedValue)")
Button("Increment") {
viewModel.publishedValue = 1
}
}
}
.ignoresSafeArea()
.statusBarStyle(viewModel.publishedValue % 2 == 0 ? .lightContent : .darkContent)
}
Or even better, move the logic into a separate computed property:
var body: some View {
....
.statusBarStyle(statusBarStyle)
}
private var statusBarStyle: UIStatusBarStyle {
viewModel.publishedValue % 2 == 0 ? .lightContent : .darkContent
}
CodePudding user response:
The short answer is no, but that doesn't mean you can't use it to have views change based on some .onChange(..)
action. For example.
@State var somethingChanged = false
Text(somethingChanged ? "First Value" : "Second Value")
// Your code/view
.onChange(..) {
//Some Condition or whatever you want.
somethingChanged = true
}
Your usage might look something like this.
content
.foregroundColor(somethingChanged ? .red : .blue)
.onChange(ofPublishedValue) {
somethingChanged = true
}