Select all text in TextField upon click SwiftUI
Asked Answered
R

6

17

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)
    
  }
}
Redhead answered 12/5, 2021 at 10:47 Comment(2)
Does highlight text mean select all text?Brathwaite
@RajaKishan yesRedhead
B
29

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)
        }
    }
}
Brathwaite answered 12/5, 2021 at 11:11 Comment(6)
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’Redhead
Same here. It's like the target market for people using macOS is different from those using iOS🤦‍♂️Montfort
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 TextFields throughout your app. Too bad macOS doesn't have the same notification available!Megrim
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.Reichert
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.Diet
I've added an answer that worked for me without the selector issue mentioned in my previous comment: https://mcmap.net/q/688291/-select-all-text-in-textfield-upon-click-swiftuiDiet
K
6

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()
Kilogram answered 15/2, 2023 at 10:25 Comment(3)
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.Persian
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.Diet
I've added an answer that worked for me without the selector issue mentioned in my previous comment: https://mcmap.net/q/688291/-select-all-text-in-textfield-upon-click-swiftuiDiet
S
6

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

Sideway answered 2/5, 2023 at 3:25 Comment(1)
I found this solution works better for me. I have other text fields on the view, and for some reason the other solutions would unintentionally select all the text in those also. I only needed this for 1 text field. This solution also works even if I don't tap exactly on the text in the text field.Calenture
D
5

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()
Diet answered 8/8, 2023 at 12:30 Comment(2)
Nice. What about macOS though? SwiftUI works on both platforms after all.Wifely
@DuncanC I wouldn't know. I only tested on iOS.Diet
S
3

The native way

With Xcode 16 and from iOS 18 / macOS 15

You can use the pass a TextSelection to text editing controls like:

@State private var selection: TextSelection?

var body: some View {
    TextField("Enter a URL", text: $site, selection: $selection)
}
Scarbrough answered 10/6 at 22:25 Comment(0)
O
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)
Operculum answered 13/12, 2022 at 20:5 Comment(1)
This looks to be a MacOS only solution. (Most of the others are iOS only.)Wifely

© 2022 - 2024 — McMap. All rights reserved.