Friday, February 2, 2024
HomeiOS Developmentios - How do I enable textual content choice on a Textual...

ios – How do I enable textual content choice on a Textual content label in SwiftUI?


As of Xcode 13.0 beta 2 you need to use

Textual content("Selectable textual content")
    .textSelection(.enabled)
Textual content("Non selectable textual content")
    .textSelection(.disabled)

// making use of `textSelection` to a container
// permits textual content choice for all `Textual content` views inside it
VStack {
    Textual content("Selectable text1")
    Textual content("Selectable text2")
    // disable choice just for this `Textual content` view
    Textual content("Non selectable textual content")
        .textSelection(.disabled)
}.textSelection(.enabled)

See additionally the textSelection Documentation.

Utilizing TextField("", textual content: .fixed("Some textual content")) has two issues:

  • Minor: The cursor reveals up when choosing
  • Mayor: When a person selects some textual content he can faucet within the context menu reduce, paste and different gadgets which can change the textual content no matter utilizing .fixed(...)

My resolution to this downside entails subclassing UITextField and utilizing UIViewRepresentable to bridge between UIKit and SwiftUI.

On the finish I present the complete code to repeat and paste right into a playground in Xcode 11.3 on macOS 10.14

Subclassing the UITextField:

/// This subclass is required since we need to customise the cursor and the context menu
class CustomUITextField: UITextField, UITextFieldDelegate {
    
    /// (Not used for this workaround, see beneath for the complete code) Binding from the `CustomTextField` so modifications of the textual content may be noticed by `SwiftUI`
    fileprivate var _textBinding: Binding<String>!
    
    /// Whether it is `true` the textual content subject behaves usually.
    /// Whether it is `false` the textual content can't be modified solely chosen, copied and so forth.
    fileprivate var _isEditable = true {
        didSet {
            // set the enter view so the keyboard doesn't present up whether it is edited
            self.inputView = self._isEditable ? nil : UIView()
            // don't present autocorrection if it's not editable
            self.autocorrectionType = self._isEditable ? .default : .no
        }
    }
    
    
    // change the cursor to have zero dimension
    override func caretRect(for place: UITextPosition) -> CGRect {
        return self._isEditable ? tremendous.caretRect(for: place) : .zero
    }
    
    // override this methodology to customise the displayed gadgets of 'UIMenuController' (the context menu when choosing textual content)
    override func canPerformAction(_ motion: Selector, withSender sender: Any?) -> Bool {
    
        // disable 'reduce', 'delete', 'paste','_promptForReplace:'
        // if it's not editable
        if (!_isEditable) {
            change motion {
            case #selector(reduce(_:)),
                 #selector(delete(_:)),
                 #selector(paste(_:)):
                return false
            default:
                // don't present 'Substitute...' which may additionally exchange textual content
                // Word: This selector is personal and should change
                if (motion == Selector("_promptForReplace:")) {
                    return false
                }
            }
        }
        return tremendous.canPerformAction(motion, withSender: sender)
    }
    
    
    // === UITextFieldDelegate strategies
    
    func textFieldDidChangeSelection(_ textField: UITextField) {
        // replace the textual content of the binding
        self._textBinding.wrappedValue = textField.textual content ?? ""
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn vary: NSRange, replacementString string: String) -> Bool {
        // Enable altering the textual content relying on `self._isEditable`
        return self._isEditable
    }
    
}

Utilizing UIViewRepresentable to implement SelectableText

struct SelectableText: UIViewRepresentable {
    
    personal var textual content: String
    personal var selectable: Bool
    
    init(_ textual content: String, selectable: Bool = true) {
        self.textual content = textual content
        self.selectable = selectable
    }
    
    func makeUIView(context: Context) -> CustomUITextField {
        let textField = CustomUITextField(body: .zero)
        textField.delegate = textField
        textField.textual content = self.textual content
        textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
        textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        return textField
    }
    
    func updateUIView(_ uiView: CustomUITextField, context: Context) {
        uiView.textual content = self.textual content
        uiView._textBinding = .fixed(self.textual content)
        uiView._isEditable = false
        uiView.isEnabled = self.selectable
    }
    
    func selectable(_ selectable: Bool) -> SelectableText {
        return SelectableText(self.textual content, selectable: selectable)
    }
    
}

Within the full code beneath I additionally applied a CustomTextField the place enhancing may be turned off however nonetheless be selectable.

Playground view

Selection of text

Selection of text with context menu

Code

import PlaygroundSupport
import SwiftUI


/// This subclass is required since we need to customise the cursor and the context menu
class CustomUITextField: UITextField, UITextFieldDelegate {
    
    /// Binding from the `CustomTextField` so modifications of the textual content may be noticed by `SwiftUI`
    fileprivate var _textBinding: Binding<String>!
    
    /// Whether it is `true` the textual content subject behaves usually.
    /// Whether it is `false` the textual content can't be modified solely chosen, copied and so forth.
    fileprivate var _isEditable = true {
        didSet {
            // set the enter view so the keyboard doesn't present up whether it is edited
            self.inputView = self._isEditable ? nil : UIView()
            // don't present autocorrection if it's not editable
            self.autocorrectionType = self._isEditable ? .default : .no
        }
    }
    
    
    // change the cursor to have zero dimension
    override func caretRect(for place: UITextPosition) -> CGRect {
        return self._isEditable ? tremendous.caretRect(for: place) : .zero
    }
    
    // override this methodology to customise the displayed gadgets of 'UIMenuController' (the context menu when choosing textual content)
    override func canPerformAction(_ motion: Selector, withSender sender: Any?) -> Bool {
    
        // disable 'reduce', 'delete', 'paste','_promptForReplace:'
        // if it's not editable
        if (!_isEditable) {
            change motion {
            case #selector(reduce(_:)),
                 #selector(delete(_:)),
                 #selector(paste(_:)):
                return false
            default:
                // don't present 'Substitute...' which may additionally exchange textual content
                // Word: This selector is personal and should change
                if (motion == Selector("_promptForReplace:")) {
                    return false
                }
            }
        }
        return tremendous.canPerformAction(motion, withSender: sender)
    }
    
    
    // === UITextFieldDelegate strategies
    
    func textFieldDidChangeSelection(_ textField: UITextField) {
        // replace the textual content of the binding
        self._textBinding.wrappedValue = textField.textual content ?? ""
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn vary: NSRange, replacementString string: String) -> Bool {
        // Enable altering the textual content relying on `self._isEditable`
        return self._isEditable
    }
    
}

struct CustomTextField: UIViewRepresentable {
    
    @Binding personal var textual content: String
    personal var isEditable: Bool
    
    init(textual content: Binding<String>, isEditable: Bool = true) {
        self._text = textual content
        self.isEditable = isEditable
    }
    
    func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> CustomUITextField {
        let textField = CustomUITextField(body: .zero)
        textField.delegate = textField
        textField.textual content = self.textual content
        textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
        return textField
    }
    
    func updateUIView(_ uiView: CustomUITextField, context: UIViewRepresentableContext<CustomTextField>) {
        uiView.textual content = self.textual content
        uiView._textBinding = self.$textual content
        uiView._isEditable = self.isEditable
    }
    
    func isEditable(editable: Bool) -> CustomTextField {
        return CustomTextField(textual content: self.$textual content, isEditable: editable)
    }
}

struct SelectableText: UIViewRepresentable {
    
    personal var textual content: String
    personal var selectable: Bool
    
    init(_ textual content: String, selectable: Bool = true) {
        self.textual content = textual content
        self.selectable = selectable
    }
    
    func makeUIView(context: Context) -> CustomUITextField {
        let textField = CustomUITextField(body: .zero)
        textField.delegate = textField
        textField.textual content = self.textual content
        textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
        textField.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        return textField
    }
    
    func updateUIView(_ uiView: CustomUITextField, context: Context) {
        uiView.textual content = self.textual content
        uiView._textBinding = .fixed(self.textual content)
        uiView._isEditable = false
        uiView.isEnabled = self.selectable
    }
    
    func selectable(_ selectable: Bool) -> SelectableText {
        return SelectableText(self.textual content, selectable: selectable)
    }
    
}


struct TextTestView: View {
    
    @State personal var selectableText = true
    
    var physique: some View {
        VStack {
            
            // Although the textual content must be fixed, it's not as a result of the person can choose and e.g. 'reduce' the textual content
            TextField("", textual content: .fixed("Take a look at SwiftUI TextField"))
                .background(Coloration(purple: 0.5, inexperienced: 0.5, blue: 1))
            
            // This view behaves just like the `SelectableText` nevertheless the structure behaves like a `TextField`
            CustomTextField(textual content: .fixed("Take a look at `CustomTextField`"))
                .isEditable(editable: false)
                .background(Coloration.inexperienced)
            
            // A non selectable regular `Textual content`
            Textual content("Take a look at SwiftUI `Textual content`")
                .background(Coloration.purple)
            
            // A selectable `textual content` the place the choice skill may be modified by the button beneath
            SelectableText("Take a look at `SelectableText` possibly selectable")
                .selectable(self.selectableText)
                .background(Coloration.orange)
            
            Button(motion: {
                self.selectableText.toggle()
            }) {
                Textual content("`SelectableText` may be chosen: (self.selectableText.description)")
            }
            
            // A selectable `textual content` which can't be modified
            SelectableText("Take a look at `SelectableText` at all times selectable")
                .background(Coloration.yellow)
            
        }.padding()
    }
    
}

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

PlaygroundPage.present.liveView = viewController.view



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments