VNDocumentCameraViewController how to disable auto shutter after the first scan? - swift

I'm creating an app that has a live scan feature. I added the following code to present the view controller that shows what the document camera sees when a button is clicked
let documentCameraViewController = VNDocumentCameraViewController()
documentCameraViewController.delegate = self
present(documentCameraViewController, animated: true)
It is working, but the only issue is the auto shutter is by default set to ON, and it keeps taking scans until I click save. I can manually set the auto shutter to Off, but I don't want that. I want that the auto shutter for the first scan to be on, and then it doesn't keep taking any more photos automatically (the auto shutter switches to off). Is there any way I could do that? if not, how can I set the default auto shutter to off?
Thank you!

You can't. Also delegate will receive all the scans at once.
The closest alternative is to dismiss VNDocumentCameraViewController and take the first scan inside documentCameraViewController: didFinishWith, as shown here for example:
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
documentCameraViewController?.dismiss(animated: true, completion: nil)
documentCameraViewController = nil
// ...
let firstImage = scan.imageOfPage(at: 0)
However VNDocumentCameraViewController is there to provide a basic functionality. So if you need anything more fine-tuned, it's more common to build a custom camera and process frames with the Vision framework

Related

AppleTv - CustomOverlayViewController force popup at timecode

I'm using the CustomOverlayViewController on an AVPlayerViewController to display movies related to what the user is currently watching. (https://developer.apple.com/documentation/avkit/avplayerviewcontroller/3229856-customoverlayviewcontroller)
I want to bring that up into focus automatically when the credits start playing. I have a timecode in my metadata and can detect when that occurs. But, I can't find a way to force the view to popup without the user swiping to it on the remote. Is there a way to force this view into focus without user input?
P.S. It is fine if it can be accessed at other times manually by the user as well. I just need to be able to also autofocus it at a certain point.
I ran into the same issue and instead of using the customOverlayViewController property of AVPlayerViewController, I just call
vcToPresent = ViewController()
vcToPresent.modalPresentationStyle = .overCurrentContext
self.avPlayerVC?.present(vcToPresent, animated: true, completion: nil)
to present the desired VC on screen at the time you want it to display.

AVPlayer Audio Lock Screen updates stop working, only after another AVPlayer instance plays Video content

Summary:
If you only use AVPlayer to play streaming audio only in your application, then Lock Screen Updates will work just fine (many streams - one after another). However, if you use AVPlayer to play both Streaming Audio followed by a Local Video file (even while using separate instances of AVPlayer and on different view controllers), then when you try to play Streaming Audio again - nothing will show up on the Lock Screen any more. Not related to cleanup - because playing only multiple audio files in a row works fine and shows up on the Lock Screen every time, but as soon as you play 1 video file (Or even initialize it to any AVPlayer without calling .play() on it), audio Lock Screen updates permanently stop working, and you need to restart the app for it to work again. It sounds like an AVFoundation bug to me...
Detailed Breakdown:
I have an appliction with View Controllers A and B. View controller A is the presenter of view controller B (pushes it using a navigation controller). Both A and B have a seperate AVPlayer inside them (A's plays audio. B's plays video. Audio is streamed. Video is local file in docs directory).
VC A configures its AVPlayer to update the Lock Screen with the Artwork Image, progress status, forward 15s, back 15s buttons. Everything works great if only view controller A's player is used to play audio - no matter how many times I go back and forward to it (I.E. VC B's player is never used to play()). (The whole controller gets deinit correctly) The Lock Screen always works, and always updates all required playback information.
VC A (AVPlayer1) --> PUSH --> VC B (AVPlayer2)
This is the code to register for Lock Screen updates (including button and progress callbacks):
func setupNowPlaying() {
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = title
nowPlayingInfo[MPMediaItemPropertyArtist] = artist
let artwork = MPMediaItemArtwork.init(boundsSize: self.artImage.size, requestHandler: { (size) -> UIImage in
return self.artImage
})
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
guard let player = self.player else { return }
guard let currentItem = self.player?.currentItem else { return }
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = currentItem.asset.duration.seconds
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentItem.currentTime().seconds
let rcc = MPRemoteCommandCenter.shared()
let skipBackwardCommand = rcc.skipBackwardCommand
skipBackwardCommand.isEnabled = true
skipBackwardCommand.addTarget(handler: skipBackward)
skipBackwardCommand.preferredIntervals = [15]
let skipForwardCommand = rcc.skipForwardCommand
skipForwardCommand.isEnabled = true
skipForwardCommand.addTarget(handler: skipForward)
skipForwardCommand.preferredIntervals = [15]
UIApplication.shared.beginReceivingRemoteControlEvents()
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
Additionally, the following is hooked up in AppDelegate (applicationWillResignActive), to enable background playback (standard stuff):
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay, .allowBluetooth, .allowBluetoothA2DP ])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
The problem of Not showing anything on the lock screen starts to occur when VC B gets presented and plays video using its own separate AVPlayer. VC A's AVPlayer is first paused. If I call .play() on VC B's player then when I come back to VC A (by clicking Back < in the nav bar), the progress updates to the Lock Screen will never show up again (the whole playback UI doesn't show up), however the background playback still works.
So as soon as I use another AVPlayer on another View Controller, my original player will never be able to post updates to the Lock Screen UI, ever again, until the application is terminated and re-launched.
I tried to do full cleanup and re-init on the player in VC A.
I tried to just not call .play() on VC B. (Everything works fine with VC A in that case).
VC B's player is not registered for Lock Screen updates, because it just plays video in the app for preview purposes.
Something under-the-hood with Apple's AVPlayer breaks VC A's AVPlayer's Lock Screen updates capability, as soon as another instance of AVPlayer is introduced and used to play in another View Controller.
I know the issue is related to the second AVPlayer on the separate controller, because just not calling .play() on VC B's player, fixes the screen lock update disappearance issue on VC A's player.
How can I work around this? I don't want to remove VC A's player from superview, and pass it to VC B, and reconfigure it, then reconfigure it back again in VC A if the user comes back. (very laborious and error prone). It also currently bricks Lock Screen updates permanently, after the first time the user goes through VC B's playback (which is part of my happy path)
Thanks in advance,
Alex
I was able to avoid this problem by removing dependence on AVPlayerViewController in VC-B. Now I use a third party component called: "Player" (Cocoapods: "Player"), which uses an AVPlayerLayer, but not AVPlayerViewController. https://cocoapods.org/pods/Player
Unfortunately I lost my playback UI controls (progress bar, play button, full screen button - which AVPlayerViewController was giving me for free). So I will have to build some custom playback controls.
So it seems that AVPlayerViewController was causing the problem with the lock screen and breaking it. After switching to using "Player", which still uses AVPlayer, but not AVPlayerViewController, the problem went away, and I now always see my playback controls on the Lock Screen.

Dismissing SKScene Completely

Okay so I have an app that starts off with a menu view controller that prompts user to press one of 4ish buttons which then loads a viewcontroller which then presents a scene and the user plays the game based on which button was pressed.
I am then having the user being redirected to another viewcontroller which presents another another scene, once a condition is met (they lose the game). Only problem is, the 2nd viewcontroller(and i'm assuming it's scene) is still running. I know this because I have a print statement inside of it's override function update method to see if it's still there.
In addition, I have audio playing in my old gamesene and it's still currently playing. I wouldn't EXACTLY mind that since later on i'm going to just end up passing audio data (mute all) between all 3 viewcontrollers and their presented scenes.
Only problem with what is going on right now is that when I run the app since the old viewiewcontroller an it's scene seem to still be running underneath, it keeps calling the transition which causes a weird look where when the condition meets, the transition loops endlessly to the new viewcontroller then back to the beginning of the transition then to the new viewcontroller again.
I've tried this piece of code:
let theVC = self.viewController?.storyboard?.instantiateViewController(withIdentifier: "TrumpVC") as! TrumpViewController
self.viewController?.navigationController?.pushViewController(theVC, animated: true)
self.viewController?.dismiss(animated: true, completion: {});
But it doesn't seem to help at all :( Essentially I navigate to a new viewcontroller and dismiss the current one (this is all in my scene)
Thanks
Solution:
let theVC = self.viewController?.storyboard?.instantiateViewController(withIdentifier: "TrumpVC") as! TrumpViewController
self.viewController?.navigationController?.pushViewController(theVC, animated: true)
self.viewController?.removeFromParentViewController()
self.viewController?.dismiss(animated: true, completion: nil)
self.view?.presentScene(nil)

Change key to true then show view in swift

OK, so I've added a view onto my Application that asks the user to accept or decline the Terms of Service. I have it so when they click accept, it changes the key "TermsAccepted" to true. If they close the app, and re-open it, it gives them access. However I'd like to be able to give them access without re-opening the app first.
So in my ViewController (Main Screen), in my viewDidLoad I have the following:
if NSUserDefaults.standardUserDefaults().boolForKey("TermsAccepted") {
// They've been accepted, do nothing.
} else {
let termsView = self.storyboard?.instantiateViewControllerWithIdentifier("FirstLaunchTerms") as! FirstLaunchTermsView
self.presentViewController(termsView, animated: true, completion: null
}
In the 'LaunchTermsView' I have the following code for when they accept the terms.
#IBAction func acceptTerms(sender : AnyObject)
{
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "TermsAccepted")
}
And thi works fine, but they have to re-open the application.
I tried to just have it so the button opens the Main View at the same time as those terms are accepted (After the key is updated) but it gives me the following error.
fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)
I assumed this meant that it was wanting to re-open the launch terms view again, so I tried to move all the code from viewDidLoad to viewWillAppear so it checks each time, but it just gives the same error. (it was a long shot try before I posted on here).
I had a look at some posts on here, but a lot of them were in ObjC or just didn't give me a solution or any form of help to trying to find one myself.
As you're presenting your terms view controller you should simply be able to dismiss it when you're done with it. You don't show any other code so I'm assuming that your 'home' view controller is waiting to be revealed underneath (you don't need to try to show it again).

Keep window always on top?

In Objective-C for Cocoa Apps it's possible to use such way to keep window always on top?
How to achieve the same with Swift?
self.view.window?.level = NSFloatingWindowLevel
Causes build error Use of unresolved identifier 'NSFloatingWindowLevel'
To change the window level you can't do it inside viewDidload because view's window property will always be nil there but it can be done overriding viewDidAppear method or any other method that runs after view is installed in a window (do not use it inside viewDidLoad):
Swift 4 or later
override func viewDidAppear() {
super.viewDidAppear()
view.window?.level = .floating
}
For older Swift syntax check the edit history
I would prefer this way. This ignores all other active apps, and makes your app upfront.
override func viewWillAppear() {
NSApplication.sharedApplication().activateIgnoringOtherApps(true)
}
While the other answers are technically correct - when your app will or did resigns active, setting the window level to .floating will have no effect.
.floating means on top of all the windows from the app you are working on, it means not on top of all apps windows.
Yes there are other levels available you could set, like kCGDesktopWindowLevel which you can and should not set in swift to make your window float above all.
None of them will change the fact that your window will go behind the focused and active apps window. To circumvent i chose to observe if the app resigns active notification and act accordingly.
var observer : Any;
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(
forName: NSApplication.didResignActiveNotification,
object: nil,
queue: OperationQueue.main ) { (note) in
self.view.window?.level = .floating;
// you can also make your users hate you, to take care, don't use them.
//NSApplication.shared.activate(ignoringOtherApps: true)
//self.view.window?.orderFrontRegardless();
}
}
another way could be subclassing NSWindow and override the property .level with an always returning .floating, but the code above is less work and keeps control in the place where you want to set the window floating.
I spent a long time trying to make this work. I then realized there was a simple answer, just worded in a different way. Here it is: Change macOS window level with SwiftUI (to make a floating window)
As explained there:
You can access your windows with NSApplication.shared.windows and set the level for each one.
for window in NSApplication.shared.windows {
window.level = .floating
}
EDIT: you can use other levels, including .screenSaver (highest, I think) and ```.normal`` if you want to return to standard behavior. Source: https://developer.apple.com/documentation/appkit/nswindow/level