如何从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?

有啥方法可以将视图从 swiftUI 更改为 UIKit?

将数据从 SwiftUI 传递到 UIKit 的回调

如何知道 UIKit 视图控制器中的 swiftUI 事件? ||如何提供 swiftUI 和 uikit 之间的通信

从 UIKit 调整 SwiftUI 组件

值更改时如何在 UIKit 中更新 SwiftUI 视图?