Omar Hegazy
Omar Hegazy

Reputation: 9

Keyboard Avoidance Issue in SwiftUI Sheet View

I am working on a chat view and trying to override keyboard avoidance. As it is, when I tap on the TextField to enter a comment, the entire view gets pushed upward, instead of just the textfield itself with the keyboard popping up below it. The chat view is presented via .sheet with a presentationDetent of 0.8.

I have tried using .ignoresSafeArea(.keyboard, edges: .bottom) after embedding the view in a GeometryReader and a ZStack but to no avail. I've also tried separating the components into different views.

Here is the relevant code:

struct TakesChatView: View {
    
    @ObservedObject var viewModel: TakeCommentViewModel
    @FocusState var focus: Bool
    @State private var selectedMedia: [PhotosPickerItem] = []
    @State private var selectedImageData: [Data] = []
    @State private var selectedGIFData: [Data] = []
    @State private var selectedVideoData: [Data] = []
    
    var textFieldNotEmpty: Bool {
        !viewModel.textToPost.isEmpty ||
        !selectedImageData.isEmpty ||
        !selectedGIFData.isEmpty ||
        !selectedVideoData.isEmpty
    }
    
    var body: some View {
        ZStack {
            GeometryReader { _ in
                VStack {
                    contentView
                    commentTextField
                        .padding(.horizontal, 10)
                }
            }
        }
        .ignoresSafeArea(.keyboard, edges: .bottom)
        .navigationTitle(viewModel.take.title)
        .navigationBarTitleDisplayMode(.inline)
        .background(Color.containerBackground)
        .onChange(of: viewModel.focus) { focus in
            self.focus = focus
        }
        .onChange(of: selectedMedia) { _ in
            loadMedia()
        }
    }
    
    var contentView: some View {
        VStack {
            Spacer().frame(height: 105)
            ScrollViewReader { scroll in
                ScrollView(showsIndicators: true) {
                    ForEach(viewModel.comments, id: \.self) { comment in
                        TakeCommentView(
                            comment: comment,
                            viewModel: self.viewModel
                        )
                        .padding(.horizontal, 10)
                        .id(comment.documentID)
                    }
                    .onChange(of: viewModel.commentAdded) { id in
                        scroll.scrollTo(id, anchor: .bottom)
                        viewModel.commentAdded = nil
                    }
                }
                .scrollDismissesKeyboard(.immediately)
            }
        }
    }
}
extension TakesChatView {
    @ViewBuilder
    var commentTextField: some View {
        VStack {       
            HStack {
                TextField("Type your comment", text: $viewModel.textToPost, axis: .vertical)
                    .keyboardType(.twitter)
                    .padding([.leading, .vertical], 6)
                    .focused($focus)
                
                PhotosPicker(selection: $selectedMedia, matching: .any(of: [.images, .videos]), photoLibrary: .shared()) {
                    ComposeType.media.image
                        .frame(width: 40, height: 40, alignment: .center)
                }
                .onAppear {
                    selectedMedia.removeAll()
                    selectedImageData.removeAll()
                    selectedGIFData.removeAll()
                    selectedVideoData.removeAll()
                }
                
                postButton
            }
            .border(.lightGray, width: 1, cornerRadius: 10)
            .padding([.bottom, .horizontal], 10)
        }
    }
}

What should I do in order to have the keyboard avoidance be removed so that when I tap on the TextField, it'll be like Instagram's comment section for example where the textfield moves up with the keyboard popping up below it, but the view itself that's displaying the comments isn't shifted upward entirely?

Edit: I have made reproducible code to represent my issue. The code is found below:

struct ButtonView: View {
    @State private var showChat: Bool = false
    var body: some View {
        VStack {
            Button {
                showChat.toggle()
            } label: {
                Text("Tap Me!")
            }
        }
        .sheet(isPresented: $showChat, content: {
            ContentView(viewModel: ContentViewViewModel())
                .presentationDetents([.fraction(0.8)])
                .presentationDragIndicator(.hidden)
                .overlay(
                    VStack {
                        RoundedRectangle(cornerRadius: 2)
                            .fill(Color.gray)
                            .frame(width: 40, height: 5)
                            .padding(.top, 15)
                            .opacity(0.8)
                        Label("Chat", systemImage: "message.badge")
                            .lineLimit(nil)
                            .padding(.top, 5)
                            .padding([.leading, .trailing], 16)
                        Divider()
                            .padding(.top, 5)
                            .padding([.leading, .trailing], 16)
                        Spacer()
                    }
                        .frame(maxWidth: .infinity, alignment: .top)
                )
        })
    }
}
struct UserMockComments: Identifiable {
    let userName: String
    var comment: String
    var id = UUID().uuidString
}

class ContentViewViewModel: ObservableObject {
    @Published var mock = [UserMockComments]()
    @Published var text: String = ""
    
    init() {
        fetchData()
    }
    
    func fetchData() {
        let mockComments: [UserMockComments] = [
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!"),
            .init(userName: "OHeg12", comment: "Hello World!")
        ]
        
        self.mock = mockComments
    }
}

struct ContentView: View {
    @StateObject var viewModel = ContentViewViewModel()
    @FocusState var focus: Bool
    
    var body: some View {
        ZStack {
            GeometryReader { _ in
                VStack {
                    comments
                    commentTextField
                        .padding(.horizontal, 10)
                }
            }
        }
        .ignoresSafeArea(.keyboard, edges: .bottom)
    }
    
    var comments: some View {
        VStack {
            Spacer().frame(height: 105)
            ScrollViewReader { scroll in
                ScrollView(showsIndicators: true) {
                    ForEach(viewModel.mock) { mockComments in
                        Text(mockComments.userName)
                        Text(mockComments.comment)
                            .padding(.horizontal, 10)
                            .id(mockComments.id)
                    }
                }
                .scrollDismissesKeyboard(.immediately)
            }
        }
    }
    
    @ViewBuilder
    var commentTextField: some View {
        VStack {
            HStack {
                TextField("Type your comment", text: $viewModel.text, axis: .vertical)
                    .keyboardType(.twitter)
                    .padding([.leading, .vertical], 6)
                    .focused($focus)
            }
            .padding([.bottom, .horizontal], 10)
        }
    }
}

#Preview {
    @StateObject var viewModel = ContentViewViewModel()
    return ContentView(viewModel: viewModel)
}

ButtonView is the main view of the app and tapping on the button loads up the chat view with the issue I'm having.

Upvotes: -1

Views: 395

Answers (1)

Andrei G.
Andrei G.

Reputation: 1557

Try the code below as your ContentView body:

    var body: some View {
        ZStack {
            GeometryReader { _ in
                VStack {
                    comments
                    .frame(maxWidth: .infinity, alignment: .center) //center the comments
                    // commentTextField
                        // .padding(.horizontal, 10)
                }
            }
        }
        .safeAreaInset(edge: .bottom) {
            commentTextField
                .padding(.top,5)
                .background(.white)
        }
        // .ignoresSafeArea(.keyboard, edges: .bottom)
    }

Upvotes: 0

Related Questions