Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
edce693
Use `NSTextStorageDelegate` instead of method swizzling
tomekzaw Oct 17, 2024
1ea65e4
Fix spellcheck
tomekzaw Oct 17, 2024
4214989
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Oct 21, 2024
20166dd
Add singleline implementation
tomekzaw Oct 21, 2024
b089830
Improve multiline cleanup
tomekzaw Oct 21, 2024
d5cc035
Add TODO about spellcheck
tomekzaw Oct 21, 2024
80c0686
Add missing imports
tomekzaw Oct 21, 2024
0d57eeb
Add support for old architecture
tomekzaw Oct 21, 2024
5675e04
Add #ifdef for imports
tomekzaw Oct 21, 2024
e1fca80
Restore original App.tsx
tomekzaw Oct 23, 2024
5a1651f
Don't mix styles
tomekzaw Oct 23, 2024
1b016bf
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Nov 5, 2024
e9e0e60
Update Podfile.lock
tomekzaw Nov 5, 2024
d0ef4ef
Assert that delegate is nil
tomekzaw Nov 5, 2024
8c06be0
Add comment with link to the issue
tomekzaw Nov 5, 2024
9ff01d5
Reformat singleline input on `markdownStyle` change
tomekzaw Nov 5, 2024
40abeba
Add nice constructors for `MarkdownTextStorageDelegate` and `Markdown…
tomekzaw Nov 5, 2024
67968ec
Fix emoji not visible inside text (seriously, this took like 3 hours)
tomekzaw Nov 5, 2024
d377043
Fix updating `style` without updating `markdownStyle` for multiline i…
tomekzaw Nov 5, 2024
6e40703
Add comments
tomekzaw Nov 5, 2024
9fc1d8e
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Nov 6, 2024
1fa1b54
Update Podfile.lock
tomekzaw Nov 6, 2024
2518c5f
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Nov 20, 2024
7b475de
Update Podfile.lock
tomekzaw Nov 20, 2024
1c432fe
Fix flickering and missing formatting when toggling multiline prop
tomekzaw Nov 20, 2024
2a37b85
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Nov 21, 2024
7a13eac
Update Podfile.lock
tomekzaw Nov 21, 2024
3a6a58a
Update Podfile.lock
tomekzaw Nov 21, 2024
003ed5e
Update Podfile.lock
tomekzaw Nov 21, 2024
d402ca9
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Nov 26, 2024
5cf9b9b
Update Podfile.lock
tomekzaw Nov 26, 2024
c00c440
Fix cursor position in new line after blockquote
tomekzaw Nov 26, 2024
7425d76
Remove trailing whitespace
tomekzaw Nov 27, 2024
511d885
Optimize updating paragraph style in typing attributes
tomekzaw Nov 27, 2024
ebd9813
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Dec 9, 2024
b5c0e11
Remove outdated TODO
tomekzaw Dec 9, 2024
8400a91
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Dec 10, 2024
4b05c62
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Dec 10, 2024
0c381f6
Update Podfile.lock
tomekzaw Dec 10, 2024
1363312
Fix applying underline to the whole text on singleline input blur
tomekzaw Dec 10, 2024
1f53ff2
Eliminate underline blinks while typing if previous text ends with a …
tomekzaw Dec 10, 2024
7977d83
Introduce `applyMarkdownFormatting` method in `MarkdownTextFieldObser…
tomekzaw Dec 10, 2024
a3e9169
Fix underline blinks while typing after a link
tomekzaw Dec 11, 2024
1fb4a00
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Dec 12, 2024
edfa822
Fix zombie blockquote ribbon and indent
tomekzaw Dec 12, 2024
03ec16f
Add comment
tomekzaw Dec 12, 2024
4c70fba
Fix regression with spellcheck disappearing immediately
tomekzaw Dec 12, 2024
b503121
Fix blockquote line height on iOS
tomekzaw Dec 16, 2024
63f2af2
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Dec 21, 2024
ca0b10d
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Jan 10, 2025
ab99a0c
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Mar 14, 2025
fbefca5
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Mar 23, 2025
5c80195
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Mar 25, 2025
2d31525
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Mar 26, 2025
ec09b0f
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Apr 14, 2025
67cfbaa
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw Apr 24, 2025
c68ff30
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw May 5, 2025
36a5afd
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw May 6, 2025
d52f354
Merge branch 'main' into @tomekzaw/NSTextStorageDelegate
tomekzaw May 12, 2025
28715f4
Update Podfile.lock
tomekzaw May 12, 2025
b121910
Fix cursor falling behind while typing fast in the middle of the text
tomekzaw May 12, 2025
3f85c72
Fix cursor falling behind when text doesn't contain any emoji and `_t…
tomekzaw May 12, 2025
c30464e
Remove outdated code
tomekzaw May 12, 2025
e2cb08b
Add TODO
tomekzaw May 12, 2025
60fa9ff
Add comment with explanation
tomekzaw May 12, 2025
83a8bff
Remove duplicate delegate creation
tomekzaw May 12, 2025
b7d849f
Remove unnecessary key prop
tomekzaw May 12, 2025
36ebfc9
Use `setAttributes` instead of removing custom and adding default att…
tomekzaw May 12, 2025
a55bd9c
Remove misleading comment
tomekzaw May 12, 2025
4c25344
Use `fullRange` instead of `NSMakeRange`
tomekzaw May 12, 2025
1c67cff
Restore emoji font family
tomekzaw May 12, 2025
0e38ca8
Fix applying new styles on text input styles update
tomekzaw May 12, 2025
8a57d96
Fix text view observer cleanup
tomekzaw May 12, 2025
5cca12f
Remove TODO
tomekzaw May 12, 2025
9220015
Remove TODO
tomekzaw May 12, 2025
e39892b
Restore original order of delegates
tomekzaw May 12, 2025
c8a6530
Restore original comment
tomekzaw May 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions apple/MarkdownFormatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

NS_ASSUME_NONNULL_BEGIN

const NSAttributedStringKey RCTLiveMarkdownTextAttributeName = @"RCTLiveMarkdownText";

const NSAttributedStringKey RCTLiveMarkdownBlockquoteDepthAttributeName = @"RCTLiveMarkdownBlockquoteDepth";

@interface MarkdownFormatter : NSObject

- (nonnull NSAttributedString *)format:(nonnull NSString *)text
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle;
- (void)formatAttributedString:(nonnull NSMutableAttributedString *)attributedString
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle;

NS_ASSUME_NONNULL_END

Expand Down
26 changes: 12 additions & 14 deletions apple/MarkdownFormatter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,19 @@

@implementation MarkdownFormatter

- (nonnull NSAttributedString *)format:(nonnull NSString *)text
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle
- (void)formatAttributedString:(nonnull NSMutableAttributedString *)attributedString
withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
withMarkdownRanges:(nonnull NSArray<MarkdownRange *> *)markdownRanges
withMarkdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:defaultTextAttributes];
NSRange fullRange = NSMakeRange(0, attributedString.length);

[attributedString beginEditing];

// If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string.
// It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting.
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
[attributedString addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInteger:NSUnderlineStyleNone]
range:NSMakeRange(0, attributedString.length)];
[attributedString setAttributes:defaultTextAttributes range:fullRange];

// We add a custom attribute to force a different comparison mode in swizzled `_textOf` method.
[attributedString addAttribute:RCTLiveMarkdownTextAttributeName value:@(YES) range:fullRange];

for (MarkdownRange *markdownRange in markdownRanges) {
[self applyRangeToAttributedString:attributedString
Expand All @@ -28,15 +26,15 @@ - (nonnull NSAttributedString *)format:(nonnull NSString *)text
defaultTextAttributes:defaultTextAttributes];
}

[attributedString.string enumerateSubstringsInRange:NSMakeRange(0, attributedString.length)
[attributedString.string enumerateSubstringsInRange:fullRange
options:NSStringEnumerationByLines | NSStringEnumerationSubstringNotRequired
usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
RCTApplyBaselineOffset(attributedString, enclosingRange);
}];

[attributedString endEditing];
[attributedString fixAttributesInRange:fullRange];

return attributedString;
[attributedString endEditing];
}

- (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedString
Expand Down
17 changes: 17 additions & 0 deletions apple/MarkdownTextFieldObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#import <UIKit/UIKit.h>
#import <React/RCTUITextField.h>
#import <RNLiveMarkdown/RCTMarkdownUtils.h>

NS_ASSUME_NONNULL_BEGIN

@interface MarkdownTextFieldObserver : NSObject

- (instancetype)initWithTextField:(nonnull RCTUITextField *)textField markdownUtils:(nonnull RCTMarkdownUtils *)markdownUtils;

- (void)textFieldDidChange:(UITextField *)textField;

- (void)textFieldDidEndEditing:(UITextField *)textField;

@end

NS_ASSUME_NONNULL_END
73 changes: 73 additions & 0 deletions apple/MarkdownTextFieldObserver.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#import <RNLiveMarkdown/MarkdownTextFieldObserver.h>
#import "react_native_assert.h"

@implementation MarkdownTextFieldObserver {
RCTUITextField *_textField;
RCTMarkdownUtils *_markdownUtils;
BOOL _active;
}

- (instancetype)initWithTextField:(nonnull RCTUITextField *)textField markdownUtils:(nonnull RCTMarkdownUtils *)markdownUtils
{
if ((self = [super init])) {
react_native_assert(textField != nil);
react_native_assert(markdownUtils != nil);

_textField = textField;
_markdownUtils = markdownUtils;
_active = YES;
}
return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if (_active && ([keyPath isEqualToString:@"text"] || [keyPath isEqualToString:@"attributedText"])) {
[self applyMarkdownFormatting];
}
}

- (void)textFieldDidChange:(__unused UITextField *)textField
{
[self applyMarkdownFormatting];
}

- (void)textFieldDidEndEditing:(__unused UITextField *)textField
{
// In order to prevent iOS from applying underline to the whole text if text ends with a link on blur,
// we need to update `defaultTextAttributes` which at this point doesn't contain NSUnderline attribute yet.
// It seems like the setter performs deep comparision, so we differentiate the new value using a counter,
// otherwise this trick would work only once.
static NSAttributedStringKey RCTLiveMarkdownForceUpdateAttributeName = @"RCTLiveMarkdownForceUpdate";
static NSUInteger counter = 0;
NSMutableDictionary *defaultTextAttributes = [_textField.defaultTextAttributes mutableCopy];
defaultTextAttributes[RCTLiveMarkdownForceUpdateAttributeName] = @(counter++);
_textField.defaultTextAttributes = defaultTextAttributes;
[self applyMarkdownFormatting];
}

- (void)applyMarkdownFormatting
{
react_native_assert(_textField.defaultTextAttributes != nil);

if (_textField.markedTextRange != nil) {
return; // skip formatting during multi-stage input to avoid breaking internal state
}

NSMutableAttributedString *attributedText = [_textField.attributedText mutableCopy];
[_markdownUtils applyMarkdownFormatting:attributedText withDefaultTextAttributes:_textField.defaultTextAttributes];

UITextRange *textRange = _textField.selectedTextRange;

_active = NO; // prevent recursion
_textField.attributedText = attributedText;
_active = YES;
Comment thread
tomekzaw marked this conversation as resolved.

// Restore cursor position
[_textField setSelectedTextRange:textRange notifyDelegate:NO];

// Eliminate underline blinks while typing if previous text ends with a link
_textField.typingAttributes = _textField.defaultTextAttributes;
}

@end
93 changes: 64 additions & 29 deletions apple/MarkdownTextInputDecoratorComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
#import <react/renderer/components/RNLiveMarkdownSpec/Props.h>
#import <React/RCTFabricComponentsPlugins.h>
#import <React/RCTUITextField.h>
#import <React/RCTUITextView.h>
#import <React/RCTTextInputComponentView.h>

#import <RNLiveMarkdown/MarkdownBackedTextInputDelegate.h>
#import <RNLiveMarkdown/MarkdownLayoutManager.h>
#import <RNLiveMarkdown/MarkdownTextFieldObserver.h>
#import <RNLiveMarkdown/MarkdownTextViewObserver.h>
#import <RNLiveMarkdown/MarkdownTextInputDecoratorComponentView.h>
#import <RNLiveMarkdown/MarkdownTextInputDecoratorViewComponentDescriptor.h>
#import <RNLiveMarkdown/RCTBackedTextFieldDelegateAdapter+Markdown.h>
#import <RNLiveMarkdown/MarkdownTextStorageDelegate.h>
#import <RNLiveMarkdown/RCTMarkdownStyle.h>
#import <RNLiveMarkdown/RCTTextInputComponentView+Markdown.h>
#import <RNLiveMarkdown/RCTUITextView+Markdown.h>

#import <objc/runtime.h>

Expand All @@ -21,10 +23,11 @@ @implementation MarkdownTextInputDecoratorComponentView {
RCTMarkdownStyle *_markdownStyle;
NSNumber *_parserId;
MarkdownBackedTextInputDelegate *_markdownBackedTextInputDelegate;
__weak RCTTextInputComponentView *_textInput;
__weak UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
__weak RCTBackedTextFieldDelegateAdapter *_adapter;
MarkdownTextStorageDelegate *_markdownTextStorageDelegate;
MarkdownTextViewObserver *_markdownTextViewObserver;
MarkdownTextFieldObserver *_markdownTextFieldObserver;
__weak RCTUITextView *_textView;
__weak RCTUITextField *_textField;
}

+ (ComponentDescriptorProvider)componentDescriptorProvider
Expand All @@ -51,21 +54,47 @@ - (instancetype)initWithFrame:(CGRect)frame
- (void)didAddSubview:(UIView *)subview
{
react_native_assert([subview isKindOfClass:[RCTTextInputComponentView class]] && "Child component of MarkdownTextInputDecoratorComponentView is not an instance of RCTTextInputComponentView.");
_textInput = (RCTTextInputComponentView *)subview;
_backedTextInputView = [_textInput valueForKey:@"_backedTextInputView"];
RCTTextInputComponentView *textInputComponentView = (RCTTextInputComponentView *)subview;
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = [textInputComponentView valueForKey:@"_backedTextInputView"];

_markdownUtils = [[RCTMarkdownUtils alloc] init];
[_markdownUtils setMarkdownStyle:_markdownStyle];
[_markdownUtils setParserId:_parserId];

[_textInput setMarkdownUtils:_markdownUtils];
if ([_backedTextInputView isKindOfClass:[RCTUITextField class]]) {
RCTUITextField *textField = (RCTUITextField *)_backedTextInputView;
_adapter = [textField valueForKey:@"textInputDelegateAdapter"];
[_adapter setMarkdownUtils:_markdownUtils];
} else if ([_backedTextInputView isKindOfClass:[RCTUITextView class]]) {
_textView = (RCTUITextView *)_backedTextInputView;
[_textView setMarkdownUtils:_markdownUtils];
if ([backedTextInputView isKindOfClass:[RCTUITextField class]]) {
_textField = (RCTUITextField *)backedTextInputView;

// make sure `adjustsFontSizeToFitWidth` is disabled, otherwise formatting will be overwritten
react_native_assert(_textField.adjustsFontSizeToFitWidth == NO);

_markdownTextFieldObserver = [[MarkdownTextFieldObserver alloc] initWithTextField:_textField markdownUtils:_markdownUtils];

// register observers for future edits
[_textField addTarget:_markdownTextFieldObserver action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
[_textField addTarget:_markdownTextFieldObserver action:@selector(textFieldDidEndEditing:) forControlEvents:UIControlEventEditingDidEnd];
[_textField addObserver:_markdownTextFieldObserver forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:NULL];
[_textField addObserver:_markdownTextFieldObserver forKeyPath:@"attributedText" options:NSKeyValueObservingOptionNew context:NULL];

// format initial value
[_markdownTextFieldObserver textFieldDidChange:_textField];

// TODO: register blockquotes layout manager
// https://github.com/Expensify/react-native-live-markdown/issues/87
} else if ([backedTextInputView isKindOfClass:[RCTUITextView class]]) {
_textView = (RCTUITextView *)backedTextInputView;

// register delegate for future edits
react_native_assert(_textView.textStorage.delegate == nil);
_markdownTextStorageDelegate = [[MarkdownTextStorageDelegate alloc] initWithTextView:_textView markdownUtils:_markdownUtils];
_textView.textStorage.delegate = _markdownTextStorageDelegate;

// register observer for default text attributes
_markdownTextViewObserver = [[MarkdownTextViewObserver alloc] initWithTextView:_textView markdownUtils:_markdownUtils];
[_textView addObserver:_markdownTextViewObserver forKeyPath:@"defaultTextAttributes" options:NSKeyValueObservingOptionNew context:NULL];

// format initial value
[_textView.textStorage setAttributedString:_textView.attributedText];

NSLayoutManager *layoutManager = _textView.layoutManager; // switching to TextKit 1 compatibility mode

// Correct content height in TextKit 1 compatibility mode. (See https://github.com/Expensify/App/issues/41567)
Expand All @@ -91,19 +120,26 @@ - (void)willMoveToWindow:(UIWindow *)newWindow
if (newWindow != nil) {
return;
}
if (_textInput != nil) {
[_textInput setMarkdownUtils:nil];
}
if (_adapter != nil) {
[_adapter setMarkdownUtils:nil];
}
if (_textView != nil) {
_markdownBackedTextInputDelegate = nil;
[_textView setMarkdownUtils:nil];
if (_textView.layoutManager != nil && [object_getClass(_textView.layoutManager) isEqual:[MarkdownLayoutManager class]]) {
[_textView.layoutManager setValue:nil forKey:@"markdownUtils"];
object_setClass(_textView.layoutManager, [NSLayoutManager class]);
}
_markdownBackedTextInputDelegate = nil;
[_textView removeObserver:_markdownTextViewObserver forKeyPath:@"defaultTextAttributes" context:NULL];
_markdownTextViewObserver = nil;
_markdownTextStorageDelegate = nil;
_textView.textStorage.delegate = nil;
_textView = nil;
}

if (_textField != nil) {
[_textField removeTarget:_markdownTextFieldObserver action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
[_textField removeTarget:_markdownTextFieldObserver action:@selector(textFieldDidEndEditing:) forControlEvents:UIControlEventEditingDidEnd];
[_textField removeObserver:_markdownTextFieldObserver forKeyPath:@"text" context:NULL];
[_textField removeObserver:_markdownTextFieldObserver forKeyPath:@"attributedText" context:NULL];
_markdownTextFieldObserver = nil;
_textField = nil;
}
}

Expand All @@ -130,11 +166,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
- (void)applyNewStyles
{
if (_textView != nil) {
// We want to use `textStorage` for applying markdown when possible. Currently it's only available for UITextView
[_textView textDidChange];
} else {
// apply new styles
[_textInput _setAttributedString:_backedTextInputView.attributedText];
[_textView.textStorage setAttributedString:_textView.attributedText];
}
if (_textField != nil) {
[_markdownTextFieldObserver textFieldDidChange:_textField];
}
}

Expand Down
8 changes: 4 additions & 4 deletions apple/MarkdownTextInputDecoratorShadowNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@
}

// apply markdown
auto newString = [utils parseMarkdown:nsAttributedString
withDefaultTextAttributes:defaultNSTextAttributes];
NSMutableAttributedString *newString = [nsAttributedString mutableCopy];
[utils applyMarkdownFormatting:newString withDefaultTextAttributes:defaultNSTextAttributes];

// create a clone of the old TextInputState and update the
// attributed string box to point to the string with markdown
Expand All @@ -200,8 +200,8 @@
AttributedStringBox::Mode::OpaquePointer) {

// apply markdown
auto newString = [utils parseMarkdown:nsAttributedString
withDefaultTextAttributes:defaultNSTextAttributes];
NSMutableAttributedString *newString = [nsAttributedString mutableCopy];
[utils applyMarkdownFormatting:newString withDefaultTextAttributes:defaultNSTextAttributes];

// create a clone of the old TextInputState and update the
// attributed string box to point to the string with markdown
Expand Down
13 changes: 13 additions & 0 deletions apple/MarkdownTextStorageDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <UIKit/UIKit.h>
#import <React/RCTUITextView.h>
#import <RNLiveMarkdown/RCTMarkdownUtils.h>

NS_ASSUME_NONNULL_BEGIN

@interface MarkdownTextStorageDelegate : NSObject <NSTextStorageDelegate>

- (instancetype)initWithTextView:(nonnull RCTUITextView *)textView markdownUtils:(nonnull RCTMarkdownUtils *)markdownUtils;

@end

NS_ASSUME_NONNULL_END
27 changes: 27 additions & 0 deletions apple/MarkdownTextStorageDelegate.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#import <RNLiveMarkdown/MarkdownTextStorageDelegate.h>
#import "react_native_assert.h"

@implementation MarkdownTextStorageDelegate {
RCTUITextView *_textView;
RCTMarkdownUtils *_markdownUtils;
}

- (instancetype)initWithTextView:(nonnull RCTUITextView *)textView markdownUtils:(nonnull RCTMarkdownUtils *)markdownUtils
{
if ((self = [super init])) {
react_native_assert(textView != nil);
react_native_assert(markdownUtils != nil);

_textView = textView;
_markdownUtils = markdownUtils;
}
return self;
}

- (void)textStorage:(NSTextStorage *)textStorage didProcessEditing:(NSTextStorageEditActions)editedMask range:(NSRange)editedRange changeInLength:(NSInteger)delta {
react_native_assert(_textView.defaultTextAttributes != nil);

[_markdownUtils applyMarkdownFormatting:textStorage withDefaultTextAttributes:_textView.defaultTextAttributes];
}

@end
13 changes: 13 additions & 0 deletions apple/MarkdownTextViewObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <UIKit/UIKit.h>
#import <React/RCTUITextView.h>
#import <RNLiveMarkdown/RCTMarkdownUtils.h>

NS_ASSUME_NONNULL_BEGIN

@interface MarkdownTextViewObserver : NSObject

- (instancetype)initWithTextView:(nonnull RCTUITextView *)textView markdownUtils:(nonnull RCTMarkdownUtils *)markdownUtils;

@end

NS_ASSUME_NONNULL_END
Loading
Loading