SwiftUI не могу подключиться к Spacer of 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() есть) действие крана не зарегистрировано. Действие касания произойдет только тогда, когда я коснусь текста или изображения.

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

13 ответов

Решение

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

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

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

У меня хорошо работает.

работает как по волшебству на всех представлениях:

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

На мой взгляд, лучший подход из соображений доступности - обернуть HStack внутри метки кнопки, и для решения проблемы с 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())
    }

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

extension Spacer {
    /// https://stackru.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
}

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

ZStack {
    Color.black.opacity(0.001)
    Spacer()
}

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

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

})

Просто добавление пустого модификатора фона работает.

      HStack {
  ...
}
.background()
.onTapGesture { ... }

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

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

Пример

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

НастройкиNavigationCell

      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, К сожалению, вам придется подобрать цвет фона, и это предполагает, что вам нечего отображать за кнопкой.

Обновление ответа sergioblancoo.

      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

Этот веб-сайт помог мне ответить на тот же вопрос: https://www.hackingwithswift.com/quick-start/swiftui/how-to-control-the-tappable-area-of-a-view-using-contentshape .

Попробуйте применить.ContentShape(Rectangle())кHStack.

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