SwiftUI:结合 TabView 和 UIViewRepresentable 的问题

问题描述 投票:0回答:1

我在将 SwiftUI

TabView
UIViewRepresentable
组合时遇到问题。

  • 我使用
    MobileVLCKit
    UIViewRepresentable
    自定义了视频/流播放器。
  • 我有一个视图,可以同时显示多个视频(每帧有 4 个项目,采用网格布局),并允许滑动选项卡(这就是我使用
    TabView
    的原因)。
  • 我有一个
    strokeView
    ,当选择某个项目时会出现,以指示当前选择了哪个项目。
  • 我注意到这个问题可能与使用
    tabViewStyle
    有关。
  • 问题如下:
    • 应用
      tabViewStyle
      时:单击视频项目不会显示
      strokeView
    • 不应用
      tabViewStyle
      时:单击视频项目会按预期显示
      strokeView
      ,但无法在选项卡之间滑动。

下面是我的代码

struct Test2View: View {
    var body: some View {
        let cameras = [
            CameraCommonItemData(name: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"),
            CameraCommonItemData(name: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"),
            CameraCommonItemData(name: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"),
            CameraCommonItemData(name: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4"),
            CameraCommonItemData(name: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4"),
        ]

        CameraGridView(cameras: cameras)
    }
}

struct CameraGridView: View {
    let cameras: [CameraCommonItemData]

    @State private var itemSelected: CameraCommonItemData?
    @State private var dict: [String: CameraCommonItemData] = [:]

    private let columns = [
        GridItem(.flexible(), spacing: 1),
        GridItem(.flexible(), spacing: 1),
    ]

    var width: CGFloat {
        let deviceWidth = UIScreen.main.bounds.width
        return deviceWidth / 2 - 1
    }

    var height: CGFloat {
        return width / (16 / 9)
    }

    func overlay(_ camera: CameraCommonItemData) -> some View {
        let isSelected = itemSelected?.id == camera.id
        let overlay = AnyView(Rectangle()
            .stroke(isSelected ? Color.red : Color.clear, lineWidth: 3)
            .frame(width: width, height: height))
        return overlay
    }

    var body: some View {
        let chunks = cameras.chunked(into: 4)

        TabView {
            ForEach(chunks.indices, id: \.self) { index in
                LazyVGrid(columns: columns, spacing: 1) {
                    ForEach(chunks[index]) { camera in
                        VLCItemView(store: camera.store, urlString: camera.name)
                            .frame(width: width, height: height)
                            .overlay(strokeView(camera))
                            .onTapGesture {
                                itemSelected = camera
                            }
                            .onAppear {
                                if self.dict[camera.id] == nil {
                                    self.dict[camera.id] = camera
                                    camera.store.loadURL(camera.name)
                                } else {
                                    camera.store.play()
                                }
                            }.onDisappear {
                                camera.store.pause()
                            }
                    }
                }
            }
        }
        .tabViewStyle(PageTabViewStyle())
    }

    func strokeView(_ data: CameraCommonItemData) -> some View {
        Rectangle()
            .stroke(itemSelected == data ? Color.red : Color.clear, lineWidth: 3)
            .frame(width: width, height: height)
    }
}

struct CameraCommonItemData: Identifiable, Equatable {
    let id = UUID().uuidString
    let name: String
    var store: CommonMonitoringCameraStore

    static func == (lhs: CameraCommonItemData, rhs: CameraCommonItemData) -> Bool {
        return lhs.id == rhs.id
    }

    init(name: String) {
        self.name = name
        self.store = CommonMonitoringCameraStore()
    }
}

public extension Array {
    func chunked(into size: Int) -> [[Element]] {
        return stride(from: 0, to: count, by: size).map {
            Array(self[$0 ..< Swift.min($0 + size, count)])
        }
    }
}

VLCItemView

struct VLCItemView: View {
    @ObservedObject private var store: CommonMonitoringCameraStore
    private var urlString: String
    private var isSelfHandling: Bool

    init(store: CommonMonitoringCameraStore, urlString: String, isSelfHandling: Bool = true) {
        self.store = store
        self.urlString = urlString
        self.isSelfHandling = isSelfHandling
    }

    var body: some View {
        CommonMonitoringCameraView(store: store)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .onAppear {
                if isSelfHandling { store.loadURL(urlString) }
            }.onDisappear {
                if isSelfHandling { store.stop() }
            }
    }
}

常用监控摄像头视图

public struct CommonMonitoringCameraView: UIViewRepresentable {
    @ObservedObject private var store: CommonMonitoringCameraStore

    public init(store: CommonMonitoringCameraStore) {
        self.store = store
    }

    public func makeUIView(context: Context) -> some UIView {
        let uiView = store.uiView
        return uiView
    }

    public func updateUIView(_ uiView: UIViewType, context: Context) {}
}

public class CommonMonitoringCameraStore: NSObject, ObservableObject {
    var uiView: UIView = .init()

    private lazy var mediaPlayer: VLCMediaPlayer = {
        var player = VLCMediaPlayer()
        return player
    }()

    private var isFirstLoad: Bool = true
    private var isMediaPlayerTimeChanged: Bool = false
    private var timerOpening: Timer.TimerPublisher?
    private var counterOpening: Int = 0
    private var cancellableSet: Set<AnyCancellable> = []

    private let openingTimeout: Int = 30

    public var onError = PassthroughSubject<Void, Never>()
    public var onVideoStartPlaying = PassthroughSubject<Void, Never>()

    @Published public var isPlaying: Bool = false
    @Published public var isMute: Bool = false
    @Published public var mediaPlayerState: MediaPlayerState = .unAvailable

    override public init() {
        super.init()
    }

    deinit {
        stop()
    }

    public func loadURL(_ urlString: String) {
        configMediaPlayer(urlString)
    }

    public func pause() {
        if mediaPlayer.isPlaying {
            mediaPlayer.pause()
            isPlaying = false
        }
    }

    public func play() {
        mediaPlayer.play()
        isPlaying = true
    }

    public func stop() {
        mediaPlayer.stop()
        isPlaying = false
    }

    var mediaOptions: [String] {
        return [
            "--network-caching=\(VLCPlayerConfig.networkCaching)",
            "--rtsp-tcp",
            "--rtsp-http",
            "--rtsp-mcast",
            "--rtsp-frame-buffer-size=\(VLCPlayerConfig.rtspFrameBufferSize)",
            "--codec=avcodec",
            "--avcodec-hw=none",
            "--avcodec-skiploopfilter=2",
            "--avcodec-skip-frame=2",
            "--avcodec-skip-idct=2",
            "--mms-timeout=\(VLCPlayerConfig.mmsTimeout)",
            "--file-caching=3000",
            "--live-caching=3000",
//            "--drop-late-frames", default enable
//            "--skip-frames", default enable
            "--network-synchronisation",
            "--video-on-top",
            ":clock-jitter=0",
            ":clock-synchro=0",
        ]
    }

    private func configMediaPlayer(_ urlString: String) {
        guard !urlString.isEmpty, let url = URL(string: urlString) else {
            mediaPlayerState = .error
            return
        }

        mediaPlayer.videoAspectRatio = UnsafeMutablePointer<Int8>(mutating: (VLCPlayerConfig.aspectRatio as NSString).utf8String)
        mediaPlayer.drawable = uiView

        uiView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        let media = VLCMedia(url: url)

        mediaPlayer.media = media
        mediaPlayer.delegate = self
        play()
    }

    private func startTimerOpening() {
        stopTimerOpening()
        timerOpening = Timer.publish(every: 3, on: .main, in: .default)
        timerOpening?.autoconnect()
            .sink { [weak self] _ in
                guard let self = self else { return }
                if counterOpening >= openingTimeout {
                    self.stopTimerOpening()
                    self.stop()
                    self.mediaPlayerState = .error
                }
                counterOpening += 3
            }.store(in: &cancellableSet)
    }

    private func stopTimerOpening() {
        counterOpening = 0
        timerOpening?.autoconnect().upstream.connect().cancel()
    }
}

// MARK: - VLCMediaPlayerDelegate

extension CommonMonitoringCameraStore: VLCMediaPlayerDelegate {
    public func mediaPlayerStateChanged(_ aNotification: Notification) {
        guard let mediaPlayer = aNotification.object as? VLCMediaPlayer else { return }
        switch mediaPlayer.state {
        case .opening:
            mediaPlayerState = .openning
            startTimerOpening()
        case .playing:
            mediaPlayerState = .playing
            isPlaying = true
        case .paused:
            mediaPlayerState = .paused
            isPlaying = false
        case .ended:
            mediaPlayerState = .ended
            stopTimerOpening()
            isPlaying = false
        case .error:
            mediaPlayerState = .error
            stopTimerOpening()
            isPlaying = false
        default:
            break
        }
    }

    public func mediaPlayerTimeChanged(_ aNotification: Notification) {
        if isFirstLoad {
            onVideoStartPlaying.send(())
            stopTimerOpening()
            isFirstLoad = false
        }
    }
}
  • 我尝试用矩形替换
    VLCItemView
    (应用
    tabViewStyle
    时),发现它按预期工作。另外,我尝试用
    overlay
    background
    替换
    ZStack
    ,但无济于事。 我将非常感谢任何帮助。
  • 视频演示:enter image description here
swiftui vlc tabview uiviewrepresentable
1个回答
0
投票

您缺少

updateUIView
makeCoordinator
,请将其更改为如下所示:


    let url: URL

    func makeCoordinator() -> MonitoringCamera {
        return MonitoringCamera()
    }

    func makeUIView(context: Context) -> some UIView {
        store.uiView
    }

    // called when url changes
    func updateUIView(_ uiView: UIViewType, context: Context) {
        if context.coordinator.loadedUrl != url {
            context.coordinator.loadURL(url)
        }
    }

© www.soinside.com 2019 - 2024. All rights reserved.