Skip to content

Commit 56aab8e

Browse files
committed
Update package
1 parent 264cb59 commit 56aab8e

32 files changed

+1182
-618
lines changed

Sources/SwiftUIX/Intermodular/Extensions/AppKit or UIKit/AppKitOrUIKitTextView++.swift

Lines changed: 153 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,34 @@
44

55
#if os(iOS) || os(macOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst)
66

7+
import _SwiftUIX
8+
import CoreGraphics
79
import SwiftUI
810

11+
extension AppKitOrUIKitTextView {
12+
static func _SwiftUIX_initialize(
13+
customTextStorage textStorage: NSTextStorage?
14+
) -> AppKitOrUIKitTextView {
15+
let result: Self
16+
17+
if let textStorage: NSTextStorage = textStorage {
18+
let layoutManager = NSLayoutManager()
19+
let textContainer = NSTextContainer(size: .zero)
20+
21+
textStorage.addLayoutManager(layoutManager)
22+
layoutManager.addTextContainer(textContainer)
23+
24+
result = self.init(frame: .zero, textContainer: textContainer) as! Self
25+
} else {
26+
assertionFailure()
27+
28+
result = self.init() as! Self
29+
}
30+
31+
return result
32+
}
33+
}
34+
935
#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst)
1036
extension AppKitOrUIKitTextView {
1137
public var _SwiftUIX_textContainer: NSTextContainer? {
@@ -19,11 +45,67 @@ extension AppKitOrUIKitTextView {
1945
public var _SwiftUIX_textStorage: NSTextStorage? {
2046
textStorage
2147
}
48+
49+
public var _SwiftUIX_defaultParagraphStyle: NSParagraphStyle? {
50+
NSParagraphStyle.default
51+
}
52+
}
53+
54+
extension AppKitOrUIKitTextView {
55+
@objc public convenience init(
56+
_SwiftUIX_usingTextLayoutManager usingTextLayoutManager: Bool
57+
) {
58+
if #available(iOS 16.0, tvOS 16.0, *) {
59+
if Self.responds(to: #selector(UITextView.init(usingTextLayoutManager:))) {
60+
self.init(usingTextLayoutManager: usingTextLayoutManager)
61+
62+
return
63+
}
64+
}
65+
66+
if #available(iOS 15.0, tvOS 15.0, *) {
67+
if usingTextLayoutManager {
68+
let textContainer = NSTextContainer(size: CGSize(width: 0.0, height: 1.0e7))
69+
let textContentManager = NSTextContentStorage()
70+
let textLayoutManager = NSTextLayoutManager()
71+
72+
textLayoutManager.textContainer = textContainer
73+
textContentManager.addTextLayoutManager(textLayoutManager)
74+
75+
self.init(frame: .zero, textContainer: textContainer)
76+
} else {
77+
let textStorage = NSTextStorage()
78+
let layoutManager = NSLayoutManager()
79+
let textContainer = NSTextContainer()
80+
81+
textStorage.addLayoutManager(layoutManager)
82+
layoutManager.addTextContainer(textContainer)
83+
84+
self.init(frame: .zero, textContainer: textContainer)
85+
}
86+
} else {
87+
assertionFailure()
88+
89+
self.init(frame: .zero)
90+
}
91+
}
2292
}
2393

2494
extension AppKitOrUIKitTextView {
25-
public var _SwiftUIX_selectedTextRange: NSRange? {
26-
selectedRange
95+
public var _SwiftUIX_naiveSelectedTextRange: NSRange? {
96+
get {
97+
if selectedTextRange != nil {
98+
return selectedRange
99+
} else {
100+
return nil
101+
}
102+
} set {
103+
if let newValue {
104+
selectedRange = newValue
105+
} else {
106+
self.selectedRange = NSRange(location: 0, length: 0)
107+
}
108+
}
27109
}
28110

29111
public var _SwiftUIX_text: String {
@@ -33,31 +115,7 @@ extension AppKitOrUIKitTextView {
33115
public var _SwiftUIX_attributedText: NSAttributedString {
34116
attributedText ?? NSAttributedString()
35117
}
36-
37-
var defaultParagraphStyle: NSParagraphStyle? {
38-
NSParagraphStyle.default
39-
}
40-
41-
func adjustFontSizeToFitWidth() {
42-
guard !text.isEmpty && !bounds.size.equalTo(CGSize.zero) else {
43-
return
44-
}
45118

46-
let textViewSize = frame.size
47-
let fixedWidth = textViewSize.width;
48-
let expectSize = sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
49-
50-
if expectSize.height > textViewSize.height {
51-
while sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude)).height > textViewSize.height {
52-
font = font!.withSize(font!.pointSize - 1)
53-
}
54-
} else {
55-
while sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude)).height < textViewSize.height {
56-
font = font!.withSize(font!.pointSize + 1)
57-
}
58-
}
59-
}
60-
61119
public func insertText(
62120
_ insertString: Any,
63121
replacementRange: NSRange
@@ -103,15 +161,35 @@ extension AppKitOrUIKitTextView {
103161
public var _SwiftUIX_textStorage: NSTextStorage? {
104162
textStorage
105163
}
164+
165+
public var _SwiftUIX_defaultParagraphStyle: NSParagraphStyle? {
166+
defaultParagraphStyle
167+
}
106168
}
107169

108170
extension AppKitOrUIKitTextView {
109-
public var _SwiftUIX_selectedTextRange: NSRange? {
110-
guard let range = selectedRanges.first as? NSRange else {
111-
return nil
171+
@objc public convenience init(
172+
_SwiftUIX_usingTextLayoutManager usingTextLayoutManager: Bool
173+
) {
174+
self.init(usingTextLayoutManager: usingTextLayoutManager)
175+
}
176+
}
177+
178+
extension AppKitOrUIKitTextView {
179+
public var _SwiftUIX_naiveSelectedTextRange: NSRange? {
180+
get {
181+
guard let range = selectedRanges.first as? NSRange else {
182+
return nil
183+
}
184+
185+
return range
186+
} set {
187+
if let newValue {
188+
setSelectedRange(newValue)
189+
} else {
190+
setSelectedRange(NSRange(location: string.count, length: 0))
191+
}
112192
}
113-
114-
return range
115193
}
116194

117195
public var _SwiftUIX_text: String {
@@ -175,7 +253,7 @@ extension AppKitOrUIKitTextView {
175253
}
176254

177255
var lineHeight = font.ascender + font.descender + font.leading
178-
let lineSpacing = _lastLineParagraphStyle?.lineSpacing ?? 0
256+
let lineSpacing = _SwiftUIX_paragraphStyleOfLastLine?.lineSpacing ?? 0
179257

180258
if let layoutManager = _SwiftUIX_layoutManager {
181259
lineHeight = max(lineHeight, layoutManager.defaultLineHeight(for: font))
@@ -184,17 +262,17 @@ extension AppKitOrUIKitTextView {
184262
return lineHeight + lineSpacing
185263
}
186264

187-
var _lastLineParagraphStyle: NSParagraphStyle? {
265+
public var _SwiftUIX_paragraphStyleOfLastLine: NSParagraphStyle? {
188266
guard let textStorage = _SwiftUIX_textStorage else {
189-
return defaultParagraphStyle
267+
return _SwiftUIX_defaultParagraphStyle
190268
}
191269

192270
if textStorage.length == 0 {
193-
return defaultParagraphStyle
271+
return _SwiftUIX_defaultParagraphStyle
194272
}
195273

196-
guard let selectedRange = _SwiftUIX_selectedTextRange else {
197-
return defaultParagraphStyle
274+
guard let selectedRange = _SwiftUIX_naiveSelectedTextRange else {
275+
return _SwiftUIX_defaultParagraphStyle
198276
}
199277

200278
let location: Int
@@ -208,134 +286,64 @@ extension AppKitOrUIKitTextView {
208286
}
209287

210288
guard location < textStorage.length else {
211-
return defaultParagraphStyle
289+
return _SwiftUIX_defaultParagraphStyle
212290
}
213291

214292
let paragraphStyle = textStorage.attributes(at: location, effectiveRange: nil)[.paragraphStyle] as? NSParagraphStyle
215293

216294
guard let paragraphStyle else {
217-
return defaultParagraphStyle
295+
return _SwiftUIX_defaultParagraphStyle
218296
}
219297

220298
return paragraphStyle
221299
}
222-
223-
func _sizeThatFitsWidth(
224-
_ width: CGFloat
225-
) -> CGSize? {
226-
_sizeThatFitsWithoutCopying(width: width)
227-
}
228-
229-
private func _sizeThatFitsWithoutCopying(
230-
width: CGFloat
231-
) -> CGSize? {
232-
guard
233-
let textContainer = _SwiftUIX_textContainer,
234-
let layoutManager = _SwiftUIX_layoutManager,
235-
let textStorage = _SwiftUIX_textStorage
236-
else {
237-
return nil
238-
}
300+
}
239301

240-
let originalSize = frame.size
241-
let originalTextContainerSize = textContainer.containerSize
242-
243-
guard width.isNormal && width != .greatestFiniteMagnitude else {
244-
return nil
245-
}
246-
247-
// frame.size.width = width
248-
textContainer.containerSize = CGSize(width: width, height: 10000000.0)
249-
250-
defer {
251-
textContainer.size = originalTextContainerSize
252-
frame.size.width = originalSize.width
302+
extension AppKitOrUIKitTextView {
303+
public func invalidateGlyphs(
304+
for range: NSRange,
305+
changeInLength: Int
306+
) {
307+
guard let layoutManager: NSLayoutManager = _SwiftUIX_layoutManager else {
308+
assertionFailure()
309+
310+
return
253311
}
254312

255-
layoutManager.invalidateLayout(
256-
forCharacterRange: NSRange(location: 0, length: textStorage.length),
313+
layoutManager.invalidateGlyphs(
314+
forCharacterRange: range,
315+
changeInLength: changeInLength,
257316
actualCharacterRange: nil
258317
)
259-
260-
/// Uncommenting out this line without also uncommenting out `frame.size.width = width` will result in placeholder max width being returned.
261-
// let glyphRange = layoutManager.glyphRange(for: textContainer)
262-
263-
layoutManager.ensureLayout(for: textContainer)
264-
265-
let usedRect = layoutManager.usedRect(for: textContainer)
266-
// let boundingRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
267-
268-
if usedRect.isEmpty {
269-
if (!width.isNormal && !textStorage.string.isEmpty) {
270-
return nil
271-
}
272-
273-
guard textStorage.string.isEmpty else {
274-
frame.size.width = width
275-
276-
defer {
277-
frame.size.width = originalSize.width
278-
}
279-
280-
layoutManager.ensureLayout(for: textContainer)
281-
282-
let usedRect2 = layoutManager.usedRect(for: textContainer)
283-
284-
guard !usedRect2.isEmpty else {
285-
return nil
286-
}
287-
288-
if usedRect2.size._hasPlaceholderDimensions(for: .textContainer) {
289-
assertionFailure()
290-
}
291-
292-
return usedRect2.size
293-
}
294-
}
295-
296-
if usedRect.size._hasPlaceholderDimensions(for: .textContainer) {
318+
}
319+
320+
public func invalidateLayout(
321+
for range: NSRange
322+
) {
323+
guard let layoutManager: NSLayoutManager = _SwiftUIX_layoutManager else {
297324
assertionFailure()
325+
326+
return
298327
}
299328

300-
return usedRect.size
329+
layoutManager.invalidateLayout(
330+
forCharacterRange: range,
331+
actualCharacterRange: nil
332+
)
301333
}
302334

303-
private func _sizeThatFitsByCopying(
304-
width: CGFloat,
305-
accountForNewline: Bool
306-
) -> CGSize? {
307-
guard let textContainer = _SwiftUIX_textContainer, let textStorage = _SwiftUIX_textStorage else {
308-
return nil
335+
public func invalidateDisplay(
336+
for range: NSRange
337+
) {
338+
guard let layoutManager: NSLayoutManager = _SwiftUIX_layoutManager else {
339+
assertionFailure()
340+
341+
return
309342
}
310343

311-
let temporaryTextStorage = NSTextStorage(attributedString: textStorage)
312-
let width = bounds.width - textContainerInset.horizontal
313-
let containerSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
314-
let temporaryTextContainer = NSTextContainer(size: containerSize)
315-
let temporaryLayoutManager = NSLayoutManager()
316-
317-
temporaryLayoutManager.addTextContainer(temporaryTextContainer)
318-
temporaryTextStorage.addLayoutManager(temporaryLayoutManager)
319-
320-
temporaryTextContainer.lineFragmentPadding = textContainer.lineFragmentPadding
321-
temporaryTextContainer.lineBreakMode = textContainer.lineBreakMode
322-
323-
_ = temporaryLayoutManager.glyphRange(for: temporaryTextContainer)
324-
325-
let usedRect = temporaryLayoutManager.usedRect(for: temporaryTextContainer)
326-
327-
var result = CGSize(
328-
width: ceil(usedRect.size.width + textContainerInset.horizontal),
329-
height: ceil(usedRect.size.height + textContainerInset.vertical)
344+
layoutManager.invalidateDisplay(
345+
forCharacterRange: range
330346
)
331-
332-
if accountForNewline {
333-
if temporaryTextStorage.string.hasSuffix("\n") {
334-
result.height += (_heightDifferenceForNewline ?? 0)
335-
}
336-
}
337-
338-
return result
339347
}
340348
}
341349

@@ -351,11 +359,11 @@ extension NSLayoutManager {
351359
}
352360
#elseif os(macOS)
353361
extension NSSize {
354-
fileprivate var horizontal: CGFloat {
362+
public var horizontal: CGFloat {
355363
width
356364
}
357365

358-
fileprivate var vertical: CGFloat {
366+
public var vertical: CGFloat {
359367
height
360368
}
361369
}

0 commit comments

Comments
 (0)