4
4
5
5
#if os(iOS) || os(macOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst)
6
6
7
+ import _SwiftUIX
8
+ import CoreGraphics
7
9
import SwiftUI
8
10
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
+
9
35
#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst)
10
36
extension AppKitOrUIKitTextView {
11
37
public var _SwiftUIX_textContainer : NSTextContainer ? {
@@ -19,11 +45,67 @@ extension AppKitOrUIKitTextView {
19
45
public var _SwiftUIX_textStorage : NSTextStorage ? {
20
46
textStorage
21
47
}
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
+ }
22
92
}
23
93
24
94
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
+ }
27
109
}
28
110
29
111
public var _SwiftUIX_text : String {
@@ -33,31 +115,7 @@ extension AppKitOrUIKitTextView {
33
115
public var _SwiftUIX_attributedText : NSAttributedString {
34
116
attributedText ?? NSAttributedString ( )
35
117
}
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
- }
45
118
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
-
61
119
public func insertText(
62
120
_ insertString: Any ,
63
121
replacementRange: NSRange
@@ -103,15 +161,35 @@ extension AppKitOrUIKitTextView {
103
161
public var _SwiftUIX_textStorage : NSTextStorage ? {
104
162
textStorage
105
163
}
164
+
165
+ public var _SwiftUIX_defaultParagraphStyle : NSParagraphStyle ? {
166
+ defaultParagraphStyle
167
+ }
106
168
}
107
169
108
170
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
+ }
112
192
}
113
-
114
- return range
115
193
}
116
194
117
195
public var _SwiftUIX_text : String {
@@ -175,7 +253,7 @@ extension AppKitOrUIKitTextView {
175
253
}
176
254
177
255
var lineHeight = font. ascender + font. descender + font. leading
178
- let lineSpacing = _lastLineParagraphStyle ? . lineSpacing ?? 0
256
+ let lineSpacing = _SwiftUIX_paragraphStyleOfLastLine ? . lineSpacing ?? 0
179
257
180
258
if let layoutManager = _SwiftUIX_layoutManager {
181
259
lineHeight = max ( lineHeight, layoutManager. defaultLineHeight ( for: font) )
@@ -184,17 +262,17 @@ extension AppKitOrUIKitTextView {
184
262
return lineHeight + lineSpacing
185
263
}
186
264
187
- var _lastLineParagraphStyle : NSParagraphStyle ? {
265
+ public var _SwiftUIX_paragraphStyleOfLastLine : NSParagraphStyle ? {
188
266
guard let textStorage = _SwiftUIX_textStorage else {
189
- return defaultParagraphStyle
267
+ return _SwiftUIX_defaultParagraphStyle
190
268
}
191
269
192
270
if textStorage. length == 0 {
193
- return defaultParagraphStyle
271
+ return _SwiftUIX_defaultParagraphStyle
194
272
}
195
273
196
- guard let selectedRange = _SwiftUIX_selectedTextRange else {
197
- return defaultParagraphStyle
274
+ guard let selectedRange = _SwiftUIX_naiveSelectedTextRange else {
275
+ return _SwiftUIX_defaultParagraphStyle
198
276
}
199
277
200
278
let location : Int
@@ -208,134 +286,64 @@ extension AppKitOrUIKitTextView {
208
286
}
209
287
210
288
guard location < textStorage. length else {
211
- return defaultParagraphStyle
289
+ return _SwiftUIX_defaultParagraphStyle
212
290
}
213
291
214
292
let paragraphStyle = textStorage. attributes ( at: location, effectiveRange: nil ) [ . paragraphStyle] as? NSParagraphStyle
215
293
216
294
guard let paragraphStyle else {
217
- return defaultParagraphStyle
295
+ return _SwiftUIX_defaultParagraphStyle
218
296
}
219
297
220
298
return paragraphStyle
221
299
}
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
+ }
239
301
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
253
311
}
254
312
255
- layoutManager. invalidateLayout (
256
- forCharacterRange: NSRange ( location: 0 , length: textStorage. length) ,
313
+ layoutManager. invalidateGlyphs (
314
+ forCharacterRange: range,
315
+ changeInLength: changeInLength,
257
316
actualCharacterRange: nil
258
317
)
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 {
297
324
assertionFailure ( )
325
+
326
+ return
298
327
}
299
328
300
- return usedRect. size
329
+ layoutManager. invalidateLayout (
330
+ forCharacterRange: range,
331
+ actualCharacterRange: nil
332
+ )
301
333
}
302
334
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
309
342
}
310
343
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
330
346
)
331
-
332
- if accountForNewline {
333
- if temporaryTextStorage. string. hasSuffix ( " \n " ) {
334
- result. height += ( _heightDifferenceForNewline ?? 0 )
335
- }
336
- }
337
-
338
- return result
339
347
}
340
348
}
341
349
@@ -351,11 +359,11 @@ extension NSLayoutManager {
351
359
}
352
360
#elseif os(macOS)
353
361
extension NSSize {
354
- fileprivate var horizontal : CGFloat {
362
+ public var horizontal : CGFloat {
355
363
width
356
364
}
357
365
358
- fileprivate var vertical : CGFloat {
366
+ public var vertical : CGFloat {
359
367
height
360
368
}
361
369
}
0 commit comments