ReplayKit stopRecording not entered the first time - ios

I am trying to video capture an ARKIt app using ReplayKit. I have a record button, when pressed turned red and start recording, then pressed again to turn white and stop recording.
But the stopRecording method never worked on the first time.
if recorder.isAvailable {
recorder.delegate = self
if recorder.isRecording {
print("Recorder is recording...")
// Stop recording
recorder.stopRecording { previewController, error in
print("Stop recording...")
self.recordImage.color = UIColor.white
self.recordImage.colorBlendFactor = 1
if let controller = previewController {
controller.previewControllerDelegate = self
self.present(controller, animated:true, completion:nil)
}
}
}
else {
// Start recording
recorder.startRecording { error in
print("Starting to record…")
if error == nil {
print("Start Recording…")
self.recordImage.color = UIColor.red
self.recordImage.colorBlendFactor = 1
}
}
}
When first pressed, I can see the recording started. Then when I pressed again, I can see that recorder.isRecording is entered, but the block in recorder.stopRecording does not work. I have to press again to start recording, then stop again before the recorder.stopRecording block is entered.
Any idea? Help is appreciated.
Press Record!
Starting to record…
Start Recording…
Press Record!
Recorder is recording...

I fixed this issue based on the replies at https://forums.developer.apple.com/thread/62624
This is definitely a bug in iOS; but removing the "Localization native development region" entry from the Info.plist seems to solve this issue.

Which iOS version are you using? I've seen cases where the completion handler doesn't get called, often on the first try, but then works thereafter. This happened a lot on iOS 9 and again in 11.0, but seems to be better in 11.0.3.
I'm not sure if you are trying this on an iPad, but your code above won't work on an iPad. You need to set a presentation style.
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.phone {
self.present(controller, animated: true, completion: nil)
}
else {
controller.popoverPresentationController?.sourceRect = self.recordingButton.bounds
controller.popoverPresentationController?.sourceView = self.view
controller.modalPresentationStyle = UIModalPresentationStyle.popover
controller.preferredContentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
self.present(controller, animated: true, completion: nil)
}

Related

UIVideoEditorController keeps cancelling instead of saving

I have set up UIVideoEditorController as followed
if UIVideoEditorController.canEditVideo(atPath: video.path) {
let editController = UIVideoEditorController()
editController.videoPath = video.path
editController.delegate = self
present(editController, animated:true)
}
It will end up in the cancel delegate call and then dismisses the editor
func videoEditorControllerDidCancel(_ editor: UIVideoEditorController) {
print("in here")
editor.dismiss(animated: true, completion: nil)
}
Does anyone know why this is or if I need additional configurations to get it to work?

iOS Swift cannot dismiss avplayer modal

so here is my use case - we are loading a video using avplayer and playing it, and user can click on the default fullscreen button to take the video fullscreen. The user may be viewing the video in 2 different conditions, if he is logged in and if he is not logged in.
If the user is logged in (determined by a variables value), he can watch full video, otherwise, playback should stop after playing a defined number of seconds (depending on the video the no. of seconds changes), and a banner comes up over the player asking him to login.
Everything is working fine while the video is being played inline. However, when the video is playing fullscreen, then even if we stop playback using didTapPause(), the fullscreen window does not get dismissed. I have even tried dismissing it using self.playerController.dismiss(animated: true, completion: nil), the fullscreen modal is not dismissed. The code snippet it as follows -
playerController.player?.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if self.playerController.player?.currentItem?.status == .readyToPlay {
self.videoCurrentTimeDuration = CMTimeGetSeconds((self.playerController.player?.currentItem!.currentTime())!);
self.videoTimeDuration = CMTimeGetSeconds((self.playerController.player?.currentItem?.duration)!);
if self.moveToTime != nil{
let timeWithSecond = CMTimeMakeWithSeconds(self.videoTimeDuration! * self.moveToTime! / 100, Int32(kCMTimeMaxTimescale))
self.playerController.player?.seek(to: timeWithSecond, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
self.moveToTime = nil
}
guard let videoD = self.videoData else { return }
let timeTToPlay: Double = Double(videoD.freeDuration)
if videoD.isFree {
if videoD.registrationNeeded && !CurrentLoginUser.shared.isLogin{
if self.videoCurrentTimeDuration! > timeTToPlay {
self.didTapPause()
self.playerController.dismiss(animated: true, completion: nil)
self.loginNeedView = UINib.get(withNib: self)
self.loginNeedView?.frame = self.bounds
self.loginNeedView?.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
self.addSubview(self.loginNeedView!)
AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)
}
}
else{
self.loginNeedView?.removeFromSuperview()
AppUtility.lockOrientation(UIInterfaceOrientationMask.all)
}
}
The player controller is added onto the view by calling the setupView function which is as follows -
private func setUpView() {
self.backgroundColor = .black
addVideoPlayerView()
configurateControls()
}
fileprivate func addVideoPlayerView() {
playerController.view.frame = self.bounds
playerController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
playerController.showsPlaybackControls = true
playerController.addObserver(self, forKeyPath: "videoBounds", options: NSKeyValueObservingOptions.new, context: nil)
self.insertSubview(playerController.view, at: 0)
}
I am not sure if this is the proper way to do it, any ideas ?
EDIT:
Next as per anbu.karthik's advice I tried to forcibly remove the fullscreen view by locating the top view controller as :
func currentTopViewController() -> UIViewController {
var topVC: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController
while ((topVC?.presentedViewController) != nil) {
topVC = topVC?.presentedViewController
}
return topVC!
}
and then, used it as follows-
let currentTopVC: UIViewController? = self.currentTopViewController()
if (currentTopVC?.description.contains("AVFullScreenViewController"))! {
print("found it")
currentTopVC?.dismiss(animated: true) { _ in }
}
And it works but crashes the app with the following exception -
Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'child view controller:<AVEmbeddedPlaybackControlsViewController: 0x7fdcc2264400> should have parent view controller:<AVPlayerViewController: 0x7fdcc222a800> but actual parent is:<AVFullScreenViewController: 0x7fdcce2f3c50>'
To my knowledge it is not possible to play full screen directly. It seems that AVPlayerViewController comes pretty much as is and does not offer much in the way of customization of UI or behavior. If you wanted to play full screen directly you would need to either present the controller containing your AVPlayerViewController modally in full screen or change the frame yourself to make it full screen. But there is no API to control full screen programmatically on AVPlayerViewController...

Swift 3 - Display alert controller asynchronously with long function running in background

I am using Swift 3.
The behavior I am trying to do is: the user clicks on a button, a spinning gear alert controller displays while it kicks off a long-running function. Once that function is done executing, the spinning gear goes away and the view controller dismisses.
The code below kicks off the doProcessing function but doesn't display the spinning gear until about a second before the view dismisses. So this isn't quite right.
func displaySpinningGear() {
print("display spinning gear")
// show the alert window box
let activityAlertController = UIAlertController(title: "Processing", message: "Please wait while the photo is being processed.", preferredStyle: .alert)
//create an activity indicator
let indicator = UIActivityIndicatorView(frame: activityAlertController.view.bounds)
indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight]
indicator.hidesWhenStopped = true
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
//add the activity indicator as a subview of the alert controller's view
activityAlertController.view.addSubview(indicator)
indicator.isUserInteractionEnabled = false // required otherwise if there buttons in the UIAlertController you will not be able to press them
indicator.startAnimating()
print("start animating")
self.present(activityAlertController, animated: true, completion: nil)
}
func onButtonClick() {
self.displaySpinningGear()
DispatchQueue.main.async {
self.doProcessing() // long running function
}
if let viewController = presentingViewController {
// This block will dismiss both current and a view controller presenting current
viewController.dismiss(animated: true, completion: nil)
}
else {
// This block will dismiss only current view controller
self.dismiss(animated: true, completion: nil)
}
}
The code below kicks off the doProcessing function but the view dismisses immediately and I can tell from the console that my doProcessing function is still running. This is not right either.
function onButtonClick() {
DispatchQueue.global(qos: .background).async {
print("Processing")
self.doProcessing() // run in background
DispatchQueue.main.async {
self.displaySpinningGear()
}
}
if let viewController = presentingViewController {
// This block will dismiss both current and a view controller presenting current
viewController.dismiss(animated: true, completion: nil)
}
else {
// This block will dismiss only current view controller
self.dismiss(animated: true, completion: nil)
}
}
How do I get the background function to kick off while displaying a spinning gear and dismiss the view and alert controller when the background function is done running (not before)?
EDIT
Tried moving the code to spin the gear outside the background block as per #Honey's suggestion in the comment but to no avail. The view immediately dismisses while the process function is still processing (I can tell through print statements).
func onButtonClick() {
DispatchQueue.main.async {
self.displaySpinningGear()
}
DispatchQueue.global(qos: .background).async {
print("Processing")
self.doProcessing() // run in background
}
if let viewController = presentingViewController {
// This block will dismiss both current and a view controller presenting current
viewController.dismiss(animated: true, completion: nil)
}
else {
// This block will dismiss only current view controller
self.dismiss(animated: true, completion: nil)
}
}
Make a Callback from long running function so when it ends returns a value and catch it to disappear the alert.
Try it:
typealias DoProcessingCallback = (_ finished: Bool) -> Void
func onButtonClick() {
self.displaySpinningGear()
self.doProcessing(callback: { (finished) in
if finished {
// Here you DismissViewController
// Here you DismissAlert
}
}) // long running function
}
func doProcessing(callback: DoProcessingCallback) {
// YOUR LONG CODE....
// When you know it already finished
callback(true)
}
Hope it helps you
I had the same issue and tried a bunch of different things and this is what worked:
activityView.alpha = 0.8
DispatchQueue.global(qos: .default).async(execute: {
DispatchQueue.main.async(execute: {
self.performSegue(withIdentifier: "cropToProcessed", sender: self)
})
})
Basically I set the alpha for activity indicator to 0.0 initially and when the button is pressed I set it to 0.8 and I set it back to 0.0 in viewWillDisappear and it works

Integrating GameCenter in Swift with NSNotification, using SpriteKit - ViewController issue

I've tried a whole bunch of ways to get Game Center working in my SpriteKit game. Unfortunately the way I've done it in the past using ObjC and ViewControllers don't work because I'm using SKScene/ a GameScene.
This is the swift version of the code (I think):
// MARK: Game Center Integration
//login and make available
func authenticateLocalPlayer(){
let localPlayer = GKLocalPlayer()
print(localPlayer)
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if ((viewController) != nil) {
self.presentViewController(viewController!, animated: true, completion: nil)
}else{
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
//submit a score to leaderboard
func reportScoreToLeaderboard(thisScore:Int){
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "LeaderboardID")
scoreReporter.value = Int64(thisScore)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: { (error: NSError?) -> Void in
if error != nil {
print(error!.localizedDescription)
} else {
print("Score submitted")
}
})
}
}
//show leaderboard (call from button or touch)
func showLeaderboard() {
let vc = self.view?.window?.rootViewController
let gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
//hides view when finished
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController){
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
Unfortunetely, no matter what I try, I either get this error:
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior
... or it just crashes.
I've read that NSNotifications can be used? But how?
I'm guessing the best way is to set it all up in the GameViewController.swift and use NSNotifications to communicate with the RootViewController from the GameScene? I can't seem to find a tutorial or example though.
Use delegation when a view controller needs to change views, do not have the view itself change views, this could cause the view trying to deallocating while the new view is being presented, thus the error you are getting

Login with FBSDK, cannot perform segue

I have installed the Facebook SDK, followed all the requirements, it works as I can extract data from the user's Facebook account, however I cannot perform the segue once logged in.
I get an error message:
Warning: Attempt to present <viewController1> on <FBSDKContainerViewController> whose view is not in the window hierarchy!
Here are my codes:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if (FBSDKAccessToken.currentAccessToken() == nil) {
print("not logged in")
}
else {
print("logged in")
}
let loginButton = FBSDKLoginButton()
loginButton.readPermissions = ["public_profile", "email", "user_friends"]
loginButton.center = self.view.center
loginButton.delegate = self
self.view.addSubview(loginButton)
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if ((error) != nil)
{
}
else if result.isCancelled {
}
else {
if result.grantedPermissions.contains("email")
{
// Do work
returnUserData()
addViewHierarchy()
// self.performSegueWithIdentifier("signUpFollowPage", sender: self)
}
}
}
Anyone can help ?
Thanks
I've been struggling with the same problem (although I'm using FBSDKLoginManager's logInWithReadPermissions).
Whilst the problem seems to be that the callback (or your didCompleteWithResult delegate call) is invoked before the Safari web view is dismissed, I can't find a nice solution.
My (horrific) answer has been to delay the segue, to give the FBSDKContainerViewController time to be dismissed naturally. In your case:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { () -> Void in
self.performSegueWithIdentifier("signUpFollowPage", sender: self)
}
It may be possible to get away with a shorter delay.
This strikes me as a bug with the Facebook Login SDK. If I find time I might look at the SDK source. Fortunately the App I'm working on is not due for release until the new year, so I'm hoping that the issue might be fixed in a later version anyway.
You should use the viewDidAppear() function and check the FBSDKAccessToken.currentAccessToken :
override func viewDidAppear(animated:Bool){
if FBSDKAccessToken.currentAccessToken() != nil{
performSegueWithIdentifier("YOUR_SEGUE",sender:self)
}
}
I have faced the same issue while logging in with FB. Here is how I solved it. The issue is while navigating, the FBContainerView is still open. Using self.controller will refer to the FBContainerView and NOT the Topmost controller and hence no navigation. We will need to switch back to the top Most Controller and then perform the navigation.
You can do this without performing segue, by presenting view controller from top controller like this
let topController = self.topMostController()
topController.presentViewController(secondController, animated: true, completion: nil)
to get the top most controller define a method which will always return topmost controller
func topMostController()-> UIViewController {
var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
while((topController?.presentedViewController) != nil){
topController = topController?.presentedViewController
}
if(topController != nil){
return topController!
}
else{
return self
}
}
My solution is similar to the ones above, but it is a bit more deterministic (and for me, safer).
This solution ensures we ARE the top most VC before presenting.
Timer.scheduledTimer(withTimeInterval: 0.01 / 30.0, repeats: true, block: { (timer) in
if let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window, let rootVC = window.rootViewController {
var topController = rootVC
while let nextTop = topController.presentedViewController {
topController = nextTop;
}
if topController == self {
// we're good, go for it
self.performSegue(withIdentifier: "afterLoginSegue", sender: nil)
timer.invalidate()
} else {
log.debug("waiting to transition until we're presenting...")
}
} else {
log.error("couldn't resolve a key variable needed to segue, aborting.")
timer.invalidate()
}
})
I still feel like there should be a way to get a callback once the presenting facebook login safari VC is gone, but until that solution comes along, this is as good as i can do (obviously this code could be cleaned up and modularized for use in multiple VCs, but you can just dump this in where needed).

Resources