美文网首页
用YYTextView 实现填空题作答功能

用YYTextView 实现填空题作答功能

作者: i叶舞飘零 | 来源:发表于2018-08-29 16:40 被阅读415次

    整理了一份Demo,因为每个项目具体的需求不一样,我只把基本的功能整理出来了
    Demo放在GitHub上
    项目中要实现填空题的作答功能,比如诗词填空:床前明月光,___________。举头望明月,________。要求只能编辑横线部分。
    首先想到的是强大的YYKit,先在网上找了找,发现有一种方案是用label 加textfield的这种富文本编辑的方式实现的,虽然大体符合需求,但是排版会比较难看。
    最后决定用YYTextView去实现,原理就是根据正则匹配题干和下划线,整个题目会被填空部分分割成几块,把各个分块binding,然后控制光标位置,只让光标落在下划线上。

    创建题目:

       NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@"#(填空题)枯藤老树昏鸦,#      #,古道西风瘦马。#      #,断肠人在天涯。# "];
       text.yy_font = [UIFont systemFontOfSize:17];
       text.yy_lineSpacing = 5;
       text.yy_color = [UIColor blackColor];
       YYTextView *textView = [YYTextView new];
       textView.textParser = [YYTextEditBindingParser new];
       textView.attributedText = text;
       textView.frame = CGRectMake(5, 100, CGRectGetWidth(self.view.frame)-10, 200);
       textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
       textView.delegate = self;
       if (kiOS7Later) {
           textView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
       }
       textView.scrollIndicatorInsets = textView.contentInset;
       [self.view addSubview:textView];
       self.textView = textView;
    

    在代理方法里面控制光标位置

    #pragma mark YYTextViewDelegate
    - (BOOL)textViewShouldBeginEditing:(YYTextView *)textView{
        
        if (textView.selectedRange.location==0 || textView.selectedRange.location >= textView.text.length) {
            return NO;
        }else{
            return [self controllCursorRangeForTextView:textView];
        }
    }
    - (void)textViewDidChangeSelection:(YYTextView *)textView{
        if (textView.selectedRange.location==0 || textView.selectedRange.location >= textView.text.length) {
            [textView endEditing:YES];
        }else {
            [self controllCursorRangeForTextView:textView];
        }
        
    }
    

    控制光标

    - (BOOL)controllCursorRangeForTextView:(YYTextView *)textView{
        YYTextEditBindingParser *textParser = textView.textParser;
        for (NSString *rangeStr in textParser.gapRangeArr) {
            NSRange range = NSRangeFromString(rangeStr);
            if (textView.selectedRange.location >= range.location && textView.selectedRange.location < range.location + 3) {
                textView.selectedRange = NSMakeRange(range.location + 3,0);
                return YES;
            }else if(textView.selectedRange.location > (range.location + range.length -3)&&textView.selectedRange.location <= (range.location + range.length)){
                textView.selectedRange = NSMakeRange(range.location + range.length - 3,0);
                return YES;
            }
        }
        return YES;
    }
    

    为了不让删除binding的字符串我在YYtextView里面加了个代理方法,在我这里实现一下:

    - (BOOL)textViewShouldDeleteBinding:(YYTextView *)textView{
        return NO;
    }
    

    YYTextEditBindingParser这个类用于binding和对输入内容加下划线,有个属性gapRangeArr用来保存填空部分的range

    @interface YYTextEditBindingParser :NSObject <YYTextParser>
    @property (nonatomic, strong) NSRegularExpression *regex;
    @property (nonatomic, strong) NSRegularExpression *gapRegex;
    @property(nonatomic, strong)NSArray <NSString *>*gapRangeArr;
    
    @end
    
    @implementation YYTextEditBindingParser
    
    - (instancetype)init {
        self = [super init];
        NSString *pattern1 = @"#([^#]*)#";
        self.regex = [[NSRegularExpression alloc] initWithPattern:pattern1 options:kNilOptions error:nil];
        NSString *pattern2 = @"\\s{3}([^\\s{3}]*)\\s{3}";
        self.gapRegex = [[NSRegularExpression alloc] initWithPattern:pattern2 options:kNilOptions error:nil];
        return self;
    }
    - (NSRange)_replaceTextInRange:(NSRange)range withLength:(NSUInteger)length selectedRange:(NSRange)selectedRange {
        // no change
        if (range.length == length) return selectedRange;
        // right
        if (range.location >= selectedRange.location + selectedRange.length) return selectedRange;
        // left
        if (selectedRange.location >= range.location + range.length) {
            selectedRange.location = selectedRange.location + length - range.length;
            return selectedRange;
        }
        // same
        if (NSEqualRanges(range, selectedRange)) {
            selectedRange.length = length;
            return selectedRange;
        }
        // one edge same
        if ((range.location == selectedRange.location && range.length < selectedRange.length) ||
            (range.location + range.length == selectedRange.location + selectedRange.length && range.length < selectedRange.length)) {
            selectedRange.length = selectedRange.length + length - range.length;
            return selectedRange;
        }
        selectedRange.location = range.location + length;
        selectedRange.length = 0;
        return selectedRange;
    }
    - (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range {
        __block BOOL changed = NO;
        NSArray *matches = [_regex matchesInString:text.string options:kNilOptions range:NSMakeRange(0, text.length)];
        NSRange selectedRange = range ? *range : NSMakeRange(0, 0);
        NSUInteger cutLength = 0;
        for (NSUInteger i = 0, max = matches.count; i < max; i++) {
            NSTextCheckingResult *one = matches[i];
            NSRange oneRange = one.range;
            if (oneRange.length == 0) continue;
            oneRange.location -= cutLength;
            NSString *subStr = [text.string substringWithRange:NSMakeRange(oneRange.location+1, oneRange.length-2)];
            CGFloat fontSize = 12; // CoreText default value
            CTFontRef font = (__bridge CTFontRef)([text yy_attribute:NSFontAttributeName atIndex:oneRange.location]);
            if (font) fontSize = CTFontGetSize(font);
            NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:subStr];
            [text replaceCharactersInRange:oneRange withString:atr.string];
            [text yy_removeDiscontinuousAttributesInRange:NSMakeRange(oneRange.location, atr.length)];
            [text addAttributes:atr.yy_attributes range:NSMakeRange(oneRange.location, atr.length)];
            selectedRange = [self _replaceTextInRange:oneRange withLength:atr.length selectedRange:selectedRange];
            NSRange bindlingRange = NSMakeRange(oneRange.location, oneRange.length-2);
            YYTextBinding *binding = [YYTextBinding bindingWithDeleteConfirm:YES];
            [text yy_setTextBinding:binding range:bindlingRange]; /// Text binding
            [text yy_setColor:[UIColor colorWithRed:0.000 green:0.519 blue:1.000 alpha:1.000] range:bindlingRange];
            cutLength += 2;
    
        }
        NSArray *gapMatches = [_gapRegex matchesInString:text.string options:kNilOptions range:NSMakeRange(0, text.length)];
        if (gapMatches.count == 0) return NO;
    
    //    NSRange lastOneRange = NSMakeRange(0, 0);
        NSMutableArray *gapRangeTempArr = [NSMutableArray array];
        for (NSUInteger i = 0, max = gapMatches.count; i < max; i++) {
            NSTextCheckingResult *one = gapMatches[i];
            NSRange oneRange = one.range;
            YYTextDecoration *decoration = [YYTextDecoration new];
            [text yy_setTextUnderline:decoration range:oneRange];
            [gapRangeTempArr addObject:NSStringFromRange(oneRange)];
    
        }
        self.gapRangeArr  = gapRangeTempArr;
        if (range) *range = selectedRange;
        
        return changed;
    }
    
    @end
    

    YYTextView.mdeleteBackward方法里加了一段代码:

    if (binding && binding.deleteConfirm) {
                if ([self.delegate respondsToSelector:@selector(textViewShouldDeleteBinding:)]) {
                    if (![self.delegate textViewShouldDeleteBinding:self]) {
                        return;
                    }
                }
                _state.deleteConfirm = YES;
                [_inputDelegate selectionWillChange:self];
                _selectedTextRange = [YYTextRange rangeWithRange:effectiveRange];
                _selectedTextRange = [self _correctedTextRange:_selectedTextRange];
                [_inputDelegate selectionDidChange:self];
                
                [self _updateOuterProperties];
                [self _updateSelectionView];
                return;
            }
    

    就是在删除的时候,如果是binding的字符串,就return;

    好啦,就这样吧!第一次发文章😄

    相关文章

      网友评论

          本文标题:用YYTextView 实现填空题作答功能

          本文链接:https://www.haomeiwen.com/subject/rtckwftx.html