这个的要求是到了某个页面,需要锁定用户通过手机重力旋转屏幕,只能通过点击“旋转到横屏”按钮或者“回退到竖屏”按钮来改变webview的方向,所以大致流程如下,要有两个桥接方法“rotateScreen”,"lockScreen"或者合成一个方法:"rotateAndLockScreen()";在退出该页面的时候,要“unlockRotate()”
存在三个不同层面的方向控制:
1. 手机硬件的实际方向
- 这是物理设备的真实朝向(重力感应器检测)
- 用户如何持握手机:竖直、左横、右横、倒置等
2. WebView的方向
- 这是原生容器(iOS/Android App)强制设置的显示方向
- 通过
rotateScreen方法控制 - iOS: UIInterfaceOrientationMask.landscape 或 .portrait
- Android: 类似的原生旋转API
3. Layout的方向(H5页面布局)
- 这是前端页面的布局状态,由 isLandscape 状态控制
- 决定渲染哪个组件: LandscapePage 还是竖屏页面
- 通过 setIsLandscape(true/false) 手动控制
问题的核心
理想状态 :三个方向应该协调工作
- 用户点击"切换横屏批改" → setIsLandscape(true) → 调用原生 rotateScreen('horizontal') → 强制WebView横屏显示
- 无论用户如何旋转手机,WebView都保持横屏,Layout也保持横屏布局
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if isOrientationLocked {
return lockedOrientation
}
return .all
}
override var shouldAutorotate: Bool {
return !isOrientationLocked
}
private func rotateAndLockOrientation(_ orientation: UIInterfaceOrientationMask) {
// 第一步:确保支持目标方向(临时解锁)
isOrientationLocked = false
log.info("rotateAndLockOrientation isOrientationLocked: \(isOrientationLocked)")
log.info("rotateAndLockOrientation: \(orientation)")
// 强制系统重新查询支持的方向
UIViewController.attemptRotationToDeviceOrientation()
// 添加短暂延迟,确保系统有时间处理方向变化
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// 第二步:旋转到目标方向
if #available(iOS 16.0, *) {
log.info("requestGeometryUpdate11")
guard let window = self.view.window else {
log.error("view.window is nil")
return
}
guard let windowScene = window.windowScene else {
log.error("windowScene is nil")
return
}
log.info("window and windowScene are available")
var callbackExecuted = false
let preferences = UIWindowScene.GeometryPreferences.iOS(interfaceOrientations: orientation)
// 修正:使用 Task 来处理异步调用
Task { @MainActor in
do {
try await windowScene.requestGeometryUpdate(preferences)
callbackExecuted = true
log.info("requestGeometryUpdate 成功完成")
log.info("旋转屏幕成功")
// 延迟锁定方向
try await Task.sleep(nanoseconds: 100_000_000) // 0.1秒
self.isOrientationLocked = true
self.lockedOrientation = orientation
log.info("屏幕方向已锁定")
UIViewController.attemptRotationToDeviceOrientation() // 添加这行
} catch {
callbackExecuted = true
log.error("requestGeometryUpdate 出错: \(error)")
// 即使出错也锁定方向
self.isOrientationLocked = true
self.lockedOrientation = orientation
log.info("屏幕方向已锁定(出错后)")
}
}
// 添加超时机制
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
if !callbackExecuted {
log.warning("requestGeometryUpdate 回调超时,强制执行锁定逻辑")
self.isOrientationLocked = true
self.lockedOrientation = orientation
log.info("屏幕方向已强制锁定")
}
}
} else {
log.info("requestGeometryUpdate22")
// iOS 15 及以下版本的处理方式
if let singleOrientation = orientation.toSingleOrientation() {
UIDevice.current.setValue(singleOrientation.rawValue, forKey: "orientation")
}
UIViewController.attemptRotationToDeviceOrientation()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.isOrientationLocked = true
self.lockedOrientation = orientation
log.info("屏幕方向已锁定")
UIViewController.attemptRotationToDeviceOrientation() // 添加这行
}
}
}
}
bridge.registerHandler("lockOrientation") {[weak self] data, callback in
guard let orientationString = data as? String else {
log.error("webviewBridge call lockOrientation with invalid data")
callback?(nil)
return
}
log.info("webviewBridge call lockOrientation: \(orientationString)")
let orientation: UIInterfaceOrientationMask
if orientationString == "horizontal" {
orientation = .landscape
} else if orientationString == "vertical" {
orientation = .portrait
} else {
log.error("webviewBridge call lockOrientation with invalid orientation")
callback?(nil)
return
}
DispatchQueue.main.async {
self?.rotateAndLockOrientation(orientation)
}
callback?(nil)
}
bridge.registerHandler("unlockOrientation") {[weak self] data, callback in
log.info("webviewBridge call unlockOrientation")
DispatchQueue.main.async {
self?.unlockOrientation()
}
callback?(nil)
}