用YYTextView 實現(xiàn)填空題作答功能

整理了一份Demo旷祸,因為每個項目具體的需求不一樣椰棘,我只把基本的功能整理出來了
Demo放在GitHub上
項目中要實現(xiàn)填空題的作答功能椅亚,比如詩詞填空:床前明月光恢恼,___________。舉頭望明月弓熏,________恋谭。要求只能編輯橫線部分。
首先想到的是強大的YYKit挽鞠,先在網(wǎng)上找了找疚颊,發(fā)現(xiàn)有一種方案是用label 加textfield的這種富文本編輯的方式實現(xiàn)的,雖然大體符合需求信认,但是排版會比較難看材义。
最后決定用YYTextView去實現(xiàn),原理就是根據(jù)正則匹配題干和下劃線嫁赏,整個題目會被填空部分分割成幾塊其掂,把各個分塊binding,然后控制光標位置潦蝇,只讓光標落在下劃線上款熬。

創(chuàng)建題目:

   NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@"#(填空題)枯藤老樹昏鴉,#      #,古道西風(fēng)瘦馬深寥。#      #,斷腸人在天涯。# "];
   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里面加了個代理方法贤牛,在我這里實現(xiàn)一下:

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

YYTextEditBindingParser這個類用于binding和對輸入內(nèi)容加下劃線,有個屬性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殉簸;

好啦闰集,就這樣吧!第一次發(fā)文章??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末般卑,一起剝皮案震驚了整個濱河市武鲁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蝠检,老刑警劉巖沐鼠,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蝇率,居然都是意外死亡,警方通過查閱死者的電腦和手機刽沾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門本慕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侧漓,你說我怎么就攤上這事锅尘。” “怎么了布蔗?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵藤违,是天一觀的道長。 經(jīng)常有香客問我纵揍,道長顿乒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任泽谨,我火速辦了婚禮璧榄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吧雹。我一直安慰自己骨杂,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布雄卷。 她就那樣靜靜地躺著搓蚪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丁鹉。 梳的紋絲不亂的頭發(fā)上妒潭,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天悴能,我揣著相機與錄音,去河邊找鬼杜耙。 笑死搜骡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的佑女。 我是一名探鬼主播记靡,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼团驱!你這毒婦竟也來了摸吠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嚎花,失蹤者是張志新(化名)和其女友劉穎寸痢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體紊选,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡啼止,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兵罢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片献烦。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卖词,靈堂內(nèi)的尸體忽然破棺而出巩那,到底是詐尸還是另有隱情,我是刑警寧澤此蜈,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布即横,位于F島的核電站,受9級特大地震影響裆赵,放射性物質(zhì)發(fā)生泄漏东囚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一战授、第九天 我趴在偏房一處隱蔽的房頂上張望舔庶。 院中可真熱鬧,春花似錦陈醒、人聲如沸惕橙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弥鹦。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間彬坏,已是汗流浹背朦促。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留栓始,地道東北人务冕。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像幻赚,于是被迫代替她去往敵國和親禀忆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容