如何从自定义委托类内部的回调更新视图



我正在开发一个基督教应用程序,一切都很顺利,除了一件事:我无法解决如何在我的AVSpeechSynthesizer说完后让标签更新其文本。

例如,在祈祷完成后,文本应该更新为"阅读";播放";再一次它在所有其他已知场景中都能正确地做到这一点(暂停工作、恢复工作、停止工作、重新启动工作等,如标签中相应更新的内容(。

请在这里查看我的代码:

import SwiftUI
import AVFoundation
class GlobalVarsModel: ObservableObject {
@Published var prayerAudioID: UUID?
@Published var uttPrayerAudio = ""
@Published var strAudioBtnImgStr = "play.fill"
@Published var strAudioBtnText = "Play Audio"
static let audioSession = AVAudioSession.sharedInstance()
static var synthesizer = CustomAVSpeechSynth()
}
class CustomAVSpeechSynth: AVSpeechSynthesizer, AVSpeechSynthesizerDelegate {

//NOT DESIRED OUTPUT LIST
//@Published
//@ObservedObject
//@State

@StateObject var gVars = GlobalVarsModel()

override init() {
super.init()
delegate = self
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didPause utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
print("Finished praying.")
print(gVars.strAudioBtnText)
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didContinue utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
}
}
struct TappedPrayerView: View {
public var tappedPrayer: Prayer
@StateObject public var gVars = GlobalVarsModel()
@Environment(.scenePhase) var scenePhase

var body: some View {
ScrollView {
VStack {
Text(tappedPrayer.strTitle).font(.title2).padding()
HStack {
Spacer()
Button {
gVars.prayerAudioID = tappedPrayer.id
gVars.uttPrayerAudio = tappedPrayer.strText

if (gVars.strAudioBtnText == "Play Audio") {
gVars.strAudioBtnImgStr = "pause.fill"
gVars.strAudioBtnText = "Pause Audio"
if (GlobalVarsModel.synthesizer.isSpeaking || GlobalVarsModel.synthesizer.isPaused) {
GlobalVarsModel.synthesizer.stopSpeaking(at: .immediate)
GlobalVarsModel.synthesizer.speak(AVSpeechUtterance(string: gVars.uttPrayerAudio))
} else {
GlobalVarsModel.synthesizer.speak(AVSpeechUtterance(string: gVars.uttPrayerAudio))
}
} else if (gVars.strAudioBtnText == "Pause Audio") {
GlobalVarsModel.synthesizer.pauseSpeaking(at: .immediate)
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Continue Audio"
} else if (gVars.strAudioBtnText == "Continue Audio") {
if (GlobalVarsModel.synthesizer.isPaused) {
GlobalVarsModel.synthesizer.continueSpeaking()
gVars.strAudioBtnImgStr = "pause.fill"
gVars.strAudioBtnText = "Pause Audio"
}
}
} label: {
Label(gVars.strAudioBtnText, systemImage: gVars.strAudioBtnImgStr).font(.title3).padding()
}.onAppear {
if ((GlobalVarsModel.synthesizer.isSpeaking || GlobalVarsModel.synthesizer.isPaused) && tappedPrayer.id != gVars.prayerAudioID) {
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Play Audio"
}
}
Spacer()
Button {
if (GlobalVarsModel.synthesizer.isSpeaking || GlobalVarsModel.synthesizer.isPaused) {
GlobalVarsModel.synthesizer.stopSpeaking(at: .immediate)
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Play Audio"
gVars.prayerAudioID = UUID(uuidString: String(Int.random(in: 0..<7)) + (gVars.prayerAudioID?.uuidString ?? "777"))
}
} label: {
Label("Restart", systemImage: "restart.circle.fill").font(.title3).padding()
}
Spacer()
}
Spacer()
Text(tappedPrayer.strText).padding()
Spacer()
}
}.onAppear {
if (GlobalVarsModel.synthesizer.isPaused) {
if (tappedPrayer.id == gVars.prayerAudioID) {
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Continue Audio"
}
} else if (GlobalVarsModel.synthesizer.isSpeaking) {
if (tappedPrayer.id == gVars.prayerAudioID) {
gVars.strAudioBtnImgStr = "pause.fill"
gVars.strAudioBtnText = "Pause Audio"
}
} else {
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Play Audio"
}
}.onChange(of: scenePhase) { newPhase in
if (newPhase == .active) {
} else if (newPhase == .inactive) {
} else if (newPhase == .background) {
}
}
}

struct TappedPrayerView_Previews: PreviewProvider {
static var previews: some View {
let defaultPrayer = Prayer(strTitle: "Default title", strText: "Default text")
TappedPrayerView(tappedPrayer: defaultPrayer)
}
}
}

您的代码存在多个问题。

  1. 您正在初始化GlobalVarsModel两次。一次在视图中,一次在代理中。因此,一个方面的变化不会反映在另一个方面。

  2. 您正在AVSpeechSynthesizer的子类中实现委托,因此它被封装在其中,当事件发生时,您无法更新视图。

我更改了实现以解决以下问题:


class GlobalVarsViewmodel: NSObject, ObservableObject { //You need to derive from NSObject first, because `AVSpeechSynthesizer` is `objc` related
@Published var prayerAudioID: UUID?
@Published var uttPrayerAudio = ""
@Published var strAudioBtnImgStr = "play.fill"
@Published var strAudioBtnText = "Play Audio"
let audioSession = AVAudioSession.sharedInstance()
var synthesizer = CustomAVSpeechSynth()

override init(){
super.init()
synthesizer.delegate = self // assign the delegate
}
}
extension GlobalVarsViewmodel: AVSpeechSynthesizerDelegate{ // extend the viewmodel to implement the delegate

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didPause utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
print("Finished praying.")
strAudioBtnImgStr = "play.fill" // here assign the text and button appearance
strAudioBtnText = "Play Audio"
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didContinue utterance: AVSpeechUtterance) {
}

func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
}
}
// I don´t think you need this anymore
class CustomAVSpeechSynth: AVSpeechSynthesizer {

//NOT DESIRED OUTPUT LIST
//@Published
//@ObservedObject
//@State
}
struct TappedPrayerView: View {
var tappedPrayer: Prayer
@StateObject private var gVars = GlobalVarsViewmodel()
@Environment(.scenePhase) var scenePhase

var body: some View {
ScrollView {
VStack {
Text(tappedPrayer.strTitle).font(.title2).padding()
HStack {
Spacer()
Button {
gVars.prayerAudioID = tappedPrayer.id
gVars.uttPrayerAudio = tappedPrayer.strText

if (gVars.strAudioBtnText == "Play Audio") {
gVars.strAudioBtnImgStr = "pause.fill"
gVars.strAudioBtnText = "Pause Audio"
if (gVars.synthesizer.isSpeaking || gVars.synthesizer.isPaused) {
gVars.synthesizer.stopSpeaking(at: .immediate)
gVars.synthesizer.speak(AVSpeechUtterance(string: gVars.uttPrayerAudio))
} else {
gVars.synthesizer.speak(AVSpeechUtterance(string: gVars.uttPrayerAudio))
}
} else if (gVars.strAudioBtnText == "Pause Audio") {
gVars.synthesizer.pauseSpeaking(at: .immediate)
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Continue Audio"
} else if (gVars.strAudioBtnText == "Continue Audio") {
if (gVars.synthesizer.isPaused) {
gVars.synthesizer.continueSpeaking()
gVars.strAudioBtnImgStr = "pause.fill"
gVars.strAudioBtnText = "Pause Audio"
}
}
} label: {
Label(gVars.strAudioBtnText, systemImage: gVars.strAudioBtnImgStr).font(.title3).padding()
}.onAppear {
if ((gVars.synthesizer.isSpeaking || gVars.synthesizer.isPaused) && tappedPrayer.id != gVars.prayerAudioID) {
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Play Audio"
}
}
Spacer()
Button {
if (gVars.synthesizer.isSpeaking || gVars.synthesizer.isPaused) {
gVars.synthesizer.stopSpeaking(at: .immediate)
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Play Audio"
gVars.prayerAudioID = UUID(uuidString: String(Int.random(in: 0..<7)) + (gVars.prayerAudioID?.uuidString ?? "777"))
}
} label: {
Label("Restart", systemImage: "restart.circle.fill").font(.title3).padding()
}
Spacer()
}
Spacer()
Text(tappedPrayer.strText).padding()
Spacer()
}
}.onAppear {
if (gVars.synthesizer.isPaused) {
if (tappedPrayer.id == gVars.prayerAudioID) {
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Continue Audio"
}
} else if (gVars.synthesizer.isSpeaking) {
if (tappedPrayer.id == gVars.prayerAudioID) {
gVars.strAudioBtnImgStr = "pause.fill"
gVars.strAudioBtnText = "Pause Audio"
}
} else {
gVars.strAudioBtnImgStr = "play.fill"
gVars.strAudioBtnText = "Play Audio"
}
}.onChange(of: scenePhase) { newPhase in
if (newPhase == .active) {
} else if (newPhase == .inactive) {
} else if (newPhase == .background) {
}
}
}

struct TappedPrayerView_Previews: PreviewProvider {
static var previews: some View {
let defaultPrayer = Prayer(strTitle: "Default title", strText: "Default text")
TappedPrayerView(tappedPrayer: defaultPrayer)
}
}
}

备注:

  • 我将GlobalVarsModel的名称更改为GlobalVarsViewmodel,因为它正是Viewmodel
  • 我将合成器var更改为与实例相关,而不是静态
  • AVAudioSession相同

编辑以回复注释以进行澄清:我将实现从静态更改为静态,因为这里不需要它。你可以在这里阅读更多信息->https://www.donnywals.com/effectively-using-static-and-class-methods-and-properties/

相关内容

  • 没有找到相关文章

最新更新