Могу ли я остановить отставание макета NSViewRepresentable для NSTextView?

Я использую внутреннюю часть в приложении SwiftUI.

Размер правильно изменяется до высоты NSTextView, поэтому текст перетекает в несколько строк, но при первом рендеринге после создания он показывает только одну строку .

Похоже, что в этом первом кадре отображается правильно, с текстом, перенесенным в нужной точке, но не отображается полная высота.

Создается, и текст устанавливается наNSTextStorageвmakeCoordinator()метод , т.е. доNSViewRepresentableзапрашивает вид.

Я переопределяю, чтобы установитьintrinsicContentSize:

          override func layout() {
        invalidateIntrinsicContentSize()
        super.layout()
    }

    override var intrinsicContentSize: NSSize {
            layoutManager!.ensureLayout(for: textContainer!)
            return CGSize(width: -1.0,
                          height: ceil(layoutManager!.usedRect(for: textContainer!).size.height))
    }

Я думаю, что на эту проблему намекает «помпон», упомянутый в этом посте . Метод отслеживания высоты отличается, но также не может установить первый кадр.

Есть ли что-нибудь, что я могу сделать, чтобы ускорить макет наNSTextView? Представление не имеет фиксированной ширины, поэтому оно должно перемещаться динамически.

Вот минимальная репродукция:

      import SwiftUI

    let font = NSFont.systemFont(ofSize: 20, weight: .bold)
    let testString = "This long string demonstrates how its full extent is not shown on the first frame of an NSTextView rendered within an NSViewRepresentable."
    let fontAttribute = [NSAttributedString.Key.font: font]
    let testText = NSAttributedString(string: testString, attributes: fontAttribute)
    
    class InternalTextView: NSTextView
    {
        init() {
            super.init(frame: NSRect.zero)
            setContentHuggingPriority(.defaultHigh, for: .vertical)
        }
        
        override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) {
            super.init(frame: frameRect, textContainer: container) }
        
        required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }

        override func layout() {
            invalidateIntrinsicContentSize()
            super.layout()
        }
            
        override var intrinsicContentSize: NSSize {
            layoutManager!.ensureLayout(for: textContainer!)
            return CGSize(width: -1.0,
                          height: ceil(layoutManager!.usedRect(for: textContainer!).size.height))
        }
    }
    
    
    class TextViewCoordinator: NSObject {
        private(set) var view: InternalTextView
        
        init(withText text: NSAttributedString) {
            view = InternalTextView()
            view.textStorage?.setAttributedString(text)
            super.init()
        }
    }
    
    struct TextView: NSViewRepresentable
    {
        let text: NSAttributedString
        
        func makeNSView(context: Context) -> NSTextView { context.coordinator.view }
        
        func updateNSView(_ nsView: NSTextView, context: Context) { }
        
        func makeCoordinator() -> TextViewCoordinator { TextViewCoordinator(withText: text) }
    }
    
    struct ContentView: View
    {
        let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
        @State var showText: Bool = false
        
        var body: some View {
            HStack {
                Spacer()
                ScrollView {
                    VStack {
                        if showText {
                            ForEach(1..<10) { _ in
                                TextView(text: testText)
                            } }
                        
                        Spacer()
                    }.frame(maxWidth: 600)
                }
                Spacer()
            }
            .onReceive(timer) { _ in
                showText.toggle()
            }
        }
    }

Кадр 1:

Кадр 2:

1 ответ

У меня была очень похожая проблема с ViewRepresentable на iOS, где я использовал CoreText для выполнения некоторых задач по рендерингу текста. Симптомы были практически идентичны.

Решением в моем случае было переопределить sizeThatFits() в ViewRepresentable и предоставить ему правильную информацию (вычисление высоты текста для фрейма в макете) в исходном представлении.

      func sizeThatFits(_ proposal: ProposedViewSize, uiView: <YOUR VIEW>, context: Context) -> CGSize? {
        
        return uiView.sizeThatFits(CGSize(width: proposal.width ?? 0, height: proposal.height ?? 0))
    }

Я не уверен, что это сработает в вашей конкретной ситуации, но я потратил около недели на то, чтобы рвать на себе волосы, пытаясь сделать одно и то же, поэтому решил поделиться.

Другие вопросы по тегам