11

I'm developing a Python-based graphic calculator for MacOS using SwiftUI.

https://github.com/snakajima/macplot

I am using SwiftUI's TextEditor as the editor for Python code, but I am not able to figure out how to disable the smart quote (UITextInputTraits, smartQuotesType: UITextSmartQuotesType).

        VStack {
            TextEditor(text: $pythonScript.script)
            HStack {
                Button(action: {
                    pythonScript.run(clear: settings.shouldClear)
                }, label: {
                    Text("Plot")
                })
                Toggle("Clear", isOn: $settings.shouldClear)
            }
            if let errorMsg = pythonScript.errorMsg {
                Text(errorMsg)
                    .foregroundColor(.pink)
            }
        }
Community
  • 1
  • 1
Satoshi Nakajima
  • 1,863
  • 18
  • 29

4 Answers4

8

After several trials, I came up with the following work-around. It relies on the fact that TextEditor is implemented on top of NSTextView, and changes its behavior across the entire application. It is ugly, but works.

// HACK to work-around the smart quote issue
extension NSTextView {
    open override var frame: CGRect {
        didSet {
            self.isAutomaticQuoteSubstitutionEnabled = false
        }
    }
}
Satoshi Nakajima
  • 1,863
  • 18
  • 29
2

For those who are looking for an answer for UIKit (iOS, iPadOS) instead of AppKit (macOS), this works for me using a similar approach. Thanks Satoshi!

extension UITextView {
    open override var frame: CGRect {
        didSet {
            self.smartQuotesType = UITextSmartQuotesType.no
        }
    }
}

This has the same drawbacks, which is that all text fields in your application will lose auto-smart-quotes, but at least you can control this if you need it.

Jared Updike
  • 7,165
  • 8
  • 46
  • 72
0

Another solution would be to write an NSTextView wrapper:

struct TextView: NSViewRepresentable {
    @Binding var text: String
    private var customizations = [(NSTextView) -> Void]()

    init(text: Binding<String>) {
        _text = text
    }

    func makeNSView(context: Context) -> NSView {
        NSTextView()
    }

    func updateNSView(_ nsView: NSView, context: Context) {
        let textView = nsView as! NSTextView
        textView.string = text
        customizations.forEach { $0(textView) }
    }

    func automaticDashSubstitutionEnabled(_ enabled: Bool) -> Self {
        customized { $0.isAutomaticDashSubstitutionEnabled = enabled }
    }

    func automaticQuoteSubstitutionEnabled(_ enabled: Bool) -> Self {
        customized { $0.isAutomaticQuoteSubstitutionEnabled = enabled }
    }

    func automaticSpellingCorrectionEnabled(_ enabled: Bool) -> Self {
        customized { $0.isAutomaticSpellingCorrectionEnabled = enabled }
    }
}

private extension TextView {
    func customized(_ customization: @escaping (NSTextView) -> Void) -> Self {
        var copy = self
        copy.customizations.append(customization)
        return copy
    }
}

, which can be used like this:

TextView(text: $myText)
    .automaticDashSubstitutionEnabled(false)
    .automaticQuoteSubstitutionEnabled(false)
    .automaticSpellingCorrectionEnabled(false)
Cristik
  • 30,989
  • 25
  • 91
  • 127
0

I also struggled with this and so in the end I fixed the smart quotes by replacing them back to straight quotes as the Textfield was typed. I know its a hack, but it works.

TextEditor(text: $Mytext)
.onChange(of: Mytext) { 
                        newValue in
                        Mytext = Mytext.replacingOccurrences(of: "“", with: "\"") // replaces smart quotes
                        Mytext = Mytext.replacingOccurrences(of: "”", with: "\"")
                        Mytext = Mytext.replacingOccurrences(of: "‘", with: "'")
                        Mytext = Mytext.replacingOccurrences(of: "’", with: "'")
}
James Abela
  • 97
  • 1
  • 3