I am using a TextView UIViewRepresentable as outlined here https://www.appcoda.com/swiftui-textview-uiviewrepresentable/.
It’s working as expected, apart from one issue concerning line spacing. SwiftUI’s lineSpacing modifier seems to have no effect on it. So, I have worked around it by adding the following to the UIViewRepresentable’s
func updateUIView(_ uiView: UITextView, context: Context)
:
let style = NSMutableParagraphStyle()
style.lineSpacing = 4
let attributes = [NSAttributedString.Key.paragraphStyle : style]
uiView.attributedText = NSAttributedString(string: self.text, attributes:attributes)
This was as advised by Adjusting the line spacing of UITextView.
So, the full function looks like:
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
// line spacing
let style = NSMutableParagraphStyle()
style.lineSpacing = 4
let attributes = [NSAttributedString.Key.paragraphStyle : style]
uiView.attributedText = NSAttributedString(string: self.text, attributes:attributes)
}
This does the job, however it results in the UndoManager resetting. That is, as soon as I make any change, the UndoManager doesn’t believe there is anything that can be undone (or redone). From my searches, it appears this is a general side-effect of changing the value of attributedText. I’m wondering if there’s a workaround available, whether a tweak to my approach or an altogether different way of achieving lineSpacing without resetting the UndoManager state.
UPDATE: attempted Asperi’s recommendation but with mixed results.
This is full code of TextView and corresponding Coordinator:
import SwiftUI
struct TextView: UIViewRepresentable {
// MARK: Bindings
@Binding var text: String
@Binding var textStyle: UIFont.TextStyle
// MARK: -
// MARK: Functions
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.backgroundColor = UIColor.clear
textView.delegate = context.coordinator
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.adjustsFontForContentSizeCategory = true
return textView
}
func makeCoordinator() -> Coordinator {
Coordinator($text)
}
func updateUIView(_ uiView: UITextView, context: Context) {
let storage = uiView.textStorage
storage.beginEditing()
// line spacing
let style = NSMutableParagraphStyle()
style.lineSpacing = 4
let attributes = [NSAttributedString.Key.paragraphStyle : style]
storage.replaceCharacters(in: NSRange(location: 0, length: storage.length),
with: NSAttributedString(string: self.text, attributes:attributes))
storage.endEditing()
}
// MARK: -
// MARK: Internal classes
class Coordinator: NSObject, UITextViewDelegate {
// MARK: Local
var text: Binding<String>
// MARK: -
init(_ text: Binding<String>) {
self.text = text
}
// MARK: -
// MARK: Functions
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
}
}
}
If I keep self.text.wrappedValue = textView.text in textViewDidChange (which was taken from the AppCoda tutorial linked at the top) then the recommendation doesn’t work. However, if I remove it, it appears to work but there are other issues whereby the text automatically resets to the original state at the start of the session whenever the view (I think) is refreshed – for example, if I try switching to another app, I could see the text reset before doing so, or when I open a piece of UI that, for example, reduces the opacity of the TextView.
I may have stumbled across the correct approach thanks to Ivan’s answer in https://stackoverflow.com/a/44414510/698971.
Inside func makeUIView(context: Context) -> UITextView
for the TextView UIViewRepresentable I needed to add:
let spacing = NSMutableParagraphStyle()
spacing.lineSpacing = 4
let attr = [NSAttributedString.Key.paragraphStyle : spacing]
textView.typingAttributes = attr