如何从 swiftui 视图调用 uikit viewcontroller 方法

Joh*_*n M 5 viewcontroller ios swiftui

我已经到处寻找这个问题的答案,但似乎找不到。如何从 swiftUI 调用 viewController 方法(例如单击按钮)?

我有一个看起来像这样的视图控制器:

import Player

class PlayerViewController: UIViewController {
    var player = Player()
    func play() {
        self.player.play()
    }
}
Run Code Online (Sandbox Code Playgroud)

我有一个看起来像这样的包装:

import SwiftUI
import AVFoundation

struct ProjectEditorPlayerBridge: UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> PlayerViewController {
        let player = PlayerViewController()
        return player
    }
    
    func updateUIViewController(_ uiViewController: PlayerViewController, context: Context) {
    }
    
    typealias UIViewControllerType = PlayerViewController
}
Run Code Online (Sandbox Code Playgroud)

我希望能够在 swiftUI 中使用按钮操作并调用 viewControllerplay方法一次。我已经看到建议在包装器上设置状态/绑定并调用 updateUIViewController 中的方法的答案,但是当我这样做时,我看到它被调用多次,而不仅仅是一次。

Asp*_*eri 6

这是可能的基于协议/配置器的方法,它允许直接使用从代码简单性和可读性来看更合适的操作。

protocol Player { // use protocol to hide implementation
    func play()
}

class PlayerViewController: UIViewController, Player {
    var player = Player()
    func play() {
        self.player.play()
    }
}

struct ProjectEditorPlayerBridge: UIViewControllerRepresentable {
    var configurator: ((Player) -> Void)? // callback

    func makeUIViewController(context: Context) -> PlayerViewController {
        let player = PlayerViewController()

        // callback to provide active component to caller
        configurator?(player)

        return player
    }

    func updateUIViewController(_ uiViewController: PlayerViewController, context: Context) {
    }

    typealias UIViewControllerType = PlayerViewController
}

struct DemoPlayerView: View {
    @State private var player: Player?     // always have current player

    var body: some View {
        VStack {
            ProjectEditorPlayerBridge { self.player = $0 }  // << here !!

            // use player action directly !!
            Button("Play", action: player?.play ?? {})
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

  • 我以同样的方式做了,但它使 SwiftUI 出现问题:在视图更新期间修改状态,这将导致未定义的行为。 (2认同)

Rob*_*b N 3

好问题。在我看来,SwiftUI 这里缺少一些东西。

如果您的应用程序中只有这些视图控制器之一,那么您可以通过使用全局PassthroughSubject(或传递事件的其他方式)来解决此问题。您UIViewController可以订阅它,您的 SwiftUI 代码可以向它发布点击。

如果您不想这样做,这里有另一种解决方法,用于UUID消除您提到的多次调用。

也许我们会在 WWDC 2020 上看到新的选择。

struct ContentView: View {
    @State var buttonClickID: UUID? = nil       
    var body: some View {
        VStack {
            Button(action: self.callPlay) { Text("Play") }
            ProjectEditorPlayerBridge(clickID: $buttonClickID)
        }
    }       
    func callPlay() {
        buttonClickID = UUID()
    }
}

struct ProjectEditorPlayerBridge: UIViewControllerRepresentable {

    @Binding var clickID: UUID?        
    
    func makeUIViewController(context: Context) -> PlayerViewController {
        let player = PlayerViewController()
        return player
    }
    
    class Coordinator {
        var previousClickID: UUID? = nil
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    func updateUIViewController(_ uiViewController: PlayerViewController, context: Context) {
        print("Update")
        if clickID != context.coordinator.previousClickID {
            uiViewController.play()
            context.coordinator.previousClickID = clickID
        } else {
            print("Not calling play")
        }
    }
    
    typealias UIViewControllerType = PlayerViewController
}
Run Code Online (Sandbox Code Playgroud)