Amzd / ResponderChain

Cross-platform first responder handling without subclassing views or making custom ViewRepresentables in SwiftUI. Similar to FocusState but for iOS 13+

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Can't find responder for tag ..., make sure to set a tag using `.responderTag(_:)`

ninnios opened this issue · comments

Hello,

I tried to integrate ResponderChain in order to fix the issue with TextField's tappable area as described here https://stackoverflow.com/questions/56795712/swiftui-textfield-touchable-area & here https://gist.github.com/Amzd/d7d0c7de8eae8a771cb0ae3b99eab73d.

I have my own custom text field which I use everywhere and there I added the extension and the .textFieldFocusableArea() view modifier to the TextField.

However, whenever I click on the text field I get "Can't find responder for tag 9C01...64, make sure to set a tag using .responderTag(_:)" in the logs and it's not working.

How can I fix this, please?

Okay so what .responderTag(_:) does is look for a view that returns canBecomeFirstResponder as true.

It can't find that for your custom textfield I think? Kind of hard to speculate without knowing your exact implementation.

Hello,

Here's a part of what the DefaultTextFieldView.swift looks like.

import SwiftUI
import ResponderChain

struct DefaultTextFieldView: View {
    @ObservedObject var viewModel: DefaultTextFieldViewModel
    
    var body: some View {
        VStack(spacing: 5) {
            if viewModel.isSecureField {
                SecureField(viewModel.placeholder, text: $viewModel.text)
                    .font(viewModel.font)
                    .foregroundColor(viewModel.fontColor)
                    .disabled(viewModel.isTappable)
                    .frame(height: viewModel.height)
                    .disableAutocorrection(true)
                    .autocapitalization(.none)
            } else {
                TextField(viewModel.placeholder, text: $viewModel.text)
                    .frame(height: viewModel.height)
                    .textFieldFocusableArea()
                    .font(viewModel.font)
                    .foregroundColor(viewModel.fontColor)
                    .disabled(viewModel.isTappable)
                    .disableAutocorrection(true)
                    .autocapitalization(.none)
            }
            
        }.frame(height: viewModel.height)
        .background(viewModel.backgroundColor)
        .cornerRadius(viewModel.cornerRadius)
        .overlay(
            RoundedRectangle(cornerRadius: viewModel.cornerRadius)
                .stroke(viewModel.borderColor,
                        lineWidth: viewModel.borderWidth))
        .padding([.leading, .trailing], viewModel.leadingAndTrailingPadding)
    }
    
}

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
            }
    }
}

I am using this text field in the sign-in screen and I show the sign-in screen like this:
SignInView().environmentObject(ResponderChain(forWindow: UIApplication.shared.window))

Uhh, yeah first of all, you're swapping out textfields. So ResponderChain wont know which one you're trying to make firstResponder.

Second, I think the problem with canBecomeFirstResponder returning false is maybe because you disable the textfield? Not sure.

I added the .textFieldFocusableArea() at the end, right after the padding and that works great :) Thanks