UIAlertController 实用程序 class 无法调用 UITextField 委托或目标,但在 UIViewController 中工作
UIAlertController utility class fails to call UITextField delegate or target but works in UIViewController
我遇到了一个奇怪的问题,也许这只是缺乏对 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?){ ... }
我希望它如何工作
到目前为止一切顺利,但我将要求用户在不同位置输入文本,因此我想使用实用程序 class 来为我处理所有这些事情。最终调用应如下所示:
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:
中实现了所有内容
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 中执行时有效,但在实用程序 class 中执行时却无效?
解决方案(更新)
显然我弄错了。在我的 viewcontroller 中,我创建了一个 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 的当前实例设置为 delegate/target 就足够了,但似乎 MyPopUp 实例立即被释放,因此从未被调用。我非常简单的解决方法是在实例变量中创建 MyPopUp 实例,并在需要时调用 present 方法。
let popup = MyPopUp()
func renameDevice(action: UIAlertAction) {
popup.presentTextDialog(...){ userinput in
renameDevice(newName: userinput)
}
}
而不是在 NSObject 中呈现对话 class。您必须使用委托和协议来发出警报。用这个替换你的代码。在视图控制器中,我们有一个名为 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.
}
}
在你的 MyPopUp
class 中,首先你需要符合 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 {
这将解决您的 problem.Also 检查
好的,这正是我做错的地方。
我创建了一个实用程序 class,我必须 实例化
class 本身基本上是空的,它的唯一目的是成为 UITextField
[的目标或委托
我实例化了 class 并立即调用了表示函数 而没有在
周围保留引用
通过不保留对我的实例的引用,对象必须在我的 viewcontroller 中呈现 UIAlertController 后立即被释放。
解决方案:只需在 viewcontroller 中保留一个参考即可。当然局部变量不行。我将引用存储在我的 viewcontroller 的实例变量中,但这感觉不太 "swifty"。我仍然是 swift 的初学者,也许我的想法是 "damaged" 其他语言(java,c#)。我可能会首先让我的实用程序 class 成为一个单例,或者为 UIViewController 创建一个扩展来显示警报。如果您有其他好的想法,请随时教我:)
我遇到了一个奇怪的问题,也许这只是缺乏对 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?){ ... }
我希望它如何工作
到目前为止一切顺利,但我将要求用户在不同位置输入文本,因此我想使用实用程序 class 来为我处理所有这些事情。最终调用应如下所示:
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:
中实现了所有内容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 中执行时有效,但在实用程序 class 中执行时却无效?
解决方案(更新)
显然我弄错了。在我的 viewcontroller 中,我创建了一个 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 的当前实例设置为 delegate/target 就足够了,但似乎 MyPopUp 实例立即被释放,因此从未被调用。我非常简单的解决方法是在实例变量中创建 MyPopUp 实例,并在需要时调用 present 方法。
let popup = MyPopUp()
func renameDevice(action: UIAlertAction) {
popup.presentTextDialog(...){ userinput in
renameDevice(newName: userinput)
}
}
而不是在 NSObject 中呈现对话 class。您必须使用委托和协议来发出警报。用这个替换你的代码。在视图控制器中,我们有一个名为 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.
}
}
在你的 MyPopUp
class 中,首先你需要符合 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 {
这将解决您的 problem.Also 检查
好的,这正是我做错的地方。
我创建了一个实用程序 class,我必须 实例化
class 本身基本上是空的,它的唯一目的是成为 UITextField
[的目标或委托我实例化了 class 并立即调用了表示函数 而没有在
周围保留引用
通过不保留对我的实例的引用,对象必须在我的 viewcontroller 中呈现 UIAlertController 后立即被释放。
解决方案:只需在 viewcontroller 中保留一个参考即可。当然局部变量不行。我将引用存储在我的 viewcontroller 的实例变量中,但这感觉不太 "swifty"。我仍然是 swift 的初学者,也许我的想法是 "damaged" 其他语言(java,c#)。我可能会首先让我的实用程序 class 成为一个单例,或者为 UIViewController 创建一个扩展来显示警报。如果您有其他好的想法,请随时教我:)