Reputation: 466
I am trying to present a view in landscape orientation from a portrait view using the below code
Button {
isLandscapeViewPresented.toggle()
} label: {
Text("Click for landscape view")
}.fullScreenCover(isPresented: $isLandscapeViewPresented) {
LandscapeOnlyView {
LandscapeView()
}
}
And in LandscapeOnlyView wrapper I have as below
struct LandscapeOnlyView<Content: View>: UIViewControllerRepresentable {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
func makeUIViewController(context: Context) -> some UIViewController {
return LandscapeHostingController(rootView: content)
}
}
For LandscapeHostingController which holds the rootview as below:
class LandscapeHostingController<Content: View>: UIHostingController<Content> {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
AppDelegate.orientationLock = .landscape
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
scene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight), errorHandler: { error in
print(error)
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
AppDelegate.orientationLock = .portrait
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
scene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait), errorHandler: { error in
print(error)
})
}
}
I am using AppDelegate, as you can see where I update the orientationLock, with supportedInterfaceOrientations override returning the updated orientation whenever I need to change orientations.
I am getting the below output.
I need the LandscapeView to be in landscape even before presenting unlike what is show above where the view switches to landscape as the orientation update is done in viewWillAppear of the LandscapeHostingController wrapper for a swift ui view.
I have tried many ways. Is there something wrong in my approach which should be changed.
Any ideas on how that it be achieved?
Upvotes: 1
Views: 59
Reputation: 370
This is difficult to do in general. SwiftUI doesn't directly respect the supportedInterfaceOrientations
of UIViewControllerRepresentable
view controllers, but you can (kind of) fool it into doing so by presenting another full-screen UIViewController
from your representable controller. You'll have to remove some of your requestGeometryUpdate
calls as well. Something like this:
class LockOrientationContainer: UIViewController {
private let vcToPresent: UIViewController
init(vcToPresent: UIViewController) {
self.vcToPresent = vcToPresent
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
if !vcToPresent.isBeingPresented {
vcToPresent.modalPresentationStyle = .fullScreen
present(vcToPresent, animated: false)
}
}
}
class LandscapeHostingController<Content: View>: UIHostingController<Content> {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
}
struct LandscapeOnlyView<Content: View>: UIViewControllerRepresentable {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
func makeUIViewController(context: Context) -> some UIViewController {
// The `supportedInterfaceOrientations` of the view controller returned from this function will not be respected,
// but if that view controller presents another full-screen view controller with its own `supportedInterfaceOrientations`,
// those will be respected
let vc = LockOrientationContainer(vcToPresent: LandscapeHostingController(rootView: content))
return vc
}
}
This will have a small delay before presenting the content, but it will at least get rid of the unwanted rotation animation.
Upvotes: 0