一個(gè)簡單富文本輸入框控件的設(shè)計(jì)歷程(三)

上一節(jié)遺留了幾個(gè)問題帖世,問題一,如何更新光標(biāo)位置沸枯;問題二日矫,選擇的文本必須在全包含單個(gè)富文本。其實(shí)也就是光標(biāo)位置的變化绑榴。光標(biāo)位置的變化有三種情況:
1.移動(dòng)光標(biāo)哪轿。
2.刪除文本;
3.輸入文本翔怎;

效果圖.png

移動(dòng)光標(biāo)

當(dāng)光標(biāo)移動(dòng)時(shí)窃诉,不能光標(biāo)的位置落在富文本里面杨耙,這個(gè)時(shí)我們需要一個(gè)富文本 range 的數(shù)組。這里需要解釋一下為什么需要 range.length + 1 飘痛,這里是因?yàn)槲以趧?chuàng)建富文本的時(shí)候后面多增加了一個(gè)空格珊膜,我不想那個(gè)空格被刪掉,因此把長度加 1.

- (NSArray *)rangeArray
{
    NSMutableArray *array = [NSMutableArray array];
    
    NSMutableAttributedString *newRichText = [[NSMutableAttributedString alloc] initWithAttributedString:_richImp];
    [newRichText enumerateAttributesInRange:NSMakeRange(0, newRichText.length) options:NSAttributedStringEnumerationReverse usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
        NSString *type = [attrs valueForKey:@"type"];
        NSRange newRange = NSMakeRange(range.location, range.length + 1);
        
        if ([type isEqualToString:TYPE_HASHTAG] || [type isEqualToString:TYPE_USER]) {
            [array addObject:[NSValue valueWithRange:newRange]];
        }
    }];
    
    return array;
}

獲取到 range 數(shù)組后敦冬,我們還需要一個(gè)方法來修正 range辅搬。

// 修正指定范圍的邊緣(可能有某些元素剛好落在指定范圍的邊緣)
- (NSRange)alignRangeWithElementList:(NSArray *)elementList range:(NSRange)range
{
    NSRange alignRange = range;
    
    NSUInteger elementCount = elementList.count;
    for (NSUInteger i = 0; i < elementCount; i++) {
        NSValue *rangeValue = elementList[i];
        NSRange elementRange = rangeValue.rangeValue;
                
        // 判斷前面的邊緣
        if (NSLocationInRange(range.location, elementRange)) {
            alignRange.location = elementRange.location;
            alignRange.length = NSMaxRange(range) - alignRange.location;
            break;
        }
    }
    
    for (NSUInteger i = elementCount; i > 0; i--) {
        NSValue *rangeValue = elementList[i - 1];
        NSRange elementRange = rangeValue.rangeValue;
        
        // 判斷后面的邊緣
        if (NSMaxRange(alignRange) > elementRange.location && NSMaxRange(alignRange) < NSMaxRange(elementRange)) {
            alignRange.length = NSMaxRange(elementRange) - alignRange.location;
            break;
        }
    }
    
    return alignRange;
}

下面是在 UITextView 中,移動(dòng)光標(biāo)時(shí)的回調(diào)方法脖旱。在該方法中處理光標(biāo)位置的變化堪遂。然后更新到 textView 的 selectedRange。

- (void)textViewDidChangeSelection:(UITextView *)textView
{
    if (textView.markedTextRange != nil) { // 中文的輸入還沒有完成
        return;
    }
    
    NSRange selectedRange = [self alignRangeWithElementList:_richTextStorage.rangeArray range:self.selectedRange];
    
    if (self.selectedRange.location == selectedRange.location &&
        self.selectedRange.length == selectedRange.length) {
        return;
    }
    
    self.selectedRange = selectedRange;
}

刪除文本

下面方法中萌庆,當(dāng) str 長度為 0 時(shí)溶褪,表示刪除。刪除時(shí)需要修正傳入的 range 的邊界是否在富文本中践险。如果修正后和修正前的 range 不同相等猿妈,則選中修正后的 range,反之則刪除巍虫。_updateSelectionRange 這個(gè)方法為 block 回調(diào)方法彭则。

- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
{
    // 修正 range 的邊界值不在富文本中
    NSRange selectedRange = [self alignRangeWithElementList:self.rangeArray range:range];
    
    if (str.length == 0) { // 刪除操作
        if (selectedRange.length != range.length ||
            selectedRange.location != range.location) {
            if (_updateSelectionRange != nil) {
                self.selectedRange = selectedRange;
                _updateSelectionRange(selectedRange);
            }
            
            return;
        }
    }

    // 計(jì)算富文本
    NSAttributedString *attributedString = [self parseRichText:str];
    
    [_richImp replaceCharactersInRange:selectedRange withAttributedString:attributedString];
    [_imp replaceCharactersInRange:selectedRange withString:attributedString.string];
    [self edited:NSTextStorageEditedCharacters range:selectedRange changeInLength:(NSInteger)attributedString.length - (NSInteger)selectedRange.length];
    
    // 記錄光標(biāo)位置
    NSRange selectionRange = NSMakeRange(selectedRange.location + attributedString.length, 0);
    if (attributedString.length == 0) {
        selectedRange = NSMakeRange(selectedRange.location, 0);
    }
    
    if (_updateSelectionRange != nil) {
        _updateSelectionRange(selectionRange);
    }
    
    _selectedRange = selectionRange;
}

輸入文本

當(dāng)上面方法中的 str 大于 0 時(shí)表示輸入文本。將 str 轉(zhuǎn)為有富文本格式的文本占遥,計(jì)算完后俯抖,更新到 _imp 和 _richImp 中,并移動(dòng)光標(biāo)的位置到輸入的文本結(jié)尾處瓦胎。

這樣我們的富文本輸入框基本上就完成了芬萍。為了方便用戶使用,提供三個(gè)屬性搔啊,富文本內(nèi)容柬祠,富文本顏色;提供兩個(gè)方法更新富文本负芋。

@property(nonatomic,copy, readonly) NSString *richText;

@property(nonatomic,assign) NSInteger allowedMaxTextCount; // 最大字符數(shù)漫蛔,0 為無限制

@property(nonatomic,strong) UIColor *tagNameColor;

@property(nonatomic,strong) UIColor *userNameColor;

@property(nonatomic,weak) id<RichTextViewDelegate> textViewDelegate;


- (void)insertRichText:(NSString *)richText;

- (void)replacingCharactersInRange:(NSRange)range withString:(NSString *)replacement;

- (NSArray *)queryElementIdList:(NSString *)elementType;

這里有一個(gè)地方需要注意,更新富文本顏色的時(shí)候旧蛾,需要調(diào)用下面的方法來更新莽龟,否則更新的屬性只對你設(shè)置后改變的文本有效。

    [self edited:NSTextStorageEditedAttributes range:NSMakeRange(0, self.string.length) changeInLength:0];

到這里就結(jié)束了該控件的設(shè)計(jì)歷程蚜点,下面是項(xiàng)目地址,如果有問題或者建議拌阴,歡迎與我交流绍绘。當(dāng)然也歡迎你掃碼加入【猿圈】,一個(gè)程序員的圈子,每天都會(huì)分享一些心得陪拘,經(jīng)驗(yàn)或者感想厂镇。也可以關(guān)注我的公眾號(hào),定期會(huì)更新技術(shù)文章和徒步旅行的游記左刽。
https://github.com/zhuguojie/PersonalProduct.git

猿圈.jpg

公眾號(hào).jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捺信,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子欠痴,更是在濱河造成了極大的恐慌迄靠,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喇辽,死亡現(xiàn)場離奇詭異掌挚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)菩咨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門吠式,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抽米,你說我怎么就攤上這事特占。” “怎么了云茸?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵市怎,是天一觀的道長琢感。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么馏臭? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮姜贡,結(jié)果婚禮上囱怕,老公的妹妹穿的比我還像新娘。我一直安慰自己萍倡,他們只是感情好身弊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著列敲,像睡著了一般阱佛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上戴而,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天凑术,我揣著相機(jī)與錄音,去河邊找鬼所意。 笑死淮逊,一個(gè)胖子當(dāng)著我的面吹牛催首,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泄鹏,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼郎任,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了备籽?” 一聲冷哼從身側(cè)響起舶治,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎车猬,沒想到半個(gè)月后霉猛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诈唬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年韩脏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铸磅。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赡矢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阅仔,到底是詐尸還是另有隱情吹散,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布八酒,位于F島的核電站空民,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏羞迷。R本人自食惡果不足惜界轩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衔瓮。 院中可真熱鬧浊猾,春花似錦、人聲如沸热鞍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薇宠。三九已至偷办,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間澄港,已是汗流浹背椒涯。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留回梧,地道東北人废岂。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓铡溪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泪喊。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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