В iOS 14 как программно нажать кнопку?

В iOS 14 я настроил кнопку для отображения меню вопросов. Сама по себе кнопка работает отлично. Вот как это настроено:

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

Я установил его в текстовом поле rightView. Вот как это выглядит при отображении меню.

При нажатии на элемент меню этот вопрос появляется в метке прямо над текстовым полем. Это прекрасно работает. Кроме….

Я также хочу, чтобы меню появлялось, когда пользователь нажимает в текстовом поле. Я не хочу, чтобы им приходилось специально нажимать на кнопку. В старые времена целевого действия это было легко: button.sendActions(for: .touchUpInside). Я попробовал это и .menuActionTriggered и .primaryActionTriggeredно ни один из них не работает. Я думаю, что 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).

Любые идеи о том, как заставить кнопку отображать свое меню при нажатии на текстовое поле?

пс. да, textFieldShouldBeginEditingнужно будет знать, был ли выбран вопрос, и в этом случае нужно будет разрешить редактирование. Эта часть проста.

1 ответ

Из того, что я читал, вы не можете программно запускать UIContextMenuInteractionпоскольку взаимодействия, кажется, обрабатываются внутри себя, в отличие от send(_: UIControl.Event)

Я вижу это упомянутым в этом сообщении SO здесь

Также в документах кажется, что у нас нет доступа к управлению взаимодействием. Apple должна решить, доступно ли 3D-касание или по умолчанию вернуться к долгому касанию.

Из документов

Объект взаимодействия с контекстным меню отслеживает жесты Force Touch на устройствах, поддерживающих 3D Touch, и жесты длительного нажатия на устройствах, которые его не поддерживают.

Обходной путь

Я могу предложить следующий обходной путь для вашего варианта использования. Мой пример был создан с использованием фрейма вместо автоматического макета, так как быстрее и проще продемонстрировать концепцию таким образом, однако вам нужно будет внести коррективы с помощью автомакета.

1. Создайте элементы пользовательского интерфейса

      // 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. Это был пример использования кадров со случайными значениями, необходимо внести коррективы для автомакета.

Правки из ОП (слишком длинные для комментария):

  • Я пытался установить contentEdgeInsetsтак что кнопка была далеко слева, но закрывала текст-заполнитель.
  • Ваша идея просто добавить кнопку в качестве подвида текстового поля была ключевой, но...
  • После выбора вопроса и изменения размера кнопки, если текстовое поле было первым ответившим, нажатие кнопки не имело никакого эффекта. Кнопка была в иерархии представлений, но текстовое поле получило нажатие.
  • Итак, когда вопрос выбран, я удаляю кнопку из ее супервизора (текстового поля) и добавляю ее в текстовое поле. rightView. Затем он примет касание, даже если текстовое поле будет первым ответчиком.
  • И, как вы подозревали, мне пришлось прикрепить кнопку к textField с ограничениями.
Другие вопросы по тегам