Skip to content

Commit 15f6bfb

Browse files
fix(iOS): apply zero width space for headings (#597)
# Summary This PR: - apply zero width space for headings - adds `applyStylingToTypingAttrs` method in `StyleBase` to properly align cursor styles on empty line ## Test Plan Play around with headings, and check If the style work properly, cursor is properly styled. ## Screenshots / Videos Before: https://github.com/user-attachments/assets/965e8d58-21dc-4f0e-a9b4-8c8ea69084dd After: https://github.com/user-attachments/assets/97b5ee50-5fc5-4908-b5ad-dff5858cb10c ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | Android | ❌ | ## Checklist - [x] E2E tests are passing - [ ] Required E2E tests have been added (if applicable) ---------
1 parent 4f63738 commit 15f6bfb

5 files changed

Lines changed: 52 additions & 2 deletions

File tree

.maestro/enrichedInput/flows/conflicting_paragraph_merge.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ appId: swmansion.enriched.example
2828
- pressKey: Backspace
2929

3030
- runFlow:
31-
when:
32-
platform: "android"
3331
commands:
3432
- pressKey: Backspace
3533
- inputText: " "

ios/inputAttributesManager/InputAttributesManager.mm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,15 @@ - (void)manageTypingAttributesWithOnlySelection:(BOOL)onlySelectionChanged {
192192
newAttrs[entry.key] = entry.value;
193193
}
194194

195+
// Apply active styles to typing attributes only for styles that require it so
196+
// the cursor correctly reflects the current formatting state (e.g. heading
197+
// size).
198+
for (StyleBase *style in _input->stylesDict.allValues) {
199+
if ([style appliesStylingToTyping] && [style detect:selectedRange]) {
200+
[style applyStylingToTypingAttrs:newAttrs];
201+
}
202+
}
203+
195204
textView.typingAttributes = newAttrs;
196205
}
197206

ios/interfaces/StyleBase.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- (NSString *)getValue;
1313
- (BOOL)isParagraph;
1414
- (BOOL)needsZWS;
15+
- (BOOL)appliesStylingToTyping;
1516
- (instancetype)initWithHost:(id<EnrichedViewHost>)host;
1617
- (NSRange)actualUsedRange:(NSRange)range;
1718
- (void)toggle:(NSRange)range;
@@ -30,6 +31,7 @@
3031
- (BOOL)any:(NSRange)range;
3132
- (NSArray<StylePair *> *)all:(NSRange)range;
3233
- (void)applyStyling:(NSRange)range;
34+
- (void)applyStylingToTypingAttrs:(NSMutableDictionary *)attributes;
3335
- (void)reapplyFromStylePair:(StylePair *)pair;
3436
- (AttributeEntry *)getEntryIfPresent:(NSRange)range;
3537
@end

ios/interfaces/StyleBase.mm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ - (BOOL)needsZWS {
3434
return NO;
3535
}
3636

37+
- (BOOL)appliesStylingToTyping {
38+
return NO;
39+
}
40+
3741
- (instancetype)initWithHost:(id<EnrichedViewHost>)host {
3842
self = [super init];
3943
_host = host;
@@ -232,6 +236,11 @@ - (BOOL)any:(NSRange)range {
232236
- (void)applyStyling:(NSRange)range {
233237
}
234238

239+
// This method gets overridden when the style needs to apply certain typing
240+
// attributes
241+
- (void)applyStylingToTypingAttrs:(NSMutableDictionary *)attributes {
242+
}
243+
235244
// Called during dirty range re-application to restore a style from a saved
236245
// StylePair
237246
- (void)reapplyFromStylePair:(StylePair *)pair {

ios/styles/HeadingStyleBase.mm

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ - (BOOL)isParagraph {
2222
return YES;
2323
}
2424

25+
- (BOOL)needsZWS {
26+
return YES;
27+
}
28+
29+
- (BOOL)appliesStylingToTyping {
30+
return YES;
31+
}
32+
2533
- (void)applyStyling:(NSRange)range {
2634
[self.host.textView.textStorage
2735
enumerateAttribute:NSFontAttributeName
@@ -42,12 +50,36 @@ - (void)applyStyling:(NSRange)range {
4250
}];
4351
}
4452

53+
- (void)applyStylingToTypingAttrs:(NSMutableDictionary *)attributes {
54+
UIFont *currentFont = attributes[NSFontAttributeName];
55+
56+
if (currentFont == nil) {
57+
currentFont = [self.host.config primaryFont];
58+
}
59+
60+
UIFont *newFont = [currentFont setSize:[self getHeadingFontSize]];
61+
if ([self isHeadingBold]) {
62+
newFont = [newFont setBold];
63+
}
64+
65+
attributes[NSFontAttributeName] = newFont;
66+
}
67+
4568
// used to make sure headings dont persist after a newline is placed
4669
- (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text {
4770
// in a heading and a new text ends with a newline
4871
if ([self detect:self.host.textView.selectedRange] && text.length > 0 &&
4972
[[NSCharacterSet newlineCharacterSet]
5073
characterIsMember:[text characterAtIndex:text.length - 1]]) {
74+
// If the cursor sits directly before a ZWS, skip past it so the newline
75+
// is appended after the ZWS. This keeps the ZWS (and the heading) on the
76+
// current line while the new line the cursor lands on has no heading.
77+
// Without this the lone '\n' inherits heading attributes
78+
NSString *string = self.host.textView.textStorage.string;
79+
if (range.length == 0 && range.location < string.length &&
80+
[string characterAtIndex:range.location] == 0x200B) {
81+
range = NSMakeRange(range.location + 1, 0);
82+
}
5183
// do the replacement manually
5284
[TextInsertionUtils replaceText:text
5385
at:range

0 commit comments

Comments
 (0)