diff --git a/Sources/AttributedText/AttributedText.swift b/Sources/AttributedText/AttributedText.swift index 13472a8..7404efc 100644 --- a/Sources/AttributedText/AttributedText.swift +++ b/Sources/AttributedText/AttributedText.swift @@ -4,7 +4,7 @@ @available(macOS 11.0, iOS 14.0, tvOS 14.0, *) public struct AttributedText: View { - @StateObject private var store = TextViewStore() + @StateObject private var textViewStore = TextViewStore() private let attributedText: NSAttributedString @@ -14,15 +14,15 @@ public var body: some View { GeometryReader { geometry in - TextViewWrapper(attributedText: attributedText, store: store) - .preference(key: ContainerSizePreference.self, value: geometry.maxSize) - } - .onPreferenceChange(ContainerSizePreference.self) { value in - store.onContainerSizeChange(value) + TextViewWrapper( + attributedText: attributedText, + maxLayoutWidth: geometry.maxWidth, + textViewStore: textViewStore + ) } .frame( - idealWidth: store.intrinsicContentSize?.width, - idealHeight: store.intrinsicContentSize?.height + idealWidth: textViewStore.intrinsicContentSize?.width, + idealHeight: textViewStore.intrinsicContentSize?.height ) .fixedSize(horizontal: false, vertical: true) } @@ -30,20 +30,8 @@ @available(macOS 11.0, iOS 14.0, tvOS 14.0, *) private extension GeometryProxy { - var maxSize: CGSize { - CGSize( - width: size.width - safeAreaInsets.leading - safeAreaInsets.trailing, - height: size.height - safeAreaInsets.top - safeAreaInsets.bottom - ) - } - } - - @available(macOS 11.0, iOS 14.0, tvOS 14.0, *) - private struct ContainerSizePreference: PreferenceKey { - static var defaultValue: CGSize? - - static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) { - value = nextValue() + var maxWidth: CGFloat { + size.width - safeAreaInsets.leading - safeAreaInsets.trailing } } diff --git a/Sources/AttributedText/TextViewStore.swift b/Sources/AttributedText/TextViewStore.swift index b991bdc..c925215 100644 --- a/Sources/AttributedText/TextViewStore.swift +++ b/Sources/AttributedText/TextViewStore.swift @@ -6,19 +6,8 @@ final class TextViewStore: ObservableObject { @Published var intrinsicContentSize: CGSize? - weak var view: TextViewWrapper.View? - - func onContainerSizeChange(_ containerSize: CGSize?) { - guard let containerSize = containerSize, - let view = self.view else { return } - - view.maxWidth = containerSize.width - } - - func didInvalidateIntrinsicContentSize() { - guard let view = self.view else { return } - - intrinsicContentSize = view.intrinsicContentSize + func didUpdateTextView(_ textView: TextViewWrapper.View) { + intrinsicContentSize = textView.intrinsicContentSize } } diff --git a/Sources/AttributedText/TextViewWrapper+NSTextView.swift b/Sources/AttributedText/TextViewWrapper+NSTextView.swift index 0fbbba2..aae01b5 100644 --- a/Sources/AttributedText/TextViewWrapper+NSTextView.swift +++ b/Sources/AttributedText/TextViewWrapper+NSTextView.swift @@ -5,9 +5,7 @@ @available(macOS 11.0, *) struct TextViewWrapper: NSViewRepresentable { final class View: NSTextView { - weak var store: TextViewStore? - - var maxWidth: CGFloat { + var maxLayoutWidth: CGFloat { get { textContainer?.containerSize.width ?? 0 } set { guard textContainer?.containerSize.width != newValue else { return } @@ -17,7 +15,7 @@ } override var intrinsicContentSize: NSSize { - guard maxWidth > 0, + guard maxLayoutWidth > 0, let textContainer = self.textContainer, let layoutManager = self.layoutManager else { @@ -27,19 +25,6 @@ layoutManager.ensureLayout(for: textContainer) return layoutManager.usedRect(for: textContainer).size } - - override func invalidateIntrinsicContentSize() { - super.invalidateIntrinsicContentSize() - store?.didInvalidateIntrinsicContentSize() - } - - func setAttributedText(_ attributedText: NSAttributedString) { - // Avoid notifiying the store while the text storage is processing edits - let store = self.store - self.store = nil - textStorage?.setAttributedString(attributedText) - self.store = store - } } final class Coordinator: NSObject, NSTextViewDelegate { @@ -56,7 +41,8 @@ } let attributedText: NSAttributedString - let store: TextViewStore + let maxLayoutWidth: CGFloat + let textViewStore: TextViewStore func makeNSView(context: Context) -> View { let nsView = View(frame: .zero) @@ -70,19 +56,19 @@ nsView.textContainer?.widthTracksTextView = false nsView.delegate = context.coordinator - nsView.store = store - store.view = nsView - return nsView } func updateNSView(_ nsView: View, context: Context) { - nsView.setAttributedText(attributedText) + nsView.textStorage?.setAttributedString(attributedText) + nsView.maxLayoutWidth = maxLayoutWidth + nsView.textContainer?.maximumNumberOfLines = context.environment.lineLimit ?? 0 nsView.textContainer?.lineBreakMode = NSLineBreakMode(truncationMode: context.environment.truncationMode) - nsView.invalidateIntrinsicContentSize() context.coordinator.openURL = context.environment.openURL + + textViewStore.didUpdateTextView(nsView) } func makeCoordinator() -> Coordinator { diff --git a/Sources/AttributedText/TextViewWrapper+UITextView.swift b/Sources/AttributedText/TextViewWrapper+UITextView.swift index 823cc4a..0d294c0 100644 --- a/Sources/AttributedText/TextViewWrapper+UITextView.swift +++ b/Sources/AttributedText/TextViewWrapper+UITextView.swift @@ -5,37 +5,22 @@ @available(iOS 14.0, tvOS 14.0, macCatalyst 14.0, *) struct TextViewWrapper: UIViewRepresentable { final class View: UITextView { - weak var store: TextViewStore? - - var maxWidth: CGFloat = 0 { + var maxLayoutWidth: CGFloat = 0 { didSet { - guard maxWidth != oldValue else { return } + guard maxLayoutWidth != oldValue else { return } invalidateIntrinsicContentSize() } } override var intrinsicContentSize: CGSize { - guard maxWidth > 0 else { + guard maxLayoutWidth > 0 else { return super.intrinsicContentSize } return sizeThatFits( - CGSize(width: maxWidth, height: .greatestFiniteMagnitude) + CGSize(width: maxLayoutWidth, height: .greatestFiniteMagnitude) ) } - - override func invalidateIntrinsicContentSize() { - super.invalidateIntrinsicContentSize() - store?.didInvalidateIntrinsicContentSize() - } - - func setAttributedText(_ attributedText: NSAttributedString) { - // Avoid notifiying the store while the text storage is processing edits - let store = self.store - self.store = nil - self.attributedText = attributedText - self.store = store - } } final class Coordinator: NSObject, UITextViewDelegate { @@ -48,7 +33,8 @@ } let attributedText: NSAttributedString - let store: TextViewStore + let maxLayoutWidth: CGFloat + let textViewStore: TextViewStore func makeUIView(context: Context) -> View { let uiView = View() @@ -62,19 +48,19 @@ uiView.textContainer.lineFragmentPadding = 0 uiView.delegate = context.coordinator - uiView.store = store - store.view = uiView - return uiView } func updateUIView(_ uiView: View, context: Context) { - uiView.setAttributedText(attributedText) + uiView.attributedText = attributedText + uiView.maxLayoutWidth = maxLayoutWidth + uiView.textContainer.maximumNumberOfLines = context.environment.lineLimit ?? 0 uiView.textContainer.lineBreakMode = NSLineBreakMode(truncationMode: context.environment.truncationMode) - uiView.invalidateIntrinsicContentSize() context.coordinator.openURL = context.environment.openURL + + textViewStore.didUpdateTextView(uiView) } func makeCoordinator() -> Coordinator {