A "working" version of Keyboard Accessory Buttons. Probably lots of mistakes in here, and items that could be streamlined to make it work better. As I mentioned in another post, I'd like the usage to have the ability to add modifiers (background color, style, etc) so it would be more versatile. Any thoughts?
KeyboardApp.swift
import SwiftUI
@main
struct KeyboardApp: App {
let keyboard = KeyboardResponder()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(keyboard)
}
}
}
KeyboardResponder.swift (Also includes TextFieldView and TextEditorView)
import SwiftUI
// MARK: - TextField
struct TextFieldView: View {
let fontsize: CGFloat = 14
let backgroundColor = Color.blue
let textColor = Color.white
@Binding var field: String
@EnvironmentObject var keyboard: KeyboardResponder
@State var bottom = 0
var body: some View {
ZStack {
GeometryReader { geometry in
Color(.clear)
.onAppear() {
bottom = Int(geometry.frame(in: CoordinateSpace.global).maxY)
}
}
TextField(field, text: $field)
.font(Font.system(size: fontsize))
.padding()
.background(RoundedRectangle(cornerRadius: 10).fill(backgroundColor))
.foregroundColor(textColor)
.padding()
.onTapGesture {
print("On Tap Gesture - \(field)")
keyboard.fieldBeingEdited = $field
keyboard.currentText = field
keyboard.bottom = bottom
}
}
}
}
struct TextEditorView: View {
let fontsize: CGFloat = 14
let width: CGFloat = 350
let height: CGFloat = 200
let backgroundColor = Color.blue
let textColor = Color.white
@Binding var field: String
@EnvironmentObject var keyboard: KeyboardResponder
@State var bottom = 0
var body: some View {
ZStack {
GeometryReader { geometry in
Color(.clear)
.onAppear() {
bottom = Int(geometry.frame(in: CoordinateSpace.global).maxY)
}
}
TextEditor(text: $field)
.frame(width: width, height: height, alignment: .leading)
.font(Font.system(size: fontsize))
.background(RoundedRectangle(cornerRadius: 10).fill(backgroundColor))
.foregroundColor(textColor)
.padding()
.onTapGesture {
keyboard.fieldBeingEdited = $field
keyboard.currentText = field
keyboard.bottom = bottom
}
}
}
}
// MARK: - Keyboard Buttons
struct KeyboardButtonsView: View {
var keyboard = KeyboardResponder()
let keyboardButtonsHeight = 50
var body: some View {
HStack {
Button(action: {
self.keyboard.cancel()
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
}) {
Text("Cancel")
}
Spacer()
Button(action: {
self.keyboard.clear()
}) {
Text("Clear")
}
Spacer()
Button(action: {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
}) {
Text("Done")
}
}
.padding()
.frame(height: CGFloat(keyboardButtonsHeight))
.background(Color("KeyboardBackground"))
}
}
// MARK: - Keyboard
final class KeyboardResponder: ObservableObject {
private var notificationCenter: NotificationCenter
var fieldBeingEdited: Binding<String> = .constant("init")
@Published private(set) var currentHeight: CGFloat = 0
@Published private(set) var keyboardHeight: CGFloat = 0
@Published var keyboardIsShowing: Bool = false
@Published var textNeedsToMove: Bool = false
var currentText = ""
var screenHeight = UIScreen.main.bounds.height
var bottom = 0
func clear() {
fieldBeingEdited.wrappedValue = ""
}
func cancel() {
fieldBeingEdited.wrappedValue = currentText
}
init(center: NotificationCenter = .default) {
notificationCenter = center
notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
deinit {
notificationCenter.removeObserver(self)
}
@objc func keyBoardWillShow(notification: Notification) {
let keyboardButtonsHeight = 50
let buffer = 10
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
keyboardHeight = keyboardSize.height
let keyboardTotalHeight = Int(keyboardHeight) + keyboardButtonsHeight + buffer
let topOfKeyboard = Int(screenHeight) - keyboardTotalHeight
if bottom > Int(screenHeight) - keyboardTotalHeight {
currentHeight = CGFloat(bottom - topOfKeyboard)
print("Current Height = \(currentHeight)")
keyboardIsShowing = true
textNeedsToMove = true
} else {
print("Setting currentHeight to ZERO")
currentHeight = 0
keyboardIsShowing = true
textNeedsToMove = false
}
}
}
@objc func keyBoardWillHide(notification: Notification) {
currentHeight = 0
keyboardIsShowing = false
textNeedsToMove = false
}
}
Usage - ContentView.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var keyboard: KeyboardResponder
@State private var firstName = "ShadowDES"
@State private var firstLikes = "Likes @TwoStraws"
@State private var lastName = "Dennis"
@State private var lastLikes = "Lots of other things."
@State var screenHeight = 0
@State var screenWidth = 0
init() {
UITextView.appearance().backgroundColor = .clear
}
var body: some View {
ZStack {
ScrollView(.vertical, showsIndicators: false) {
VStack {
TextEditorView(field:$firstLikes)
.padding()
TextFieldView(field: $firstName)
.padding()
TextEditorView(field:$lastLikes)
.padding()
TextFieldView(field: $lastName)
.padding()
}
}// ScrollView
.offset(x: 0, y: keyboard.textNeedsToMove ? -keyboard.currentHeight : 0)
.edgesIgnoringSafeArea(.bottom)
.animation(.easeOut(duration: 0.16))
VStack {
// Keyboard Buttons
Spacer()
if keyboard.keyboardIsShowing {
KeyboardButtonsView(keyboard: keyboard)
}
}
.padding(.bottom, keyboard.keyboardIsShowing ? keyboard.keyboardHeight : 0)
.edgesIgnoringSafeArea(.bottom)
.animation(.easeOut(duration: 0.16))
}
}
}