Main problem:
SwiftUI's Text() have no ability to fill large amounts of text inside. In case of text have too many symbols or in case of too large font it's displaying the following error:
[Window] Warning: Window SwiftUI.AppKitWindow 0x13be8e970 ordered front from a non-active application and may order beneath the active application's windows. 2023-08-19 22:48:49.743611+0300 Videq[83718:3987311] -[<_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer: 0x6000031d8e40> display]: Ignoring bogus layer size (479.000000, 323046.000000), contentsScale 2.000000, backing store size (958.000000, 646092.000000) 2023-08-19 22:48:49.791437+0300 Videq[83718:3987311] Metal API Validation Enabled
So I need to write custom Text() using NSTextView
myCode is:
import SwiftUI
import Cocoa
@available(OSX 11.0, *)
public struct AttributedText2: View {
@Binding var text: AttributedString
var nsText: Binding<NSAttributedString> {
Binding(get: { NSAttributedString(text) }, set: { _ in })
}
@State var width: CGFloat = 0
public var body: some View {
AttributedTextInternal(attributedString: nsText)
}
}
//@available(OSX 11.0, *)
//public struct AttributedText: View {
// @Binding var text: NSAttributedString
//
// public init(attributedString: Binding<NSAttributedString>) {
// _text = attributedString
// }
//
// public var body: some View {
// AttributedTextInternal(attributedString: $text)
// }
//}
@available(OSX 11.0, *)
public struct AttributedTextInternal: NSViewRepresentable {
@Binding var text: NSAttributedString
public init(attributedString: Binding<NSAttributedString>) {
_text = attributedString
}
public func makeNSView(context: Context) -> CustTextFld {
let textView = CustTextFld()
textView.setContent(text: text, makeNotEditable: true)
textView.backgroundColor = .clear
textView.layerContentsPlacement = .top
textView.layerContentsRedrawPolicy = .crossfade
return textView
}
public func updateNSView(_ textView: CustTextFld, context: Context) {
textView.setContent(text: text, makeNotEditable: true)
}
}
public class CustTextFld: NSTextView {
func setContent(text: NSAttributedString, makeNotEditable: Bool) {
self.isEditable = true
self.selectAll(nil)
self.insertText(text, replacementRange: self.selectedRange())
self.isEditable = !makeNotEditable
}
// remove cursor
public override func mouseMoved(with event: NSEvent) {
}
public override func viewWillDraw() {
isHorizontallyResizable = true
isVerticallyResizable = true
isRichText = true
isSelectable = false
// isRichText = false
}
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path = NSBezierPath(rect: bounds)
NSColor.white.setStroke()
path.stroke()
}
}
extension NSAttributedString {
func height(containerWidth: CGFloat) -> CGFloat {
let rect = self.boundingRect(with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
return ceil(rect.size.height)
}
func width(containerHeight: CGFloat) -> CGFloat {
let rect = self.boundingRect(with: CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: containerHeight),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
return ceil(rect.size.width)
}
}
issue here that I'm to able to force size of AttributedText2 for being sure that it's width and height will be correct:
- width must be the same as parent's view content
- height must be calculated from width using
NSAttributedString.height(containerWidth: )
I had tried to get content size but didn't have any results. Also I have tried to do textView.sizeToFit() - also no results.
Tried to use Geometry reader to get view width - but it's breaking the view and view still have incorrect size.
So code:
GeometryReader { geometry in
AttributedTextInternal(attributedString: nsText)
.frame(width: geometry.size.width, height: nsText.wrappedValue.height(containerWidth: geometry.size.width))
}
doesn't work on large texts. Try to set text with 20 000 words and to locate AttributedText2() inside of ScrollView()
Is anyone have some ideas?
Attempt #2
This is super simple but it seems from your comment this might be what you are looking for:
Text()might actually work.let heightis how tall you want the text area to be.This gif shows it working.
Ignore below!
If I understand you correctly, then I think this solves it. All you have to do is replace the
AttributedText2struct with the following:Notice that it uses
GeometryReaderfor the size data. When integrated, it simply becomes:What is looks like: