How to override focus engine update sound in tvOS - tvos

I am trying to override the sound that plays when a focus change is made on tvOS, but I cannot seem to find anything indicating if this is possible. I have looked through the Apple documentation a bit, and looked at some of the sound API's but none seemed to fit. Does anybody know if this is even possible? If this is possible how can this be achieved?

This can be achieved with soundIdentifierForFocusUpdate, which was added to the SDK in tvOS 11
Using this method, you can customize or remove the default sound of tvOS played on focus updates.
To remove the sound you can return UIFocusSoundIdentifier.none
override func soundIdentifierForFocusUpdate(in context: UIFocusUpdateContext) -> UIFocusSoundIdentifier? {
return UIFocusSoundIdentifier.none
}
To use a different sound insted, you must include the new sound file in your target, and to load as shown here below:
let myPing = UIFocusSoundIdentifier.init(rawValue: "customPing")
let soundURL = Bundle.main.url(forResource: "ping", withExtension: "aif")!
UIFocusSystem.register(_: soundURL, forSoundIdentifier: myPing)
Then you have to return that sound new from soundIdentifierForFocusUpdate:
override func soundIdentifierForFocusUpdate(in context: UIFocusUpdateContext) -> UIFocusSoundIdentifier? {
return myPing
}
Everything is documented by Apple in the following link:
Using Custom Sounds for Focus Movement

Related

WKWebView Canvas issue. Links inside canvas aren't doing anything

I'm having an issue with WKWebview.
I'm loading a canvas from html into the Webview.
This is the link:
https://cdn-factory.marketjs.com/en/color-fill-playable-ad-demo/index.html
it runs perfectly the html game loads and everything seems to be okay, the touch events are working when you play the mini game tough there is a button "install now"
that should open the app store url and when i tap it nothing happens.
On browser it works and i have tried few of those games but nothings works.
Any advise? the webview preference allowjs and allow inline media are set to true.
Aside that i couldn't find any info on it.
Help is appreciated :D thanks !
iOS: 13.4.1
Swift: 5
iPhone xs max
Okay i figured it out.
In case someone will bash into it in the future.
So the webpage used:
window.open("http://asdfsadfsda")
javascript code to open the url in a new window.
WKWebview wont do anything unless you will implement the WKUIDelegate and the method
webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction,
windowFeatures: WKWindowFeatures) -> WKWebView?;
another important thing in order to make this method to get called is to set in the webview preferences like this:
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
configuration.allowsInlineMediaPlayback = true;
configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
configuration.preferences.javaScriptEnabled = true
And then you can handle the new page opening in the delegate method.
If you still want a new window you should create a new webview and return it in the delegate method.
In my case it was to do nothing and just take the url etc..
so i returned nil.
Good luck :)

How to manage focus handling in AVPlayerViewController with custom controls?

I try to add a skip-intro-button (like Netflix) to the AVPlayerViewController of a tvOS movie app. I added it as subview to the contentOverlayView and when I let it appear I force giving it the focus with preferredFocusEnvironments. Everything's fine...
Until the user navigates somewhere else (e.g. seek bar or video asset info view). The button then loses focus (expected) and never can be focused again by user interaction.
I tried to:
add a UIFocusGuide()
put my button directly on the AVPlayerViewController's view
add a own subview, which contains the buttons to the AVPlayerViewController's view
add a own subview, which contains the buttons to the contentOverlayView
add several other buttons next to, below, above my button on the same subview (for each of the cases above)
The last approach shows that none of the other buttons can ever get focus by user interaction, so it seems, that, for the same reason, the skip-intro-button cannot be focused by the user. But what is this reason?
What is the right practice to add custom interaction elements to AVPlayerViewController?
Anyone any ideas?
Try to answer my main question "What is the right practice to add custom interaction elements to AVPlayerViewController?" myself:
Since I posted this question, 1.5 years ago, I didn't change the implementation of the skip intro feature very much. So whenever the user uses the remote, the button will lose focus and the only thing I changed is, that I hide the button whenever this happens, by implementing didUpdateFocus(in:with:), similar to this:
if let previouslyFocusedView = context.previouslyFocusedView {
if previouslyFocusedView == skipIntroButton {
changeSkipIntroButtonVisibility(to: 0.0)//animates alpha value
}
}
(I'm not completely sure, why I don't set it to isHidden = true)
However, in the meantime I had to implement a more complex overlay to our player, i.e. a "Start Next Video / Watch Credits" thing, with a teaser, some buttons, a countdown/progress bar and more. With the problems described above, it is obvious, that I couldn't go with the contentOverlayView approach.
So I decided to implement it the "traditional way", by presenting a complete UIViewController on top of the player's view, like this:
func showNextVideoOverlay() {
guard let nextVideoTeaser = nextVideoTeaser else { return }
let nextVideoOverlay = NextVideoAnnouncementViewController(withTeaser: nextVideoTeaser, player: player)
nextVideoOverlay.nextVideoAnnouncementDelegate = self
nextVideoOverlay.modalPresentationStyle = .overFullScreen
present(nextVideoOverlay, animated: true, completion: nil)
}
Of course the NextVideoAnnouncementViewController is transparent, so video watching is still possible. I turned out that this straight forward approach works pretty well and I really don't know, why I haven't thought about it, when implementing skip intro.
My colleagues in QA found one tricky thing, you should be aware of (and I think, I remember, that this is different with different Devices and different remotes and on different tvOS versions - try it):
The overlying view controller blocks most of the commands coming from the remote, respectively you can navigate inside the view controller without affecting the player - except the play/pause button. That one will pause the player, but it's not possible to resume from there. This is, because a playing (av)player always listens to this button, while a paused one doesn't. So I also had to implement something like this:
func addRemoteButtonRecognizer() {
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(playPauseButtonPressed))
tapRecognizer.allowedPressTypes = [NSNumber(value: UIPress.PressType.playPause.rawValue)]
self.view.addGestureRecognizer(tapRecognizer)
}
#objc func playPauseButtonPressed(sender: AnyObject) {
nextVideoAnnouncementDelegate?.remotePlayButtonPressed()
}
func remotePlayButtonPressed() {
if player?.playerStatus == .paused {
player?.play()
} else {
player?.pause()
}
}
I hope this helps some of you, who come by here and find no other answer.

Open universal links (deep links) with XCUITest

I would like to create several tests for native iOS application. To be more precise, I want to test deep links. But I am not sure how to trigger deep link with XCUITest and I don't really see how launch() and launcArguments (https://developer.apple.com/documentation/xctest/xcuiapplication) can help me. Did anybody have a chance to open deep link with XCUITest?
In iOS 14 using Spotlight seems to work well:
private func open(_ urlString: String) {
XCUIDevice.shared.press(.home)
XCUIApplication(bundleIdentifier: "com.apple.springboard").swipeDown()
let spotlight = XCUIApplication(bundleIdentifier: "com.apple.Spotlight")
spotlight.textFields["SpotlightSearchField"].typeText(urlString)
spotlight.buttons["Go"].tap()
}
Set safari as app like this
let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
Open your email in safari
Tap on the link
Normally assert some element on app
I never tried this, but this idea comes to my mind. Create a new dummy project/application that should only contain some links pointing to URLs that you expect your original app to open. From that new application, write some UI tests that tap a link, like this:
func testOpeningLinks() {
let app = XCUIApplication()
app.links["Some link text"].tap()
// This is the place where your original app should be opened...
// Find the XCUIApplication object:
let originalApp = XCUIApplication(bundleIdentifier: "original.app.bundle.identifier")
// You should be able to find some views from original app from here, eg. a button:
let button = originalApp.buttons.element
}
This would work only if you have previously installed your app on the device/simulator where you're running your UI tests.

presentControllerWithName in tvOS

in watchOS I used presentControllerWithName to show a View Controller and to pass the context in this way
presentControllerWithName("NameOfTheViewController", context:"PassedContext")
Which is the equivalent in tvOS?
Best Regards
As noted in other answers, the way to programmatically show another view controller in tvOS (or iOS) is performSegueWithIdentifier:sender:. (Or presentViewController:animated:completion: if you're not getting your VCs from a storyboard flow.)
But you might not need to do it programmatically. In watchOS it's sometimes easiest to do it that way, but in iOS & tvOS, it's common to make controls directly perform storyboard transitions entirely from Interface Builder. Just control-drag (right-click-drag) from the button to another view controller. (More step-by-step instructions in Xcode Help.)
Unlike watchOS, the view controller transitions in iOS & tvOS don't include a way to pass context information. Not as part of the API, at least — you have to include a bit of glue code yourself to do that. How to do that is a pretty common question.
If you're using storyboard segues (generally, you should), the prepareForSegue:sender: method is typically where you do this — you get a reference to the new view controller that's about to be shown, and use some function or property you've defined on that view controller to pass it some context. It often looks something like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == mySegueIdentifier {
guard let destination = segue.destinationViewController as? MyViewControllerClass
else { abort("unexpected storyboard segue") }
destination.someProperty = someValue
}
}
You can find good examples of this when you create a new Xcode project with the Master-Detail App template.
tvOS is more similar to iOS than it is to watchOS, although they all have some similarities. In tvOS (like in iOS) you can use both performSegueWithIdentifier:sender: or presentViewController:animated:completion: depending on your situation.
For more on this, you can check out the UIViewController class reference.

Make focus change sound in tvOS

I'm programatically changing focus in a tvOS app in response to a UISwipeGestureRecognizer. This all works fine, except there the nice 'boop' sound that plays when changing focus normally doesn't play, which makes the user experience a bit odd. Is there a way to programatically play this sound, or is there some other way I should be trying to handle the focus change?
You can make the button that is in focus have a background border in a different color or something.
In regards to sounds why don't you run a SKAction or play a AVFoundation file when changing focus.
You said you did all the focus stuff programmatically with gesture recognisers (I have done the same for my SpriteKit game) so I assume in the methods that get called when swiping you need to add the sound effect.
In my game those methods look something like this
func swipedRightTV() {
if menuButton.isFocused {
menuButton.isFocused = false
// play your sound here
playButton.isFocused = true
}
func swipeLeftTV() {
if playButton.isFocused {
playButton.isFocused = false
// play your sound here
menuButton.isFocused = true
}
If I have more than 2 buttons I use more if/else if statements in the methods, for simplicity tho I just used 2 in the example.
Does this help or are your methods totally different?