如何从UIVIew以编程方式呈现视图控制器

4

我正在使用一个类,它是 MessageView 的子类(Swift Message Library),而 MessageView 是从 UIView 继承而来。在其中,我有一个 UIButton,我想通过它以编程方式呈现另一个 ViewController

下面是我的代码:

import Foundation
import SwiftMessages
import UIKit

class MyClass: MessageView {

    var hideBanner: (() -> Void)?


    @IBAction func helpButtonPressed(_ sender: UIButton) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
        self.present(newViewController, animated: true, completion: nil)

    @IBAction func tryAgainButtonPressed(_ sender: UIButton) {
        hideBanner?()
    }

    open override func awakeFromNib() {

    }

}

我尝试过这个方法,但由于UIView没有present方法,所以无法工作。


4
可能是 Using presentViewController from UIView 的重复问题。 - Abdelahad Darwish
2
我认为这是一个不好的编程模式。你应该使用委托,从视图控制器中进行确认,然后可以使用present方法来呈现你的控制器。 - serhat sezer
希望你不会遇到困难。给出的答案关注于视图向控制器传达需要发生某些事情的想法。控制器应该采取实际操作来使其发生。不要忘记为答案点赞。 - Tommie C.
7个回答

2

首先使用以下方法获取顶部的ViewController。然后您就可以呈现您的viewController

if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController now can use for present.
    let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
    topController.present(newViewController, animated: true, completion: nil)
}

这在许多方面都可能有些脆弱。多个故事板、导航控制器等等。遍历控制器/视图层次结构可能会变得非常复杂。 - Tommie C.
1
这个对我有用,必须使用self.present.......才能使它工作。 - David_2877

1

.presentUIViewController 类中的一个方法,这就是为什么你不能从 UIView 类中呈现视图控制器的原因。

要实现这一点,请获取根视图控制器,并按以下方式呈现控制器:

let appDelegate  = UIApplication.shared.delegate as! AppDelegate
let viewController = appDelegate.window!.rootViewController as! YourViewController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
        viewController .present(newViewController, animated: true, completion: nil)

谢谢,但是MyClass仍然没有present方法: "类型'MyClass'的值没有成员'present'" - Blisko
1
请按照我更新的答案,将self替换为viewController。抱歉打错字了。 - pkc456

1
尝试一下这个#简单的代码。
import Foundation
import SwiftMessages
import UIKit

class MyClass: MessageView {

var hideBanner: (() -> Void)?


@IBAction func helpButtonPressed(_ sender: UIButton) {
    let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
    UIApplication.shared.keyWindow?.rootViewController?.present(newViewController, animated: true, completion: nil)
}

@IBAction func tryAgainButtonPressed(_ sender: UIButton) {
    hideBanner?()
}

open override func awakeFromNib() {

}

}

最好展示控制器逻辑驱动故事板调用。这应该来自控制器而不是视图。使用闭包对象来管理回调对于通知控制器对象何时进行新的演示非常有用。 - Tommie C.

1
这里是使用委托模式的示例代码。
class YourViewController: UIViewController {
  var yourView: MyClass // may be outlet

    override func viewDidLoad() {
        super.viewDidLoad()

        yourView.delegate = self
    }

}

protocol MyClassDelegate:class {
    func tryAgainButtonDidPressed(sender: UIButton)
}

class MyClass: MessageView {

  weak var delegate: MyClassDelegate?


   @IBAction func tryAgainButtonPressed(_ sender: UIButton) {
        delegate?.tryAgainButtonDidPressed(sender: sender)
    }

}

1
抱歉回复晚了。 MessageView 已经为您提供了一个 buttonTapHandler 回调函数:
/// An optional button tap handler. The `button` is automatically
/// configured to call this tap handler on `.TouchUpInside`.
open var buttonTapHandler: ((_ button: UIButton) -> Void)?

@objc func buttonTapped(_ button: UIButton) {
    buttonTapHandler?(button)
}

/// An optional button. This buttons' `.TouchUpInside` event will automatically
/// invoke the optional `buttonTapHandler`, but its fine to add other target
/// action handlers can be added.
@IBOutlet open var button: UIButton? {
    didSet {
        if let old = oldValue {
            old.removeTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
        }
        if let button = button {
            button.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
        }
    }
}

这个方法会自动为任何连接到button出口的按钮调用。因此,推荐展示另一个视图控制器的方法是让展示视图控制器在此回调中配置展示逻辑:

messageView.tapHandler = { [weak self] in
    guard let strongSelf = self else { return }
    let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
    strongSelf.present(newViewController, animated: true, completion: nil)
}

如果您的视图有多个按钮,您可以通过buttonTapHandler处理它们,因为它需要一个button参数。您只需要为每个按钮配置目标-动作机制即可:
otherButton.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)

或者您可以通过复制上述模式为每个按钮添加一个回调函数。


1
iOS的惯例是只有ViewController可以呈现另一个ViewController
因此,上面的答案 - 在这些答案中,View通过UIApplication.sharedApplication().keyWindow?....找到当前的ViewController,虽然可行,但非常反模式。
首选的方法应该是:
  • MyClass视图仅具有表示代码
  • 您必须拥有一个ViewController,其中包含对此MyClass视图的引用
  • 此ViewController具有@IBAction func tryAgainButtonPressed
  • 从那里,您可以呈现下一个ViewController

1
你可以通过两种方式实现这个功能:
  • 协议
  • 在初始化视图时向视图传递该视图控制器的引用

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接