Tenho um SwiftUI List
(no macOS) onde quero exibir alguns TextFields
em cada linha e observar como o foco muda conforme um campo de texto individual é selecionado ou desmarcado. Isso parece funcionar bem se eu apenas tiver a List
exibição pura TextField
em cada linha, mas se eu incorporar TextField
dentro de uma Button
visualização, as notificações de mudança de foco param de funcionar. Quero a funcionalidade de 'botão' para que o usuário possa tocar em qualquer lugar da linha e ter o campo de texto ativado, em vez de tocar exatamente no limite do campo de texto.
Aqui está um código para demonstrar o problema. Se List
você tiver RowView
descomentado (1), então as notificações de mudança de foco funcionam. Se você comentar isso e descomentar RowViewWithButton
(2), então a funcionalidade do botão funciona, mas não consigo mais receber notificações de mudança de foco.
import SwiftUI
// test on macOS target
struct ContentView: View {
@State private var textFields = Array(repeating: "", count: 4)
@FocusState private var focusedField: Int?
var body: some View {
List(0..<4, id: \.self) { index in
// 1. works with focus notification changes
RowView(index: index, text: $textFields[index], focusedField: $focusedField)
// 2. button works, but no focus notifications on text field
//RowViewWithButton(index: index, text: $textFields[index], focusedField: $focusedField)
}
}
}
struct RowView: View {
let index: Int
@Binding var text: String
@FocusState.Binding var focusedField: Int?
var body: some View {
HStack {
Text("Row \(index + 1):")
TextField("", text: $text)
.multilineTextAlignment(.leading)
.focused($focusedField, equals: index)
}
.onChange(of: focusedField) { newFocus in
if newFocus == index {
print("TextField \(index) is in focus")
} else {
print("TextField \(index) lost focus")
}
}
.padding(.vertical, 4)
}
}
struct RowViewWithButton: View {
let index: Int
@Binding var text: String
@FocusState.Binding var focusedField: Int?
var body: some View {
Button (action: {
print("RowView - button selected at index \(index)")
focusedField = index
}) {
HStack {
Text("Row \(index + 1):")
TextField("", text: $text)
.multilineTextAlignment(.leading)
.focused($focusedField, equals: index)
}
.onChange(of: focusedField) { newFocus in
if newFocus == index {
print("TextField \(index) is in focus")
} else {
print("TextField \(index) lost focus")
}
}
.foregroundColor(.primary)
}
.buttonStyle(BorderlessButtonStyle())
.padding(.vertical, 4)
}
}
#Preview {
ContentView()
.frame(width: 320, height: 250)
}
Curiosamente, quando eu testo isso no iOS, funciona bem em ambos os casos. Mas eu preciso da solução para macOS.
ATUALIZAR:
Usar onTapGesture
em vez de a Button
melhora as coisas, mas introduz um novo bug. Se eu tocar na linha inicialmente, inicialmente funciona bem, e tocar em qualquer lugar ativará o campo de texto. Mas depois de digitar algum texto e pressionar a tecla Enter (ou perder o foco de qualquer forma), se eu tentar tocar na parte da linha onde TextField
está o, ele só funciona se tocado no texto existente... não no espaço vazio na frente dele.
Adicionei .background(.red)
ao HStack e .background(.blue)
adicionei uma captura de tela para demonstrar o problema.
Em
RowViewWithButton
, altere o estilo do botão para.plain
:No macOS,
.borderless
absorve toda a interação, afetando diretamente as visualizações focáveis, enquanto.plain
preserva a interação normal.Atualizar:
Como alterar o estilo
.plain
faz com que o botão seja acionado ao digitar um espaço no TextField, talvez o mais simples seja não usar nenhum botão e apenas usar.onTapGesture
:Do seu comentário,
I want the 'button' functionality so that the user can tap anywhere on the row and have the text field activated, instead of tapping exactly on the text field boundary
.Você pode conseguir isso sem usar um
Button
.Experimente esta abordagem usando um
.overlay(...)
paraHStack
capturar os toques em uma linha da lista e ainda conseguir verificar
a mudança de foco ao mesmo tempo.
Código de exemplo, testado no MacOS 15.4
}