It appears Apple broke the following hack in iOS 16:
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
With the following console output:
[Orientation] BUG IN CLIENT OF UIKIT: Setting UIDevice.orientation is not supported. Please use UIWindowScene.requestGeometryUpdate(_:)
Based on some research, I'm using requestGeometryUpdate:
func toggleFullScreen() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if fullScreen.wrappedValue {
if #available(iOS 16.0, *) {
let windowScene = UIApplication.shared.keyWindow?.windowScene
AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) { error in print(error) }
UIApplication.shared.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
fullScreen.wrappedValue = false
} else {
// Fallback on earlier versions
AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
fullScreen.wrappedValue = false
}
} else {
if #available(iOS 16.0, *) {
let windowScene = UIApplication.shared.keyWindow?.windowScene
AppDelegate.orientationLock = UIInterfaceOrientationMask.landscape
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight)) { error in print(error) }
UIApplication.shared.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
fullScreen.wrappedValue = true
} else {
// Fallback on earlier versions
AppDelegate.orientationLock = UIInterfaceOrientationMask.landscape
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
fullScreen.wrappedValue = true
}
}
}
}
The rotation now works – however, my view is not actually taking account of the appropriate frame on rotation. In landscape, the view has black bars above and below – it seems the view doesn't quite get the new screen bounds somehow. Is anybody facing the same issue?
Related
My app has certain orientations that are set depending on device. For iPads it is set to landscape and portrait for iPhones. This works fine except I want to allow one view to be shown in landscape for iPhone. I need the view to automatically rotate to landscape and rotate back to portrait after leaving that view. I have the following code that locks the orientation for that view but the device has to be manually rotated back and forth. Is there a way to auto-rotate the device?
.onAppear {
if(deviceType == .phone){
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
AppDelegate.orientationLock = .landscape
}
}
.onDisappear {
if(deviceType == .phone){
AppDelegate.orientationLock = .portrait
}
}
This can be achieved using a View extension that will update the view's orientation. Let’s make our own extension which will rotate the view on given orientation, back and forth.
extension View {
#ViewBuilder
func forceRotation(orientation: UIInterfaceOrientationMask) -> some View {
if UIDevice.current.userInterfaceIdiom == .phone {
self.onAppear() {
AppDelegate.orientationLock = orientation
}
// Reset orientation to previous setting
let currentOrientation = AppDelegate.orientationLock
self.onDisappear() {
AppDelegate.orientationLock = currentOrientation
}
} else {
self
}
}
}
In AppDelegate, add:
class AppDelegate: NSObject, UIApplicationDelegate {
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, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return AppDelegate.orientationLock
}
}
This little example demonstrate how you can use this extension:
struct ContentView: View {
var body: some View {
NavigationView {
List(Orientation.allCases, id: \.self) { orientation in
NavigationLink(orientation.title) {
ZStack {
Text(orientation.title)
}
.forceRotation(orientation: orientation.mask) // << Update the required orientation here..
}
}
}
}
}
enum Orientation: Int CaseIterable {
case landscapeLeft
case landscapeRight
var title: String {
switch self {
case .landscapeLeft:
return "LandscapeLeft"
case .landscapeRight:
return "LandscapeRight"
}
}
var mask: UIInterfaceOrientationMask {
switch self {
case .landscapeLeft:
return .landscapeLeft
case .landscapeRight:
return .landscapeRight
}
}
}
I am trying to use flash when taking an image using AV Foundation in Swift UI. However, when I try to take a picture, I get the following error code.
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x28004e790 {Error Domain=NSOSStatusErrorDomain Code=-16800 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16800), AVErrorRecordingFailureDomainKey=4, NSLocalizedDescription=The operation could not be completed}
Below is the code that I am using for my camera that is generating this issue. I have gone through and commented some areas that I thought might be the source of the issue as I was trying to figure this out, but I may be wrong.
import SwiftUI
import AVFoundation
struct Camera: View {
var body: some View {
CameraView()
}
}
struct Camera_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Test code: Ignore
struct globalVariable {
public var isBack = false
}
class GlobalModel : ObservableObject {
#Published var isBack = false
func get() -> Bool{
return isBack
}
func setTrue() {
isBack = true
}
func setFalse() {
isBack = false
}
}
//
struct CameraView: View { // Creates the camera preview elements
#StateObject var camera = CameraModel()
#State var img : UIImage? = nil
#State var navigated = false
#ObservedObject var nextScreen = GlobalModel()
var body: some View{
ZStack{
CameraPreview(camera: camera)
.ignoresSafeArea(.all, edges: .all)
VStack{
Spacer()
HStack{
if camera.isTaken {
Button(action: {
camera.reTake()
self.nextScreen.setFalse()
print(nextScreen.get())
}, label: {
Text("Retake").foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical, 10)
.padding(.horizontal, 30)
.background(Color.white)
.clipShape(Capsule())
}).padding(.trailing)
Spacer()
ZStack{
NavigationLink("", destination: Classify(originalImage: img, label: "", confidence: 0.0), isActive: $navigated)
Button(action:
{if !camera.isLoaded{
img = camera.savePic()
if img != nil{
print("is not nil")
}
self.navigated.toggle()
self.nextScreen.setTrue()
print(nextScreen.get())
}
}, label: {
Text("Continue").foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical, 10)
.padding(.horizontal, 30)
.background(Color.white)
.clipShape(Capsule())
}).padding(.leading).opacity(nextScreen.get() ? 0.01 : 1)
}
}
else{
Button(action: camera.takePic, label: {
ZStack{
Image(systemName: "camera.circle")
.frame(width: 70, height: 75).font(.system(size: 60))
}
})
}
}.frame(height: 75)
}
}.onAppear(perform: {
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") // Forcing the rotation to portrait
AppDelegate.orientationLock = .portrait // And making sure it stays that way
//UITabBar.appearance().isHidden = true
camera.Check()
})
.onDisappear(){
AppDelegate.orientationLock = .all
UITabBar.appearance().isHidden = false
}
}
}
class CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {
#Published var isTaken = false
#Published var session = AVCaptureSession()
#Published var alert = false
#Published var output = AVCapturePhotoOutput()
#Published var preview : AVCaptureVideoPreviewLayer!
#Published var isLoaded = false
#Published var picData = Data(count: 0)
var flashMode: AVCaptureDevice.FlashMode = .on // set the camera to on
var device : AVCaptureDevice? // for camera device
private func getSettings(camera: AVCaptureDevice, flashMode: AVCaptureDevice.FlashMode) -> AVCapturePhotoSettings {
let settings = AVCapturePhotoSettings() // get the default settings
and change them to enable flash
if camera.hasFlash {
settings.flashMode = self.flashMode
}
return settings
}
func Check() {
switch AVCaptureDevice.authorizationStatus(for: .video){
case .authorized:
setUp()
return
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { (status) in
if status{
self.setUp()
}
}
case .denied:
self.alert.toggle()
return
default:
return
}
}
func setUp(){
do{
self.session.beginConfiguration()
self.device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
let input = try AVCaptureDeviceInput(device: self.device!)
if self.session.canAddInput(input){
self.session.addInput(input)
}
if self.session.canAddOutput(self.output){
self.session.addOutput(self.output)
}
self.session.commitConfiguration()
}
catch{
print(error.localizedDescription)
}
}
func takePic(){
DispatchQueue.global(qos: .background).async {
let currentSettings = self.getSettings(camera: self.device!, flashMode: self.flashMode)
self.output.capturePhoto(with: currentSettings, delegate: self) // Capture photo with flash settings; doesn't work
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false){
(timer) in self.session.stopRunning()
//isBack.setTrue()
}
}
}
DispatchQueue.main.async {
withAnimation{
self.isTaken.toggle()
}
}
}
func reTake() {
DispatchQueue.global(qos: .background).async {
self.session.startRunning()
DispatchQueue.main.async {
withAnimation{
self.isTaken.toggle()
self.isLoaded = false
}
}
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) // not sure if there is something wrong here that is messing up the program but need this function to work ultimately{
if error != nil{
print(error!)
}
print("photoOuput function")
print(photo)
guard let imageData = photo.fileDataRepresentation() else{return }
self.picData = imageData
}
func savePic () -> UIImage{
let image = UIImage(data: self.picData)!
self.isLoaded = true
return image
}
}
struct CameraPreview: UIViewRepresentable {
#ObservedObject var camera : CameraModel
func makeUIView(context: Context) -> some UIView {
let view = UIView(frame: UIScreen.main.bounds)
camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
camera.preview.frame = view.frame
camera.preview.videoGravity = .resizeAspectFill
view.layer.addSublayer(camera.preview)
camera.session.startRunning()
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
I noticed that if I set the following line from above
var flashMode: AVCaptureDevice.FlashMode = .on
to
var flashMode: AVCaptureDevice.FlashMode = .off
the app doesn't produce the above error (but flash stays off). I am asking this because I need to save the output of the camera (with flash) as an image, however, with flash enabled, the picData is nil which leads to an unwrapping error (see the savePic() and photoOutput() functions for reference). Ultimately, I need the savePic() function to work
Any help with this will be appreciated.
I have tried .listRowBackground(/*#START_MENU_TOKEN#*//*#PLACEHOLDER=Background View#*/Color.black/*#END_MENU_TOKEN#*/) and a few other methods but I am at a loss. here is the swift file:
ContactUsView()
.tabItem {
Label("Contact Us", systemImage: "ellipsis.bubble")
.font(Font.title.weight(.ultraLight))
}
.tag("Eight")
}
.accentColor(.white)
.tint(.white)
.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
}
.onAppear {
let appearance = UITabBarAppearance()
appearance.backgroundEffect = UIBlurEffect(style: .systemUltraThinMaterial)
appearance.backgroundColor = UIColor(Color.black.opacity(1.0))
// Use this appearance when scrolling behind the TabView:
UITabBar.appearance().standardAppearance = appearance
// Use this appearance when scrolled all the way up:
UITabBar.appearance().scrollEdgeAppearance = appearance
}
}
}
I'm assuming this might be a 16 beta issue that I should submit to feedback?
.preferredColorScheme(/*#START_MENU_TOKEN#*/.dark/*#END_MENU_TOKEN#*/)
adding this will at least get you black
I open a full modal view
.fullScreenCover(isPresented: self.$isPresentedPlayerView){
NavigationLazyView((MainPlayerView(playerVM: PlayerVM(asset: self.mediaVM.asset), showModal: self.$isPresentedPlayerView)))
}
and in playerView .onApper i force screen to Landscape mode
with this code:
func forceLandscapeLeftPlayerView(){
AppDelegate.orientationLock = UIInterfaceOrientationMask.landscape
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
}
and when it try close view or by set isPresentedPlayerView to false or by presentationMode.wrappedValue.dismiss()
screen not close!
any idea???
this is close code:
func closeView(){
DispatchQueue.main.async {
withAnimation{
self.playerVM.pause()
self.playerVM.destropyPlayer()
AppDelegate.orientationLock = UIInterfaceOrientationMask.portrait
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
self.isPresentedPlayerView = false
}
}
}
BTW, this code work on Xcode 12.2 and stop work on xcode 12.3/ .4
This code works. I removed DispatchQueue, withAnimation and the first three lines. Perhaps, the problem lies somewhere else.
struct MainPlayerView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
Button(action: {
self.resetOrientation()
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Click")
})
}
.onAppear(perform: {
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
})
}
func resetOrientation() {
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
}
}
I am trying to integrate UIGestureRecognizer into my swift playground so that wherever I tap in my playground, my sprite goes to that point. I've tried everything! Watching countless youtube videos, and going on stack overflow for answers but everyone starts with making a class called view that is a UIView and in their code they keep referring to "self". I instead made a variable named "view" that was a SKView. I try to get parts of their code, put it in mine, and change it, but it just doesn't work. Here's the work that I got so far.
view.addGestureRecognizer(UIGestureRecognizer(target: view, action: #selector(handleTap(sender:))))
func handleTap(sender: UITapGestureRecognizer) {
player.position = sender.location(in: view)
}
My playground keeps telling me that i'm using an unresolved identifier 'handleTap(sender:)'
UIGestureRecognizer example with different states of Gesture recogniser in Playground
swift3+
import UIKit
import PlaygroundSupport
class ViewController : UIViewController {
var yellowView: UIView!
var redView: UIView!
var yellowViewOrigin: CGPoint!
override func loadView() {
// UI
let view = UIView()
view.backgroundColor = .white
yellowView = UIView()
yellowView.backgroundColor = .yellow
view.addSubview(yellowView)
redView = UIView()
redView.backgroundColor = .red
view.addSubview(redView)
// Layout
redView.translatesAutoresizingMaskIntoConstraints = false
yellowView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
yellowView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20.0),
yellowView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
yellowView.heightAnchor.constraint(equalToConstant: 80.0),
yellowView.widthAnchor.constraint(equalToConstant: 80.0),
redView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20.0),
redView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
redView.heightAnchor.constraint(equalToConstant: 80.0),
redView.widthAnchor.constraint(equalToConstant: 80.0)
])
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(self.handlePanGesture(_:)))
yellowView.addGestureRecognizer(pan)
yellowViewOrigin = yellowView.frame.origin
view.bringSubview(toFront: yellowView)
}
#objc func handlePanGesture(_ sender: UIPanGestureRecognizer) {
let targetView = sender.view!
let translation = sender.translation(in: view)
switch sender.state {
case .began,.changed:
targetView.center = CGPoint(x: targetView.center.x + translation.x
,y: targetView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: view)
case .ended:
if targetView.frame.intersects(redView.frame){
UIView.animate(withDuration: 0.3) {
targetView.alpha = 0.0
}
}
else{
UIView.animate(withDuration: 0.3) {
targetView.frame.origin = self.yellowViewOrigin
}
}
break
default:
break
}
}
}
PlaygroundPage.current.liveView = ViewController()
I think you need to add the #objc mark in front of the function declaration, so that handleTap will be visible to #selector.
#objc func handleTap(sender: UITapGestureRecognizer) {
player.position = sender.location(in: view)
}
func handleTap has to be called from your view type.
Please try to add it inside an SKView extension:
extension SKView {
func handleTap(sender: UITapGestureRecognizer) {
player.position = sender.location(in: view)
}
}