【问题标题】:SwiftUI TextField touchable AreaSwiftUI TextField 可触摸区域
【发布时间】:2019-11-09 18:02:19
【问题描述】:

SwiftUI 布局与我们习惯的非常不同。目前我正在对抗TextFields。特别是他们的可触摸区域。

TextField(
    .constant(""),
    placeholder: Text("My text field")
    )
        .padding([.leading, .trailing])
        .font(.body)

这导致一个非常小的TextField(高度)

添加框架修饰符可以解决问题(视觉上)

TextField(
    .constant(""),
    placeholder: Text("My text field")
    ).frame(height: 60)
        .padding([.leading, .trailing])
        .font(.body)

但可触摸区域保持不变。

我知道框架修饰符除了将 textField 包装在具有指定高度的另一个视图中之外没有其他任何作用。

是否有任何等效于resizable()Image 允许更高的TextField 和更宽的可触摸区域?

【问题讨论】:

  • 我面临同样的问题
  • 你找到解决办法了吗?
  • 不,我没有。我向苹果公司提交了一份雷达。他们说它是固定的,但它不是......我最终放弃了
  • @GiuseppeLanza “他们说它已经修复”是什么意思?它应该通过应用框架来工作吗?还是填充?
  • 在 Swift v2 (2020) 中仍然没有解决方案(除了UIViewRepresentable'ing UITextField)

标签: swiftui


【解决方案1】:

按钮解决方案

如果您不介意使用 Introspect,您可以通过保存 UITextField 并在按下按钮时调用 becomeFirstResponder() 来实现。

extension View {
    public func textFieldFocusableArea() -> some View {
        TextFieldButton { self.contentShape(Rectangle()) }
    }
}

fileprivate struct TextFieldButton<Label: View>: View {
    init(label: @escaping () -> Label) {
        self.label = label
    }
    
    var label: () -> Label
    
    private var textField = Weak<UITextField>(nil)
    
    var body: some View {
        Button(action: {
            self.textField.value?.becomeFirstResponder()
        }, label: {
            label().introspectTextField {
                self.textField.value = $0
            }
        }).buttonStyle(PlainButtonStyle())
    }
}

/// Holds a weak reference to a value
public class Weak<T: AnyObject> {
    public weak var value: T?
    public init(_ value: T?) {
        self.value = value
    }
}

示例用法:

TextField(...)
    .padding(100)
    .textFieldFocusableArea()

由于我自己也使用这个,所以我会在github上保持更新:https://gist.github.com/Amzd/d7d0c7de8eae8a771cb0ae3b99eab73d

使用 ResponderChain 的新解决方案

Button 解决方案将添加可能不需要的样式和动画,因此我现在使用ResponderChain package 的新方法

import ResponderChain

extension View {
    public func textFieldFocusableArea() -> some View {
        self.modifier(TextFieldFocusableAreaModifier())
    }
}

fileprivate struct TextFieldFocusableAreaModifier: ViewModifier {
    @EnvironmentObject private var chain: ResponderChain
    @State private var id = UUID()
    
    func body(content: Content) -> some View {
        content
            .contentShape(Rectangle())
            .responderTag(id)
            .onTapGesture {
                chain.firstResponder = id
            }
    }
}

您必须在 SceneDelegate 中将 ResponderChain 设置为环境对象,查看 ResponderChain 的 README 以获得更多信息。

【讨论】:

  • 我可以验证这是一个很好的解决方案。必须使用第三方包并不理想,但在 Apple 发布本地方法之前它会运行得很好
【解决方案2】:

没有任何第三方的解决方案

无需第三方即可增加可点击区域:

第一步:创建修改后的TextField。这样做是为了我们可以定义新 TextField 的填充:

使用的代码来自 - https://stackoverflow.com/a/27066764/2217750

class ModifiedTextField: UITextField {
  let padding = UIEdgeInsets(top: 20, left: 5, bottom: 0, right: 5)

  override open func textRect(forBounds bounds: CGRect) -> CGRect {
    bounds.inset(by: padding)
  }

  override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
    bounds.inset(by: padding)
  }

  override open func editingRect(forBounds bounds: CGRect) -> CGRect {
    bounds.inset(by: padding)
  }
}

第 2 步: 制作新的ModifiedTexField UIViewRepresentable,以便我们可以使用它SwiftUI

struct EnhancedTextField: UIViewRepresentable {
  @Binding var text: String
  
  
  init(text: Binding<String>) {
    self._text = text
  }

  func makeUIView(context: Context) -> ModifiedTextField {
    let textField = ModifiedTextField(frame: .zero)
    textField.delegate = context.coordinator
    
    return textField
  }
  

  func updateUIView(_ uiView: ModifiedTextField, context: Context) {
    uiView.text = text
  }


  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
  
  class Coordinator: NSObject, UITextFieldDelegate {
    let parent: EnhancedTextField

    init(_ parent: EnhancedTextField) {
      self.parent = parent
    }
  

    func textFieldDidChangeSelection(_ textField: UITextField) {
      parent.text = textField.text ?? ""
    }
  }
} 

第三步:在需要的地方使用新的EnhancedTextField

EnhancedTextField(placeholder: placeholder, text: $binding)

注意:要增加或减少可点击区域,只需更改 ModifiedTextField 中的填充

let padding = UIEdgeInsets(top: 20, left: 5, bottom: 0, right: 5)  

【讨论】:

    【解决方案3】:

    有点解决方法,但有效。

      struct CustomTextField: View {
    @State var name = ""
    @State var isFocused = false
    let textFieldsize : CGFloat = 20
    var textFieldTouchAbleHeight : CGFloat = 200
    var body: some View {
            ZStack {
                HStack{
                    Text(name)
                        .font(.system(size: textFieldsize))
                        .lineLimit(1)
                        .foregroundColor(isFocused ? Color.clear : Color.black)
                        .disabled(true)
                    Spacer()
                }
                .frame(alignment: .leading)
                TextField(name, text: $name , onEditingChanged: { editingChanged in
                    isFocused = editingChanged
                })
                .font(.system(size: isFocused ? textFieldsize :  textFieldTouchAbleHeight ))
                .foregroundColor(isFocused ? Color.black  : Color.clear)
                .frame( height: isFocused ? 50 :  textFieldTouchAbleHeight , alignment: .leading)
                 
            }.frame(width: 300, height: textFieldTouchAbleHeight + 10,alignment: .leading)
            .disableAutocorrection(true)
            .background(Color.white)
            .padding(.horizontal,10)
            .padding(.vertical,10)
            .border(Color.red, width: 2) 
        
          }
    
        }
    

    【讨论】:

    • 我投了赞成票,但后来意识到它不能 100% 工作 - 如果之前输入了文本,我必须点击文本字段两次才能显示光标。
    【解决方案4】:

    我不知道哪个更适合你。 所以,我发布了两个解决方案。

    1) 如果你只想缩小输入区域。

      var body: some View {
         Form {
            HStack {
               Spacer().frame(width: 30)
               TextField("input text", text: $inputText)
               Spacer().frame(width: 30)
            }
         }
       }
    

    2) 缩小整个表单区域

      var body: some View {
        HStack {
            Spacer().frame(width: 30)
            Form {
                TextField("input text", text: $restrictInput.text)
            }
            Spacer().frame(width: 30)
        }
     }
    

    【讨论】:

    • 但我不希望我的文本字段出现在表单中。我想要的是视图上的文本字段,并且我希望它具有我选择的活动大小。就像你可以用 UIKit 做的那样:创建一个文本字段,给它框架,然后把它放在屏幕上。现在,虽然我可以添加框架修饰符,但文本字段被包裹在视图中,并且文本字段的可触摸区域仍然包裹在非常小的字段内容中
    【解决方案5】:

    快速的解决方法是将 TextField 放在一个按钮中,无论您在哪里点击(在按钮中),它都会打开键盘;我知道这不是一个解决方案,但它可以完成工作(有点)。

    【讨论】:

    • 这是一个很好的解决方法......但是当我们想要一个文本字段时我们需要一个按钮的想法很麻烦。这会起作用,但我仍然认为必须有另一种方法。我不相信苹果没有考虑在表单之外使用 textField 的可能性。
    • 我完全同意;老实说,我很震惊,您不仅可以调整一个属性来增加可点击区域,或者只是让可点击区域自动成为框架/边框的大小。
    • @ThomasBahmandeji 你是怎么做到的?实在想不通。
    • 对我不起作用。按钮会点击,但 TextField 不会成为第一响应者。
    • 基于此答案:stackoverflow.com/questions/58618470/… 我想您是说将文本字段放在按钮内,对吗?我试过这个,它对我不起作用。你到底是怎么做到的@ThomasBahmandeji
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-21
    • 2023-04-08
    • 2020-02-25
    相关资源
    最近更新 更多