Как обрабатывать UITapGestureRecognizer для представлений, созданных в циклах?

#ios #swift #uitapgesturerecognizer

#iOS #swift #uitapgesturerecognizer распознаватель

Вопрос:

У меня есть вертикальный UIStackView, и я добавляю горизонтальный UIStackView и некоторые UILabels внутри этого представления. Я подключаю UITapGestureRecognizer для обработки действий с касанием. Как я могу изменить, например, метку (которая является вложенным представлением), когда я нажимаю на строку?

Вот мой код на данный момент:

 for article in articleType {
    //Create a row
    let row = UIStackView()
    row.axis = NSLayoutConstraint.Axis.horizontal
    
    //Create a label and indicator
    let label = UILabel()
    label.text = article.label

    let indicator = UILabel()
    indicator.text = "off"

    //Add label to row and row to parent stack view
    row.addSubView(label)
    row.addSubView(indicator)
    mainUiStackView.addArrangedSubview(row)

    //Create tap handled
    let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(sender:)))
    row.addGestureRecognizer(tap)
}

@objc func handleTap(sender: UITapGestureRecognizer? = nil) {
    print(sender.view)

    //This is where i'm stuck, how can i change the second label's text to "on" for example?
    sender.view.indicator.text = "on"

}
 

Комментарии:

1. Вы не можете нажать на представление стека.

2. @Мэтт, почему бы и нет? Я только что создал тестовое приложение, в котором я добавил распознаватели жестов касания как в упорядоченные подвиды, так и в представление стека, и оба они сработали, как и ожидалось. (Мне пришлось нажимать на пробелы между подвидами, чтобы сработал распознаватель жестов в стековом представлении, но это сработало.)

3. @DuncanC Ну, может быть, они изменили это. Раньше считалось, что представление стека было особым видом представления.

4. Таким образом, каждое представление стека всегда будет содержать ровно 2 метки, и когда пользователь нажимает на заданное представление стека, вы всегда хотите перейти в представление стека и установить для текста метки значение «вкл.»?

5. ДА. На самом деле я хочу изменить изображение флажка на вкл / выкл, но использовал UILabel в примере кода, так что это не так сложно, но логика будет применяться таким же образом.

Ответ №1:

Ваш код проходит через цикл и создает целую серию представлений стека в локальной переменной row . Тогда вы не добавляете эти представления стека в свою иерархию представлений, поэтому они «падают на пол».

Для этой строки:

 let row = UIStackView()
 

После каждого прохождения цикла for созданное вами представление стека будет выходить за пределы области видимости и освобождаться.

Редактировать:

После того, как вы указали на это, теперь я вижу, где вы добавляете свои row представления стека к своим mainUiStackView .

Основываясь на ваших комментариях, я понял, что вы хотите, чтобы при нажатии на одно из ваших представлений стека «строка» indicator для метки в этом представлении стека было установлено значение «вкл.».

Я бы предложил создать массив ваших представлений стека строк и их представлений меток (в виде структур). Затем найдите строку, которая была задействована, и установите ее метку в состояние on:

 struct RowStackViewStruct { 
   let stackView: UIStackView
   let label: UILabel
}

var rowStackViews = [RowStackViewStruct]()


for article in articleType {
    //Create a row
    let row = UIStackView()
    row.axis = NSLayoutConstraint.Axis.horizontal
    
    //Create a label and indicator
    let label = UILabel()
    label.text = article.label

    let indicator = UILabel()
    indicator.text = "off"

    //Add label to row and row to parent stack view
    row.addSubView(label)
    row.addSubView(indicator)
    mainUiStackView.addArrangedSubview(row)

    //Create tap handled
    let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(sender:)))
    row.addGestureRecognizer(tap)

    //Build a RowStackViewStruct for this row
    let newRowStackViewStruct = RowStackViewStruct(stackView: row,
      label: indicator)
    rowStackViews.append(newRowStackViewStruct)
}

And then in your tap handler code:

@objc func handleTap(sender: UITapGestureRecognizer? = nil) {
    print(sender.view)

    if let aStruct = rowStackViews.first(where: { sender?.view == $0.stackView }) {
       aStruct.label.text = "on"
}
 

Я удалил приведенный выше код в редакторе SO. Вероятно, он содержит незначительные синтаксические ошибки и предназначен в качестве руководства, а не для копирования и вставки кода, который вы можете использовать.

Похоже, вам действительно следует отслеживать состояние включения-выключения ваших строк. Похоже, у вас должен быть массив объектов модели, которые представляют состояние ваших строк, и используйте его для создания и поддержки вашего основного представления вертикального стека. На самом деле, возможно, было бы лучше переключиться на табличное представление или представление коллекции, поскольку они созданы для управления из модели.

Комментарии:

1. Привет! Эта часть кода на самом деле работает нормально. Проблема, с которой я сталкиваюсь, заключается в том, как получить доступ к определенному вложенному представлению UILabel после нажатия на всю строку. Итак, sender.view.indicator.text, очевидно, не будет работать, но я не знаю, как получить доступ к этой метке включения / выключения, не имея для этого IBOutlet (а у меня его нет, поскольку я создаю эти метки в цикле из кода)

2. Опубликованный вами код является неполным, как и ваше описание того, что вы хотите сделать. Опубликованный вами код не будет работать по причинам, которые я указал в своем ответе. Если вы пытаетесь получить доступ к представлению стека, выясните, какой вид внутри представления стека был задействован, отредактируйте свой вопрос, чтобы указать это, и сформулируйте его четко. Сводит с ума необходимость проигрывать 20 вопросов, чтобы понять, что кто-то спрашивает.

3. Извините, я не совсем понимаю, что вы имеете в виду. Я добавляю всю строку в другой UIStackView в этой строке: mainUiStackView.addArrangedSubview(строка). Действие нажатия отлично работает для строки, печать (sender.view) правильно регистрирует действие для каждой строки отдельно. Я не хочу добавлять жест касания к UILabels внутри строки, только к основной строке. И когда нажата основная строка, я хочу изменить один из текстов метки внутри этой строки. Это то, что я описал в своем вопросе, я не знаю, как сделать это более понятным.

4. Я пропустил эту фразу мимо ушей. См. Редактирование моего ответа.

5. Спасибо, работает отлично, нужно изменить только одно, обновил ваш ответ.

Ответ №2:

Во-первых, убедитесь isUserInteractionEnabled , что для свойства любой метки, на которую вы хотите нажать, установлено true значение, поскольку оно установлено false по умолчанию.

 @objc func handleTap(_ sender: UITapGestureRecognizer) {
    guard let tappedView = sender.view else { return }
    let location = sender.location(in: tappedView.superview)
    if case let hitView as UILabel = tappedView.hitTest(location, with: nil) {
        hitView.text = hitView.text == "off" ? "on" : "off"
    }
}
 

Комментарии:

1. Извините, мне кажется, я не совсем ясно выразился в своем вопросе. Поэтому я хочу, чтобы действие нажатия выполнялось во всей строке. Когда я нажимаю на строку (в любом случае, а не на конкретное вложенное представление), я хочу изменить значение UILabel вложенного представления с off на on. Так что это было бы легко, если бы у меня был IBOutlet для второй UILabel, но поскольку я генерирую их из кода и в цикле, не уверен, как получить доступ к этим меткам в функции handleTap.