如何从swiftui视图调用uikit viewcontroller方法
Posted
技术标签:
【中文标题】如何从swiftui视图调用uikit viewcontroller方法【英文标题】:How to call a uikit viewcontroller method from swiftui view 【发布时间】:2020-06-21 21:08:07 【问题描述】:我已经找遍了这个问题的答案,但似乎找不到。如何从 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 中使用按钮操作并调用 viewController play
方法一次。我已经看到建议在包装器上设置状态/绑定并在 updateUIViewController 中调用该方法的答案,但是当我这样做时,我看到它被多次调用,而不仅仅是一次。
【问题讨论】:
我停止使用 SwiftUI 1.0(并且可能在 SwiftUI 3.0 之前不会移植任何东西),但我知道会起作用的一件事 - 肯定不会 i> SwiftUI 也不是 Combine - 是通知。去年八月的工作就像一个魅力。 这能回答你的问题吗? Call UIKIT function from SWIFTUI 【参考方案1】:这是可能的基于协议/配置器的方法,它允许直接使用从代码简单性和可读性来看更合适的操作。
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 问题:在视图更新期间修改状态,这将导致未定义的行为。【参考方案2】:好问题。在我看来,这里的 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
【讨论】:
【参考方案3】:我曾经也遇到过这个问题,并使用了通知中心。但还有另一种方式。您可以在 SwiftUIView 中创建一个@StateObject 'Coordinator',将其传递给 UIViewControllerRepresentable 并将 ViewController 传递回 viewDidLoad 中的 Coordinator,您可以通过 coordinator 调用其函数。
// 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")
【讨论】:
以上是关于如何从swiftui视图调用uikit viewcontroller方法的主要内容,如果未能解决你的问题,请参考以下文章
如何从 SwiftUI 视图转到 UIKit TabViewController?