15

How do i select all text when clicking inside the textfield? Just like how a web browser like chrome would when you click inside the address bar.

import SwiftUI
import AppKit

   struct ContentView: View {

    var body: some View {
        TextField("Enter a URL", text: $site)
    
  }
}
Eric
  • 673
  • 1
  • 7
  • 23

5 Answers5

26

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)
        }
    }
}
Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • 1
    I replaced UITextfield with NSTextfield since i'm using Appkit. i'm getting these 4 errors now. Value of type ’NSTextField’ has no member ’selectedTextRange’ ,textRange’, ‘beginningOfDocument’, ‘endOfDocument’ – Eric May 12 '21 at 11:33
  • Same here. It's like the target market for people using macOS is different from those using iOS‍♂️ – SaganRitual Nov 28 '21 at 18:37
  • The SwiftUI solution with the `NotificationCenter` receiver is pretty clever. Keep in mind that regardless of where you put it in your code, the notification will fire for *all* your `TextField`s throughout your app. Too bad macOS doesn't have the same notification available! – Clifton Labrum Jan 26 '22 at 01:20
  • Side question, when one uses the `UIViewRepresentable` approach, is it possible to use SwiftUI properties like `multilineTextAlignment` on that instance? I know when using `UIViewRepresentable` we are creating a brand new view but is there an approach to implement support for such build in SwiftUI properties or one has to manually implement them? Thanks. – Vladimir Amiorkov Oct 30 '22 at 15:11
  • Just like the comment I left on the other answer. This doesn't work for me if I tap on the TextField area before or after the text. In those cases, it just shows the keyboard and displays the selector before/after my text. – budiDino Aug 08 '23 at 12:02
  • I've added an answer that worked for me without the selector issue mentioned in my previous comment: https://stackoverflow.com/a/76859589/611879 – budiDino Aug 08 '23 at 12:35
5

I've created a ViewModifier to select all the text in a TextField. Only downside is, it won't work with multiple TextFields.

public struct SelectTextOnEditingModifier: ViewModifier {
    public func body(content: Content) -> some View {
        content
            .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)
                }
            }
    }
}

extension View {

    /// Select all the text in a TextField when starting to edit.
    /// This will not work with multiple TextField's in a single view due to not able to match the selected TextField with underlying UITextField
    public func selectAllTextOnEditing() -> some View {
        modifier(SelectTextOnEditingModifier())
    }
}

usage:

TextField("Placeholder", text: .constant("This is text data"))
    .selectAllTextOnEditing()
Rick Pasveer
  • 480
  • 7
  • 12
  • I thought maybe setting a tag on the TextField and passing this into the modifier and then checking this before selecting might work, but it does not appear to be set on the underlying UITextField. Unless I have the order of the modifiers wrong. – Ants May 04 '23 at 23:56
  • Sadly this doesn't work for me if I tap on the TextField area before or after the text. In those cases, it just shows the keyboard and displays the selector before/after my text. – budiDino Aug 08 '23 at 12:00
  • I've added an answer that worked for me without the selector issue mentioned in my previous comment: https://stackoverflow.com/a/76859589/611879 – budiDino Aug 08 '23 at 12:35
1

Here is my solution

import SwiftUI
import PlaygroundSupport

struct ContentView: View {

    @State private var renameTmpText: String = ""
    @FocusState var isFocused: Bool
    @State private var textSelected = false

    var body: some View {
        TextEditor(text: $renameTmpText)
            .padding(3)
            .border(Color.accentColor, width: 1)
            .frame(width: 120, height: 40)
            .onExitCommand(perform: {
                renameTmpText = ""
            })
            .onAppear {
                renameTmpText = "Test"
                isFocused = true
            }
            .focused($isFocused)
            .onReceive(NotificationCenter.default.publisher(for: NSTextView.didChangeSelectionNotification)) { obj in
                if let textView = obj.object as? NSTextView {
                    guard !textSelected else { return }
                    let range = NSRange(location: 0, length:     textView.string.count)
                    textView.setSelectedRange(range)
                    textSelected = true
                }
            }
            .onDisappear { textSelected = false }
    }
}

let view = ContentView()
PlaygroundPage.current.setLiveView(view)
Reinchold
  • 11
  • 1
  • 2
1

Here’s my solution:

    struct ContentView: View {
    
    @FocusState var isFocused
    @State var text = "Text"
    
    var body: some View {
        TextField("Some Text", text: $text)
            .focused($isFocused)
            .onChange(of: isFocused) { focus in
                if focus {
                    DispatchQueue.main.async {
                        UIApplication.shared.sendAction(#selector(UIResponder.selectAll(_:)), to: nil, from: nil, for: nil)
                    }
                }
            }
    }
}

If .focused doesn’t work for some weird reason, you can add a .onTapGesture to the TextField and use a Bool variable.

Like this:

struct ContentView: View {
    
    @State var isFocused = false
    @State var text = "Text"
    
    var body: some View {
        TextField("Some Text", text: $text)
            .onTapGesture {
                isFocused = true
            }
            .onSubmit {
                isFocused = false
            }
            .onChange(of: isFocused) { focus in
                if focus {
                    DispatchQueue.main.async {
                        UIApplication.shared.sendAction(#selector(UIResponder.selectAll(_:)), to: nil, from: nil, for: nil)
                    }
                }
            }
    }
}

LeonardoXUI
  • 411
  • 3
  • 10
0

I had issues when using code from some of the answers previously posted here, so I'm just sharing what worked for me.

First of all, you will have to import Combine:

import Combine

Then you can add this to your TextField:

.onReceive(NotificationCenter.default.publisher(
    for: UITextField.textDidBeginEditingNotification)) { _ in
        DispatchQueue.main.async {
            UIApplication.shared.sendAction(
                #selector(UIResponder.selectAll(_:)), to: nil, from: nil, for: nil
            )
        }
    }

Alternatively, you could use a ViewModifier for a more reusable approach:

public struct SelectAllTextOnBeginEditingModifier: ViewModifier {
    public func body(content: Content) -> some View {
        content
            .onReceive(NotificationCenter.default.publisher(
                for: UITextField.textDidBeginEditingNotification)) { _ in
                    DispatchQueue.main.async {
                        UIApplication.shared.sendAction(
                            #selector(UIResponder.selectAll(_:)), to: nil, from: nil, for: nil
                        )
                    }
                }
        }
}

extension View {
    public func selectAllTextOnBeginEditing() -> some View {
        modifier(SelectAllTextOnBeginEditingModifier())
    }
}

And then just add this to your TextField:

.selectAllTextOnBeginEditing()
budiDino
  • 13,044
  • 8
  • 95
  • 91