如何在SwiftUI中检测设备旋转并重新绘制视图组件?
当第一个出现时,我有一个@State变量初始化为UIScreen.main.bounds.width的值。但是,当设备方向改变时,该值不会改变。当用户更改设备方向时,我需要重新绘制所有组件。
Kor*_*tor 25
这是一个基于通知发布者的惯用 SwiftUI 实现:
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
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您需要知道旋转是否应该动画,则发布者的输出(上面未使用,因此_
作为块参数)还包含"UIDeviceOrientationRotateAnimatedUserInfoKey"
其userInfo
属性中的键。
如果有人也对初始设备方向感兴趣。我是这样做的:
设备.swift
import Combine
final class Device: ObservableObject {
@Published var isLandscape: Bool = false
}
Run Code Online (Sandbox Code Playgroud)
SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// created instance
let device = Device() // changed here
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// ...
// added the instance as environment object here
let contentView = ContentView().environment(\.managedObjectContext, context).environmentObject(device)
if let windowScene = scene as? UIWindowScene {
// read the initial device orientation here
device.isLandscape = (windowScene.interfaceOrientation.isLandscape == true)
// ...
}
}
// added this function to register when the device is rotated
func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation, traitCollection previousTraitCollection: UITraitCollection) {
device.isLandscape.toggle()
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
这是一个未使用SceneDelegate
(在新的 SwiftUI 生命周期中缺少)的解决方案。
它还使用interfaceOrientation
来自当前窗口场景而不是
UIDevice.current.orientation
(在应用程序启动时未设置)。
这是一个演示:
struct ContentView: View {
@State private var isPortrait = false
var body: some View {
Text("isPortrait: \(String(isPortrait))")
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
guard let scene = UIApplication.shared.windows.first?.windowScene else { return }
self.isPortrait = scene.interfaceOrientation.isPortrait
}
}
}
Run Code Online (Sandbox Code Playgroud)
也可以使用扩展来访问当前窗口场景:
extension UIApplication {
var currentScene: UIWindowScene? {
connectedScenes
.first { $0.activationState == .foregroundActive } as? UIWindowScene
}
}
Run Code Online (Sandbox Code Playgroud)
并像这样使用它:
guard let scene = UIApplication.shared.currentScene else { return }
Run Code Online (Sandbox Code Playgroud)
我认为通过添加可以轻松重新粉刷
@Environment(\.verticalSizeClass) var sizeClass
Run Code Online (Sandbox Code Playgroud)
查看结构。
我有这样的例子:
struct MainView: View {
@EnvironmentObject var model: HamburgerMenuModel
@Environment(\.verticalSizeClass) var sizeClass
var body: some View {
let tabBarHeight = UITabBarController().tabBar.frame.height
return ZStack {
HamburgerTabView()
HamburgerExtraView()
.padding(.bottom, tabBarHeight)
}
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,我需要重新计算 tabBarHeight 以在 Extra View 上应用正确的底部填充,并且添加此属性似乎可以正确触发重绘。
只需一行代码!
@dfd提供了两个不错的选择,我添加了第三个选择,这是我使用的一个。
就我而言,我继承了UIHostingController的子类,并在函数viewWillTransition中发布了自定义通知。
然后,在我的环境模型中,我侦听此类通知,然后可以在任何视图中使用。
struct ContentView: View {
@EnvironmentObject var model: Model
var body: some View {
Group {
if model.landscape {
Text("LANDSCAPE")
} else {
Text("PORTRAIT")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
在SceneDelegate.swift中:
struct ContentView: View {
@EnvironmentObject var model: Model
var body: some View {
Group {
if model.landscape {
Text("LANDSCAPE")
} else {
Text("PORTRAIT")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我的UIHostingController子类:
window.rootViewController = MyUIHostingController(rootView: ContentView().environmentObject(Model(isLandscape: windowScene.interfaceOrientation.isLandscape)))
Run Code Online (Sandbox Code Playgroud)
而我的模型:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
@kontiki提供了一种更简单的解决方案,无需通知或与UIKit集成。
在SceneDelegate.swift中:
func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation, traitCollection previousTraitCollection: UITraitCollection) {
model.environment.toggle()
}
Run Code Online (Sandbox Code Playgroud)
在Model.swift中:
func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation, traitCollection previousTraitCollection: UITraitCollection) {
model.environment.toggle()
}
Run Code Online (Sandbox Code Playgroud)
最终结果是,@EnvironmentObject model
每次环境更改时(无论是旋转还是大小更改),都将重绘依赖的视图。
我尝试了之前的一些答案,但遇到了一些问题。其中一种解决方案在 95% 的时间内都可以工作,但时不时就会搞乱布局。其他解决方案似乎与 SwiftUI 的工作方式不一致。所以我想出了自己的解决方案。您可能会注意到它结合了之前几个建议的功能。
// Device.swift
import Combine
import UIKit
final public class Device: ObservableObject {
@Published public var isLandscape: Bool = false
public init() {}
}
// SceneDelegate.swift
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var device = Device()
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
.environmentObject(device)
if let windowScene = scene as? UIWindowScene {
// standard template generated code
// Yada Yada Yada
let size = windowScene.screen.bounds.size
device.isLandscape = size.width > size.height
}
}
// more standard template generated code
// Yada Yada Yada
func windowScene(_ windowScene: UIWindowScene,
didUpdate previousCoordinateSpace: UICoordinateSpace,
interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation,
traitCollection previousTraitCollection: UITraitCollection) {
let size = windowScene.screen.bounds.size
device.isLandscape = size.width > size.height
}
// the rest of the file
// ContentView.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var device : Device
var body: some View {
VStack {
if self.device.isLandscape {
// Do something
} else {
// Do something else
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1273 次 |
最近记录: |