UIAlertController 实用程序类无法调用 UITextField 委托或目标,但在 UIViewController 中工作

Posted

技术标签:

【中文标题】UIAlertController 实用程序类无法调用 UITextField 委托或目标,但在 UIViewController 中工作【英文标题】:UIAlertController utility class fails to call UITextField delegate or target but works in UIViewController 【发布时间】:2016-11-28 05:02:18 【问题描述】:

我遇到了一个奇怪的问题,可能只是对 Swift 3.0 / ios 10 缺乏了解,所以希望你能引导我走向正确的方向或向我解释我做错了什么。

目前的运作方式

我正在尝试创建一个样式为 .alert 的 UIAlertController,以便我可以为我的应用程序获取用户文本输入。我的要求是文本不能为空,如果之前有文本,它必须是不同的。

我可以使用下面的代码来实现我想要的:

//This function gets called by a UIAlertController of style .actionSheet
func renameDevice(action: UIAlertAction) 

    //The AlertController
    let alertController = UIAlertController(title: "Enter Name",
                                            message: "Please enter the new name for this device.",
                                            preferredStyle: .alert)

    //The cancel button
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)

    //The confirm button. Make sure to deactivate on first start
    let confirmAction = UIAlertAction(title: "Ok", style: .default, handler:  action in
        self.renameDevice(newName: alertController.textFields?.first?.text)
    )

    //Configure the user input UITextField
    alertController.addTextField  textField in
        log.debug("Setting up AlertDialog target")
        textField.placeholder = "Enter Name"
        textField.text = self.device.getName()
        textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged)
    
    //Disable the OK button so that the user first has to change the text
    confirmAction.isEnabled = false
    self.confirmAction = confirmAction
    //Add the actions to the AlertController
    alertController.addAction(cancelAction)
    alertController.addAction(confirmAction)
    present(alertController, animated: true, completion: nil)


var confirmAction: UIAlertAction?

func textFieldDidChange(_ textField: UITextField)
    log.debug("IT CHAGNED!=!=!=!=!")
    if let text = textField.text 
        if !text.isEmpty && text != self.device.getName() 
            confirmAction?.isEnabled = true
            return
        
    
    confirmAction?.isEnabled = false


//Finally this code gets executed if the OK button was pressed
func renameDevice(newName: String?) ... 

我希望它如何工作

到目前为止一切都很好,但我会要求用户在不同的地方输入文本,所以我想使用一个实用程序类来为我处理所有这些东西。最终调用应如下所示:

func renameDevice(action: UIAlertAction) 
     MyPopUp().presentTextDialog(title: "Enter Name",
                                 message: "Please enter the new name for this device.",
                                 placeholder: "New Name",
                                 previousText: self.device.getName(),
                                 confirmButton: "Rename",
                                 cancelButton: "Cancel",
                                 viewController: self) input: String in
        //do something with the input, e. g. call self.renameDevice(newName: input)

    

我想到了什么

所以我在这个小类中实现了所有内容:

class MyPopUp: NSObject 

var confirmAction: UIAlertAction!
var previousText: String?
var textField: UITextField?

func presentTextDialog(title: String, message: String?, placeholder: String?, previousText: String?, confirmButton: String, cancelButton: String, viewController: UIViewController, handler: ((String?) -> Swift.Void)? = nil) 

    //The AlertController
    let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)

    //The cancel button
    let cancelAction = UIAlertAction(title: cancelButton, style: .cancel)

    //The confirm button. Make sure to deactivate on first start
    confirmAction = UIAlertAction(title: confirmButton, style: .default, handler:  action in
        handler?(alertController.textFields?.first?.text)
    )

    //Configure the user input UITextField
    alertController.addTextField  textField in
        log.debug("Setting up AlertDialog target")
        self.textField = textField
    

    //Set placeholder if necessary
    if let placeholder = placeholder 
        self.textField?.placeholder = placeholder
    

    //Set original text if necessary
    if let previousText = previousText 
        self.textField?.text = previousText
    

    //Set the target for our textfield
    self.textField?.addTarget(self, action: #selector(textChanged), for: .editingChanged)

    log.debug("It appears that our textfield \(self.textField) has targets: \(self.textField?.allTargets)")

    //Store the original text for a later comparison with the new entered text
    self.previousText = previousText

    //Disable the OK button so that the user first has to change the text
    confirmAction.isEnabled = false

    //Add the actions to the AlertController
    alertController.addAction(cancelAction)
    alertController.addAction(confirmAction)
    viewController.present(alertController, animated: true, completion: nil)


func textChanged() 
    if let text = textField?.text 
        if !text.isEmpty && text != previousText 
            confirmAction.isEnabled = true
            return
        
    
    confirmAction.isEnabled = false


问题

我的问题是,无论我尝试在哪里为 UIAlertController 的 UITextField 设置目标,它都不会执行我的目标。我尝试在 alertController.addTextField 中设置 TextFields 委托,并在那里设置目标。最让我困惑的问题是设置占位符和原始文本可以正常工作,但从不调用委托或目标函数。为什么相同的代码在 UIViewController 中执行时有效,但在实用程序类中执行时无效?

解决方案(更新)

显然我犯了一个错误。在我的视图控制器中,我创建了一个 MyPopUp 实例并在其上调用 present() 函数。

MyPopUp().presentTextDialog(title: "Enter Name",
                             message: "Please enter the new name for this device.",
                             placeholder: "New Name",
                             previousText: self.device.getName(),
                             confirmButton: "Rename",
                             cancelButton: "Cancel",
                             viewController: self)

在 presentTextDialog() 中,我认为将 MyPopUp 的当前实例设置为委托/目标就足够了,但似乎 MyPopUp 实例被立即释放,因此从未被调用。我非常简单的解决方法是在实例变量中创建 MyPopUp 实例,并在需要时调用 present 方法。

let popup = MyPopUp()
func renameDevice(action: UIAlertAction) 
    popup.presentTextDialog(...) userinput in
        renameDevice(newName: userinput)
    

【问题讨论】:

您需要在哪个类中调用您的委托或目标? 你会在你试图调用这些委托的地方显示你的视图控制器类吗? 从我的 UIViewController 类中,我想调用“MyPopUp().presentTextDialog(...) input in MyPopUp 类将创建 UIAlertController 并将其呈现在视图控制器中 一旦用户输入,我想激活“确定”按钮,如果按下确定按钮,我想在我的视图控制器中调用闭包 【参考方案1】:

好的,这正是我做错的。

    我创建了一个实用程序类,我必须实例化

    类本身基本上是空的,它的唯一目的是成为 UITextField 的目标或委托

    我实例化了这个类并立即调用了表示函数没有保留引用

通过不保留对我的实例的引用,对象必须在我的视图控制器中呈现 UIAlertController 后立即被释放。

解决方案:只需在您的视图控制器中保留一个引用即可。当然局部变量是不行的。我将引用存储在我的视图控制器的实例变量中,但这感觉不是很“迅速”。我仍然是 swift 的初学者,也许我的思想被其他语言(java,c#)“损坏”了。我可能会先让我的实用程序类成为单例,或者为 UIViewController 创建一个扩展来显示警报。如果您有其他好的想法,请随时教给我:)

【讨论】:

感谢战利品,我刚刚浪费了 3 个小时。【参考方案2】:

而不是在 NSObject 类中呈现对话。您必须使用委托和协议来呈现警报。用这个替换你的代码。在 View Controller 中,我们有一个名为 renameDevice 的函数。您可以在此处显示警报。

MyPopUp.swift

import UIKit

class MyPopUp: NSObject 
    var confirmAction: UIAlertAction!
    var previousText: String?
    var textField: UITextField?
    var delegate : MyPopUpDelegate!


    func textChanged() 
        if let text = textField?.text 
            if !text.isEmpty && text != previousText 
                confirmAction.isEnabled = true
                return
            
        
        confirmAction.isEnabled = false
    

protocol MyPopUpDelegate 
    func renameDevice(action: UIAlertAction)

ViewController.swift

import UIKit

class ViewController: UIViewController,MyPopUpDelegate 

    override func viewDidLoad() 
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.



    

    override func didReceiveMemoryWarning() 
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    

    func renameDevice(action: UIAlertAction) 

    // Add dialogue that you wnat to create.

    


【讨论】:

显示 UIAlertController 不是问题,因为我在函数调用中引用了 viewcontroller,然后在 presentTextDialog 的最后一行执行 viewController.present(alertController, animated: true)。问题是,如果我通过弹出类进行操作,UITextField 永远不会调用我的委托/目标。这就是问题所在:为什么完全相同的代码在视图控制器中使用时可以工作,但在实用程序类中使用时却不起作用?我是否遗漏了一些特殊的目标 c 内容,例如某个协议之类的东西? 你在哪里给代表打电话。我看不到 在示例代码中我没有使用委托,而是使用 textField.addTarget() 我都尝试了,将目标添加到文本字段并设置其委托。两种方式都行不通 好的,你在哪里调用 renameDevice 函数?【参考方案3】:

在你的MyPopUp 类中,首先你需要遵循UITextFieldDelegate 这样的方法

class MyPopUp:NSObject,UITextFieldDelegate 

然后在添加 UITextField 以提醒您需要像这样将委托设置为该 UITextField 时

alertController.addTextField  textField in
    log.debug("Setting up AlertDialog target")
    textField.delegate = self
    self.textField = textField

那么你需要实现 UITextField Delegate 方法来像这样在你的 UITextField 中获得变化

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool 

这将解决您的问题。另请查看this

【讨论】:

这正是我所做的。正如我所提到的,我尝试了这两种方法。我尝试了委托方法和 addTarget 方法,但它们都不起作用...... 您建议的答案与我的工作示例大致相同。但是,我想把它放在一个实用程序类中,这样我不必每次都在每个视图控制器中实现委托模式

以上是关于UIAlertController 实用程序类无法调用 UITextField 委托或目标,但在 UIViewController 中工作的主要内容,如果未能解决你的问题,请参考以下文章

UIAlertController(ActionSheet)-无法同时满足约束[重复]

无法在 iOS8 上的 UIAlertController 中显示 UIPickerView

UIAlertController - 将自定义视图添加到操作表

无法在 UIAlertController 中选择按钮的顺序

UIAlertController 大坑

UIAlertController 不能被其他类调用