How to create a multiline TextField in SwiftUi ? like the notes app? - swift

I want to create a multiline textfield that automatically ads line breaks and with scroll view, just like the notes app, is there any direct way we can do this in SwiftUI ?

This works in Xcode Version 11.0 beta 6:
import SwiftUI
struct ContentView: View {
#State var text = ""
var body: some View {
VStack {
Text("text is: \(text)")
TextView(
text: $text
)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
}
struct TextView: UIViewRepresentable {
#Binding var text: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let myTextView = UITextView()
myTextView.delegate = context.coordinator
myTextView.font = UIFont(name: "HelveticaNeue", size: 15)
myTextView.isScrollEnabled = true
myTextView.isEditable = true
myTextView.isUserInteractionEnabled = true
myTextView.backgroundColor = UIColor(white: 0.0, alpha: 0.05)
return myTextView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: TextView
init(_ uiTextView: TextView) {
self.parent = uiTextView
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
print("text now: \(String(describing: textView.text!))")
self.parent.text = textView.text
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

You can use
TextEditor(text: $text)

What you're looking for is essentially a UITextView. There is no current equivalent in SwiftUI, so you'll have to use a UIViewRepresentable struct to achieve this. See Apple's SwiftUI + UIKit tutorial for how to wrap UIKit views and view controllers for use in SwiftUI and see this question for a possible implementation (and solution to an easy to overlook detail.)

Related

how to use textField Extension to disable Copy and paste in swift ios

I am trying to disable copy and paste functionality in a textField with the help of swift without using any separate cocoatouch class.
Can you suggest how to do it using Either keyboard extension or textfield extension.
Any response will be appreciated.
Create a custom TextField as use as follows
import SwiftUI
struct CustomeTextField: View {
#State var textStr = ""
var body: some View {
VStack(spacing: 10) {
Text("This is textfield:")
.font(.body)
.foregroundColor(.gray)
TextFieldWrapperView(text: self.$textStr)
.background(Color.gray)
.frame(width: 200, height: 50)
}
.frame(height: 40)
}
}
struct TextFieldWrapperView: UIViewRepresentable {
#Binding var text: String
func makeCoordinator() -> TFCoordinator {
TFCoordinator(self)
}
}
extension TextFieldWrapperView {
func makeUIView(context: UIViewRepresentableContext<TextFieldWrapperView>) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
}
class TFCoordinator: NSObject, UITextFieldDelegate {
var parent: TextFieldWrapperView
init(_ textField: TextFieldWrapperView) {
self.parent = textField
}
func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
if action == #selector(UIResponderStandardEditActions.paste(_:)) {
return false
}
return canPerformAction(action: action, withSender: sender)
}
}

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)
}
}
}

SwiftUI: Hide keyboard but show cursor

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)
}
}