Hello all,
I would like to understand how to create a SwiftUI View like the official camera app. When the device orientation changes, the view is not animating, and the buttons just rotate (4:3, 1x...).
The camera app view is compose by flash and live buttons, camera preview, config buttons, and big button to shot the photo. In portrait, it is from top to bottom, in landscape, from left to right.
Also, when the last pictures view is shown, it is adapted to the current orientation, like if the camera preview view was rendered in the same device orientation.
Ideas?
Thanks!
Hello,
I implemented a solution that fits my requirements. If you are interested on it, here is the code: https://github.com/heltena/Orientation
Basically, I'm showing a sheet using UIKit, and controlling the supportedInterfaceOrientations
in the UIViewController that is shown.
For lazy people, here is the code:
//
// RestrictedInterfaceOrientationSheet.swift
// Orientation
//
// Created by Heliodoro Tejedor Navarro on 1/3/23.
//
import SwiftUI
public enum ModalTransitionStyle: Int, @unchecked Sendable {
case coverVertical = 0
case flipHorizontal = 1
case crossDissolve = 2
fileprivate var uiKitVersion: UIModalTransitionStyle {
return UIModalTransitionStyle(rawValue: self.rawValue)!
}
}
struct RestrictedInterfaceOrientationSheet<SheetContent: View>: UIViewControllerRepresentable {
@Binding var isPresented: Bool
var restrictInterfaceOrientationTo: UIInterfaceOrientationMask
var modalTransitionStyle: ModalTransitionStyle
var animated: Bool
var content: () -> SheetContent
func makeUIViewController(context: Context) -> InternalViewController {
InternalViewController()
}
func updateUIViewController(_ uiViewController: InternalViewController, context: Context) {
if !isPresented {
uiViewController.presentedViewController?.dismiss(animated: animated)
return
}
if uiViewController.presentedViewController == nil {
let sheetViewController = PortraitHostingViewController(restrictInterfaceOrientationTo: restrictInterfaceOrientationTo, rootView: content())
sheetViewController.modalPresentationStyle = .overFullScreen // don't use .fullscreen, so it will remove the parent from the view hierarchy!
sheetViewController.modalTransitionStyle = modalTransitionStyle.uiKitVersion
uiViewController.present(sheetViewController, animated: animated)
return
}
if let presentedViewController = uiViewController.presentedViewController as? UIHostingController<SheetContent> {
presentedViewController.rootView = content()
return
}
fatalError("Invalid parent when dismissing a presented view controller")
}
class InternalViewController: UIViewController {
override func loadView() {
super.loadView()
view.backgroundColor = .clear
}
}
class PortraitHostingViewController<Content: View>: UIHostingController<Content> {
var restrictInterfaceOrientationTo: UIInterfaceOrientationMask
init(restrictInterfaceOrientationTo: UIInterfaceOrientationMask, rootView: Content) {
self.restrictInterfaceOrientationTo = restrictInterfaceOrientationTo
super.init(rootView: rootView)
}
@MainActor required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
restrictInterfaceOrientationTo
}
}
}
struct RestrictedInterfaceOrientationSheetViewModifier<SheetContent: View>: ViewModifier {
@Binding var isPresented: Bool
var restrictInterfaceOrientationTo: UIInterfaceOrientationMask
var modalTransitionStyle: ModalTransitionStyle
var animated: Bool
@ViewBuilder var content: () -> SheetContent
func body(content: Content) -> some View {
content
.background {
RestrictedInterfaceOrientationSheet(
isPresented: $isPresented,
restrictInterfaceOrientationTo: restrictInterfaceOrientationTo,
modalTransitionStyle: modalTransitionStyle,
animated: animated,
content: self.content)
}
}
}
extension View {
public func sheet<SheetContent: View>(isPresented: Binding<Bool>, restrictInterfaceOrientationTo: UIInterfaceOrientationMask = .portrait, modalTransitionStyle: ModalTransitionStyle = .crossDissolve, animated: Bool = true, onDismiss: (() -> Void)?, @ViewBuilder content: @escaping () -> SheetContent) -> some View {
self.modifier(
RestrictedInterfaceOrientationSheetViewModifier(
isPresented: isPresented,
restrictInterfaceOrientationTo: restrictInterfaceOrientationTo,
modalTransitionStyle: modalTransitionStyle,
animated: animated,
content: content))
}
}
And how to use it:
struct ContentView: View {
@Binding var document: OrientationDocument
@State var showCamera = false
var body: some View {
Form {
Section {
Text(document.text)
} header: {
Text("Example")
}
}
.toolbar {
Button {
showCamera = true
} label: {
Label("Camera", systemImage: "camera")
}
}
.orientationLockModifier(mask: .portrait)
.sheet(isPresented: $showCamera, restrictInterfaceOrientationTo: .portrait, modalTransitionStyle: .crossDissolve, animated: false) {
print("bye bye")
} content: {
VStack(spacing: 20) {
Spacer()
Text("Show Camera Preview here")
Button {
showCamera = false
} label: {
Text("Close")
}
Spacer()
}
}
}
}