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
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:
- All "tabbable"
UITextField
s are on the same parent view. - 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
Swiftui Swizzling Disabled by Default, Phone Auth Not Working
Aws S3 Transfer Manager ${Cognito-Identity.Amazonaws.Com:Sub} Policy Variable Access Denied
How to Use Storyboards with Spritekit Using Swift
Swift: Extract Float from Byte Data
Hstack with Sf Symbols Image Not Aligned Centered
How to Create Text File for Writing
Navigationview Doesn't Display Correctly When Using Tabview in Swiftui
Spawning a Spritekit Node at a Random Time
Convert Firdatasnapshot to Custom Type
How to Setup a Second Component with a Uipickerview
Why Does My Version of Filter Perform So Differently Than Swifts
Lazy Readonly Property in Swift
How to Make Protocol Associated Type Require Protocol Inheritance and Not Protocol Adoption
Swiftui: Navigation Bar Title in Reusable Cross-Platform (iOS & MACos) View
Peak Detection for Growing Time Series Using Swift
How to Get Next Case of Enum(I.E. Write a Circulating Method) in Swift 4.2