如何通过 SwiftUI 按钮在 UIViewController 中执行功能?

Posted

技术标签:

【中文标题】如何通过 SwiftUI 按钮在 UIViewController 中执行功能?【英文标题】:How to execute a function in a UIViewController through a SwiftUI Button? 【发布时间】:2019-12-02 16:32:24 【问题描述】:

在下面的代码中,我试图弄清楚如何使用SwiftUI 按钮执行savePhoto 函数。这实际上是我试图解决的问题的简化版本,但仍然无法弄清楚。我在ContentView 中创建的按钮是我想象的解决问题的方法,但由于 SwiftUI 结构,我似乎找不到解决方法。

这是我的 SwiftUI 视图:

struct ContentView: View 
    var body: some View 
        ZStack
            AnotherControllerView()
            Button(action:  AnotherControllerView.savePhoto() )
                Text("Save Photo")
            
        
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    


还有我的集成 UIViewController:

import Foundation
import SwiftUI
import UIKit

struct AnotherControllerView : UIViewControllerRepresentable 

    typealias UIViewControllerType = AnotherController

    func makeCoordinator() -> AnotherControllerView.Coordinator 
        Coordinator(self)
    

    func makeUIViewController(context: UIViewControllerRepresentableContext<AnotherControllerView>) -> AnotherController 

        return AnotherController()
    

    func updateUIViewController(_ uiViewController: AnotherController, context: UIViewControllerRepresentableContext<AnotherControllerView>) 

    


    class Coordinator : NSObject 
        var parent : AnotherControllerView
        init(_ viewController : AnotherControllerView)
            self.parent = viewController
        
    


class AnotherController : UIViewController 


    override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = UIColor.blue
    

    func savePhoto()

        let alert = UIAlertController(title: "Save Photo to Camera Roll", message: "Would you like to save your drawing to the camera roll?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
        alert.addAction(UIAlertAction(title: "Save", style: .default, handler: someHandler))
        self.present(alert, animated: true)
    

    func someHandler(alert: UIAlertAction!) 
        print("Handler executed")
    

【问题讨论】:

【参考方案1】:

你好要控制 UIViewController 我们需要在 View 和 UIViewControllerRepresentable 之间创建一个绑定 让我们用代码解释一下: 首先,您需要在AnotherControllerView 中声明一个带有@Binding 注释的新变量

会是这样的:

struct AnotherControllerView : UIViewControllerRepresentable 

    @Binding var isShown: Bool
    typealias UIViewControllerType = AnotherController

    func makeCoordinator() -> AnotherControllerView.Coordinator 
        Coordinator()
    

    func makeUIViewController(context: UIViewControllerRepresentableContext<AnotherControllerView>) -> AnotherController 

        return AnotherController(isShown: $isShown)
    

    func updateUIViewController(_ controller: AnotherController, context: UIViewControllerRepresentableContext<AnotherControllerView>) 
        if(self.isShown)
            controller.savePhoto()
        
    


    class Coordinator : NSObject 

    

为此,我们在updateUIViewController 中实现逻辑

class AnotherController : UIViewController 

     @Binding var isShown: Bool

    init(isShown: Binding<Bool>) 
        _isShown = isShown
        super.init(nibName: nil, bundle: nil)

    



    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    


    override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = UIColor.blue
    

    func savePhoto()

        let alert = UIAlertController(title: "Save Photo to Camera Roll", message: "Would you like to save your drawing to the camera roll?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: cancelAlert))
        alert.addAction(UIAlertAction(title: "Save", style: .default, handler: someHandler))
        self.present(alert, animated: true)
    

    func cancelAlert(alert: UIAlertAction!) 
        self.isShown = false
    

    func someHandler(alert: UIAlertAction!) 
        print("Handler executed")
    

如果你想捕捉图像,让我告诉你我是如何实现的:

struct CaptureImageView 
    @Binding var isShown: Bool
    @Binding var image: Image?
    var sourceType: UIImagePickerController.SourceType

    func makeCoordinator() -> Coordinator 
        return Coordinator(isShown: $isShown, image: $image)
    


extension CaptureImageView: UIViewControllerRepresentable 
    func makeUIViewController(context: UIViewControllerRepresentableContext<CaptureImageView>) -> UIImagePickerController 
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        picker.sourceType = sourceType
        return picker
    

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<CaptureImageView>) 

    

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate 
        @Binding var isCoordindatorShown: Bool
        @Binding var imageInCoordinator: Image?

        init(isShown: Binding<Bool>, image: Binding<Image?>) 
            _isCoordindatorShown = isShown
            _imageInCoordinator = image
        

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) 
            guard let unwrapImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else return
            imageInCoordinator = Image(uiImage: unwrapImage)
            isCoordindatorShown = false
        

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) 
            isCoordindatorShown = false
        
    


我只是在正文中调用:

CaptureImageView(isShown: $showCaptureImageView, image: $imageCaptured, sourceType: importSourceType)

【讨论】:

UIViewController 的指定初始化器是 initWithNibName:bundle:。您应该改为调用它。如果您没有 nib,请为 nibName 传入 nil(bundle 也是可选的)。然后你可以在 loadView 中构建自定义视图,或者在 viewDidLoad 中为 self.view 添加子视图,就像你以前一样。 非常感谢。我做了 twerk 并且能够实现我所追求的,这只是在 UIViewController 中保存一个 UIImage。但是,在执行savePhoto() 时,您没有收到双重警报吗?似乎它希望您通过 SwiftUI 运行警报。 2019-12-02 12:54:27.434741-0500 CoordinatorVC[1048:209323] Warning: Attempt to present &lt;UIAlertController: 0x101888400&gt; on &lt;CoordinatorVC.AnotherController: 0x100c13010&gt; which is already presenting (null) 是的,你是对的,我得到了两次,我添加了一个警报,正如你直接在视图上所说的 那么你检查是否设置了一个布尔值,当它设置时,调用相应的函数?当通过不同的状态更新再次调用updateUIViewController 时会发生什么?除非您在函数末尾重置布尔值,否则将错误地再次调用该函数。 然而如果你在函数结束时重置布尔值,那么你就会陷入状态修改循环:“在视图更新期间修改状态,这将导致未定义的行为。”【参考方案2】:

如果 AnotherControllerView 很大并且您很少在新版本中使用它,您可以考虑使用更简单的解决方法。

        struct ContentAnotherControllerView: View 

            let   anotherControllerView = AnotherControllerView()

            var body: some View 
                ZStack
                    anotherControllerView
                    Button(action:  self.anotherControllerView.savePhoto() )
                        Text("Save Photo")
                    
                
            
        



     struct AnotherControllerView : UIViewControllerRepresentable 

     typealias UIViewControllerType = AnotherController

     var savePhotoDelegate: AnotherController? = AnotherController()

    func savePhoto()
         savePhotoDelegate?.savePhoto()
     


     func makeUIViewController(context:   UIViewControllerRepresentableContext<AnotherControllerView>) -> AnotherController 

    if let savePhotoDelegate = savePhotoDelegate
        return savePhotoDelegate
    
    return AnotherController()
    
   ....

这种方法只是提醒我们一个视图可以在某些情况下重用。由于 UIView 桥不是纯 SwiftUI 域,你可以试试这种方式。

【讨论】:

以上是关于如何通过 SwiftUI 按钮在 UIViewController 中执行功能?的主要内容,如果未能解决你的问题,请参考以下文章

Mac 上的 SwiftUI - 如何将按钮指定为主按钮?

如何使用 SwiftUI 按钮打开本地 PDF 文件?

如何在没有按钮的 SwiftUI 中显示警报

当我们在文本和图像之间切换按钮内容时,如何停止 SwiftUI 更改固定填充?

swiftUI:登录完成后导航到主屏幕。通过按钮单击导航视图

如何通过单击按钮在 Swift UI 中从一个视图导航到另一个视图