-
Notifications
You must be signed in to change notification settings - Fork 67
Expand file tree
/
Copy pathAttachmentLayoutUtils.mm
More file actions
147 lines (120 loc) · 5.81 KB
/
Copy pathAttachmentLayoutUtils.mm
File metadata and controls
147 lines (120 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#import "AttachmentLayoutUtils.h"
@implementation AttachmentLayoutUtils
+ (void)handleAttachmentUpdate:(MediaAttachment *)attachment
textView:(UITextView *)textView
onLayoutBlock:(dispatch_block_t)layoutBlock {
NSTextStorage *storage = textView.textStorage;
NSRange fullRange = NSMakeRange(0, storage.length);
__block NSRange foundRange = NSMakeRange(NSNotFound, 0);
[storage enumerateAttribute:NSAttachmentAttributeName
inRange:fullRange
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value == attachment) {
foundRange = range;
*stop = YES;
}
}];
if (foundRange.location == NSNotFound) {
return;
}
[storage edited:NSTextStorageEditedAttributes
range:foundRange
changeInLength:0];
dispatch_async(dispatch_get_main_queue(), layoutBlock);
}
+ (NSMutableDictionary<NSValue *, UIImageView *> *)
layoutAttachmentsInTextView:(UITextView *)textView
config:(EnrichedConfig *)config
existingViews:
(NSMutableDictionary<NSValue *, UIImageView *> *)
attachmentViews {
NSTextStorage *storage = textView.textStorage;
NSMutableDictionary<NSValue *, UIImageView *> *activeAttachmentViews =
[NSMutableDictionary dictionary];
if (storage.length > 0) {
// Iterate over the entire text to find ImageAttachments
[storage enumerateAttribute:NSAttachmentAttributeName
inRange:NSMakeRange(0, storage.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if ([value isKindOfClass:[ImageAttachment class]]) {
ImageAttachment *attachment = (ImageAttachment *)value;
CGRect rect = [self frameForAttachment:attachment
atRange:range
textView:textView
config:config];
// Get or Create the UIImageView for this specific
// attachment key
NSValue *key =
[NSValue valueWithNonretainedObject:attachment];
UIImageView *imgView = attachmentViews[key];
if (!imgView) {
// It doesn't exist yet, create it
imgView = [[UIImageView alloc] initWithFrame:rect];
imgView.contentMode =
UIViewContentModeScaleAspectFit;
// Add it directly to the TextView
[textView addSubview:imgView];
}
// Update position (in case text moved/scrolled)
if (!CGRectEqualToRect(imgView.frame, rect)) {
imgView.frame = rect;
}
// Keep the placeholder tint in sync with the
// current font color
imgView.tintColor = [config primaryColor];
UIImage *targetImage =
attachment.storedAnimatedImage ?: attachment.image;
// Only set if different to avoid resetting the
// animation loop
if (imgView.image != targetImage) {
imgView.image = targetImage;
}
// Ensure it is visible on top
imgView.hidden = NO;
[textView bringSubviewToFront:imgView];
activeAttachmentViews[key] = imgView;
// Remove from the old map so we know it has been
// claimed
[attachmentViews removeObjectForKey:key];
}
}];
}
// Everything remaining in attachmentViews is dead or off-screen
for (UIImageView *danglingView in attachmentViews.allValues) {
[danglingView removeFromSuperview];
}
return activeAttachmentViews;
}
+ (CGRect)frameForAttachment:(ImageAttachment *)attachment
atRange:(NSRange)range
textView:(UITextView *)textView
config:(EnrichedConfig *)config {
NSLayoutManager *layoutManager = textView.layoutManager;
NSTextContainer *textContainer = textView.textContainer;
NSTextStorage *storage = textView.textStorage;
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range
actualCharacterRange:NULL];
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:glyphRange
inTextContainer:textContainer];
CGRect lineRect =
[layoutManager lineFragmentRectForGlyphAtIndex:glyphRange.location
effectiveRange:NULL];
CGSize attachmentSize = attachment.bounds.size;
UIFont *font = [storage attribute:NSFontAttributeName
atIndex:range.location
effectiveRange:NULL];
if (!font) {
font = [config primaryFont];
}
// Calculate (Baseline Alignment)
CGFloat targetY =
CGRectGetMaxY(lineRect) + font.descender - attachmentSize.height;
CGRect rect =
CGRectMake(glyphRect.origin.x + textView.textContainerInset.left,
targetY + textView.textContainerInset.top,
attachmentSize.width, attachmentSize.height);
return CGRectIntegral(rect);
}
@end