You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:59:17