SwiftUI: Hide keyboard but show cursor - swift

I want to use custom buttons to input text into a TextField, but still show and move the cursor. Is there a way to hide the default keyboard while still showing the cursor?
I was hoping for something like this:
TextField("", text: $text)
.keyboardType(.none)
Here is what it currently looks like.

You can use UIViewRepresentable class and pass the input view as an empty view.
struct HideKeyboardTextField: UIViewRepresentable {
var placeholder: String
#Binding var text: String
func makeUIView(context: UIViewRepresentableContext<HideKeyboardTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.placeholder = placeholder
textField.inputView = UIView()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<HideKeyboardTextField>) {
uiView.text = text
}
func makeCoordinator() -> HideKeyboardTextField.Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: HideKeyboardTextField
init(parent: HideKeyboardTextField) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
parent.text = textField.text ?? ""
}
}
}
}
Usage:
struct ContentView: View {
#State var text: String = ""
var body: some View {
HideKeyboardTextField(placeholder: "Input", text: $text)
}
}

Related

I am unable to use a spacer to push a text view to the top of my swift ui VStack view, when adding UI wrappers

I have got a piece of code below but the issue is the text field is positioned in the middle of the view and when I add spacers below to push the textfield to the top of the view, it doesn't get pushed the textfield to the top of the view but rather it only moves when i specify the minLength of the spacer but we know this isnt the right approach because different phones have different dimensions. so can someone provide a proper fix to this or spot what is hindering spacer() from working
import SwiftUI
struct FocusedTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
var parent: FocusedTextField
init(_ parent: FocusedTextField) {
self.parent = parent
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
#Binding var text: String
#Binding var isFocused: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<FocusedTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.textColor = .black
textField.placeholder = "Enter some text"
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<FocusedTextField>) {
uiView.text = text
if isFocused && !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
}
struct testList: View {
#State private var text: String = ""
#State private var isFocused: Bool = false
var body: some View {
VStack{
FocusedTextField(text: $text, isFocused: $isFocused)
}
.onAppear {
self.text = ""
self.isFocused = true
}
.padding()
}
}
struct textList_Previews: PreviewProvider {
static var previews: some View {
testList()
}
}
For vertical wrap you need to make add some code in makeUIView:
textField.setContentHuggingPriority(.required, for: .vertical)
and add spacer() in testList()
Try below code:
struct FocusedTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
var parent: FocusedTextField
init(_ parent: FocusedTextField) {
self.parent = parent
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
#Binding var text: String
#Binding var isFocused: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<FocusedTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.textColor = .black
textField.placeholder = "Enter some text"
textField.setContentHuggingPriority(.required, for: .vertical) //<---------------here
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<FocusedTextField>) {
uiView.text = text
if isFocused && !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
}
struct testList: View {
#State private var text: String = ""
#State private var isFocused: Bool = false
var body: some View {
VStack{
FocusedTextField(text: $text, isFocused: $isFocused)
Spacer() //<--------- here
}
.onAppear {
self.text = ""
self.isFocused = true
}
.padding()
}
}
struct textList_Previews: PreviewProvider {
static var previews: some View {
testList()
}
}
The problem is in the FocusedTextField. If you use the TextField from SwiftUI itself, you can easily push it up in the VStack by using Spacer().
struct ContentView: View {
#State private var text: String = ""
var body: some View {
VStack{
TextField("Enter some Text", text: $text)
Spacer()
}
.onAppear {
self.text = ""
}
.padding()
}
}
I highly recommend you look into the #FocusState property wrapper to implement the focus state.
You can read more about it here:
https://developer.apple.com/documentation/swiftui/focusstate

How to add line numbers to UITextView in Swift (structure of app built using SwiftUI)

I am making a code editor for iPad (as a fun little project). I am trying to add line numbers to a UITextView that is in the SwiftUI code via a UIViewRepresentable. Any ideas on how to go about this? I should mention, I use an NSAttributedString for syntax hilighting. Here is a paired down version of my code (to fit better).
The view that references the UITextView
struct TestEditorView: View {
#State var text = NSAttributedString(string: "")
#State var selection: String?
var body: some View {
NavigationView {
VStack {
if let _ = selection {
ScrollView {
HStack {
TextView(text: $text)
.padding(.horizontal, 5)
}
}
}
}
}
}
}
The UITextView
struct TextView: UIViewRepresentable {
#Binding var text: NSAttributedString
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.attributedText = TextFormatter.format(text.string)
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = TextFormatter.format(text.string)
}
func makeCoordinator() -> TextViewCoordinator {
TextViewCoordinator(text: $text)
}
}
class TextViewCoordinator: NSObject, UITextViewDelegate {
#Binding private var text: NSAttributedString
init(text: Binding<NSAttributedString>) {
self._text = text
}
func textViewDidChange(_ textView: UITextView) {
text = TextFormatter.format(textView.attributedText.string)
}
}
P.S. I do not believe this applies because it is in objective-c and does not interface with SwiftUI.

SwiftUI: UITextView text does not wrap when inside a ForEach/LazyVGrid (Self-sizing/Dynamic height UITextView)

The issue
What I'm creating is a LazyVGrid with many UITextView as "cells" inside, but the text does not wrap when it exceeds the parent width.
When the text is a single line, all is fine:
But when the text reaches the limit, something weird happens:
What I want to achieve is simply that the text wraps, can anyone help me?
The code
To have more flexibility, I chose to use a UIKit UITextView created using the UIViewRepresentable protocol, rather than using the new TextEditor provided by SwiftUI:
struct TextView: UIViewRepresentable {
#Binding private var text: String
init(text: Binding<String>) {
self._text = text
}
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.isScrollEnabled = false
textView.backgroundColor = .systemIndigo // debug
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
context.coordinator.parent = self
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// MARK: - Coordinator
class Coordinator: NSObject, UITextViewDelegate {
var parent: TextView
init(_ parent: TextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
}
}
This is my SwiftUI View:
struct ContentView: View {
private let columns: [GridItem] = Array(repeating: .init(.flexible(), spacing: 0.0), count: 1)
#ObservedObject private var viewModel: ContentViewModel
init(viewModel: ContentViewModel) {
self.viewModel = viewModel
}
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach($viewModel.blocks) { $block in
TextView(text: $block.content)
.frame(minHeight: 24.0)
}
}
.padding(.horizontal, 24.0)
}
}
}
I'll skip the ViewModel part because it's not relevant.
I've already seen and tried this Dynamic row hight containing TextEditor inside a List in SwiftUI, without success
Thanks everyone in advance ;)

How to get user selected text in SwiftUI TextEditor

I have this code and I need to get user-selected text from TextEditor. How do I do such a thing in SwiftUI?
struct ContentView: View {
#Binding var document: AppDocument
var body: some View {
TextEditor(text: $document.text)
.disableAutocorrection(true)
}
}
While added in iOS 15 .textSelection modifier enables the end-user of an app to select and copy text, it doesn't help the developer obtain user-selected text or range of selection. I don't think that, as of early 2022, there is a way to do it natively in SwiftUI.
However, UIKit's UITextView has selectedRange property, and UITextViewDelegate has textViewDidChangeSelection(_:) method that fires off every time the user changes the selection. To use that in SwiftUI, we need to build a bridge using UIViewRepresentable protocol like so:
struct ContentView: View {
#State private var text = ""
var body: some View {
UITextViewRepresentable(text: $text)
}
}
struct UITextViewRepresentable: UIViewRepresentable {
let textView = UITextView()
#Binding var text: String
func makeUIView(context: Context) -> UITextView {
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
// SwiftUI -> UIKit
uiView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
class Coordinator: NSObject, UITextViewDelegate {
#Binding var text: String
init(text: Binding<String>) {
self._text = text
}
func textViewDidChange(_ textView: UITextView) {
// UIKit -> SwiftUI
_text.wrappedValue = textView.text
}
func textViewDidChangeSelection(_ textView: UITextView) {
// Fires off every time the user changes the selection.
print(textView.selectedRange)
}
}
}

Why does a binding in UIViewRepresentables Coordinator have a constant read value

I have been writing a UIViewRepresentable and noticing some curios effects in regards to a binding I'm passing into the view.
When I read the bindings value in the coordinator through the saved UIViewRepresentable the value is always the value that it was initialized with. Trying to update the same binding however triggers an update in the surrounding UI.
This is code produces this behavior:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView)
}
func makeCoordinator() -> Coordinator {
Coordinator(_text)
}
class Coordinator: NSObject {
#Binding var text: String
init(_ text: Binding<String>){
_text = text
}
#objc func updateText(sender: UITextField){
text=sender.text!
}
func updateUI(_ uiView: UITextField) {
uiView.text = text
}
}
}
If I hover give my updateUI method a NativeTextView parameter, and use the .text field of it through the parameter, I read the correct value and the UI works correctly:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView, view: self)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var myView: NativeTextView
init(_ view: NativeTextView){
self.myView=view
}
#objc func updateText(sender: UITextField){
myView.text=sender.text!
}
func updateUI(_ uiView: UITextField, view: NativeTextView) {
uiView.text = view.text
}
}
}
It seems that the binding retains the ability to write to the outside #State variable but does not manage to access the current states value correctly. I'm guessing that this has something to do with the recreation of the NativeTextView view when SwiftUI notices an update of the #State, but I have not been able to find any documentation that would explain this behavior.
Does anyone know why this happens?
PS: for completeness this is my ContentViews body:
ZStack {
Color.red
VStack {
Text(test)
.padding()
.onTapGesture() {
test = "Bla"
}
NativeTextView(text: $test)
}
}