Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SwiftUI ViewRepresentable and UIViewRepresentable is not correctly placed in element hierarchy. #1909

Open
Bobbyphtr opened this issue Mar 20, 2023 · 2 comments

Comments

@Bobbyphtr
Copy link

Bobbyphtr commented Mar 20, 2023

Background

Hi there! I'm a beginner and still learning about EarlGrey and how to use them.

I created a simple SwiftUI View as simple as this. And want to test them.

Screen Shot 2023-03-20 at 18 56 01

Here's the SwiftUI Code

struct RegisterView: View {
    
    @EnvironmentObject var viewModel: RegisterViewModel
   
    @State var ageText: String = ""
    @State var nameText: String = ""

    @State var isFormValid: Bool = false
    
    var body: some View {
        VStack {
            Text("Welcome to SwiftUI")
                .font(Font.title)
                .fontWeight(.semibold)
            Text("Register here!")
                .multilineTextAlignment(.center)
                .padding(.bottom, 16.0)
            
            VStack(spacing: 16.0) {
                BorderedTextField(placeholder: "Name",
                                  textBind: $nameText,
                                  errorTextBind: $viewModel.nameValidationError)
                .onChange(of: nameText) { newValue in
                    self.viewModel.name = newValue.isEmpty ? nil : newValue
                }
                .accessibilityIdentifier("register.name.field")
                
                BorderedTextField(placeholder: "Age",
                                  textBind: $ageText,
                                  errorTextBind: $viewModel.ageValidationError)
                .onChange(of: ageText, perform: { newValue in
                    // Convert into Integers
                    self.viewModel.age = Int(newValue)
                })
                .accessibilityIdentifier("register.age.field")
                .keyboardType(.numberPad)
                Button {
                    // Submit action
                } label: {
                    NavigationLink("Next") {
                        PhoneNumberView()
                            .environmentObject(viewModel)
                    }
                    .frame(maxWidth: .infinity)
                    .padding(8.0)
                }
                .disabled(!isFormValid)
                .buttonStyle(.automatic)
                .accessibilityIdentifier("register.button.next")
            } // - Register Form
            Text("RegisterViewModel")
            Text("Name: \(viewModel.name ?? "nil")")
            Text("Age: \(viewModel.age == nil ? "nil" : String(viewModel.age!))")
        } // - Main Stack
        .padding(.horizontal, 16.0)
        .onReceive(self.viewModel.isFirstFormValid
            .throttle(for: .milliseconds(500), scheduler: RunLoop.main, latest: true)
        ) { output in
            self.isFormValid = output
        }
    }
    
}

Here's the BorderedTextField


struct BorderedTextField: View {
    /// Placeholder localize string key
    private var placeholder: LocalizedStringKey
    
    /// Text binding.
    @Binding
    private var textBind: String
    
    @Binding
    private var errorTextBind: String?
    
    /// Initialize a textfield with bordered pattern
    /// - Parameters:
    ///   - placeholder: Localized Key for Placeholder.
    ///   - textBind: Text binding
    init(placeholder: LocalizedStringKey,
         textBind: Binding<String>,
         errorTextBind: Binding<String?>? = nil) {
        self.id = id
        self.placeholder = placeholder
        self._textBind = textBind
        self._errorTextBind = errorTextBind ?? Binding<String?>.constant(nil)
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            TextField(placeholder, text: $textBind)
                .padding()
                .overlay(
                    RoundedRectangle(cornerRadius: 8.0)
                        .stroke()
                )
                .accessibilityIdentifier("borderedtextfield.textfield")
            Text(errorTextBind ?? "")
                .foregroundColor(Color.red)
                .accessibilityIdentifier("borderedtextfield.errortext")
        }
        .accessibilityElement(children: .contain)
    }
}

I used accessibilityElement to retain all the child accessibilityIdentifier so I can differentiate them by their parents. Thus a ViewHierarchy structure is important.

Expectation

I hope that the View Hierarchy on EarlGrey is shown like what XCUIApplication does.
Screen Shot 2023-03-10 at 21 12 01

I thought my current accessibilityIdentifier structure using accessibilityElement can work on UITest. But I was wrong when I'm using EarlGrey.

The Issue

After taking sometime looking at the View Hierarchy I discover this.

Screen Shot 2023-03-10 at 21 09 57

All UIViewControllerRepresentable and UIViewRepresentable somewhat being put outside of the View Hierarchy. This will eliminate my chance on using the parent identifier to get a certain component.

I checked with a UIViewControllerRepresentable and a UIButton wrapped inside UIViewRepresentable. It will put the View outside of the view hierarchy.

And yes. I'm surprised, underneath Apple still use UIViewRepresentable on UITextField for SwiftUI TextField haha.

Any help is really appreciated. Thank you!

@tirodkar
Copy link
Collaborator

Thanks a lot for this issue. We'll take a look - we are trying to add SwiftUI support at the moment.

@Bobbyphtr
Copy link
Author

Thanks for checking out my issue! Waiting for the heads up soon! @tirodkar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants