Swift协议方法未触发求助:跨类AVAudioPlayer与UIImageView交互
解决AVAudioPlayer代理未触发与跨类禁用UIImageView的问题
先揪出你代码里最关键的问题:你手动调用了audioPlayerDidFinishPlaying(player),而不是让AVAudioPlayer在播放结束后自动触发代理回调,这也是自定义协议方法始终没执行的核心原因。下面一步步帮你修正:
1. 修复Card类的AVAudioPlayer代理逻辑
首先,你的Card类必须遵守AVAudioPlayerDelegate协议,才能接收播放完成的系统回调,同时要把之前注释掉的player?.delegate = self打开:
import Foundation import UIKit import AVFoundation // 让协议遵守AnyObject,后续可以用weak修饰delegate避免循环引用 protocol isOnProtocol: AnyObject { func isOn() } class Card: NSObject, AVAudioPlayerDelegate { // 添加AVAudioPlayerDelegate协议 weak var delegate: isOnProtocol? // 用weak修饰delegate var player: AVAudioPlayer? var image: UIImage var soundUrl: String init(image: UIImage, soundUrl: String, isActive:Bool = true) { self.image = image self.soundUrl = soundUrl super.init() // NSObject子类必须调用super.init() } func playSound() { guard let url = Bundle.main.url(forResource: self.soundUrl, withExtension: "m4a") else { return } do { try AVAudioSession.sharedInstance().setCategory(.playback) try AVAudioSession.sharedInstance().setActive(true) player = try AVAudioPlayer(contentsOf: url) player?.delegate = self // 必须设置这行,让AVAudioPlayer把回调发给当前Card实例 guard let player = player else { return } player.prepareToPlay() player.play() print("play") // 这里绝对不能手动调用audioPlayerDidFinishPlaying! } catch let error { print(error.localizedDescription) // 播放出错时也建议恢复UI状态 delegate?.isOn() } } // 实现AVAudioPlayer的系统代理方法,播放完成后自动触发 func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { print("播放完成") // 安全解包并调用自定义协议方法 delegate?.isOn() // 停用AVAudioSession释放音频资源 try? AVAudioSession.sharedInstance().setActive(false) } }
2. 确保UIImageView所在类正确设置delegate并实现协议
假设你的UIImageView在ViewController里,需要让ViewController遵守isOnProtocol,并在初始化Card时把自己设为delegate:
class ViewController: UIViewController, isOnProtocol { @IBOutlet weak var targetImageView: UIImageView! var card: Card? override func viewDidLoad() { super.viewDidLoad() // 初始化Card实例 guard let cardImage = UIImage(named: "your-image-name") else { return } card = Card(image: cardImage, soundUrl: "your-sound-filename") card?.delegate = self // 关键:把当前ViewController设为Card的delegate } // 实现自定义协议方法,播放完成后启用UIImageView func isOn() { // UI操作必须在主线程执行,因为AVAudioPlayer代理回调在后台线程 DispatchQueue.main.async { self.targetImageView.isUserInteractionEnabled = true } } // 触发播放的按钮点击事件(示例) @IBAction func startPlayback(_ sender: UIButton) { // 播放前先禁用UIImageView targetImageView.isUserInteractionEnabled = false card?.playSound() } }
3. 额外注意点
- 循环引用问题:因为Card的delegate用了
weak修饰,所以必须让isOnProtocol遵守AnyObject,确保只有类类型能遵守这个协议。 - 线程安全:AVAudioPlayer的代理方法是在后台线程触发的,修改UI时一定要用
DispatchQueue.main.async包裹,避免UI卡顿或崩溃。 - 异常处理:播放出错时也要记得调用
delegate?.isOn()恢复UI状态,避免用户界面一直处于禁用状态。
这样修改后,当AVAudioPlayer播放完成时,会自动触发audioPlayerDidFinishPlaying方法,进而调用你的自定义协议方法isOn(),完成UIImageView的启用逻辑。
内容的提问来源于stack exchange,提问作者redtomato
相关产品推荐
相关产品推荐

