2019-12-11 14:14:20 +08:00
|
|
|
/*
|
|
|
|
* Copyright [2019] [Doric.Pub]
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
//
|
|
|
|
// DoricInputNode.m
|
|
|
|
// Doric
|
|
|
|
//
|
|
|
|
// Created by 姜腾 on 2019/12/11.
|
|
|
|
//
|
|
|
|
|
2021-06-11 17:47:06 +08:00
|
|
|
#import <JavaScriptCore/JavaScriptCore.h>
|
2019-12-11 14:14:20 +08:00
|
|
|
#import "DoricInputNode.h"
|
|
|
|
#import "DoricUtil.h"
|
|
|
|
#import "DoricPromise.h"
|
|
|
|
|
2020-06-13 11:53:21 +08:00
|
|
|
typedef void (^onTextChangeBlock)(NSString *text, DoricInputNode *node);
|
|
|
|
|
|
|
|
typedef void (^onFocusChangeBlock)(BOOL focused, DoricInputNode *node);
|
2019-12-11 14:14:20 +08:00
|
|
|
|
2021-06-11 15:17:20 +08:00
|
|
|
typedef void (^onSubmitEditingBlock)(NSString *text, DoricInputNode *node);
|
|
|
|
|
2020-05-06 15:11:57 +08:00
|
|
|
@implementation DoricInputView
|
|
|
|
|
|
|
|
- (instancetype)init {
|
|
|
|
if (self = [super init]) {
|
|
|
|
self.font = [UIFont systemFontOfSize:12];
|
|
|
|
_placeholderLabel = [UILabel new];
|
|
|
|
_placeholderLabel.numberOfLines = 0;
|
|
|
|
_placeholderLabel.textColor = [UIColor grayColor];
|
|
|
|
_placeholderLabel.userInteractionEnabled = NO;
|
|
|
|
_placeholderLabel.font = self.font;
|
|
|
|
[self insertSubview:_placeholderLabel atIndex:0];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)layoutSubviews {
|
|
|
|
[super layoutSubviews];
|
|
|
|
self.placeholderLabel.hidden = self.text.length > 0;
|
|
|
|
if (self.placeholderLabel.hidden) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
CGFloat lineFragmentPadding = self.textContainer.lineFragmentPadding;
|
|
|
|
UIEdgeInsets textContainerInset = self.textContainerInset;
|
|
|
|
self.placeholderLabel.x = lineFragmentPadding + textContainerInset.left;
|
|
|
|
self.placeholderLabel.y = textContainerInset.top;
|
|
|
|
self.placeholderLabel.width = self.width - lineFragmentPadding * 2 - textContainerInset.left - textContainerInset.right;
|
|
|
|
[self.placeholderLabel sizeToFit];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (CGSize)sizeThatFits:(CGSize)size {
|
|
|
|
if (self.text.length > 0) {
|
|
|
|
CGSize ret = [super sizeThatFits:size];
|
|
|
|
return CGSizeMake(ret.width - self.doricLayout.paddingLeft - self.doricLayout.paddingRight, ret.height - self.doricLayout.paddingTop - self.doricLayout.paddingBottom);
|
|
|
|
} else {
|
|
|
|
CGSize ret = [self.placeholderLabel sizeThatFits:size];
|
|
|
|
return CGSizeMake(ret.width, ret.height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2020-06-13 11:53:21 +08:00
|
|
|
@interface DoricInputNode () <UITextViewDelegate>
|
2019-12-11 14:14:20 +08:00
|
|
|
@property(nonatomic, copy) onTextChangeBlock onTextChange;
|
2021-06-11 15:17:20 +08:00
|
|
|
@property(nonatomic, copy) onFocusChangeBlock onFocusChange;
|
|
|
|
@property(nonatomic, copy) onSubmitEditingBlock onSubmitEditing;
|
2020-04-30 18:31:26 +08:00
|
|
|
@property(nonatomic, strong) NSNumber *maxLength;
|
2021-06-11 17:47:06 +08:00
|
|
|
@property(nonatomic, copy) NSString *beforeTextChangeFuncId;
|
2019-12-11 14:14:20 +08:00
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation DoricInputNode
|
2020-05-06 15:11:57 +08:00
|
|
|
- (DoricInputView *)build {
|
|
|
|
DoricInputView *v = [[DoricInputView alloc] init];
|
2019-12-11 14:14:20 +08:00
|
|
|
v.delegate = self;
|
2020-05-06 15:11:57 +08:00
|
|
|
v.textContainer.lineFragmentPadding = 0;
|
|
|
|
v.doricLayout.paddingTop = v.textContainerInset.top;
|
|
|
|
v.doricLayout.paddingBottom = v.textContainerInset.bottom;
|
|
|
|
v.doricLayout.paddingLeft = v.textContainerInset.left;
|
|
|
|
v.doricLayout.paddingRight = v.textContainerInset.right;
|
2019-12-11 14:14:20 +08:00
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2020-05-06 15:11:57 +08:00
|
|
|
- (void)blendView:(DoricInputView *)view forPropName:(NSString *)name propValue:(id)prop {
|
2019-12-11 14:14:20 +08:00
|
|
|
if ([name isEqualToString:@"text"]) {
|
|
|
|
view.text = prop;
|
|
|
|
} else if ([name isEqualToString:@"textSize"]) {
|
|
|
|
view.font = [UIFont systemFontOfSize:[(NSNumber *) prop floatValue]];
|
|
|
|
} else if ([name isEqualToString:@"textColor"]) {
|
|
|
|
view.textColor = DoricColor(prop);
|
|
|
|
} else if ([name isEqualToString:@"textAlignment"]) {
|
|
|
|
DoricGravity gravity = (DoricGravity) [(NSNumber *) prop integerValue];
|
|
|
|
NSTextAlignment alignment = NSTextAlignmentCenter;
|
2020-03-27 10:07:53 +08:00
|
|
|
if ((gravity & DoricGravityLeft) == DoricGravityLeft) {
|
2019-12-11 14:14:20 +08:00
|
|
|
alignment = NSTextAlignmentLeft;
|
2020-03-27 10:07:53 +08:00
|
|
|
} else if ((gravity & DoricGravityRight) == DoricGravityRight) {
|
2019-12-11 14:14:20 +08:00
|
|
|
alignment = NSTextAlignmentRight;
|
|
|
|
}
|
|
|
|
view.textAlignment = alignment;
|
|
|
|
} else if ([name isEqualToString:@"multiline"]) {
|
2021-02-08 18:18:37 +08:00
|
|
|
BOOL value = [(NSNumber *) prop boolValue];
|
|
|
|
if (!value) {
|
2019-12-11 14:14:20 +08:00
|
|
|
view.textContainer.maximumNumberOfLines = 1;
|
2021-06-11 15:17:20 +08:00
|
|
|
if (view.text.length > 0) {
|
|
|
|
view.text = [view.text stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
|
|
|
|
}
|
2020-06-13 11:53:21 +08:00
|
|
|
} else {
|
2019-12-11 14:14:20 +08:00
|
|
|
view.textContainer.maximumNumberOfLines = 0;
|
|
|
|
}
|
2021-06-11 17:47:06 +08:00
|
|
|
} else if ([name isEqualToString:@"beforeTextChange"]) {
|
|
|
|
self.beforeTextChangeFuncId = prop;
|
2019-12-11 14:14:20 +08:00
|
|
|
} else if ([name isEqualToString:@"hintText"]) {
|
2020-06-13 11:53:21 +08:00
|
|
|
view.placeholderLabel.text = (NSString *) prop;
|
2019-12-11 14:14:20 +08:00
|
|
|
} else if ([name isEqualToString:@"hintTextColor"]) {
|
2020-05-06 15:11:57 +08:00
|
|
|
view.placeholderLabel.textColor = DoricColor(prop);
|
2019-12-11 14:14:20 +08:00
|
|
|
} else if ([name isEqualToString:@"onTextChange"]) {
|
|
|
|
if ([prop isKindOfClass:[NSString class]]) {
|
|
|
|
self.onTextChange = ^(NSString *text, DoricInputNode *node) {
|
2020-06-13 11:53:21 +08:00
|
|
|
[node callJSResponse:prop, text, nil];
|
2019-12-11 14:14:20 +08:00
|
|
|
};
|
2020-06-13 11:53:21 +08:00
|
|
|
} else {
|
2019-12-11 14:14:20 +08:00
|
|
|
self.onTextChange = nil;
|
|
|
|
}
|
|
|
|
} else if ([name isEqualToString:@"onFocusChange"]) {
|
|
|
|
if ([prop isKindOfClass:[NSString class]]) {
|
2021-06-11 15:17:20 +08:00
|
|
|
self.onFocusChange = ^(BOOL focused, DoricInputNode *node) {
|
2020-06-13 11:53:21 +08:00
|
|
|
[node callJSResponse:prop, @(focused), nil];
|
2019-12-11 14:14:20 +08:00
|
|
|
};
|
2020-06-13 11:53:21 +08:00
|
|
|
} else {
|
2021-06-11 15:17:20 +08:00
|
|
|
self.onFocusChange = nil;
|
2019-12-11 14:14:20 +08:00
|
|
|
}
|
|
|
|
|
2020-04-30 18:31:26 +08:00
|
|
|
} else if ([name isEqualToString:@"maxLength"]) {
|
|
|
|
self.maxLength = prop;
|
2020-06-13 11:53:21 +08:00
|
|
|
} else if ([name isEqualToString:@"inputType"]) {
|
2020-06-12 16:33:16 +08:00
|
|
|
switch ([prop integerValue]) {
|
|
|
|
case 1: {
|
2020-06-13 11:53:21 +08:00
|
|
|
[self.view setKeyboardType:UIKeyboardTypeNumberPad];
|
2020-06-12 16:33:16 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 2: {
|
2020-06-13 11:53:21 +08:00
|
|
|
[self.view setKeyboardType:UIKeyboardTypeDecimalPad];
|
2020-06-12 16:33:16 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 3: {
|
2020-06-13 11:53:21 +08:00
|
|
|
[self.view setKeyboardType:UIKeyboardTypeAlphabet];
|
2020-06-12 16:33:16 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 4: {
|
2020-06-13 11:53:21 +08:00
|
|
|
[self.view setKeyboardType:UIKeyboardTypePhonePad];
|
2020-06-12 16:33:16 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
2020-06-13 11:53:21 +08:00
|
|
|
[self.view setKeyboardType:UIKeyboardTypeDefault];
|
2020-06-12 16:33:16 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-02-08 18:18:37 +08:00
|
|
|
} else if ([name isEqualToString:@"password"]) {
|
2021-06-11 10:35:41 +08:00
|
|
|
view.secureTextEntry = [(NSNumber *) prop boolValue];
|
|
|
|
} else if ([name isEqualToString:@"editable"]) {
|
|
|
|
view.editable = [(NSNumber *) prop boolValue];
|
|
|
|
} else if ([name isEqualToString:@"returnKeyType"]) {
|
2021-06-11 15:17:20 +08:00
|
|
|
if (view.textContainer.maximumNumberOfLines == 1) {
|
|
|
|
return;
|
|
|
|
}
|
2021-06-11 10:35:41 +08:00
|
|
|
switch ([(NSNumber *) prop integerValue]) {
|
|
|
|
case 1:
|
|
|
|
view.returnKeyType = UIReturnKeyDone;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
view.returnKeyType = UIReturnKeySearch;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
view.returnKeyType = UIReturnKeyNext;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
view.returnKeyType = UIReturnKeyGo;
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
view.returnKeyType = UIReturnKeySend;
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
default:
|
|
|
|
view.returnKeyType = UIReturnKeyDefault;
|
|
|
|
break;
|
|
|
|
}
|
2021-06-11 15:17:20 +08:00
|
|
|
} else if ([name isEqualToString:@"onSubmitEditing"]) {
|
|
|
|
if ([prop isKindOfClass:[NSString class]]) {
|
|
|
|
self.onSubmitEditing = ^(NSString *text, DoricInputNode *node) {
|
|
|
|
[node callJSResponse:prop, text, nil];
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
self.onSubmitEditing = nil;
|
|
|
|
}
|
2020-06-13 11:53:21 +08:00
|
|
|
} else {
|
2019-12-11 14:14:20 +08:00
|
|
|
[super blendView:view forPropName:name propValue:prop];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)blend:(NSDictionary *)props {
|
|
|
|
[super blend:props];
|
2020-04-30 18:43:57 +08:00
|
|
|
}
|
2020-06-13 11:53:21 +08:00
|
|
|
|
2020-05-06 15:11:57 +08:00
|
|
|
- (void)afterBlended:(NSDictionary *)props {
|
|
|
|
[super afterBlended:props];
|
|
|
|
if (self.view.doricLayout.paddingTop != self.view.textContainerInset.top
|
2020-06-13 11:53:21 +08:00
|
|
|
|| self.view.doricLayout.paddingLeft != self.view.textContainerInset.left
|
|
|
|
|| self.view.doricLayout.paddingBottom != self.view.textContainerInset.bottom
|
|
|
|
|| self.view.doricLayout.paddingRight != self.view.textContainerInset.right) {
|
2020-05-06 15:11:57 +08:00
|
|
|
self.view.textContainerInset = UIEdgeInsetsMake(self.view.doricLayout.paddingTop, self.view.doricLayout.paddingLeft, self.view.doricLayout.paddingBottom, self.view.doricLayout.paddingRight);
|
|
|
|
}
|
|
|
|
self.view.placeholderLabel.font = self.view.font;
|
|
|
|
self.view.placeholderLabel.numberOfLines = self.view.textContainer.maximumNumberOfLines;
|
|
|
|
}
|
2020-04-30 18:43:57 +08:00
|
|
|
|
|
|
|
- (void)requestLayout {
|
|
|
|
[super requestLayout];
|
2020-05-06 15:11:57 +08:00
|
|
|
[self.view setNeedsLayout];
|
2019-12-11 14:14:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Doric-JS api
|
2020-06-13 11:53:21 +08:00
|
|
|
|
2019-12-11 14:14:20 +08:00
|
|
|
- (NSString *)getText {
|
|
|
|
return self.view.text;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setSelection:(NSDictionary *)params withPromise:(DoricPromise *)promise {
|
2019-12-11 14:29:29 +08:00
|
|
|
NSString *start = params[@"start"];
|
|
|
|
NSString *end = params[@"end"];
|
2020-06-13 11:53:21 +08:00
|
|
|
|
2019-12-11 14:14:20 +08:00
|
|
|
if (([start isKindOfClass:[NSString class]] || [start isKindOfClass:[NSNumber class]]) &&
|
2020-06-13 11:53:21 +08:00
|
|
|
([start isKindOfClass:[NSString class]] || [start isKindOfClass:[NSNumber class]])) {
|
2019-12-11 14:14:20 +08:00
|
|
|
self.view.selectedRange = NSMakeRange(start.intValue, end.intValue - start.intValue);
|
|
|
|
}
|
2020-06-13 11:53:21 +08:00
|
|
|
|
2019-12-11 14:14:20 +08:00
|
|
|
[promise resolve:nil];
|
|
|
|
}
|
|
|
|
|
2021-06-11 17:40:02 +08:00
|
|
|
- (NSDictionary *)getSelection {
|
|
|
|
return @{
|
|
|
|
@"start": @([self.view offsetFromPosition:self.view.beginningOfDocument toPosition:self.view.selectedTextRange.start]),
|
|
|
|
@"end": @([self.view offsetFromPosition:self.view.beginningOfDocument toPosition:self.view.selectedTextRange.end]),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-12-11 14:14:20 +08:00
|
|
|
- (void)requestFocus {
|
|
|
|
[self.view becomeFirstResponder];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)releaseFocus {
|
|
|
|
[self.view resignFirstResponder];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - UITextViewDelegate
|
2020-06-13 11:53:21 +08:00
|
|
|
|
2019-12-11 14:14:20 +08:00
|
|
|
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
|
2021-06-11 15:17:20 +08:00
|
|
|
if (self.onFocusChange) {
|
|
|
|
self.onFocusChange(YES, self);
|
2019-12-11 14:14:20 +08:00
|
|
|
}
|
|
|
|
return YES;
|
|
|
|
}
|
2020-06-13 11:53:21 +08:00
|
|
|
|
2019-12-11 14:14:20 +08:00
|
|
|
- (BOOL)textViewShouldEndEditing:(UITextView *)textView {
|
2021-06-11 15:17:20 +08:00
|
|
|
if (self.onFocusChange) {
|
|
|
|
self.onFocusChange(NO, self);
|
|
|
|
}
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
|
|
|
|
if ([text isEqualToString:@"\n"]) {
|
|
|
|
if (textView.textContainer.maximumNumberOfLines == 1) {
|
|
|
|
if (self.onSubmitEditing) {
|
|
|
|
self.onSubmitEditing(textView.text, self);
|
|
|
|
}
|
|
|
|
return NO;
|
|
|
|
}
|
2019-12-11 14:14:20 +08:00
|
|
|
}
|
2021-06-11 17:47:06 +08:00
|
|
|
|
|
|
|
if (self.beforeTextChangeFuncId) {
|
|
|
|
DoricAsyncResult *asyncResult = [self
|
|
|
|
pureCallJSResponse:self.beforeTextChangeFuncId,
|
|
|
|
@{
|
|
|
|
@"editing": textView.text,
|
|
|
|
@"start": @(range.location),
|
|
|
|
@"length": @(range.length),
|
|
|
|
@"replacement": text,
|
|
|
|
},
|
|
|
|
nil];
|
|
|
|
NSNumber *ret = [asyncResult waitUntilResult:^(JSValue *model) {
|
|
|
|
return [model toNumber];
|
|
|
|
}];
|
|
|
|
return [ret boolValue];
|
|
|
|
}
|
2019-12-11 14:14:20 +08:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)textViewDidChange:(UITextView *)textView {
|
2021-06-18 13:48:44 +08:00
|
|
|
if (textView.markedTextRange || textView.text.length > 0) {
|
|
|
|
self.view.placeholderLabel.hidden = YES;
|
|
|
|
} else {
|
|
|
|
self.view.placeholderLabel.hidden = NO;
|
|
|
|
}
|
|
|
|
|
2021-06-07 17:14:55 +08:00
|
|
|
if (textView.markedTextRange) return;
|
2021-06-08 18:20:37 +08:00
|
|
|
|
2020-04-30 18:31:26 +08:00
|
|
|
if (self.maxLength) {
|
2021-06-08 18:20:37 +08:00
|
|
|
textView.text = [self limitToHansMaxLength:self.maxLength.unsignedIntValue text:textView.text];
|
2020-04-30 18:31:26 +08:00
|
|
|
}
|
2019-12-11 14:14:20 +08:00
|
|
|
if (self.onTextChange) {
|
|
|
|
self.onTextChange(textView.text, self);
|
|
|
|
}
|
2020-05-06 15:11:57 +08:00
|
|
|
[textView setNeedsLayout];
|
2019-12-11 14:14:20 +08:00
|
|
|
}
|
2020-05-09 16:21:43 +08:00
|
|
|
|
|
|
|
|
2020-06-13 11:53:21 +08:00
|
|
|
- (NSString *)limitToHansMaxLength:(NSUInteger)maxLen text:(NSString *)text {
|
2020-05-09 16:21:43 +08:00
|
|
|
NSUInteger asciiMaxLen = 2 * maxLen;
|
|
|
|
__block NSUInteger asciiLen = 0;
|
|
|
|
__block NSUInteger subStringRangeLen = 0;
|
|
|
|
[text enumerateSubstringsInRange:NSMakeRange(0, text.length)
|
|
|
|
options:NSStringEnumerationByComposedCharacterSequences
|
|
|
|
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
|
2020-06-13 11:53:21 +08:00
|
|
|
if ([substring canBeConvertedToEncoding:NSASCIIStringEncoding]) {
|
|
|
|
asciiLen += 2;
|
|
|
|
} else {
|
|
|
|
asciiLen += [substring lengthOfBytesUsingEncoding:NSUTF16StringEncoding];
|
|
|
|
}
|
|
|
|
if (asciiLen <= asciiMaxLen) {
|
|
|
|
subStringRangeLen = substringRange.location + substringRange.length;
|
|
|
|
} else {
|
|
|
|
*stop = YES;
|
|
|
|
}
|
|
|
|
}];
|
2020-05-09 16:21:43 +08:00
|
|
|
return [text substringWithRange:NSMakeRange(0, subStringRangeLen)];
|
|
|
|
}
|
2019-12-11 14:14:20 +08:00
|
|
|
@end
|