如何从 swiftui 视图调用 uikit 视图控制器方法



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

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

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

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

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
}

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

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

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 ?? {})
}
}
}

好问题。在我看来,这里的 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
}

我曾经也遇到过这个问题,并使用了通知中心。但还有另一种方式。您可以在 SwiftUIView 中创建@StateObject"协调器",将其传递给 UIViewControllerRepresentable,然后将 ViewController 传回 viewDidLoad 中的协调器,然后您可以通过协调器调用其函数。

// Create a Coordinator
class BridgingCoordinator: ObservableObject {
var vc: ViewController!
}
// SwiftUI View
struct SwiftUIView: View {
@StateObject private var coordinator: BridgingCoordinator
init() {
let coordinator = BridgingCoordinator()
self._coordinator = StateObject(wrappedValue: coordinator)
}
var body: some View {
VStack {
Text("Swift UI View")
Button(action: buttonTapped) {
Text("Call function on UIViewControllerRepresentable VC")
}
.disabled(coordinator.vc == nil)
UIViewControllerRepresentation(bridgingCoordinator: coordinator)
}
}

private func buttonTapped() {
coordinator.vc.doSomething()
}
}
// The UIViewControllerRepresentable of the ViewController
struct UIViewControllerRepresentation: UIViewControllerRepresentable {
var bridgingCoordinator: BridgingCoordinator
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: Context) -> some UIViewController {
let vc = ViewController()
vc.bridgingCoordinator = bridgingCoordinator
return vc
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
//
}
class Coordinator: NSObject {
let parent: UIViewControllerRepresentation
init(_ view: UIViewControllerRepresentation) {
self.parent = view
}
}
}
// ViewController which contains functions that need to be called from SwiftUI
class ViewController: UIViewController {
// The BridgingCoordinator received from the SwiftUI View
var bridgingCoordinator: BridgingCoordinator!
override func viewDidLoad() {
super.viewDidLoad()
// Set self to the BridgingCoordinator
bridgingCoordinator.vc = self
}
func doSomething() {
print("Received function call from SwiftUI View")
}
}

最新更新