Swift IOS TabView with .tabViewStyle(.page) not letting TextField/keyboard work correctly

55 Views Asked by At

I have a TabView with nested views, and one of the views has a TextField. When I click on the TextField, the keyboard blocks the TextField. However, if I remove the .tabViewStyle(.page) from TabView, everything works as expected! I've attached a GIF below. Any help would be appreciated.

struct ContentView: View {
    init() {
        UIPageControl.appearance().currentPageIndicatorTintColor = UIColor(Color.red)
        UIPageControl.appearance().pageIndicatorTintColor = UIColor.lightGray
        UIPageControl.appearance().tintColor = UIColor.lightGray
    }

    var body: some View {
        TabView{
            anotherView()
            TextView()
            anotherView()
        }
        .edgesIgnoringSafeArea(.all)
        .padding(.bottom, 20)
        .frame(
            width: UIScreen.main.bounds.width ,
            height: UIScreen.main.bounds.height
        )
        .tabViewStyle(.page) // issue here but I need the page style
        .indexViewStyle(.page(backgroundDisplayMode: .interactive))
    
    }
 
}
struct TextView: View {
    @State var textInput = ""

    var body: some View {
        VStack{
            Spacer()
            TextField("textfield", text: $textInput)
        }
        .padding()
    }
}

[gif]

If I remove the .tabViewStyle(.page) then it works. However I need the swipe feature and for the keyboard to not block the TextField

1

There are 1 best solutions below

0
t20e On

The issue was using TabView with the .page on it, what I did was add a modifier so that when the keyboard appears it moves the view up above the keyboard.

    import SwiftUI
    //import Combine
    
    
    struct KeyboardResponsiveModifier: ViewModifier {
        
        /*
            IMPORTANT: I had an issue using the .tabViewStyle(.page) on TabView; issue was when I clicked on the
            TextField in the TabView the keyboard would block the TextField, this struct help solve that issue, by
            moving the view up so that when the keyboard appears it doesn't block the textField
         */
        
        @State private var textFieldPosition: CGFloat = 0
        @State private var textFieldHeight: CGFloat = 0
        
        @State private var offset: CGFloat = 0
        
        func body(content: Content) -> some View {
            content
                .padding(.bottom, offset)
                .overlay(
                    GeometryReader { proxy in
                        Color.clear
                            .onAppear {
                                textFieldPosition = proxy.frame(in: .global).minY
                                textFieldHeight = proxy.size.height
                            }
                    }
                )
                .onAppear(perform: subscribeToKeyboardEvents)
        }
        
        private func calculateOffset(with keyboardHeight: CGFloat, screenHeight: CGFloat) -> CGFloat {
            let adjustedPosition = screenHeight - textFieldPosition - textFieldHeight
            let newOffset =  keyboardHeight - adjustedPosition
            return newOffset
        }
        
        private func subscribeToKeyboardEvents() {
            NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notification in
                guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
                let screenHeight = UIScreen.main.bounds.height
                let keyboardHeight = keyboardFrame.height
    //            withAnimation(.linear(duration: 0.05)) {
                // when the keyboard popups up moves the view up so that the TextField is visible above the keyboard
                offset = calculateOffset(with: keyboardHeight, screenHeight: screenHeight)
    //            }
            }
            
            NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in
    //            withAnimation(.linear(duration: 0.05)) {
                    offset = 0 // when the keyboard disappears it moves the view back down
    //            }
            }
        }
    }
    
    extension View {
        func keyboardResponsive() -> some View {
            self.modifier(KeyboardResponsiveModifier())
        }
    }
    
    
    struct TextView: View {
        @State var textInput = ""
        @State private var isKeyboardVisible = false
    
        
        var body: some View {
// also for my final project, I was able to make due without the ScrollViewReader, GeometryReader, and ScrollView
            ScrollViewReader { proxy in
                GeometryReader { geometry in
                    ScrollView {
                        VStack{
                            Spacer()
                            TextField("Prompt", text: $textInput, axis: .vertical)
                                .padding(EdgeInsets(top: 10, leading: 15, bottom: 10, trailing: 15))
                                .overlay(
                                    RoundedRectangle(cornerRadius: 14)
                                        .stroke(Color.red, lineWidth: 1)
                                )
                                .autocapitalization(.none) //stops the auto capitilize of words
                                .autocorrectionDisabled()
                                .foregroundColor(Color.black)
                                .padding( .bottom, isKeyboardVisible ? 10 : 0) // give a little padding when the keyboard appears
                                .id("TextFieldID")
    
                        }
                        .frame(width: geometry.size.width, height: geometry.size.height)
                    }
                    .keyboardResponsive() // Apply the custom modifier here
                    .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
                        isKeyboardVisible = true
                        withAnimation { // scroll to the bottom so that it doesn't appear as tho the keyboard is overlapping the TextField
                            proxy.scrollTo("TextFieldID", anchor: .bottom)
                        }
                    }
                    .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
                        isKeyboardVisible = false
                    }
                }
            }
        }
    }
    
    #Preview {
        TextView()
    }