如何在SwiftUI包装器中访问符合UIViewRepresentable的属性?

时间:2020-06-24 19:29:44

标签: swiftui

我想控制AVPlayer的实例,该实例被实例化并分配给符合VideoPlayer的类UIView中的属性。为了确保清楚我的意图,在处理SwiftUIUIViewRepresentable包装程序时,我整理了一个已知的模式,如下所示:

class VideoPlayer: UIView {
    private let playerLayer = AVPlayerLayer()
    private var previewTimer: Timer?
    var previewLength: Double
    var player: AVPlayer?
    
    init(frame: CGRect, url: URL, previewLength:Double) {
        self.previewLength = previewLength
        
        super.init(frame: frame)
        self.player = AVPlayer(url: url)
        self.player?.volume = 0
        self.player?.play()

        NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: .main) { [weak self] _ in
            self?.player?.seek(to: CMTime.zero)
            self?.player?.play()
        }
        
        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspectFill
        playerLayer.backgroundColor = UIColor.black.cgColor
        
        previewTimer = Timer.scheduledTimer(withTimeInterval: previewLength, repeats: true, block: { (timer) in
            self.player?.seek(to: CMTime(seconds: 0, preferredTimescale: CMTimeScale(1)))
        })
        
        layer.addSublayer(playerLayer)
    }
    
    required init?(coder: NSCoder) {
        self.previewLength = 15
        super.init(coder: coder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }
}

包装器MyWrapperUIView暴露给SwiftUI,而wrapper可以具有公共属性,例如myState

struct MyWrapper: UIViewRepresentable {
    var videoURL: URL
    var previewLength: Double
    @Binding var mySate: Bool

    func makeUIView(context: Context) -> UIView {
        let videoPlayer = VideoPlayer(frame: .zero, url: videoURL, previewLength: previewLength)
        return videoPlayer
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        if myState {
          // do something
        } else {
          // do something else
        }
    }
}

如果需要控制实例VideoPlayer的属性,我们可能很想直接从方法UIView中的updateUIView对象访问属性。因此,假设我们要访问player属性:

func updateUIView(_ uiView: UIView, context: Context) {
    if myState {
      uiView.player.play()
    }
}

此操作失败,并显示错误Value of type 'UIView' has no member 'player'

要解决此问题,请在MyWrapper中创建一个代理来引用AVPlayer实例,如下所示:

struct MyWrapper: UIViewRepresentable {
    var videoURL: URL
    var previewLength: Double
    @State var player: AVPlayer?
    @Binding var play: Bool

    func makeUIView(context: Context) -> UIView {
        let videoPlayer = VideoPlayer(frame: .zero, url: videoURL, previewLength: previewLength)
        DispatchQueue.main.async {
            self.player = videoPlayer.player
        }
        return videoPlayer
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        if play {
            self.player?.play()
        } else {
            self.player?.pause()
            self.player?.rate = 0
        }
    }
}

Obs:我们使用DispatchQueue.main绕过了将在Modifying state during view update, this will cause undefined behaviour中抛出的警告makeUIView

这种方法有效,但是我在Apple中找不到任何文档来确认这是处理SwiftUI和UIViewRepresentable包装程序时正确的方法,因为SwiftUI是一个黑匣子。

因此,由于这个原因,我想知道如何在SwiftUI包装器中访问符合UIViewRepresentable的属性?

1 个答案:

答案 0 :(得分:1)

在这种情况下,

UIViewRepresentable从声明的return / argument类型推断类型,因此只需突出显示所表示的类型

struct MyWrapper: UIViewRepresentable {
    var videoURL: URL
    var previewLength: Double
    @Binding var mySate: Bool

    func makeUIView(context: Context) -> VideoPlayer {
        let videoPlayer = VideoPlayer(frame: .zero, url: videoURL, previewLength: previewLength)
        return videoPlayer
    }
    
    func updateUIView(_ uiView: VideoPlayer, context: Context) {
        if myState {
          uiView.player.play()    // now it knows that uiView is-a VideoPlayer
        } else {
          // do something else
        }
    }
}