SwiftUI Repaint View Components on Device Rotation
How to detect device rotation in SwiftUI and re-draw view components?
I have a @State variable initialized to the value of UIScreen.main.bounds.width when the first appears. But this value doesn't change when the device orientation changes. I need to redraw all components when the user changes the device orientation.
Here‘s an idiomatic SwiftUI implementation based on a notification publisher:
struct ContentView: View {
@State var orientation = UIDevice.current.orientation
let orientationChanged = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)
.makeConnectable()
.autoconnect()
var body: some View {
Group {
if orientation.isLandscape {
Text("LANDSCAPE")
} else {
Text("PORTRAIT")
}
}.onReceive(orientationChanged) { _ in
self.orientation = UIDevice.current.orientation
}
}
}
The output of the publisher (not used above, therefor _
as the block parameter) also contains the key "UIDeviceOrientationRotateAnimatedUserInfoKey"
in its userInfo
property if you need to know if the rotation should be animated.
@dfd provided two good options, I am adding a third one, which is the one I use.
In my case I subclass UIHostingController, and in function viewWillTransition, I post a custom notification.
Then, in my environment model I listen for such notification which can be then used in any view.
struct ContentView: View {
@EnvironmentObject var model: Model
var body: some View {
Group {
if model.landscape {
Text("LANDSCAPE")
} else {
Text("PORTRAIT")
}
}
}
}
In SceneDelegate.swift:
window.rootViewController = MyUIHostingController(rootView: ContentView().environmentObject(Model(isLandscape: windowScene.interfaceOrientation.isLandscape)))
My UIHostingController subclass:
extension Notification.Name {
static let my_onViewWillTransition = Notification.Name("MainUIHostingController_viewWillTransition")
}
class MyUIHostingController<Content> : UIHostingController<Content> where Content : View {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
NotificationCenter.default.post(name: .my_onViewWillTransition, object: nil, userInfo: ["size": size])
super.viewWillTransition(to: size, with: coordinator)
}
}
And my model:
class Model: ObservableObject {
@Published var landscape: Bool = false
init(isLandscape: Bool) {
self.landscape = isLandscape // Initial value
NotificationCenter.default.addObserver(self, selector: #selector(onViewWillTransition(notification:)), name: .my_onViewWillTransition, object: nil)
}
@objc func onViewWillTransition(notification: Notification) {
guard let size = notification.userInfo?["size"] as? CGSize else { return }
landscape = size.width > size.height
}
}