SwiftUI не может нажать в Spacer HStack

У меня есть представление списка, и каждая строка списка содержит HStack с некоторыми текстовыми представлениями и изображением, например:

HStack{
    Text(group.name)
    Spacer()
    if (groupModel.required) { Text("Required").color(Color.gray) }
    Image("ic_collapse").renderingMode(.template).rotationEffect(Angle(degrees: 90)).foregroundColor(Color.gray)
}.tapAction { self.groupSelected(self.group) }

Кажется, это отлично работает, за исключением случаев, когда я нажимаю на пустой раздел между своим текстом и изображением (где находится Spacer()), действие касания не регистрируется. Действие касания будет происходить только тогда, когда я касаюсь текста или изображения.

Кто-нибудь еще сталкивался с этой проблемой/знает обходной путь?

Честный вопрос: Ровно Зачем вы ожидаете, что кто-то нажмет spacer? Это по определению космос. Может быть, ваш пользовательский интерфейс ожидает чего-то, что вы могли бы в UIKit? Если да, пожалуйста, уточните это.

user7014451 24.07.2019 22:52

@dfd Каждая строка представляет собой просто текст с шевроном в конце, что-то вроде Object One > , так будет выглядеть строка, и я бы хотел, чтобы пользователь мог нажать в любом месте строки (это не форматировалось с пробелы, которые, как я думал, будут - просто представьте себе пробел между текстом и >)

Quinn 24.07.2019 22:54

@dfd Я думаю, что это довольно стандартное поведение, когда пользователь может щелкнуть в любом месте ячейки таблицы, поэтому у них есть метод didSelectRowAt в их табличных представлениях UIKit.

Quinn 24.07.2019 22:57

Конечно, я согласен. Но, возможно, вместо Spacer попробуйте что-нибудь другое. Может быть, превратить все это в Button? В SwiftUI Spacer — это просто интервал.

user7014451 25.07.2019 00:50

Не могу поверить, что скажу это... но да, старина, но хороша! Когда я предлагал кнопку, я имел в виду следующее: alejandromp.com/blog/2019/06/09/игра с кнопками Swiftui

user7014451 25.07.2019 00:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
88
5
13 432
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Я подал Обратная связь по этому поводу и предлагаю вам сделать то же самое.

Тем временем непрозрачный Color должен работать так же хорошо, как Spacer. К сожалению, вам придется совпадать с цветом фона, и это предполагает, что вам нечего отображать за кнопкой.

Почему бы просто не использовать Button?

Button(action: { self.groupSelected(self.group) }) {
    HStack {
        Text(group.name)
        Spacer()
        if (groupModel.required) { Text("Required").color(Color.gray) }
        Image("ic_collapse").renderingMode(.template).rotationEffect(Angle(degrees: 90)).foregroundColor(Color.gray)
    }
}.foregroundColor(.primary)

Если вы не хотите, чтобы кнопка применяла цвет акцента к Text(group.name), вы должны установить foregroundColor, как я сделал в моем примере.

Это изменяет цвет фона при нажатии, хотя вы, вероятно, можете изменить это: stackoverflow.com/questions/56509640/… Решение ZStack от @jim-marquardt хорошо сработало для меня.

choxi 22.08.2019 02:30

Не то же самое поведение с navigationLink

iGhost 06.08.2020 00:39

Я смог обойти это, поместив Spacer в ZStack и добавив сплошной цвет с очень низкой непрозрачностью:

ZStack {
    Color.black.opacity(0.001)
    Spacer()
}
Ответ принят как подходящий

Как я недавно узнал, есть еще:

HStack {
  ...
}
.contentShape(Rectangle())
.onTapGesture { ... }

Хорошо работает для меня.

Это отлично сработало применительно к содержимому моего Button. В этом случае нет необходимости в onTapGesture.

Kris McGinnes 24.10.2019 18:43

Работает с HStack с разделителями внутри него.

LondonGuy 11.11.2019 02:19

Это хорошо работает. Принятый ответ не сработал для меня, потому что он меняет цвет акцента содержимого HStack.

Cloud9999Strife 19.11.2019 07:19

Это также лучшее решение для моего случая, чем принятый ответ, поскольку у меня есть строка с различными элементами управления, а не кнопка. Но все это должно быть доступным. Кроме того, как уже упоминал Cloud9999Strife, внешний вид не меняется.

G. Marc 24.11.2019 10:41

Ты спас мне жизнь, бро!

Quang Hà 14.02.2020 04:08

Еще несколько пояснений от Hacking With Swift: hackingwithswift.com/quick-start/swiftui/….

David L 05.04.2020 15:39

Тот факт, что это решение требуется, разочаровывает меня в SwiftUI в целом. В любом случае, отличное решение!

C. Skjerdal 03.02.2021 19:40

Изучил трудным путем порядок имеет значение для модификаторов, убедитесь, что contentShape стоит перед onTapGesture.

robotsquidward 06.03.2021 15:03

Простое расширение на основе ответа Джима

extension Spacer {
    /// https://stackoverflow.com/a/57416760/3393964
    public func onTapGesture(count: Int = 1, perform action: @escaping () -> Void) -> some View {
        ZStack {
            Color.black.opacity(0.001).onTapGesture(count: count, perform: action)
            self
        }
    }
}

Теперь это работает

Spacer().onTapGesture {
    // do something
}

Можете ли вы описать более подробно, что это делает? Это создает новый ZStack каждый раз, когда пользователь нажимает? Очищает ли он память от этого? Кстати, это сработало отлично :).

Joseph Astrahan 19.12.2019 02:53

@JosephAstrahan нет, он просто создает непрозрачное представление за вашим текущим представлением, которое регистрирует нажатия пользователя.

Casper Zandbergen 19.12.2019 10:18

Уничтожает ли он (освобождает память) представление после завершения крана?

Joseph Astrahan 19.12.2019 22:23

Итак, он создает 1-кратный просмотр? Не создавать это представление каждый раз, когда пользователь нажимает снова и снова, я думаю, это был мой главный вопрос. Причина, по которой я спросил, заключалась в том, что она находится внутри функции on TapGesture, что подразумевает создание представления снова и снова.

Joseph Astrahan 20.12.2019 19:34

Это чистый ответ, особенно для отслеживания фантомных нажатий.

DaWiseguy 23.02.2021 21:16

Я просто добавляю цвет фона (кроме clear цвета) для HStack работ.

HStack {
        Text("1")
        Spacer()
        Text("1")
}.background(Color.white)
.onTapGesture(count: 1, perform: {

})

работает как по волшебству при каждом просмотре:

extension View {
        func onTapGestureForced(count: Int = 1, perform action: @escaping () -> Void) -> some View {
            self
                .contentShape(Rectangle())
                .onTapGesture(count:count, perform:action)
        }
    }

Это круто

DaWiseguy 23.02.2021 21:16

Хотя принятый ответ позволяет имитировать функциональность кнопки, визуально он не удовлетворяет. Не заменяйте Button на .onTapGesture или UITapGestureRecognizer, если только вам не нужна область, которая принимает события касания пальцем. Такие решения считаются хакерскими и не являются хорошей практикой программирования.

Для решения вашей проблемы вам необходимо реализовать BorderlessButtonStyle ⚠️

Пример

Создайте общую ячейку, например. SettingsNavigationCell.

НастройкиНавигацияЯчейка

struct SettingsNavigationCell: View {
  
  var title: String
  var imageName: String
  let callback: (() -> Void)?

  var body: some View {
    
    Button(action: {
      callback?()
    }, label: {

      HStack {
        Image(systemName: imageName)
          .font(.headline)
          .frame(width: 20)
        
        Text(title)
          .font(.body)
          .padding(.leading, 10)
          .foregroundColor(.black)
        
        Spacer()
        
        Image(systemName: "chevron.right")
          .font(.headline)
          .foregroundColor(.gray)
      }
    })
    .buttonStyle(BorderlessButtonStyle()) // <<< This is what you need ⚠️
  }
}

НастройкиПросмотр

struct SettingsView: View {
  
  var body: some View {
    
    NavigationView {
      List {
        Section(header: "Appearance".text) {
          
          SettingsNavigationCell(title: "Themes", imageName: "sparkles") {
            openThemesSettings()
          }
          
          SettingsNavigationCell(title: "Lorem Ipsum", imageName: "star.fill") {
            // Your function
          }
        }
      }
    }
  }
}

Вроде в духе всего сказанного:

struct NoButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .background(Color.black.opacity(0.0001))
    }
}
extension View {
    func wrapInButton(action: @escaping () -> Void) -> some View {
        Button(action: action, label: {
            self
        })
        .buttonStyle(NoButtonStyle())
    }
}

Я создал NoButtonStyle, потому что BorderlessButtonStyle все еще давал анимацию, отличную от .onTapGesture.

Пример:

HStack {
    Text(title)
    Spacer()
    Text("Select Value")
    Image(systemName: "arrowtriangle.down.square.fill")
}
.wrapInButton {
    isShowingSelectionSheet = true
}

Другой вариант:

extension Spacer {
    func tappable() -> some View {
        Color.blue.opacity(0.0001)
    }
}

Обновлено:

Я заметил, что Color не всегда действует так же, как Spacer при помещении в стек, поэтому я бы посоветовал не использовать это расширение Spacer, если вы не знаете об этих различиях. (Распорка выталкивает стек в одном направлении (если в VStack, то выталкивает вертикально, если в HStack, то выталкивает горизонтально, тогда как вид Color выталкивает во всех направлениях.)

На мой взгляд, лучший подход из соображений доступности — обернуть HStack внутри метки Button, а чтобы решить проблему, когда Spacer нельзя нажать, вы можете добавить .contentShape(Rectangle()) в HStack.

Итак, на основе вашего кода будет:

    Button {
        self.groupSelected(self.group)
    } label: {
        HStack {
            Text(group.name)
            Spacer()
            if (groupModel.required) { Text("Required").color(Color.gray) }
            Image("ic_collapse").renderingMode(.template).rotationEffect(Angle(degrees: 90)).foregroundColor(Color.gray)
        }
        .contentShape(Rectangle())
    }

Другие вопросы по теме