From c3f63a55035dc6565800214f11303d96b9c35c0a Mon Sep 17 00:00:00 2001 From: bsorrentino Date: Tue, 20 Sep 2022 19:21:23 +0200 Subject: [PATCH] feat: draft implementation with UIViewRepresentable use a UIKit UITextField inputAccessoryView and inputView --- PlantUML/PlantUMLEditorView.swift | 20 +-- .../CustomKeyboardObject.swift | 12 -- .../PlantUMLKeyboard/PlantUMLKeyboard.swift | 40 ++--- .../PlantUMLTextField+Representable.swift | 156 +++++++++++++++++- .../PlantUMLKeyboard/PlantUMLTextField.swift | 71 -------- 5 files changed, 163 insertions(+), 136 deletions(-) delete mode 100644 PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField.swift diff --git a/PlantUML/PlantUMLEditorView.swift b/PlantUML/PlantUMLEditorView.swift index 260eda4..e0d0cf3 100644 --- a/PlantUML/PlantUMLEditorView.swift +++ b/PlantUML/PlantUMLEditorView.swift @@ -51,6 +51,8 @@ struct PlantUMLEditorView: View { EditButton() SaveButton() } + + } } @@ -121,6 +123,7 @@ struct PlantUMLEditorView: View { // MARK: Editor View func EditorView() -> some View { + List() { ForEach( diagram.items ) { item in @@ -134,28 +137,15 @@ struct PlantUMLEditorView: View { } } - PlantUMLTextFieldWithCustomKeyboard() -// PlantUMLTextField( value: item.rawValue, -// customKeyboard: customKeyboard, -// onChange: updateItem ) + PlantUMLTextFieldWithCustomKeyboard( value: item.rawValue, onChange: updateItem ) .focused($focusedItem, equals: .row(id: item.id)) } - } .onMove(perform: move) .onDelete( perform: delete) } - .toolbar { - ToolbarItemGroup(placement: .keyboard) { - AddBelowButton() - AddAboveButton() - ShowKeyboardButton( show: $customKeyboard.showKeyboard ) - - } - - } .font(.footnote) .listStyle(SidebarListStyle()) @@ -251,7 +241,7 @@ extension PlantUMLEditorView { struct ContentView_Previews: PreviewProvider { static var previews: some View { PlantUMLEditorView(document: .constant(PlantUMLDocument())) - .environment(\.editMode, Binding.constant(EditMode.active)) + .environment(\.editMode, Binding.constant(EditMode.inactive)) .previewInterfaceOrientation(.landscapeRight) .environmentObject( PlantUMLDiagramObject( text: """ diff --git a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/CustomKeyboardObject.swift b/PlantUMLKeyboard/Sources/PlantUMLKeyboard/CustomKeyboardObject.swift index 160ff8c..b3ff7be 100644 --- a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/CustomKeyboardObject.swift +++ b/PlantUMLKeyboard/Sources/PlantUMLKeyboard/CustomKeyboardObject.swift @@ -12,18 +12,6 @@ public class CustomKeyboardObject : ObservableObject { private var cancellable:AnyCancellable? - var keyboardView:UIView { - - let controller = UIHostingController( rootView: PlantUMLKeyboardView( customKeyboard: self ) ) - - let MAGIC_NUMBER = 50.0 // magic number .. height of keyboard top bar - var customKeyboardRect = keyboardRect - customKeyboardRect.origin.y += MAGIC_NUMBER - customKeyboardRect.size.height -= MAGIC_NUMBER - controller.view.frame = customKeyboardRect - return controller.view - - } public init() { NotificationCenter.default.addObserver( diff --git a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLKeyboard.swift b/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLKeyboard.swift index 65447ee..03bda71 100644 --- a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLKeyboard.swift +++ b/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLKeyboard.swift @@ -1,21 +1,12 @@ import SwiftUI import UIKit - - - - - -public struct PlantUMLKeyboardView: View { - - @ObservedObject var customKeyboard: CustomKeyboardObject +struct PlantUMLKeyboardView: View { -// public init( show: Binding, result:Binding<[String]> ) { -// self._show = show -// self._result = result -// } + var onHide:() -> Void + var onPressSymbol: (Symbol) -> Void - public var body : some View{ + var body : some View{ ZStack(alignment: .topLeading) { @@ -31,14 +22,14 @@ public struct PlantUMLKeyboardView: View { Button { - replaceSymbolAtCursorPosition(symbol) + onPressSymbol(symbol) } label: { ButtonLabel( rowIndex: rowIndex, cellIndex: cellIndex, symbol: symbol ) } - .buttonStyle( KeyButtonStyle() ) + .buttonStyle( KeyButtonStyle2() ) } } } @@ -50,9 +41,7 @@ public struct PlantUMLKeyboardView: View { .background(Color.gray.opacity(0.1)) .cornerRadius(25) - Button(action: { - customKeyboard.showKeyboard.toggle() - }) { + Button(action: onHide) { Image(systemName: "xmark").foregroundColor(.black) } .padding() @@ -63,6 +52,7 @@ public struct PlantUMLKeyboardView: View { // // func replaceSymbolAtCursorPosition( _ symbol: Symbol) { + /* guard let handleToYourTextView = getFirstTextFieldResponder() else { return } @@ -78,11 +68,11 @@ public struct PlantUMLKeyboardView: View { if let additionalValues = symbol.additionalValues { customKeyboard.itemsToAdd = additionalValues } - + */ } } -struct KeyButtonStyle: ButtonStyle { +fileprivate struct KeyButtonStyle2: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label @@ -115,17 +105,9 @@ extension PlantUMLKeyboardView { } struct PlantUMLKeyboardView_Previews: PreviewProvider { - - struct UserView : View { - @ObservedObject var customKeyboard = CustomKeyboardObject() - public var body : some View { - PlantUMLKeyboardView( customKeyboard: customKeyboard ) - } - } - static var previews: some View { - UserView() + PlantUMLKeyboardView( onHide: { }, onPressSymbol: { _ in } ) .previewInterfaceOrientation(.landscapeLeft) } } diff --git a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField+Representable.swift b/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField+Representable.swift index 5e73a2c..f74763f 100644 --- a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField+Representable.swift +++ b/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField+Representable.swift @@ -9,13 +9,17 @@ import UIKit import SwiftUI public struct PlantUMLTextFieldWithCustomKeyboard : UIViewRepresentable { - + public typealias ChangeHandler = ( String ) -> Void public typealias UIViewType = UITextField - private let textField = UITextField() - public init() { - + + public var value:String + public var onChange:ChangeHandler + + public init( value:String, onChange:@escaping ChangeHandler ) { + self.value = value + self.onChange = onChange } public func makeCoordinator() -> PlantUMLTextFieldWithCustomKeyboard.Coordinator { @@ -25,22 +29,156 @@ public struct PlantUMLTextFieldWithCustomKeyboard : UIViewRepresentable { public func makeUIView(context: Context) -> UITextField { textField.delegate = context.coordinator + textField.keyboardType = .asciiCapableNumberPad + textField.autocapitalizationType = .none + textField.font = UIFont.monospacedSystemFont(ofSize: 15, weight: .regular) + textField.returnKeyType = .done + textField.text = value return textField } public func updateUIView(_ uiView: UITextField, context: Context) { + context.coordinator.updateAccesoryView() + } +} + + +// MARK: - CustomKeyboardPresenter protocol +protocol CustomKeyboardPresenter { + + func toggleCustomKeyobard() -> Void + func onPressSymbol( _ symbol: Symbol ) -> Void + +} - public class Coordinator: NSObject, UITextFieldDelegate { +// MARK: - Coordinator extension +extension PlantUMLTextFieldWithCustomKeyboard { + + public class Coordinator: NSObject, UITextFieldDelegate, CustomKeyboardPresenter { + + + private var keyboardRect:CGRect = .zero + private let owner : PlantUMLTextFieldWithCustomKeyboard + private var showCustomKeyboard:Bool - private let parent : PlantUMLTextFieldWithCustomKeyboard - public init(textfield : PlantUMLTextFieldWithCustomKeyboard) { - self.parent = textfield + self.owner = textfield + self.showCustomKeyboard = false + super.init() + + updateAccesoryView() + + NotificationCenter.default.addObserver( + + forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { [weak self] notification in + + print( "keyboardDidShowNotification" ) + + if let keyboardFrameEndUser = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue) { + + print( "keyboardFrameEndUser pos=\(keyboardFrameEndUser)") + + self?.keyboardRect = keyboardFrameEndUser.cgRectValue + } + + } + + NotificationCenter.default.addObserver( + + forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { [weak self] _ in + + print( "keyboardDidHideNotification" ) + + self?.keyboardRect = .zero + + } + + + } + + func updateAccesoryView() { + if owner.textField.inputAccessoryView == nil { + + let bar = UIToolbar() + let toggleKeyboard = UIBarButtonItem(title: "PlantUML Keyboard", style: .plain, target: self, action: #selector(toggleCustomKeyobard)) + bar.items = [ + toggleKeyboard + ] + bar.sizeToFit() + owner.textField.inputAccessoryView = bar + + } + + } + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + // print( "shouldChangeCharactersIn", range, string ) + if let text = textField.text, let range = Range(range, in: text) { + owner.onChange( text.replacingCharacters(in: range, with: string) ) + } + + return true + } + + /// Lazy creation Input View + var customKeyboardView:UIView { + let keyboardView = PlantUMLKeyboardView(onHide: toggleCustomKeyobard, onPressSymbol: onPressSymbol ) + let controller = UIHostingController( rootView: keyboardView ) + + let MAGIC_NUMBER = 100.0 // 50.0 // magic number .. height of keyboard top bar + var customKeyboardRect = keyboardRect + customKeyboardRect.origin.y += MAGIC_NUMBER + customKeyboardRect.size.height -= MAGIC_NUMBER + controller.view.frame = customKeyboardRect + return controller.view + + } + + @objc public func toggleCustomKeyobard() { + print("toggleCustomKeyobard:", showCustomKeyboard) + + showCustomKeyboard.toggle() + + if( showCustomKeyboard ) { + owner.textField.inputView = customKeyboardView + } + else { + owner.textField.inputView = nil + } + owner.textField.reloadInputViews() + + } + + func onPressSymbol(_ symbol: Symbol) { + + // [How to programmatically enter text in UITextView at the current cursor position](https://stackoverflow.com/a/35888634/521197) + if let range = owner.textField.selectedTextRange { + // From your question I assume that you do not want to replace a selection, only insert some text where the cursor is. + owner.textField.replace(range, withText: symbol.value ) + + if let text = owner.textField.text { + owner.onChange( text ) + } + + /* + // https://stackoverflow.com/a/63555951/521197 + let _range = NSMakeRange( + owner.textField.offset(from: owner.textField.beginningOfDocument, to: range.start), + owner.textField.offset(from: range.start, to: range.end)) + + let _ = self.textField(owner.textField, shouldChangeCharactersIn: _range, replacementString: symbol.value) + */ + } + +// if let additionalValues = symbol.additionalValues { +// customKeyboard.itemsToAdd = additionalValues +// } + } } - + } diff --git a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField.swift b/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField.swift deleted file mode 100644 index 4f2cea1..0000000 --- a/PlantUMLKeyboard/Sources/PlantUMLKeyboard/PlantUMLTextField.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// PlantUMLTextField.swift -// PlantUML -// -// Created by Bartolomeo Sorrentino on 06/09/22. -// - -import SwiftUI - -public struct PlantUMLTextField: View { - - public typealias ChangeHandler = ( String ) -> Void - - @ObservedObject var customKeyboard: CustomKeyboardObject - - @State var value: String - - @FocusState private var isFocused: Bool - - var onChange:ChangeHandler - - public init( value: String, customKeyboard: CustomKeyboardObject, onChange: @escaping ChangeHandler ) { - self.value = value - self.customKeyboard = customKeyboard - self.onChange = onChange - } - - public var body: some View { - - VStack { - - HStack(spacing: 15) { - - TextField( "", text: $value ) - .keyboardType(.asciiCapableNumberPad) - .textInputAutocapitalization(.never) - .font(Font.system(size: 15).monospaced()) - .submitLabel(.done) - .focused($isFocused) - .onChange(of: value, perform: onChange ) -// .introspectTextField { textField in -// print( "==> introspectTextField: \(value)" ) -// } - - - if( isFocused ) { - ShowKeyboardAccessoryButton( show: $customKeyboard.showKeyboard ) - } - } - - } - .edgesIgnoringSafeArea(.all) - - } -} - -struct PlantUMLTextField_Previews: PreviewProvider { - - struct PlantUMLTextFieldProxy : View { - @ObservedObject var customKeyboard = CustomKeyboardObject() - - public var body : some View { - PlantUMLTextField(value: "test", customKeyboard: customKeyboard, onChange: { (v) in } ) - } - } - - static var previews: some View { - PlantUMLTextFieldProxy() - } - -}