Background Audio stops on navigating to different Views in a SwiftUI app? - swiftui

I'm trying to play a background sound in SwiftUI.
I can play the sound like this:
var aplayer = AVPlayer()
And
onAppear {
let aplayerItem = AVPlayerItem(url: URL(string: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3")!)
aplayer = AVPlayer(playerItem: aplayerItem)
aplayer.play()
}
But the above code has issues.
1- it only loops for a short period of time.
2- If I navigate between my Views, the sound will stop. I know this is happening because my code is inside the .onAppear {...}. But How do I make it accessible and keep on playing even if the Views being changed?

Related

SwiftUI programatic navigation loses binding

I'm building a "Charade-like-game" app with five screens and a simple GameModel class consisting of five objects, including teams, round, and scores.
Screen1 dynamically determines the number of teams playing via #StateObject.
Screen2 is a scoreboard that displays in a VStack the dynamic number of teams playing via #ObservedObject, alongside scores from two rounds of play.
Screen3 shows the Charade criteria, and Screen4 is a timer. Neither of these affect the GameModel.
Screen5 gives the other teams a chance to score the performance. That value is then passed back to the Screen2 scoreboard.
The navigation flow begins at 1 then moves to 2 > 3 > 4 > 5 then back to 2 to repeat the cycle until the users close the app, like this:
I got tangled up in the navigation because I wanted the back button on Screen2 to start the game over, rather than return to Screen5. When I used #Environment(\.presentationMode) and presentationMode.wrappedValue.dismiss(), it didn't navigate properly.
Therefore I tried building the navigation programmatically using a #State Boolean, to change the screen like this:
if playGamePressed {
Screen2View()
} else {
Screen1 code...
Select # of teams binded via #StateObject (my source of truth)
Button(action: {
playGamePressed = true
}
}
Now, the #StateObject binding no longer passes the dynamic number of teams. Everything worked prior to changing the navigation to programmatic.
I used this as a reference: Swiftui nested navigation issue while using navigation link, but the solution was .presentationMode that didn't work for me.
What am I missing?

SwiftUi - prevent update

Basic question: how can I prevent a SwiftUI subview to redraw when the parent redraws?
I am experiencing performance issues on my project because so many nested views are constantly redrawn. Now, so of them are redrawn without anything being changed. To debug my problem, I even tried to simplify the problem to its core:
struct StupidView: View {
init() {
print("redraw")
}
var body: some View {
ZStack{}
}
}
This empty view, of course, does not have any moving part that requires redrawing, but it gets redrawn every time its parent is redrawn.
I even tried to add a .id(1) to it with no results. Of course, my problem is with complex views whose useless redrawing slows down the app. How to not redraw parts of a view?
Initializing a View has no meaning of rendering!
It does not rendered but got initialized try this down code, in SwiftUI initializing a View is pretty normal thing! A View could get Initializied 100 times but it will rendered if it needs! if your View has complex Content that SwiftUI get confused we can help SwiftUI in this way that make our View Equatable, then SwiftUI will understand when really need to rendered!
struct StupidView: View {
init() {
print("initializing!")
}
var body: some View {
print("rendering!")
return ZStack{}
}
}

How not to let Home button quit your tvOS app in SwiftUI

In UIKit we can use GCEventViewController to intercept the game controllers from propagating Home button presses to the responders (and have them quit our app) by setting controllerUserInteractionEnabled = false
SwiftUI Apps do not use ViewControllers, so, except for resurrecting one to embed the whole app in it, we can't use the above solution to avoid the player leaving out game / app by accident by pressing the wrong button (not can we use buttonB)
Does anyone know a solution to this conundrum? Did Apple already implement a new way to intercept high-level events so we can deal with them internally?
Thanks!
You have to have a focusable view (like a button) and then you can use onExitCommand in the containing view to simply ignore the exit event, or do something different.
struct ContentView: View {
var body: some View {
VStack {
Button("Hello, world!") {
}
}.onExitCommand {
// Do nothing
}
}
}

Can't catch bluetooth headset button click event in swiftui 2.0

I am trying to simply execute code on a click of a bluetooth headset button in a SwiftUI 2.0 app, but after trying many different codes, nothing have worked... Does someone have solved this issue?
Based on apple docs and some answer I found on StackOverflow (https://stackoverflow.com/a/58249502/13207818), I tried this simple code
import SwiftUI
import MediaPlayer
struct ContentView: View {
init() {
MPRemoteCommandCenter.shared().pauseCommand.isEnabled = true
MPRemoteCommandCenter.shared().pauseCommand.addTarget(handler: { (event) in
print("Pause")
return MPRemoteCommandHandlerStatus.success
})
MPRemoteCommandCenter.shared().playCommand.isEnabled = true
MPRemoteCommandCenter.shared().playCommand.addTarget(handler: { (event) in
print("Play")
return MPRemoteCommandHandlerStatus.success
})
MPRemoteCommandCenter.shared().togglePlayPauseCommand.addTarget (handler: { (event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus in
// middle button (toggle/pause) is clicked
print("event:", event.command)
return .success
})
}
var body: some View {
Text("Hello World")
}
}
Of course Enabling Background Audio as per Apple doc
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
Even tried to activate my app audio session:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: .default, options: [.duckOthers, .allowBluetooth, .allowBluetoothA2DP])
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
print("audioSession is Active")
} catch {
print("audioSession properties weren't set because of an error.")
print(error)
}
But everything failed...
Would someone know what I am doing wrong or would have faced such issue with swiftUI 2.0?
Thank in advance for your support
In general you shouldn’t do actions in the initializers of views. Since they represent the state of the UI, not the actual UI they could be broken down and created again whenever SwiftUI thinks it needs to.
Im not at my pc but You can probably get a Publisher for the pause button which you can bind to a view with onReceive
Finally, I got a solution for my issue.
I don't know how it works really behind but the audio focus wasn't on my app. So I've just played a silent sound for a second and I could play properly with my play/pause button. I know that it's not a proper solution, but it works!
This reminds me of a similar bug on the galaxy s8...
If I find a better one, I'll keep you posted.

What is the equivalent of WKInterfaceController.didAppear() in SwiftUI on watchOS?

I'm building an Apple Watch app, and there is code I want to run every time the app is brought to the foreground.
Previously, if I wanted to do this in a watchOS with a WKInterfaceController, I would put this code in didAppear().
In SwiftUI, there is onAppear(), but when I call that on watchOS it only seems to be called the first time the app loads up, so it behaves like WKInterfaceController.willActivate() instead. The app has just a single view.
If onAppear() is the equivalent of WKInterfaceController.willActivate(), is there a different SwiftUI function that is the equivalent of WKInterfaceController.didAppear()?
Here's my current example code:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello World").font(.footnote)
.onAppear {
print("onAppear called")
}
}
}
In the meantime, I am going to experiment with triggering what I need to do within the ExtensionDelegate, but I'm just trying to learn my way around SwiftUI on WatchOS, so knowing the answer to this would be helpful in the future.