Select All Text in Textfield Upon Click Swiftui

Select all text in TextField upon click SwiftUI

SwiftUI Solution:

struct ContentView: View {
var body: some View {
TextField("Placeholder", text: .constant("This is text data"))
.onReceive(NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification)) { obj in
if let textField = obj.object as? UITextField {
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument)
}
}
}
}

Note : import Combine


Use UIViewRepresentable and wrap UITextField and use textField.selectedTextRange property with delegate.

Here is the sample demo

struct HighlightTextField: UIViewRepresentable {

@Binding var text: String

func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
return textField
}

func updateUIView(_ textField: UITextField, context: Context) {
textField.text = text
}

func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}

class Coordinator: NSObject, UITextFieldDelegate {
var parent: HighlightTextField

init(parent: HighlightTextField) {
self.parent = parent
}

func textFieldDidBeginEditing(_ textField: UITextField) {
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument)
}
}
}




For macOS

struct HighlightTextField: NSViewRepresentable {

@Binding var text: String

func makeNSView(context: Context) -> CustomTextField {
CustomTextField()
}

func updateNSView(_ textField: CustomTextField, context: Context) {
textField.stringValue = text
}
}

class CustomTextField: NSTextField {
override func mouseDown(with event: NSEvent) {
if let textEditor = currentEditor() {
textEditor.selectAll(self)
}
}
}

Programmatically Select all text in UITextField

Turns out, calling -selectAll: with a non-nil sender displays the menu. Calling it with nil causes it to select the text, but not display the menu.

I tried this after my bug report about it came back from Apple with the suggestion that I pass nil instead of self.

No need to muck with UIMenuController or other selection APIs.

How to detect when a TextField becomes active in SwiftUI?

The answer is to initialize TextField with the onEditingChanged parameter.

We can then execute a closure conditionally depending upon whether the text field was edited or changes were committed:

TextField("", text: $email, onEditingChanged: { changed in
if changed {
// User began editing the text field
}
else {
// User tapped the return key
}
})

How do I allow text selection on a Text label in SwiftUI?


iOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+

As of Xcode 13.0 beta 2 you can use

Text("Selectable text")
.textSelection(.enabled)
Text("Non selectable text")
.textSelection(.disabled)

// applying `textSelection` to a container
// enables text selection for all `Text` views inside it
VStack {
Text("Selectable text1")
Text("Selectable text2")
// disable selection only for this `Text` view
Text("Non selectable text")
.textSelection(.disabled)
}.textSelection(.enabled)

See also the textSelection Documentation.

iOS 14 and lower

Using TextField("", text: .constant("Some text")) has two problems:

  • Minor: The cursor shows up when selecting
  • Mayor: When a user selects some text he can tap in the context menu cut, paste and other items which can change the text regardless of using .constant(...)

My solution to this problem involves subclassing UITextField and using UIViewRepresentable to bridge between UIKit and SwiftUI.

At the end I provide the full code to copy and paste into a playground in Xcode 11.3 on macOS 10.14

Subclassing the UITextField:

/// This subclass is needed since we want to customize the cursor and the context menu
class CustomUITextField: UITextField, UITextFieldDelegate {

/// (Not used for this workaround, see below for the full code) Binding from the `CustomTextField` so changes of the text can be observed by `SwiftUI`
fileprivate var _textBinding: Binding<String>!

/// If it is `true` the text field behaves normally.
/// If it is `false` the text cannot be modified only selected, copied and so on.
fileprivate var _isEditable = true {
didSet {
// set the input view so the keyboard does not show up if it is edited
self.inputView = self._isEditable ? nil : UIView()
// do not show autocorrection if it is not editable
self.autocorrectionType = self._isEditable ? .default : .no
}
}


// change the cursor to have zero size
override func caretRect(for position: UITextPosition) -> CGRect {
return self._isEditable ? super.caretRect(for: position) : .zero
}

// override this method to customize the displayed items of 'UIMenuController' (the context menu when selecting text)
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {

// disable 'cut', 'delete', 'paste','_promptForReplace:'
// if it is not editable
if (!_isEditable) {
switch action {
case #selector(cut(_:)),
#selector(delete(_:)),
#selector(paste(_:)):
return false
default:
// do not show 'Replace...' which can also replace text
// Note: This selector is private and may change
if (action == Selector("_promptForReplace:")) {
return false
}
}
}
return super.canPerformAction(action, withSender: sender)
}


// === UITextFieldDelegate methods

func textFieldDidChangeSelection(_ textField: UITextField) {
// update the text of the binding
self._textBinding.wrappedValue = textField.text ?? ""
}

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Allow changing the text depending on `self._isEditable`
return self._isEditable
}

}

Using UIViewRepresentable to implement SelectableText

struct SelectableText: UIViewRepresentable {

private var text: String
private var selectable: Bool

init(_ text: String, selectable: Bool = true) {
self.text = text
self.selectable = selectable
}

func makeUIView(context: Context) -> CustomUITextField {
let textField = CustomUITextField(frame: .zero)
textField.delegate = textField
textField.text = self.text
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return textField
}

func updateUIView(_ uiView: CustomUITextField, context: Context) {
uiView.text = self.text
uiView._textBinding = .constant(self.text)
uiView._isEditable = false
uiView.isEnabled = self.selectable
}

func selectable(_ selectable: Bool) -> SelectableText {
return SelectableText(self.text, selectable: selectable)
}

}


The full code

In the full code below I also implemented a CustomTextField where editing can be turned off but still be selectable.

Playground view

Selection of text

Selection of text with context menu

Code

import PlaygroundSupport
import SwiftUI


/// This subclass is needed since we want to customize the cursor and the context menu
class CustomUITextField: UITextField, UITextFieldDelegate {

/// Binding from the `CustomTextField` so changes of the text can be observed by `SwiftUI`
fileprivate var _textBinding: Binding<String>!

/// If it is `true` the text field behaves normally.
/// If it is `false` the text cannot be modified only selected, copied and so on.
fileprivate var _isEditable = true {
didSet {
// set the input view so the keyboard does not show up if it is edited
self.inputView = self._isEditable ? nil : UIView()
// do not show autocorrection if it is not editable
self.autocorrectionType = self._isEditable ? .default : .no
}
}


// change the cursor to have zero size
override func caretRect(for position: UITextPosition) -> CGRect {
return self._isEditable ? super.caretRect(for: position) : .zero
}

// override this method to customize the displayed items of 'UIMenuController' (the context menu when selecting text)
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {

// disable 'cut', 'delete', 'paste','_promptForReplace:'
// if it is not editable
if (!_isEditable) {
switch action {
case #selector(cut(_:)),
#selector(delete(_:)),
#selector(paste(_:)):
return false
default:
// do not show 'Replace...' which can also replace text
// Note: This selector is private and may change
if (action == Selector("_promptForReplace:")) {
return false
}
}
}
return super.canPerformAction(action, withSender: sender)
}


// === UITextFieldDelegate methods

func textFieldDidChangeSelection(_ textField: UITextField) {
// update the text of the binding
self._textBinding.wrappedValue = textField.text ?? ""
}

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Allow changing the text depending on `self._isEditable`
return self._isEditable
}

}

struct CustomTextField: UIViewRepresentable {

@Binding private var text: String
private var isEditable: Bool

init(text: Binding<String>, isEditable: Bool = true) {
self._text = text
self.isEditable = isEditable
}

func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> CustomUITextField {
let textField = CustomUITextField(frame: .zero)
textField.delegate = textField
textField.text = self.text
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
return textField
}

func updateUIView(_ uiView: CustomUITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = self.text
uiView._textBinding = self.$text
uiView._isEditable = self.isEditable
}

func isEditable(editable: Bool) -> CustomTextField {
return CustomTextField(text: self.$text, isEditable: editable)
}
}

struct SelectableText: UIViewRepresentable {

private var text: String
private var selectable: Bool

init(_ text: String, selectable: Bool = true) {
self.text = text
self.selectable = selectable
}

func makeUIView(context: Context) -> CustomUITextField {
let textField = CustomUITextField(frame: .zero)
textField.delegate = textField
textField.text = self.text
textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return textField
}

func updateUIView(_ uiView: CustomUITextField, context: Context) {
uiView.text = self.text
uiView._textBinding = .constant(self.text)
uiView._isEditable = false
uiView.isEnabled = self.selectable
}

func selectable(_ selectable: Bool) -> SelectableText {
return SelectableText(self.text, selectable: selectable)
}

}


struct TextTestView: View {

@State private var selectableText = true

var body: some View {
VStack {

// Even though the text should be constant, it is not because the user can select and e.g. 'cut' the text
TextField("", text: .constant("Test SwiftUI TextField"))
.background(Color(red: 0.5, green: 0.5, blue: 1))

// This view behaves like the `SelectableText` however the layout behaves like a `TextField`
CustomTextField(text: .constant("Test `CustomTextField`"))
.isEditable(editable: false)
.background(Color.green)

// A non selectable normal `Text`
Text("Test SwiftUI `Text`")
.background(Color.red)

// A selectable `text` where the selection ability can be changed by the button below
SelectableText("Test `SelectableText` maybe selectable")
.selectable(self.selectableText)
.background(Color.orange)

Button(action: {
self.selectableText.toggle()
}) {
Text("`SelectableText` can be selected: \(self.selectableText.description)")
}

// A selectable `text` which cannot be changed
SelectableText("Test `SelectableText` always selectable")
.background(Color.yellow)

}.padding()
}

}

let viewController = UIHostingController(rootView: TextTestView())
viewController.view.frame = CGRect(x: 0, y: 0, width: 400, height: 200)

PlaygroundPage.current.liveView = viewController.view

InputAccessoryView / View Pinned to Keyboard with SwiftUI

I got something working which is quite near the wanted result. So at first, it's not possible to do this with SwiftUI only. You still have to use UIKit for creating the UITextField with the wanted "inputAccessoryView". The textfield in SwiftUI doesn't have the certain method.

First I created a new struct:

import UIKit
import SwiftUI

struct InputAccessory: UIViewRepresentable {

func makeUIView(context: Context) -> UITextField {

let customView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
customView.backgroundColor = UIColor.red
let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
sampleTextField.inputAccessoryView = customView
sampleTextField.placeholder = "placeholder"

return sampleTextField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
}

With that I could finally create a new textfield in the body of my view:

import SwiftUI

struct Test: View {
@State private var showInput: Bool = false
var body: some View {
HStack{
Spacer()
if showInput{
InputAccessory()
}else{
InputAccessory().hidden()
}
}
}
}

Now you can hide and show the textfield with the "showInput" state. The next problem is, that you have to open your keyboard at a certain event and show the textfield. That's again not possible with SwiftUI and you have to go back to UiKit and making it first responder. If you try my code, you should see a red background above the keyboard. Now you only have to move the field up and you got a working version.

Overall, at the current state it's not possible to work with the keyboard or with the certain textfield method.

macOS SwiftUI TextEditor keyboard shortcuts for copy, paste, & cut

I was looking for a similar solution for a TextField and found a somewhat hacky one. Here is a similar way for your situation using a TextEditor.

The first problem I tried to solve was making the textField a first responder (focus when the popup opens).

This can be done using the SwiftUI-Introspect library (https://github.com/timbersoftware/SwiftUI-Introspect) as seen in this answer for a TextField (https://stackoverflow.com/a/59277051/14847761).
Similarly for a TextEditor you can do:

TextEditor(text: $userData.note)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(10)
.font(.body)
.background(Color(red: 30 / 255, green: 30 / 255, blue: 30 / 255))

.introspect(
selector: TargetViewSelector.siblingContaining,
customize: { view in
view.becomeFirstResponder()
})

Now to get to your main issue of cut/copy/paste, you can also use Introspect.
First thing is to get a reference to the NSTextField from inside of the TextEditor:

    .introspect(
selector: TargetViewSelector.siblingContaining,
customize: { view in
view.becomeFirstResponder()

// Extract the NSText from the NSScrollView
mainText = ((view as! NSScrollView).documentView as! NSText)
//

})

The mainText variable must be declared somewhere but cannot be a @State inside of the ContentView for some reason, ran into selection issues for my TextField. I ended up just putting it at the root level inside of the swift file:

import SwiftUI
import Introspect

// Stick this somewhere
var mainText: NSText!

struct ContentView: View {

...

Next is to setup a menu with commands, this is the main reason I think there is no cut/copy/paste as you guessed.
Add a command menu to your app and add the commands you want.

@main
struct MenuBarPopoverApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
Settings{
EmptyView()
}
.commands {
MenuBarPopoverCommands(appDelegate: appDelegate)
}
}
}

struct MenuBarPopoverCommands: Commands {

let appDelegate: AppDelegate

init(appDelegate: AppDelegate) {
self.appDelegate = appDelegate
}

var body: some Commands {
CommandMenu("Edit"){ // Doesn't need to be Edit
Section {
Button("Cut") {
appDelegate.contentView.editCut()
}.keyboardShortcut(KeyEquivalent("x"), modifiers: .command)

Button("Copy") {
appDelegate.contentView.editCopy()
}.keyboardShortcut(KeyEquivalent("c"), modifiers: .command)

Button("Paste") {
appDelegate.contentView.editPaste()
}.keyboardShortcut(KeyEquivalent("v"), modifiers: .command)

// Might also want this
Button("Select All") {
appDelegate.contentView.editSelectAll()
}.keyboardShortcut(KeyEquivalent("a"), modifiers: .command)
}
}
}
}

Also need to make the contentView accessible:

class AppDelegate: NSObject, NSApplicationDelegate {
var popover = NSPopover.init()
var statusBarItem: NSStatusItem?

// making this a class variable
var contentView: ContentView!

func applicationDidFinishLaunching(_ notification: Notification) {

// assign here
contentView = ContentView()
...

And finally the actual commands.

struct ContentView: View {

...

func editCut() {
mainText?.cut(self)
}

func editCopy() {
mainText?.copy(self)
}

func editPaste() {
mainText?.paste(self)
}

func editSelectAll() {
mainText?.selectAll(self)
}

// Could also probably add undo/redo in a similar way but I haven't tried

...

}

This was my first ever answer on StackOverflow so I hope that all made sense and I did this right. But I do hope someone else comes along with a better solution, I was searching for an answer myself when I came across this question.

How to navigate through textfields (Next / Done Buttons)

In Cocoa for Mac OS X, you have the next responder chain, where you can ask the text field what control should have focus next. This is what makes tabbing between text fields work. But since iOS devices do not have a keyboard, only touch, this concept has not survived the transition to Cocoa Touch.

This can be easily done anyway, with two assumptions:

  1. All "tabbable" UITextFields are on the same parent view.
  2. Their "tab-order" is defined by the tag property.

Assuming this you can override textFieldShouldReturn: as this:

-(BOOL)textFieldShouldReturn:(UITextField*)textField
{
NSInteger nextTag = textField.tag + 1;
// Try to find next responder
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (nextResponder) {
// Found next responder, so set it.
[nextResponder becomeFirstResponder];
} else {
// Not found, so remove keyboard.
[textField resignFirstResponder];
}
return NO; // We do not want UITextField to insert line-breaks.
}

Add some more code, and the assumptions can be ignored as well.

Swift 4.0

 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
// Try to find next responder
let nextResponder = textField.superview?.viewWithTag(nextTag) as UIResponder!

if nextResponder != nil {
// Found next responder, so set it
nextResponder?.becomeFirstResponder()
} else {
// Not found, so remove keyboard
textField.resignFirstResponder()
}

return false
}

If the superview of the text field will be a UITableViewCell then next responder will be

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!

SwiftUI validate input in textfields

Try to validate what you want in the TextField onRecive method like this:

class TextValidator: ObservableObject {

@Published var text = ""

}

struct ContentView: View {

@ObservedObject var textValidator = TextValidator()
var body: some View {
TextField("Type Here", text: $textValidator.text)
.padding(.horizontal, 20.0)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onReceive(Just(textValidator.text)) { newValue in
let value = newValue.replacingOccurrences(
of: "\\W", with: "", options: .regularExpression)
if value != newValue {
self.textValidator.text = value
}
print(newValue)
}
}
}


Related Topics



Leave a reply



Submit