Gilson Cavalcanti
Gilson Cavalcanti

Reputation: 1513

Is there a SwiftUI "pure" way of auto scroll a List to show a TextField consistently? (keyboard is hiding the field)

The Context:

I have a List row that, in some cases, shows a TextField (depending on a switch condition). I managed to solve the focus problem with an excellent article from Peter Friese Link to article that suggests a very clever use of enums Associated fields.

The Issue:

BUT I'm facing an inconsistent scrolling behavior 😥 that can be seen in this screen capture:

iOS sim capture os scrolling issue

The relevant code:

            List($roundQuestions) { $question in
                HStack {
                    Group {
                        Image(systemName: "\(question.index ).circle")
                            .foregroundColor(questionColor(question: question))
                            .scaleEffect(0.6)
                        Text("\(question.factorA)")
                        Text("x")
                        question.status == .unrevealed ? Text("?") : Text("\(question.factorB)")
                        Text("=")
                        switch question.status {
                        case .unrevealed:
                            if question.index == currentQuestionArrayIndex + 1 {
                                Spacer()
                                Button("Go!") {
                                    question.status = .active
                                }
                                .buttonStyle(.borderedProminent)
                                .font(.none)
                                .lineLimit(1)
                            }
                        case .active:
                            ZStack (alignment: .trailing) {
                                TextField("??", text: $questionGuess )
                                    .textFieldStyle(.roundedBorder)
                                    .keyboardType(.numberPad)
                                    .focused($focusedQuestion, equals: .row(id: question.id))
                                    .onAppear {
                                        focusedQuestion = .row(id: question.id)
                                        print("onAppear question.id = \(question.index)")
                                    }
                                    .onDisappear {
                                        print("onDisappear question.id = \(question.index)")
                                    }
                            }
                            
                            Button("?") {
                                processUserGuess()
                            }
                            .buttonStyle(.borderedProminent)
                            .font(.none)
                        case .error:
                            Text("\(question.productGuess)").foregroundColor(.red)
                        case .right:
                            Text("\(question.productGuess)").foregroundColor(.green)
                        default:
                            Text("__")
                        }
                    }
                    .font(.custom("SF Compact", size: 40, relativeTo: .largeTitle))
                    .padding(.vertical)
                }
            }

The entire repo (for those who goes deep):

https://github.com/gilsoncav/tabulenzo

Observation #1:

I'm aware of solution paths like Move TextField up when the keyboard has appeared in SwiftUI but they seem "hacky" and the code doesn't "read well" in my opinion

Observation #2:

I'm a Swift, native iOS and SwiftUI newbie. 🤗

Upvotes: 1

Views: 2399

Answers (1)

ChrisR
ChrisR

Reputation: 12125

There is another option, but it uses ScrollView instead of List, and then facilitates ScrollViewReader to scroll to the active question. It works, but you would have to do some custom formatting to get the same look as before (I already added some Spacers to get the overall look):

            // Scrollview & reader instead of List
            ScrollViewReader { scrollProxy in
                ScrollView {
                    ForEach($roundQuestions) { $question in
                        HStack {
                            Group {
                                Image(systemName: "\(question.index ).circle")
                                    .foregroundColor(questionColor(question: question))
                                    .scaleEffect(0.6)
                                Text("\(question.factorA)")
                                Text("x")
                                question.status == .unrevealed ? Text("?") : Text("\(question.factorB)")
                                Text("=")
                                switch question.status {
                                case .unrevealed:
                                    if question.index == currentQuestionArrayIndex + 1 {
                                        Spacer()
                                        Button("Go!") {
                                            question.status = .active
                                        }
                                        .buttonStyle(.borderedProminent)
                                        .font(.none)
                                        .lineLimit(1)
                                    } else {
                                        Spacer()
                                    }
                                case .active:
                                    HStack  {
                                        TextField("??", text: $questionGuess )
                                            .textFieldStyle(.roundedBorder)
                                            .keyboardType(.numberPad)
                                            .focused($focusedQuestion, equals: .row(id: question.id))
                                            .onAppear {
                                                focusedQuestion = .row(id: question.id)
                                                // NEW: Scroll to active question
                                                withAnimation {
                                                    scrollProxy.scrollTo(question.id, anchor: .bottom)
                                                }
                                            }
                                    }
                                    
                                    Button("?") {
                                        processUserGuess()
                                    }
                                    .buttonStyle(.borderedProminent)
                                    .font(.none)
                                case .error:
                                    Text("\(question.productGuess)").foregroundColor(.red)
                                    Spacer()
                                case .right:
                                    Text("\(question.productGuess)").foregroundColor(.green)
                                    Spacer()
                                default:
                                    Text("__")
                                    Spacer()
                                }
                            }
                            .font(.custom("SF Compact", size: 40, relativeTo: .largeTitle))
                            .padding(.vertical)
                        }
                        // new: id for scrollto
                        .id(question.id)
                    }
                }
            }

Upvotes: 2

Related Questions