Keyboard Avoidance overlay Bottom Menu Fix in SwiftUI

Viewed 458

I have created a SwiftUI View for each of my menus in my app. However, when I go to the chat view and tap on the UITextField, I get the following now:

img1 img2

I am new to SwiftUI and has able to hack together this view (along with the ContentView.swift below) through some youtube videos. However, I'm not sure how to fix this (and if I just need to change my ContentView.swift file all together). My code is available below:


//Chat.swift
import SwiftUI
import Firebase
import Alamofire
import SwiftyJSON

struct ChatUIView: View {
    
    @State var message = ""
    //StateObject is the owner of the object...
    @StateObject var allMessages = Messages()
    
    @StateObject private var keyboardHandler = KeyboardHandler()
    
    var body: some View {
        VStack{
            
            ZStack{
                
                /*
                HStack{
                    Spacer()
                }*/
                
                VStack(spacing:5){
                    Text("Chat")
                        .fontWeight(.bold)
                }
                .foregroundColor(.white)
            }
            .padding(.all)
            
            //Spacer()
            VStack{
                
                //Spacer()
                //Displaying Message...
                
                ScrollView(.vertical, showsIndicators: false, content: {
                    
                    ScrollViewReader{reader in
                        
                        VStack(spacing: 20){
                            
                            ForEach(allMessages.messages){msg in
                                
                                //Chat Bubbles...
                                
                                ChatBubble(msg: msg)
                            
                                
                            }
                            //whenever new data is inserted, scroll to bottom...
                            .onChange(of: allMessages.messages) {(value) in
                                
                                //scrolling only user message...
                                
                                if value.last!.myMsg{
                                    reader.scrollTo(value.last?.id)
                                }
                                
                            }
                        }
                        .padding([.horizontal, .bottom])
                        .padding(.top, 25)
                        
                    }
                })
                
                HStack(spacing:15){
                    
                    HStack(spacing: 15){
                        TextField("Message", text: self.$message)
                    }
                    .padding(.vertical, 12)
                    .padding(.horizontal)
                    .background(Color.black
                                    .opacity(0.06)
                                    .clipShape(Capsule())
                    )
                    //.clipShape(Capsule())
                    
                    //send button
                    //hiding view...
                    if message != ""{
                        Button(action: {
                            
                            //appending message...
                            
                            //adding animation...
                            withAnimation(.easeIn){
                                allMessages.messages.append(Message(id: Date(), message: message, myMsg: true))
                            }
                            
                            message = ""
                            
                        }, label: {
                            
                            Image("send")
                                .resizable()
                                .frame(width: 25, height: 25)
                                .rotationEffect(.init(degrees: 45))
                                .padding()
                                //.aspectRatio(contentMode: .fit)
                                //.font(.system(size: 0.5))
                                //.padding(.all)
                                .background(Color.black.opacity(0.07))
                                .clipShape(Circle())
                        })
                    }
                }
                .padding(.horizontal)
                .animation(.easeOut)
                //
            }
            .padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom)
            .background(Color.white)
            //.clipShape(RoundedShape())
            .clipShape(Rectangle())
            .keyboardType(.default)
            .padding(.bottom, keyboardHandler.keyboardHeight)
            
        }
        //.edgesIgnoringSafeArea(.bottom)
        .background(Color.blue.edgesIgnoringSafeArea(.top))
        .ignoresSafeArea(.keyboard, edges: .bottom)
    }
}

//Chat Bubbles...

struct ChatBubble : View {
    
    var msg : Message
    var body: some View{
        
        //Automatics scroll to bottom...
        //First assigning id to each row
        
        HStack(alignment: .top, spacing: 10){
            
            if msg.myMsg{
                
                //pushing msg to the left...
                
                //minimum space ...
                Spacer(minLength: 25)
                
                Text(msg.message)
                    .padding(.all)
                    .background(Color.black.opacity(0.06))
                    //.cornerRadius(15)
                    .clipShape(BubbleArrow(myMsg: msg.myMsg))
            } else {
                
                //pushing msg to the right...
                
                Text(msg.message)
                    .fixedSize(horizontal: false, vertical: true)
                    .lineLimit(nil)
                    .foregroundColor(.white)
                    .padding(.all)
                    //.background(Color.black.opacity(0.06))
                    .background(Color.blue)
                    .clipShape(BubbleArrow(myMsg: msg.myMsg))
                
                Spacer(minLength: 25)
            }
        }
        .id(msg.id)
        //.padding(msg.myMsg ? .leading : .trailing, 55)
        //.padding(.vertical,10)
    }
}

// Bubble Arrow...

struct BubbleArrow : Shape {
    
    var myMsg : Bool
    
    
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: myMsg ? [.topLeft, .bottomLeft, .bottomRight] : [.topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10, height: 10))
        
        return Path(path.cgPath)
    }
}


// Custom Rounded Shape...
/*
struct RoundedShape : Shape {
    
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize())
        
        return Path(path.cgPath)
    }
}*/

// Model Data For Message...

struct Message : Identifiable, Equatable {
    
    var id: Date
    var message: String
    var myMsg: Bool
    //var profilePic: String
    //var photo: Data?
    
}

class Messages: ObservableObject {
    
    @Published var messages : [Message] = []
    
    //sample data...
    
    init() {
        let strings = ["Hi!", "hello!", "How are you doing?!", "Fine, I just want to talk about life", "ok, I may be able to help with that", "This is awesome, thanks", "So what do you want to talk about?", "movies sound like a good topic. Let's start there!", "Ok, so tell me: What's you favorite movie?", "Definitely, interstellar for sure."]
        
        for i in 0..<strings.count{
            
            //simple logic for two sided message View...
            
            messages.append(Message(id: Date(), message: strings[i], myMsg: i % 2 == 0 ? false : true))
        }
    }
    
    func writeMessage(id: Date, msg: String, photo: Data?, myMsg: Bool){
        
        messages.append((Message(id: id, message: msg, myMsg: myMsg)))
        
        
    }
}

//KeyboardHandler.swift

import Combine
import SwiftUI

final class KeyboardHandler: ObservableObject {
    
    @Published private(set) var keyboardHeight: CGFloat = 0
    
    private var cancellable: AnyCancellable?
    
    private let keyboardWillShow = NotificationCenter.default
        .publisher(for: UIResponder.keyboardWillShowNotification)
        .compactMap { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height }
    
    private let keyboardWillHide = NotificationCenter.default
        .publisher(for: UIResponder.keyboardWillHideNotification)
        .map {_ in CGFloat.zero }
    
    init() {
        cancellable = Publishers.Merge(keyboardWillShow, keyboardWillHide)
            .subscribe(on: DispatchQueue.main)
            .assign(to: \.self.keyboardHeight, on: self)
    }
}

//ContentView.Swift

import SwiftUI
import Firebase
import Alamofire

private var sessionId = ""

struct ContentView: View {
    
    @AppStorage("status") var status = false
    
    var body: some View {
        ZStack{
            if status{
                VStack(spacing: 0){
                    Home()
                }
                
                
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct Home: View{
    
    @State var selectedTab = "Home"
    var edges = UIApplication.shared.windows.first?.safeAreaInsets
    @Namespace var animation
    @StateObject var modelData = ModelView()
    @StateObject var model = ModelData()
    
    var body: some View {
        
        VStack(spacing: 0){
            
            
            GeometryReader{_ in
                
                ZStack{
                    
                    //Tabs...
                    NavigationView{
                        ScrollView{
                            ForEach(1...2,id: \.self){i in
                                
                                Button(action: model.logOut, label: {
                                      Image("peace-sign")
                                        .resizable()
                                        .renderingMode(.original)
                                        .frame(width: 45, height: 45)
                                    
                                      
                                      VStack(alignment: .leading, spacing: 12) {
                                          
                                          Text("Log Out")
                                      }
                                      
                                      Spacer(minLength: 0)
                                    
                                    
                                })
                         
                            }
                            .padding(.bottom)
                        }
                        .navigationBarHidden(true)
                        
                    }
                    .opacity(selectedTab == "Home" ? 1 : 0)
              
                    
                    Chat()
                        .opacity(selectedTab == "Chat" ? 1: 0)
                }
                
            }
            .onChange(of: selectedTab){(_) in
                
                switch(selectedTab){
                
                case "Chat": if !modelData.isChatLoad{modelData.loadChat()}
                default: ()
                }
                
            }
            
            //TabView...
            HStack(spacing: 0){
                
                ForEach(tabs,id: \.self){tab in
                    TabButton(title: tab, selectedTab: $selectedTab, animation: animation)
                    
                    if tab != tabs.last{
                        Spacer(minLength: 0)
                    }
                }
            }
            .padding(.horizontal,30)
            // for iphone like 8 and SE
            .padding(.bottom,edges!.bottom == 0 ? 15 : edges!.bottom)
            //.background(Color.primary.opacity(0.2))
        }
        /*
        .ignoresSafeArea(.all, edges: .bottom)
        .background(Color.black.opacity(0.06).ignoresSafeArea(.all, edges: .all))
 */
    }
}

struct TabButton : View{
    
    var title : String
    @Binding var selectedTab : String
    var animation : Namespace.ID
    
    var body: some View{
            
        Button(action: {
            withAnimation{
                selectedTab = title
            }
        }){
        
            
            VStack(spacing: 6){
                
                //Top Indicator....
                
                //Custom Shape...
                
                //Slide in and out animation
                
                ZStack{
                    CustomShape()
                        //.fill(Color.blue.opacity(selectedTab == title ? 1 : 0))
                        .fill(Color.clear)
                        .frame(width: 45, height: 6)
                
                    if selectedTab == title{
                        
                        CustomShape()
                            //.fill(Color.blue.opacity(selectedTab == title ? 1 : 0))
                            .fill(Color.blue)
                            .frame(width: 45, height: 6)
                            .matchedGeometryEffect(id: "Tab_Change", in: animation)
                    }
                }
                .padding(.bottom,10)
                
                
                Image(title)
                    .renderingMode(.template)
                    .resizable()
                    .foregroundColor(selectedTab == title ? Color.blue : Color.gray)
                    .frame(width:24, height:24)
                    /*
                Text(title)
                    .font(.caption)
                    .foregroundColor(Color.black.opacity(selectedTab == title ? 0.6 : 0.2))
                    .frame(width: 24, height: 24)
                    */
            }
        }
    }
}

var tabs = ["Home", "Chat"]

I think that something needs to be changed in my ContentView.swift file but I don't know how to propagate that change to the Chat.swift UI View. Any help/tips would be greatly appreciated!

0 Answers
Related