在 iOS 14 中,如何以编程方式点击按钮?

In iOS 14 how do you tap a button programmatically?

在 iOS 14 中,我配置了一个按钮来显示问题菜单。按钮本身运行良好。这是它的配置方式:

let button = UIButton()
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
button.showsMenuAsPrimaryAction = true
button.menu = self.menu

我在文本字段的 rightView 上设置了它。这是显示菜单时的样子。

点击菜单项时,该问题会出现在文本字段正上方的标签中。它很好用。除了……

我还希望当用户 taps 在文本字段中时显示菜单。我不希望他们必须专门点击按钮。在过去的目标行动时代,这很容易:button.sendActions(for: .touchUpInside)。我试过了 .menuActionTriggered.primaryActionTriggered 但其中 none 有效。我认为 sendActions() 正在寻找旧的基于选择器的操作。

但是有一个新的 sendActions(for: UIControl.Event) 方法。这是我在 textFieldShouldBeginEditing() 中尝试的内容:

func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
    let tempAction = UIAction { action in
        print("sent action")
    }
    menuButton.sendAction(tempAction)
    print("textField tapped")
    return false
}

果然,当文本字段被点击时,“发送操作”和“文本字段被点击”出现在控制台中。但我不知道我可以发送什么 UIAction 表示“显示你的菜单”。我希望有这种方法:send(_: UIControl.Event).

关于如何让按钮在点击文本字段时显示其菜单有什么想法吗?

ps。是的,textFieldShouldBeginEditing 需要知道是否选择了一个问题,在这种情况下需要允许编辑。那部分很简单。

据我了解,您无法以编程方式触发 UIContextMenuInteraction,因为与 send(_: UIControl.Event)

不同,交互似乎是在内部自行处理的

我看到 this SO post here

上提到了这个

同样在文档中,我们似乎无法访问交互管理 Apple needs to decide 3D touch is available or default back to long tap

来自文档

上下文菜单交互对象在支持 3D Touch 的设备上跟踪 Force Touch 手势,在不支持它的设备上跟踪 long-press 手势。

解决方法

我可以针对您的用例提出以下解决方法。我的示例是使用框架而不是自动布局创建的,这样可以更快更轻松地演示概念,但是您需要使用自动布局进行调整

1。创建 UI 个元素

// I assume you have a question label, answer text field and drop down button
// Set up should be adjusted in case of autolayout
let questionLabel = UILabel(frame: CGRect(x: 10, y: 100, width: 250, height: 20))
let answerTextField = UITextField(frame: CGRect(x: 10, y: 130, width: 250, height: 50))
let dropDownButton = UIButton()

2。首先定期设置标签和文本视图

// Just regular set up
private func configureQuestionLabel()
{
    questionLabel.textColor = .white
    view.addSubview(questionLabel)
}

// Just regular set up
private func configureAnswerTextField()
{
    let placeholderAttributes = [NSAttributedString.Key.foregroundColor :
                                    UIColor.lightGray]
    
    answerTextField.backgroundColor = .white
    answerTextField.attributedPlaceholder = NSAttributedString(string: "Tap to select a question",
                                                               attributes: placeholderAttributes)
    answerTextField.textColor = .black
    
    view.addSubview(answerTextField)
}

3。将按钮添加到文本视图

// Here we have something interesting
private func configureDropDownButton()
{
    // Regular set up
    dropDownButton.setImage(UIImage(systemName: "chevron.down"), for: .normal)
    dropDownButton.showsMenuAsPrimaryAction = true
    
    // 1. Create the menu options
    // 2. When an option is selected update the question label
    // 3. When an option is selected, update the button frame
    // Update button width function explained later
    dropDownButton.menu = UIMenu(title: "Select a question", children: [
        
        UIAction(title: "Favorite movie") { [weak self] action in
            self?.questionLabel.text = action.title
            self?.answerTextField.placeholder = "Add your answer"
            self?.answerTextField.text = ""
            self?.updateButtonWidthIfRequired()
        },
        
        UIAction(title: "Car make") { [weak self] action in
            self?.questionLabel.text = action.title
            self?.answerTextField.placeholder = "Add your answer"
            self?.answerTextField.text = ""
            self?.updateButtonWidthIfRequired()
        },
    ])
    
    // I right align the content and set some insets to get some padding from
    // the right of the text field
    dropDownButton.contentHorizontalAlignment = .right
    dropDownButton.contentEdgeInsets = UIEdgeInsets(top: 0.0,
                                                    left: 0.0,
                                                    bottom: 0.0,
                                                    right: 20.0)
    
    // The button initially will stretch across the whole text field hence we
    // right aligned the content above
    dropDownButton.frame = answerTextField.bounds
    
    answerTextField.addSubview(dropDownButton)
}

// Update the button width if a menu option was selected or not
func updateButtonWidthIfRequired()
{
    // Button takes the text field's width if the question isn't selected
    if let questionText = questionLabel.text, questionText.isEmpty
    {
        dropDownButton.frame = answerTextField.bounds
        return
    }
    
    // Reduce button width to right edge as a question was selected
    dropDownButton.frame = CGRect(x: answerTextField.frame.width - 50.0,
                                  y: answerTextField.bounds.origin.y,
                                  width: 50,
                                  height: answerTextField.frame.height)
}

4.最终结果

从与您相似的观点开始

然后我点击文本字段的中间

它按预期显示菜单

选择一个选项后,问题会显示在标签中并且占位符会更新

现在我可以开始使用文本字段输入我的答案,并且该按钮仅在右侧处于活动状态,因为它已调整大小

并且按钮仍然有效

最后的想法

  1. 最好将其放入 UIView / UITextField 子类
  2. 这是一个使用具有随机值的框架的示例,需要针对自动布局进行调整

来自 OP 的编辑(评论太长):

  • 我曾尝试设置 contentEdgeInsets,因此按钮位于左侧,但它覆盖了占位符文本。
  • 您将按钮简单地添加为文本字段的子视图的想法是关键,但是...
  • 选择问题并调整按钮大小后,如果文本字段是第一响应者,则点击按钮无效。该按钮位于视图层次结构中,但文本字段得到了点击。
  • So, when a question is selected, I remove the button from its superview (the textfield) and add it to the textField's rightView.然后它会接受点击,即使 textField 是第一响应者。
  • 而且,正如您所怀疑的,我不得不将按钮固定到带有约束的 textField。