How to show a sheet before multiple AlertSounds finishes? - swiftui

I'm currently developing an application using SwiftUI.
I want to show a sheet before the alert sounds with sleep and for loop multiple times finish.
In my code, the sheet appears after alert sounds finish playing.
How could I do this?
Here is the code:
import SwiftUI
import AudioToolbox
struct ContentView: View {
#State var isSheet = false
var body: some View {
VStack{
Text("SOUND and OPEN SHEET")
.padding()
}
.onTapGesture {
isSheet.toggle()
for _ in 1...2{
AudioServicesPlayAlertSoundWithCompletion(1151,nil)
sleep(2)
}
}
.sheet(isPresented: $isSheet){
Sheet()
}
}
}
struct Sheet: View {
var body: some View {
Text("Sheet")
.padding()
}
}

Play sound in background, like
.onTapGesture {
isSheet.toggle()
DispatchQueue.global(qos: .background).async { // << this !!
for _ in 1...2{
AudioServicesPlayAlertSoundWithCompletion(1151,nil)
sleep(2)
}
}
}

Related

SwiftUI: Logout and switch screens from a popover

In my app, I have a View which is set to either a login View or a home TabView, depending on if the user is logged in. From the TabView, the user can go to a profile popover and logout. I want to switch back to the login View from this popover.
I tried dismissing the popover and immediately logging the user out, but when I test on a real device, what happens is the popover stays on the screen and also no longer responds to user input. It can't be dismissed. I'm not sure why, and what should I do instead?
Starting View:
struct StartView: View {
#EnvironmentObject var authService:AuthService
var body: some View {
ZStack {
if(!authService.signedIn) {
LoginView()
} else {
HomeView()
}
}
}
}
Home TabView:
import SwiftUI
struct HomeView: View {
#State private var showingProfilePopover:Bool = false
var body: some View {
TabView {
NavigationView {
VStack(alignment: .leading) {
Text("Tab 1")
.padding(.leading, 30)
}
.toolbar {
ToolbarItem {
Button(action: {
showingProfilePopover = true
}, label: {
Image(systemName: "person.crop.circle").imageScale(.large)
}
)
}
}
}.popover(isPresented: $showingProfilePopover) {
ProfileView(isPresented: $showingProfilePopover)
}
.tabItem {
Image(systemName: "list.bullet")
.font(.system(size: 26))
Text("Tab 1")
}
NavigationView {
VStack(alignment: .leading) {
Text("Tab 2")
}
}.tabItem {
Image(systemName: "books.vertical")
.font(.system(size: 26))
Text("Tab 2")
}
}
}
}
Popover:
struct ProfileView: View {
#EnvironmentObject var authService:AuthService
#Binding var isPresented: Bool
var body: some View {
Button("Logout") {
// Close the popup and switch to LoginView
print("Tapped logout")
isPresented = false
authService.signOut()
}
.font(Font.custom("OpenSans-Regular", size: 18))
.padding(20)
}
}
LoginView:
import SwiftUI
struct LoginView: View {
#EnvironmentObject var authService:AuthService
var body: some View {
VStack {
Button("Login") {
self.authService.signIn()
}.buttonStyle(.borderedProminent)
}
}
}
AuthService:
import SwiftUI
class AuthService: ObservableObject {
#Published var signedIn:Bool
init(signedIn:Bool) {
self.signedIn = signedIn
}
func signIn() {
self.signedIn = true
}
func signOut(){
self.signedIn = false
}
}
Seems like an issue connected to .popover. I can reproduce the issue, but it works just fine using .sheet instead.
Consider attaching the .popover on the TabView or the Button itself then it seems to work just fine.
I realized the issue only happens on the older versions of iOS. it works fine on iOS 15, but not on iOS 14 and below iOS 14.
#main
struct LoginApp: App {
let authService: AuthService
init() {
authService = AuthService(signedIn: false)
}
var body: some Scene {
WindowGroup {
StartView().environmentObject(authService)
}
}
}
This is iPhone 8, iOS 14

SwiftUI sharing timer through views in navigation view

I'm pretty new at SwiftUI.
I would like to make an app that navigates through different views in a Navigation View and running a timer in background presenting the time on each of the views.
The problem is when I navigate to a second level of navigation. When the timer is on, the app returns automatically to the previous navigation view.
Here's is a screenshot video of what I mean:
https://youtu.be/eXbK9jpluvk
Here is my code:
import SwiftUI
struct ContentView: View {
#State var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State var seconds : Int = 0
#State var paused : Bool = true
var body: some View {
VStack {
Text("\(seconds)")
Button(action: {
paused.toggle()
}) {
Image(systemName: paused ? "play.fill" : "stop.fill")
}
NavigationLink(destination: FirstView( timerSeconds: $seconds, timerPaused: $paused)) {
Text("Navigate 1")
}.padding()
}
.onReceive(self.timer) { currentTime in
if !paused {
seconds = seconds + 1
print(currentTime)
}
}
.navigationViewStyle(.stack)
}
}
struct FirstView: View {
#Binding var timerSeconds : Int
#Binding var timerPaused : Bool
var body: some View {
VStack {
Text("First View")
Text("\(timerSeconds)")
Button(action: {
timerPaused.toggle()
}) {
Image(systemName: timerPaused ? "play.fill" : "stop.fill")
}
NavigationLink(destination: SecondView(seconds: $timerSeconds)) {
Text("Navigate 2")
}
}
}
}
struct SecondView: View {
#Binding var seconds : Int
var body: some View {
Text("\(seconds)")
}
}
Any help will be welcome!!
Thanks a lot
NavigationView can only push on one detail NavigationLink so to have more levels you need to set .isDetailLink(false) on the link. Alternatively, if you don't expect to run in landscape split view, you could set .navigationViewStyle(.stack) on the navigation.

Sheet freeze in SwiftUI when parent-View gets updated

Using Swift5.3.2, iOS14.4.1, XCode12.4,
I am showing a modal sheet in SwiftUI.
Unfortunately, the sheet completely freezes whenever an underlying parent-View re-renders/updates.
I am absolutely clueless on how I can circumvent that issue. Any ideas ?
Here is the parent View:
import SwiftUI
struct ParentTrialView: View {
#State var currentDate = Date()
#State private var showingGrid = false
let timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect()
var body: some View {
NavigationView {
Text("\(currentDate)")
.onReceive(timer) { input in
currentDate = input
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showingGrid.toggle()
}) {
Image(systemName: "square.grid.3x3")
}
.sheet(isPresented: $showingGrid) {
ChildTrialView(onDismiss: {
showingGrid = false
})
}
}
}
}
}
}
And here is the child View:
import SwiftUI
struct ChildTrialView: View {
var onDismiss: () -> ()
var body: some View {
NavigationView {
Text("Trial")
.onTapGesture {
onDismiss()
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
onDismiss()
}) {
Text("Cancel")
}
}
}
}
}
}
If you, for example, set the timer to 20 seconds. Then you have 20 seconds where everything works on the child View (i.e. it's tap-Gesture works, and also its Cancel button to close the child-View works perfectly).
However, when the timer fires an update to the parent-View, then no action works on the child-View anymore !!
I am more than desperate with this SwiftUI problem.
Any solution on this ?

How to repeat to play a system sound and make a vibration using `AudioServicesPlayAlertSoundWithCompletion`?

I'm currently developing an application using SwiftUI.
I want to know how to repeat playing a system sound and make a vibration using AudioServicesPlayAlertSoundWithCompletion.
I want to repeat them a couple of times and until when a user taps some button.
How could I do this?
ContentView.swift
import SwiftUI
import AudioToolbox
struct ContentView: View {
var SoundID:SystemSoundID = 1151
var body: some View {
VStack{
Text("Sound")
.padding()
.onTapGesture {
makeSoundAndVibration()
}
Text("Stop")
.padding()
.onTapGesture {
stopSoundAndVibration()
}
}
}
func makeSoundAndVibration(){
AudioServicesPlayAlertSoundWithCompletion(SoundID, nil)
AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) {}
}
func stopSoundAndVibration(){
// do someting to stop the sound and vibration
}
}
SystemSoundTestApp.swift
import SwiftUI
#main
struct SystemSoundTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Xcode: Version 12.0.1
iOS: 14.0

How to fire `onAppear` method when a view comes back from `App Settings` changing a notifications setting?

I'm currently developing an application using SwiftUI.
I'm trying to make an app using Local Notification.
I want to reflect on a set of Allow Notifications in an App Settings to some views.
But when a view comes back from the App Settings using a navigation link I attached(not back button), the onAppear method doesn't fire and I can't show a collect value...
In my codes:
1.Tap Open Settings to navigate to an App Setting
2.Change a Notifications setting
3.Tap NotificationTest button
4.Come back to ContentView then onAppear method doesn't fire
How could I solve this problem?
Here are the codes:
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var isNotification = false
var body: some View {
VStack{
Text(isNotification ? "STATUS:ON" : "STATUS:OFF")
.padding()
Text("Open Settings")
.padding()
.onTapGesture {
UIApplication.shared.open(URL(string:UIApplication.openSettingsURLString)!)
}
}
.onAppear(){
confirmNotification()
}
}
func confirmNotification(){
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .badge, .sound]){
success, error in
if success{
isNotification = true
print("Notification set")
}else{
isNotification = false
}
}
}
}
NotificationTestApp.swift
import SwiftUI
#main
struct NotificationTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Xcode: Version 12.0.1
iOS: 14.0
Life Cycle: SwiftUI App
View has already appeared, just application is in background. Try to use instead (or additionally) scenePhase
struct ContentView: View {
#Environment(\.scenePhase) private var scenePhase
#State var isNotification = false
var body: some View {
VStack{
Text(isNotification ? "STATUS:ON" : "STATUS:OFF")
.padding()
Text("Open Settings")
.padding()
.onTapGesture {
UIApplication.shared.open(URL(string:UIApplication.openSettingsURLString)!)
}
}
.onAppear(){
confirmNotification()
}
.onChange(of: scenePhase) { phase in
switch phase {
case .active:
confirmNotification()
default:
break
}
}
}