SwiftUI:如何在纵向模式下锁定特定视图,同时允许其他人更改方向?

问题描述 投票:0回答:4

环境: 使用 Swift 5.3 的 SwiftUI
场景: 根据 Xcode 常规设置,默认方向为 PortraitLandscapeLeftLandscapeRight
这允许可能按需显示横向,而不是仅将 Xcode 设置为纵向。
该项目正在使用 SwiftUI Lifecycle 与 AppDelegate。

目标: 只有特定的视图能够旋转到横向;大多数锁定在肖像。

当前操作方式: 设备在当前视图的 .upAppear{} 中设置为仅纵向模式,并通过 onReceive{} 通过 设备方向更改通知

我发现这是真正进行瞬时肖像锁定的唯一方法,允许其他人渲染风景。

问题:方向更改通知发生得太晚了:我看到实际的景观正在实时修正 - 因此图像在旋转过程中会弹回。

问题: 如何在纵向模式下锁定特定的 swiftUI 视图,以允许其他人自由改变方向?

import SwiftUI
import UIKit

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.blue
            NavigationView {
                Text("Hello, world!")
                    .padding()
                    .navigationTitle("Turkey Gizzards")
                    .navigationBarTitleDisplayMode(.inline)
            }
        }.onAppear {
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
        }.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
        }
    }
}
swiftui orientation
4个回答
28
投票

没有原生 SwiftUI 方法可以做到这一点。目前看来,必须使用 AppDelegate 适配器。

在您的应用程序主目录中,添加以下内容:

@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

然后添加这个类:

class AppDelegate: NSObject, UIApplicationDelegate {
        
    static var orientationLock = UIInterfaceOrientationMask.all //By default you want all your views to rotate freely

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return AppDelegate.orientationLock
    }
}

在设计为锁定纵向模式的特定视图中:

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.blue
            NavigationView {
                Text("Hello, world!")
                    .padding()
                    .navigationTitle("Turkey Gizzards")
                    .navigationBarTitleDisplayMode(.inline)
            }
        }.onAppear {
            UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") // Forcing the rotation to portrait
            AppDelegate.orientationLock = .portrait // And making sure it stays that way
        }.onDisappear {
            AppDelegate.orientationLock = .all // Unlocking the rotation when leaving the view
        }
    }
}

您还可以根据您的需要,在 onDisappear 内添加另一个

UIDevice.current.setValue(UIInterfaceOrientation.yourOrientation.rawValue, forKey: "orientation")
,以在离开视图时强制旋转。


5
投票

您在使用

UIHostingController
吗?另一种解决方法可能是将其子类化以实现
supportedInterfaceOrientations
/
shouldAutorotate
,就像我们通常在 UIKit 中所做的那样:

class HostingController<Content>: UIHostingController<Content> where Content: View {}

3
投票

提供的解决方案在 iOS 16 上不起作用,我清理了 SwiftUI 实现。

我的解决方案旋转到正确的方向并锁定旋转。默认为纵向,并在特定视图上强制横向。这可以根据您的需要进行更改。


AppDelegate
中添加:

static var orientationLock = UIInterfaceOrientationMask.portrait {
    didSet {
        if #available(iOS 16.0, *) {
            UIApplication.shared.connectedScenes.forEach { scene in
                if let windowScene = scene as? UIWindowScene {
                    windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: orientationLock))
                }
            }
            UIViewController.attemptRotationToDeviceOrientation()
        } else {
            if orientationLock == .landscape {
                UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
            } else {
                UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
            }
        }
    }
}

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return AppDelegate.orientationLock
}

创建一个

View+Extensions
并添加以下代码:

extension View {
    @ViewBuilder
    func forceRotation(orientation: UIInterfaceOrientationMask) -> some View {
        self.onAppear() {
            AppDelegate.orientationLock = orientation
        }
        // Reset orientation to previous setting
        let currentOrientation = AppDelegate.orientationLock
        self.onDisappear() {
            AppDelegate.orientationLock = currentOrientation
        }
    }
}

然后在 SwiftUI 中你可以这样做:

struct DummyView: View {
    var body: some View {
        ZStack {
            Text("Dummy View")
        }.forceRotation(orientation: .landscape)
    }
}

0
投票

因此,在 SwiftUI 中,Apple 试图强制您仅使用 Info.plist 和 Xcode General Deployment Info 来强制视图方向。它们不支持将 SwiftUI 视图锁定到特定方向,这很荒谬,尤其是在您使用相机时。他们还不断弃用方向变量。因此,解决这些 AppHole 问题很复杂,但下面的代码可以工作。

在AppDelegate中有这个。

static var orientationLock = UIInterfaceOrientationMask.all 
static var orientationLock = UIInterfaceOrientationMask.all

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
     return AppDelegate.orientationLock
}

现在您需要一个 SwiftUI 视图扩展

extension View {
   func detectOrientation(_ binding: Binding<UIDeviceOrientation>) -> some View {
     self.modifier(OrientationDetector(orientation: binding))
  }
}

struct OrientationDetector: ViewModifier {
  @Binding var orientation: UIDeviceOrientation
  let orientationMask = AppDelegate.orientationLock
  func body(content: Content) -> some View {
    content
      .onReceive(NotificationCenter.default.publisher(for:     UIDevice.orientationDidChangeNotification)) { _ in

      if checkOrientation(orientationMask: orientationMask)
      {
          orientation = UIDevice.current.orientation
      }
      else
      {
          orientation = AppDelegate.deviceOrientation
      }
  }
 }
}

现在它变得棘手,因为 UIDevice.current.orientation 有 .faceUp 和 .faceDown 你不关心,所以你需要忽略这些视图方向,这实际上是 checkOrieration() 方法通过保存先前的方向来完成的事情确实进入 .faceUp 或 .faceDown。

func checkOrientation(orientationMask:UIInterfaceOrientationMask) -> Bool
{
//Here we're ignoring faceUp and faceDown and storing the prior orientation in AppDelegate.deviceOrientation
//This effectively eliminates these two problematic orientations so the App will ignore these and return the prior value
var status : Bool = false
switch UIDevice.current.orientation {
case .portrait :
    if(orientationMask == .portrait || orientationMask == .portraitUpsideDown || orientationMask == .allButUpsideDown)
    {
        status = true
        AppDelegate.deviceOrientation = UIDevice.current.orientation
    }
case .landscapeLeft :
    if orientationMask == .landscapeLeft || orientationMask == .allButUpsideDown
    {
        status = true
        AppDelegate.deviceOrientation = UIDevice.current.orientation
    }
case .landscapeRight :
    if orientationMask == .landscapeRight || orientationMask == .allButUpsideDown
    {
        status = true
        AppDelegate.deviceOrientation = UIDevice.current.orientation
    }
case .portraitUpsideDown :
    if orientationMask == .portraitUpsideDown || orientationMask == .allButUpsideDown
    {
        status = true
        AppDelegate.deviceOrientation = UIDevice.current.orientation
    }
case .faceUp :
    status = false
    
case .faceDown :
    status = false
    
case .unknown:
    status = false

@unknown default:
    status = false

}
return status
}

现在在您的主 SwiftUI 视图中,您需要一个 init 和 .detectOrination($orientation)

  @State private var orientation = UIDevice.current.orientation 


init() {

    //Lock into all view rotations
    AppDelegate.orientationLock = .allButUpsideDown
    if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
        windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .allButUpsideDown))
       UIApplication.navigationTopViewController()?.setNeedsUpdateOfSupportedInterfaceOrientations()
    }
}

 //At bottom of SwiftUI view add this :
 .detectOrientation($orientation)

上面对 SwiftUI 视图所做的就是让它旋转 .allButUpsideDown 视图旋转。如果你想锁定它,请设置.portrait,或.landscapeLeft或.landscapeRight。

主 SwiftUI 视图将声明 @State var 方向,并且所有需要方向的 SwiftUI 子视图都需要使用相同的变量。

因此 SwiftUI 子视图需要这个:

 @Binding private var orientation:UIDeviceOrientation

 init(orientation:Binding<UIDeviceOrientation>)
          self._orientation = orientation
 }

现在,在主视图和子视图中,您可以执行此操作来处理特定视图方向的布局。

if orientation.isLandscape {

  //SwiftUI elements for Landscape

} else {

  //SwiftUI elements for Portrait

} 

就是这样,需要很多代码来解决这个问题,但它可以在 SwiftUI 版本 5 和 iOS 16+ 中运行,Apple 也会批准它用于应用程序版本。

我们确实需要游说苹果支持将 SwiftUI 视图本地锁定到特定的视图方向,这可以通过调用一个简单的 SwiftUI 方法来完成。他们还需要为 UIDeviceOrientation 创建一个新的 var,忽略 .faceUp 和 .faceDown,以便您可以在 SWiftUI 视图中使用此 var 并相应地布局您的视图元素。

© www.soinside.com 2019 - 2024. All rights reserved.