AVPlayerViewController play/pause not working in tvOS - tvos

I am working on tvOS app .I have a list of videos and before play main video need to play DFP ad with skip functionality.
I have added skip button on the AVPlayerViewController's view and manage focus(move focus skip button to AVPlayerViewController's view). Focus is working fine if i added AVPlayerViewController as a childViewController but in main video play/pause functionality not working.
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
If i have added AVPlayerViewController as ParentViewController then focus is not working.
self.view.addSubview(playerController.view)
playerController.didMoveToParentViewController(self)
I have implemented blow code
let playerController = AVPlayerViewController()
var playerObj:AVPlayer!
var asset:AVAsset!
var playerItem:AVPlayerItem!
override func viewDidLoad() {
super.viewDidLoad()
playerController.view.frame = CGRectMake(0.0, 0.0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
self.preferredFocusedView
self.updateFocusIfNeeded()
}
func playVideo(videoURL:String) {
self.playerController.showsPlaybackControls = false
self.asset = AVAsset(URL: NSURL(string: videoURL)!) as AVAsset
self.playerItem = AVPlayerItem(asset: self.asset)
self.playerObj = AVPlayer(playerItem: self.playerItem)
self.playerController.player = self.playerObj
self.playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VideoPlayerViewController.playerItemDidReachEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: self.playerObj.currentItem)
if !self.controlFlag{
// For main video
playFlag = true
self.playerController.showsPlaybackControls = true
}else{
// for DFP ad
playFlag = false
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VideoPlayerViewController.playerStalledNotification(_:)), name: AVPlayerItemPlaybackStalledNotification, object: self.playerObj.currentItem)
}
self.playerController.player!.play()
}
override var preferredFocusedView: UIView? {
get {
if skipAdsButton != nil {
print("focus on skip button")
return self.skipAdsButton
}else{
print("focus on super.preferredFocusedView")
return super.preferredFocusedView
}
}
}
How to fix this issue. Please suggest it.

Related

How to display view within a sheet programmatically in SwiftUI?

I am currently using Google Maps API in SwiftUI, and am trying to get a sheet to appear after a marker infoWindow is tapped programmatically.
In other parts of my app I’m displaying sheets like this, and it’s the same thing I’m trying to achieve here but programmatically:
https://blog.kaltoun.cz/swiftui-presenting-modal-sheet/
Right now I have a function that prints a message when an infoWindow is tapped, but don’t know how to make a SwiftUI view appear within a sheet using the function.
-
Since I’m using SwiftUI, the way I implement the Google Maps API is a little different than in plain Swift.
Here are the basics of my GMView.swift file that handles all the google maps stuff.
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation
struct GoogMapView: View {
var body: some View {
GoogMapControllerRepresentable()
}
}
class GoogMapController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
var locationManager = CLLocationManager()
var mapView: GMSMapView!
let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
var zoomLevel: Float = 15.0
let marker : GMSMarker = GMSMarker()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
// mapView.isHidden = true
}
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}
}
// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
}
struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
return GMController()
}
func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {
}
}
Here is the function I'm putting within GMView.swift's UIViewController (GMController) that I’m trying to use to make a view appear within a sheet:
// Function to handle when a marker's infowindow is tapped
func mapView(_ mapView: GMSMapView, didTapInfoWindowOf didTapInfoWindowOfMarker: GMSMarker) {
print("You tapped a marker's infowindow!")
return
}
Here is the view I’m trying to get to appear:
struct SortBy: View {
var body: some View {
VStack(alignment: .leading) {
Text("Sort By")
.font(.title)
.fontWeight(.black)
.padding(.trailing, 6)
Rectangle()
.fill(Color.blue)
.frame(width: 200, height: 200)
}
}
}
Does anyone know how I can get my function above to make a SwiftUI view appear within a sheet?
Use .sheet similarly like here:
https://blog.appsbymw.com/posts/how-to-present-and-dismiss-a-modal-in-swiftui-155c/
You will need:
In your UIViewControllerRepresentable, add a State object, let's say #State var showModal = false
In your parent view, Use .sheet(isPresented: $showModal) { CONTENT_VIEW(showModal: $showModal) }
In your UIViewControllerRepresentable, also add a Binding for showModal .
In your UIViewControllerRepresentable, use Coordinator to set your UIViewController and GMSMapViewDelegate
In your UIViewController, Now you can access the Binding via owner.showModal
Hope it helps. Good luck.

UIViewController lifecycle broken iOS13, makeKeyAndVisible() seems not to operate?

I have a custom UIStoryboardSegue that works as desired in iOS12.*.
One of the destination view controller is a UITabbarController: for each tab, I have a controller embedded in a navigation controller.
Unfortunately, for iOS13.*, this does not work well: the view controller lifecycle is broken, and no call the viewXXXAppear() nor the willTransition() methods are no longer issued.
It looks like makeKeyAndVisible() has no effect?!
See at the bottom how the screen UI is puzzled below without viewWillAppear() being called.
An horrible temporary workaround
I had to pull my hairs but, I have found a fix which I make public (I had to add a navigation controller on the fly).
This messes the vc hierarchy: do you have a better solution?
public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
override public func perform() {
guard let window = UIApplication.shared.delegate?.window as? UIWindow,
let sourceView = source.view,
let destinationView = destination.view else {
super.perform()
return
}
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
destinationView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
window.insertSubview(destinationView, aboveSubview: sourceView)
// **My fix**
if #available(iOS 13,*) {
// I introduced an invisible navigation controller starting in iOS13 otherwise, my controller attached to the tabbar thru a navigation, dont work correctly, no viewXAppearis called.
let navigationController = UINavigationController.init(rootViewController: self.destination)
navigationController.isNavigationBarHidden = true
window.rootViewController = navigationController
}
else {
window.rootViewController = self.destination
}
window.makeKeyAndVisible()
}
}
I found a solution thanks to Unbalanced calls to begin/end appearance transitions with custom segue
What happens here is that the creation and attaching of the destination view controller happens twice, and the first one happens too soon.
So what you need to do is:
public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
override public func perform() {
guard let window = UIApplication.shared.delegate?.window as? UIWindow,
let sourceView = source.view,
let destinationView = destination.view else {
super.perform()
return
}
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
let mock = createMockView(view: desination.view)
window.insertSubview(mock, aboveSubview: sourceView)
//DO SOME ANIMATION HERE< MIGHT NEED TO DO mock.alpha = 0
//after the animation is done:
window.rootViewController = self.destination
mock.removeFromSuperview()
}
func createMockView(view: UIView) -> UIImageView {
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, UIScreen.main.scale)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImageView(image: image)
}
}
I had a similar problem on iOS 13 when performing a custom storyboard segue that replaces the rootViewController. The original code looked like this:
#interface CustomSegue : UIStoryboardSegue
#end
#implementation CustomSegue
- (void)perform {
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController *destination = (UIViewController *) self.destinationViewController;
[destination.view removeFromSuperview];
[appDelegate.window addSubview:destination.view];
appDelegate.window.rootViewController = destination;
}
#end
Removing the line [appDelegate.window addSubview:destination]; fixed the problem to me. Apparanently, it was unnecessary to add the new VC's view as a subview to the window. It did the job correctly even after removing that line, and it also fixed the error message "unbalanced calls to begin/end appearance transitions".

func draw(_ dirtyRect: NSRect) is not called

I wrote this code in Swift 3.0 but draw method is not called from func drawBorder by needsDisplay = true; drawBorder is called by clicking button in another view.
Thanks for any hint.
class clsDrawView: NSView {
private var redraw = false
var border = NSBezierPath()
var color = NSColor()
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
if redraw {
color.setStroke()
border.stroke()
}
}
func drawBorder() {
redraw = true
color = NSColor.blue
border.lineWidth = CGFloat(10)
border.move(to: NSPoint(x: 20, y: 20))
border.line(to: NSPoint(x: 50, y: 50))
needsDisplay = true
}
}
Try to call setNeedsDisplay(_:) method on your view, this should call draw(_:) method. Never call draw(_:) method by yourself.
Finally I solved it via notification
class clsDrawView: NSView {
private var draw = false
private var border = NSBezierPath()
var color = NSColor()
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
NotificationCenter.default.addObserver(self, selector: #selector(self.drawBorder(_:)), name: NSNotification.Name(rawValue: "drawBorder"), object: nil)
// Drawing code here.
if draw {
color.setStroke()
border.stroke()
}
}
func drawBorder(_ notification: NSNotification) {
draw = true
color = NSColor.black
NSBezierPath.setDefaultLineWidth(4)
border.lineWidth = CGFloat(10)
border = NSBezierPath(rect: self.bounds)
needsDisplay = true
}
}
Notification is postec from ViewController like this
#IBAction func btnDraw(_ sender: NSButton) {
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "drawBorder"), object: sender)
}

Swift NSTimer only runs function once

So I have looked around, and I can't find what I am doing wrong, I am running Swift 1.2, and using SpriteKit by the way.
//showing you just in case
import SpriteKit
//My variables
var countDownLabel = SKLabelNode(fontNamed: "AmericanTypewriter-Light")
var time = 4
var timer = NSTimer()
//Don't want to give you my entire project, I know I can't override a function outside of its class
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if playButton.containsPoint(location) {
//I want this to run 4 times, but is only running once
func countdown() {
time--
countDownLabel.text = "\(time)"
}
//This is set to run my function every second for ever.
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector(countdown()), userInfo: nil, repeats: true)
playButton.removeFromParent()
countDownLabel.fontColor = SKColor.blackColor()
countDownLabel.fontSize = 70
self.addChild(countDownLabel)
}
}
I want my function countdown to run 4 times, but It is only running once for some reason, even though its set to run forever, I am also not getting any error or warnings, sorry if obvious, and if code is hard to understand, I just included the function it was all in.
'Selector(countdown())' is evaluated to nil. So,
1.Move 'countdown' function to outside of touchesBegin.
2.Use 'selector: "countdown"' instead of 'Selector(countdown())'
3.Invalidate your timer after called four times.
var time = 4
var timer = NSTimer()
func countdown() {
if --time == 0 { timer.invalidate() }
println( "\(time)" )
}
func X() {
timer = NSTimer.scheduledTimerWithTimeInterval( 1, target: self, selector: "countdown", userInfo: nil, repeats: true)
}

SWIFT - helper class callback

In order to manage/shorten my code a little, I decided to try to create a helper class to manage all alerts.
The idea is that the helper class will create the alert. When the user presses the button, it will then send a callback to inform the code on the viewController which button is pressed. Then based on that I can fire different bits of code.
Currently the code I have manages to pop up the alert correctly, but I cannot get it to send the callback when the button is pressed.
here is my alert manager I created so far(still basic for simplicity):
var alertMgr: alertManager = alertManager()
class alertManager: NSObject {
func showAlert(titleText:String?, confirmText:String, cancelText:String, msgText:String, showCancel:Bool, showConfirm:Bool, style:UIAlertControllerStyle, viewController:UIViewController) -> Int? {
let alertController = UIAlertController(title: titleText, message: msgText, preferredStyle: style)
var myInt:Int!
let actionLeft = UIAlertAction(title:cancelText, style: .Cancel) { action in
println("0") //works when user presses cancel button
myInt = 0
}
let actionRight = UIAlertAction(title:confirmText, style: .Default) { action in
println("1") //works when user presses confirm button
myInt = 1
}
alertController.addAction(actionLeft)
alertController.addAction(actionRight)
viewController.presentViewController(alertController, animated: true, completion: nil)
return myInt //does not return when button is pressed???
}
}
In my code when I want to show an alert I use the following code:
let titleTxt:String = "title txt goes here."
let confirmTxt:String = "confirm"
let cancelTxt:String = "cancel"
let msgTxt:String = "some msg goes here."
let showCancel:Bool = true
let showConfirm:Bool = true
let style:UIAlertControllerStyle = .ActionSheet
let alert = alertMgr.showAlert(titleTxt, confirmText: confirmTxt, cancelText: cancelTxt, msgText: msgTxt, showCancel: showCancel, showConfirm: showConfirm, style:style, viewController: self)
switch alert as Int! {
case nil:
println("nil value")
case 0:
println("cancel pressed") //never gets fired. when button is pressed
case 1:
println("confirm pressed") //never gets fired. when button is pressed
default:
break
}
The main problem is I cannot seem to get the call back to send the "myInt" value on button press. I have searched through pretty much all questions on here with [UIAlertController] and have not been able to find any hints that would push me in the right direction.
Any help would be great.
showAlert returns the value of myInt before any of the given blocks gets a chance to be executed, the solution is to pass complete action-blocks as parameters of showAlert. In order to shorten the signature you can prepare a special type with text, block and flag values and make the function to process arrays of the objects of this type.