網(wǎng)站首頁 編程語言 正文
最近接了一個(gè)需求,在發(fā)布動(dòng)態(tài)的時(shí)候,增加類似微博的#話題#
、@提及用戶
的效果,在此做一簡(jiǎn)要記錄。
#話題#
最終效果是:
- 編輯過程中
#話題內(nèi)容#
實(shí)時(shí)高亮
- 高亮部分可以響應(yīng)點(diǎn)擊事件
1.高亮
基本思路是:使用正則匹配出成對(duì)的#
,再利用UITextView
的富文本實(shí)現(xiàn)高亮效果。
func refreshTopicStyle() { let regex = try! NSRegularExpression(pattern: "此處填寫正則表達(dá)式", options:[NSRegularExpression.Options.caseInsensitive]) // 注意點(diǎn) let totalRange = NSMakeRange(0, (inputTextView.attributedText.string as NSString).length) let results = regex.matches(in: inputTextView.attributedText.string, options: NSRegularExpression.MatchingOptions.init(rawValue: 0), range: totalRange) let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: inputTextView.attributedText.string) attributedString.setAttributes(normalAttributes, range: totalRange) for result in results { attributedString.setAttributes(topicAttributes, range: result.range) } inputTextView.attributedText = attributedString }
這有一個(gè)注意點(diǎn),計(jì)算 totalRange 前,先將 String 轉(zhuǎn)成了 NSString,這是因?yàn)榇颂?NSRange 中的 length 需要的是 UTF-16
長(zhǎng)度,也就是與 NSString 的 length 定義一致,而 Swift 中的 String 沒有 length 只有 count,指的是字符數(shù),當(dāng)文本中出現(xiàn) emoji
表情時(shí),二者就不一致了。
當(dāng)然,也有一些其他辦法來處理,如:
let lengthA = inputTextView.textStorage.length let lengthB = inputTextView.attributedText.string.utf16.count
2.點(diǎn)擊事件
實(shí)現(xiàn)高亮部分的點(diǎn)擊事件,目前有3種實(shí)現(xiàn)方案:
- 直接給
UITextView
添加點(diǎn)擊事件 - 通過設(shè)置
LinkAttribute
,利用超文本鏈接的點(diǎn)擊實(shí)現(xiàn) - 重寫
UITextView
的touches...
方法
其中,第二種只限于在非編輯狀態(tài)(即 textView.isEditable = false
)下的點(diǎn)擊,故排除,①、③均可,本文采用第一種,主要實(shí)現(xiàn)如下:
inputTextView.addTapGesture(self, handler: #selector(tapAttributedText(tap:))) @objc private func tapAttributedText(tap: UITapGestureRecognizer) { guard tap.isKind(of: UITapGestureRecognizer.self), let textView = tap.view as? UITextView else { return } let layoutManager = textView.layoutManager var tapLocation = tap.location(in: textView) tapLocation.x -= textView.textContainerInset.left tapLocation.y -= textView.textContainerInset.top let characterIndex = layoutManager.characterIndex(for: tapLocation, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil) for result in getCheckResult(format: Constants.TopicRegularExpression, text: inputTextView.attributedText.string) { if result.range.location < characterIndex, characterIndex < result.range.location + result.range.length { // 此處響應(yīng)點(diǎn)擊事件 MBProgressHUD.showOnlyText(to: self.view, title: "美好時(shí)光") return } } inputTextView.becomeFirstResponder() }
@提及用戶
- 編輯過程中
@提及用戶
實(shí)時(shí)高亮,且只允許選取的用戶名高亮,手動(dòng)輸入不高亮;
- 點(diǎn)擊刪除鍵的時(shí)候,一次性刪除整個(gè)高亮部分
1.高亮
- 記錄位置
本來準(zhǔn)備用正則匹配的,但因?yàn)橹辉试S選取的用戶名高亮,純手動(dòng)輸入的不高亮,所以使用正則匹配就不合理了,這里采用實(shí)時(shí)記錄、更新已選取用戶名位置的方式實(shí)現(xiàn)。
/// 用來保存已選取用戶信息的結(jié)構(gòu)體 struct UserInfo { /// 用戶名 var userName: String /// 位置信息 var range: NSRange /// 用于臨時(shí)替換的等長(zhǎng)字符串 var placeholder: String }
- 臨時(shí)替換
因?yàn)?code>#話題#和@提及用戶
可以同時(shí)存在,所以需要考慮可能互相影響的問題,比如@提及用戶
中間可能出現(xiàn)#
,導(dǎo)致前后話題的正則匹配發(fā)生錯(cuò)亂。
解決方案是:先使用一個(gè)@
開頭且與@提及用戶
等長(zhǎng)的字符串替換@提及用戶
,再執(zhí)行#話題#
的正則匹配,最后再換回來。
2.整體刪除
刪除操作分為兩步:第一次點(diǎn)刪除僅選中整個(gè)用戶名(提醒用戶是整體刪除);第二次點(diǎn)刪除才真的刪除文本。
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if text == "" { for (num, user) in usersArray.enumerated() { // usersArray 用于存放已選取的用戶信息 // ②刪除選中的用戶名 if textView.selectedRange.location <= user.range.location && NSMaxRange(user.range) <= NSMaxRange(textView.selectedRange) { textView.replace(textView.selectedTextRange ?? UITextRange(), withText: "") return false } // ①選中用戶名 if textView.selectedRange.length == 0 && (textView.selectedRange.location == user.range.location + user.range.length) { textView.selectedRange = user.range return false } } } }
原文鏈接:https://juejin.cn/post/7105590718336335902
相關(guān)推薦
- 2022-11-30 詳解Python如何輕松實(shí)現(xiàn)定時(shí)執(zhí)行任務(wù)_python
- 2022-08-13 Android實(shí)現(xiàn)加載圈_Android
- 2023-04-01 pytorch和numpy默認(rèn)浮點(diǎn)類型位數(shù)詳解_python
- 2023-02-06 Go語言基礎(chǔ)學(xué)習(xí)之?dāng)?shù)組的使用詳解_Golang
- 2022-11-29 C#中各種泛型集合的使用方法總結(jié)_C#教程
- 2022-06-11 C#把DataTable導(dǎo)出為Excel文件_C#教程
- 2022-08-02 Golang中panic與recover的區(qū)別_Golang
- 2022-12-10 Android入門之日歷選擇與時(shí)間選擇組件的使用_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支