I'm presenting an AVPlayerViewController with an AVPlayer and are displaying a pre-roll ad in it, but I want to remove the top and button bar, or make the user unable to dismis the ViewController before the ad is done:
How do I do that?
here's my code for my AVPlayerViewController implementation:
let preRollAdPlayer = AVPlayerViewController()
let player = AVPlayer()
preRollAdPlayer.player = player
if #available(iOS 9.0, *) {
preRollAdPlayer.delegate = self
}
preRollAdPlayer.showsPlaybackControls = false
self.view?.window?.rootViewController?.presentViewController(preRollAdPlayer, animated: true, completion: {
self.preRollAdPlayer.playPrerollAdWithCompletionHandler({ (error) -> Void in
if (error != nil) {
NSLog("Error Message: %#", error)
} else {
print("preRoll loaded")
}
self.preRollAdPlayer.dismissViewControllerAnimated(true, completion: {
if (error != nil) {
NSLog("Error Message: %#", error)
}
})
})
})
According to Apple, you should use showsPlaybackControls property:
A Boolean value that indicates whether the player view controller shows playback controls.
Set it to false:
Swift:
yourAvPlayer.showsPlaybackControls = false
Objective C:
[yourAvPlayer setShowsPlaybackControls:FALSE];
Related
I am working on an iPad application that has both Portrait and Landscape orientation. The issue that I am facing is regarding the the popup presentation that iPad shows you for saving password while you log-in in the application.
Now, when the pop-up is presented, at that moment if I try to rotate the device, the application crashes with the message
Fatal Exception: NSInvalidArgumentException
Application tried to present modally a view controller <_SFAppPasswordSavingViewController: 0x107be7230> that is already being presented by <UIKeyboardHiddenViewController_Save: 0x107a49190>.
Now, the problem is that SFAppPasswordSavingViewController presentation is not controlled by me. Also UIKeyboardHiddenViewController_Save,is something I am not controlling.
Please help me out on how to solve this issue or let me know if I am missing anything.
This is how I am presenting Login screen in my application.
guard let loginVC = LoginVC.loadFromXIB() else {
Log.w("LoginVC could not be loaded")
return
}
let navVC = PopNavigationController(rootViewController: loginVC)
UIWindow.key?.replaceRootViewControllerWith(navVC, animated: true, completion: nil)
Here replaceRootViewControllerWith is a custom function that is an extension on UIWindow.
extension UIWindow {
func replaceRootViewControllerWith(_ replacementController: UIViewController,
animated: Bool,
completion: (() -> Void)?) {
let snapshotImageView = UIImageView(image: self.snapshot())
self.addSubview(snapshotImageView)
let dismissCompletion = { [weak self] in // dismiss all modal view controllers
self?.rootViewController = replacementController
self?.bringSubviewToFront(snapshotImageView)
if animated {
UIView.animate(withDuration: 0.4, animations: { () -> Void in
snapshotImageView.alpha = 0
}, completion: { (_) -> Void in
snapshotImageView.removeFromSuperview()
completion?()
})
} else {
snapshotImageView.removeFromSuperview()
completion?()
}
}
DispatchQueue.main.async { [weak self] in
if self?.rootViewController?.presentedViewController != nil {
self?.rootViewController?.dismiss(animated: false, completion: dismissCompletion)
} else {
dismissCompletion()
}
}
}
}
I am using ReplayKit in an app to record the visible screen with some text and a video playing. The issue I am facing is that ReplayKit is working just fine for the first screen recording, but if I am to record again in the same session (ie without closing the app) it runs into this error:
MyViewController[423:39346] viewServiceDidTerminateWithError:: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}
In this scenario, I am actually trying to screen record on the same ViewController (only with a different video being played and some text content altered). Below is my recording code:
#objc func startRecording() {
let recorder = RPScreenRecorder.shared()
recorder.startRecording{ [unowned self] (error) in
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
print("NOT Recording")
} else {
self.video.play()
print("Recording")
self.isRecording = true
}
}
recordIcon.isHidden = true
ring.isHidden = true
}
#objc func stopRecording() {
let recorder = RPScreenRecorder.shared()
recorder.stopRecording( handler: { previewViewController, error in
if let error = error {
print("\(error.localizedDescription)")
}
// Handling iPads
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
previewViewController?.modalPresentationStyle = UIModalPresentationStyle.popover
previewViewController?.popoverPresentationController?.sourceRect = CGRect.zero
previewViewController?.popoverPresentationController?.sourceView = self.view
}
if previewViewController != nil {
self.previewViewController = previewViewController
previewViewController?.previewControllerDelegate = self
}
self.present(previewViewController!, animated: true, completion: nil)
})
isRecording = false
recordIcon.isHidden = false
ring.isHidden = false
return
}
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
dismiss(animated: true)
}
Any help on this is greatly appreciated. I'd hate to force users to have to reopen the app before recording again.
It might be that your app keeps the screen recording longer than it should. If this is the case, try implementing the discardRecording(handler: #escaping () -> Void) function. Here are more details on the discardRecording.
Being the Swift noob that I am, I find myself copying and pasting code. I know I'm supposed to use the DRY method and not do this, but this particular bit of code has me stumped. I tried creating a struct to hold it, but the struct threw all sorts of errors. I don't quite understand classes and how I would subclass it, and so maybe that's the solution. I just don't know how to do it? Or maybe an extension?
Anyway, here is the code I keep copying and pasting in each new view controller:
import UIKit
import AVKit
class Step3JobSummaryVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
...
var sourceVCIdentity = "setup"
var initialLaunch = true
let playerVC = AVPlayerViewController()
let video = Video.step3JobsSummary
...
// ------------------------
// Autoplay Video Functions
// ------------------------
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if initialLaunch == true {
showUnplayedVideo()
initialLaunch = false
}
Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
}
func showUnplayedVideo() {
// 1. get current video data
Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in
if !watched {
// 2. show setup video popup on first load
guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
let player = AVPlayer(url: videoURL)
self.playerVC.player = player
// 3. fast forward to where user left off (if applicable)
player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))
// 4. dismiss the player once the video is over and update Firebase
NotificationCenter.default.addObserver(self,
selector: #selector(self.playerDidFinishPlaying),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.playerVC.player?.currentItem)
self.present(self.playerVC, animated: true) {
self.playerVC.player?.play()
}
}
}
}
#objc func playerDidFinishPlaying(note: NSNotification) {
self.playerVC.dismiss(animated: true)
Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
Any help would be great. I'm just trying to learn :-)
EDIT #1
This is my attempt at an extension. I simplified and refactored my code, but as before, it's giving me an error. This time the error is 'extensions must not contain stored properties'. So how do I access the AVPlayerController?!?
extension UIViewController {
let playerVC = AVPlayerViewController()
func showUnplayedVideo(video: Video) {
// 1. get current video data
Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in
if !watched {
// 2. show setup video popup on first load
guard let videoURL = URL(string: video.url) else { print("url error"); return }
let player = AVPlayer(url: videoURL)
self.playerVC.player = player
// 3. fast forward to where user left off (if applicable)
player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))
// 4. dismiss the player once the video is over and update Firebase
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
object: playerVC.player?.currentItem,
queue: .main) { (notification) in
self.playerDidFinishPlaying(note: notification as NSNotification)
self.present(self.playerVC, animated: true) {
self.playerVC.player?.play()
}
}
}
}
func playerDidFinishPlaying(note: NSNotification, video: Video) {
self.playerVC.dismiss(animated: true)
Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
}
EDIT #2
So I got the code to compile without any errors, but now it's not firing. Aargh.
extension UIViewController {
func showUnplayedVideo(playerVC: AVPlayerViewController, video: Video) {
print("does this code even fire?")
// 1. get current video data
Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in
if !watched {
// 2. show setup video popup on first load
guard let videoURL = URL(string: video.url) else { print("url error"); return }
let player = AVPlayer(url: videoURL)
playerVC.player = player
// 3. fast forward to where user left off (if applicable)
player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))
// 4. dismiss the player once the video is over and update Firebase
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
object: playerVC.player?.currentItem,
queue: .main) { (notification) in
self.playerDidFinishPlaying(playerVC: playerVC, note: notification as NSNotification, video: video)
self.present(playerVC, animated: true) {
playerVC.player?.play()
}
}
}
}
}
func playerDidFinishPlaying(playerVC: AVPlayerViewController, note: NSNotification, video: Video) {
playerVC.dismiss(animated: true)
Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
}
Why won't this work?
I would start with defining a protocol for your functionality something like this:
protocol VideoPlayable {
func showUnplayedVideo(playerVC: AVPlayerViewController, video: Video)
}
And then add a default implementation to it
extension VideoPlayable where Self: UIViewController {
func showUnplayedVideo(playerVC: AVPlayerViewController, video: Video) {
print("does this code even fire?")
// 1. get current video data
Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in
if !watched {
// 2. show setup video popup on first load
guard let videoURL = URL(string: video.url) else { print("url error"); return }
let player = AVPlayer(url: videoURL)
playerVC.player = player
// 3. fast forward to where user left off (if applicable)
player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))
// 4. dismiss the player once the video is over and update Firebase
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
object: playerVC.player?.currentItem,
queue: .main) { (notification) in
self.playerDidFinishPlaying(playerVC: playerVC, note: notification as NSNotification, video: video)
}
self.present(playerVC, animated: true) {
playerVC.player?.play()
}
}
}
}
private func playerDidFinishPlaying(playerVC: AVPlayerViewController, note: NSNotification, video: Video) {
playerVC.dismiss(animated: true)
Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
}
Thanks to this when you add the VideoPlayable protocol to a controller you will have your custom functionality available, and other controllers that shouldn't have the functionality won't have the access to this method.
Also if you really want to have access to the method
func playerDidFinishPlaying(playerVC: AVPlayerViewController, note: NSNotification, video: Video)
Add it to the protocol and remove the private statement from the implementation.
And you video player wasn't being shown because you added the presenting of the player into the notification block.
Also, consider adding proper self handling to your blocks. Right now I think its possible that the self can be caught in the blocks.
Just to let you know the statement
where Self: UIViewController
Limits the access of the implementation to UIViewControllers, so if you add the protocol to a UIView subclass you wont get access to the default implementation. You will then need to add a new one :) this prevents the missing of the protocol in places you dont want it to be used.
You can just move all the reused code into a separate class:
class Step3JobSummaryVC: UIViewController {
let videoPlayer = VideoPlayer(video: Video.step3JobsSummary)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
videoPlayer.start(on: self)
}
}
final
class VideoPlayer {
private var initialLaunch: Bool = true
private let playerVC = AVPlayerViewController()
private let video: Video
init(video: Video) {
self.video = video
}
func start(on viewController: UIViewController) {
if initialLaunch == true {
showUnplayedVideo(on: viewController)
initialLaunch = false
}
Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
}
func showUnplayedVideo(on viewController: UIViewController) {
// 1. get current video data
Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in
if !watched {
// 2. show setup video popup on first load
guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
let player = AVPlayer(url: videoURL)
self.playerVC.player = player
// 3. fast forward to where user left off (if applicable)
player.seek(to: CMTimeMakeWithSeconds(playbackTime, preferredTimescale: 1))
// 4. dismiss the player once the video is over and update Firebase
NotificationCenter.default.addObserver(self,
selector: #selector(self.playerDidFinishPlaying),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.playerVC.player?.currentItem)
viewController.present(self.playerVC, animated: true) {
self.playerVC.player?.play()
}
}
}
}
#objc func playerDidFinishPlaying(note: NSNotification) {
self.playerVC.dismiss(animated: true)
Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
}
Have you considered using a more traditional inheritance model?
class VideoPlayingBaseController: : UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if initialLaunch == true {
showUnplayedVideo()
initialLaunch = false
}
Video.updatePlaybackTime(playerVC: playerVC, videoURL: video.url, firebaseVideoID: video.firebaseID)
}
func showUnplayedVideo() {
// 1. get current video data
Video.getFirebaseData(firebaseVideoID: video.firebaseID) { (playbackTime, watched) in
if !watched {
// 2. show setup video popup on first load
guard let videoURL = URL(string: self.video.url) else { print("url error"); return }
let player = AVPlayer(url: videoURL)
self.playerVC.player = player
// 3. fast forward to where user left off (if applicable)
player.seek(to: CMTimeMakeWithSeconds(playbackTime, 1))
// 4. dismiss the player once the video is over and update Firebase
NotificationCenter.default.addObserver(self,
selector: #selector(self.playerDidFinishPlaying),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.playerVC.player?.currentItem)
self.present(self.playerVC, animated: true) {
self.playerVC.player?.play()
}
}
}
}
#objc func playerDidFinishPlaying(note: NSNotification) {
self.playerVC.dismiss(animated: true)
Video.updateFirebase(firebaseVideoID: video.firebaseID)
}
}
Then have your classes that use it:
class Step3JobSummaryVC: VideoPlayingBaseController {
//more code here
}
I'm trying to learn how to build a Game-Center based app. its a turn based extremely simple game that just basically logs what button you press and sends it to he other player. I am having a very hard time implementing all the game center features since Apple's documentation has not been updated for Swift. I've been guessing at everything and reading off Objective-C examples and hoping for the best (somehow I've managed to get a few things going, altough I'm not sure if they are correct)
Anyways, I made a new iCloud account for my Simulator and ran the app on my phone and the simulator simultaneously, trying to get them to match up in a game. However I always get a "match request is invalid" error:
EDIT I have registered the app in iTunesConnect, I've implemented leaderboards and tested them and they work (So I assume the iTunesConnect thing is properly working and set-up)
#IBAction func startTapped(_ sender: Any) {
let request = GKMatchRequest()
request.maxPlayers = 2
request.minPlayers = 1
let mmvc = GKMatchmakerViewController(matchRequest: request)
mmvc?.matchmakerDelegate = self
present(mmvc!, animated: true, completion: nil)
}
extension HomeVC: GKGameCenterControllerDelegate
{
func authenticatePlayer()
{
//CALLED ON VIEWDIDAPPEAR
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {
(view, error) in
if view != nil
{
self.present(view!, animated: true, completion: nil)
} else {
print("AUTHENTICATED!")
print(GKLocalPlayer.localPlayer().isAuthenticated)
}
}
}
func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismiss(animated: true, completion: nil)
}
}
Here's the matchmaking code. Note I can get the Game Center screen to appear and tell me "how many players" and that its missing a player and gives the choice to invite friends and all that
extension HomeVC: GKMatchmakerViewControllerDelegate {
func matchmakerViewControllerWasCancelled(_ viewController: GKMatchmakerViewController) {
print("match was cancelled")
viewController.dismiss(animated: true, completion: nil)
}
func matchmakerViewController(_ viewController: GKMatchmakerViewController, didFailWithError error: Error) {
print("didFailwithError: \(error.localizedDescription)")
}
func matchmakerViewController(_ viewController: GKMatchmakerViewController, didFind match: GKMatch) {
print("Match found, ID: \(match.description)")
let gameScreenVC = self.storyboard?.instantiateViewController(withIdentifier: "mainGame") as! ViewController
gameScreenVC.providesPresentationContextTransitionStyle = true
gameScreenVC.definesPresentationContext = true
gameScreenVC.modalPresentationStyle = UIModalPresentationStyle.fullScreen
gameScreenVC.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
match.delegate = gameScreenVC
self.present(gameScreenVC, animated: true, completion: nil)
}
}
If anyone is having this issue I fixed it by just replacing the "minelayers and max-layers" with the same amount, so the code now looks like this:
#IBAction func startTapped(_ sender: Any) {
let request = GKMatchRequest()
request.maxPlayers = 2 //min and max should be the same
request.minPlayers = 2 //apparently they need to be the same
let mmvc = GKMatchmakerViewController(matchRequest: request)
mmvc?.matchmakerDelegate = self
present(mmvc!, animated: true, completion: nil)
}
apparently Game Center does not like a min.player count of 1, its invalid. but thats how I got it to stop giving me Invalid Match Request warnings
I did everything right regarding itunes connect, but when I start the app it doesn't authenticate, and when I press my Gamecenter button it gives me a message "Gamecenter is not available, user not signed in"
Code:
ViewController:
import UIKit
import SpriteKit
import GameKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
authenticateLocalPlayer()
}
//initiate gamecenter
func authenticateLocalPlayer(){
print("Starting..1")
let localPlayer = GKLocalPlayer.localPlayer()
print("Starting..2")
localPlayer.authenticateHandler = {(viewController, error) -> Void in
print("Starting..3")
if (viewController != nil) {
print("Not signed in. Authenticating now")
var vc = self.view?.window?.rootViewController
vc?.presentViewController(viewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
GameScene:
import SpriteKit
import iAd
import GameKit
class GameScene: SKScene, SKPhysicsContactDelegate, ADBannerViewDelegate, GKGameCenterControllerDelegate {
func saveHighscore(score:Int) {
//check if user is signed in
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "CC_Leaderboard_1") //leaderboard id here
scoreReporter.value = Int64(score) //score variable here (same as above)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {(error : NSError?) -> Void in
if error != nil {
print("error") }
})
}
}
//shows leaderboard screen
func showLeader() {
var vc = self.view?.window?.rootViewController
var gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
//hides leaderboard screen
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
I also tried authenticating inside GameScene but that didn't work either.
Edit:
//initiate gamecenter
func authenticateLocalPlayer(){
print("Gamecenter..1")
let localPlayer = GKLocalPlayer.localPlayer()
print("Gamecenter..2")
localPlayer.authenticateHandler = {(viewController, error) -> Void in
print("Gamecenter..3")
if (viewController != nil) {
print("Not signed in. Authenticating now")
var vc = self.view?.window?.rootViewController
vc?.presentViewController(viewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
Take a look at the print statements at the function above, "Gamecenter..3" never get printed.
Edit 2:
The problem solved itself when I signed out of Gamecenter. (How stupid this kind of stuff)
One possibility: In your authentication handler, you never check the error code. You only check if the viewController is set or not. If the error code is set, though, the viewController will (usually) be nil, so you could be interpreting a login error as a successful login.
I would change the code to:
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (error != nil)
{
//add some stuff to report the error
}
else if (viewController != nil){
print("Not signed in. Authenticating now")
var vc = self.view?.window?.rootViewController
vc?.presentViewController(viewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
to confirm you're not getting an error on login. If you are getting an error, that will provide a clue as to what to look at next.
Edit
Some folks are reporting authentication problems until they add at least 1 leaderboard or achievement in iTunesConnect. In those reports, they're actually getting a login error that the game isn't recognized. But, given the inherent flakiness in game center, I would add a placeholder achievement or leaderboard to rule that issue out. (iOS9 “This game is not recognized by game center.”)