iOS UITextView 实现类似微博的话题、提及用户效果

目录
  • #话题#
    • 1.高亮
    • 2.点击事件
  • @提及用户
    • 1.高亮
    • 2.整体删除

最近接了一个需求,在发布动态的时候,增加类似微博的#话题#@提及用户的效果,在此做一简要记录。

#话题#

最终效果是:

  • 编辑过程中#话题内容#实时高亮

  • 高亮部分可以响应点击事件

1.高亮

基本思路是:使用正则匹配出成对的#,再利用UITextView的富文本实现高亮效果。

func refreshTopicStyle() {

        let regex = try! NSRegularExpression(pattern: "此处填写正则表达式",
                                             options:[NSRegularExpression.Options.caseInsensitive])
        // 注意点
        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
    }

这有一个注意点,计算 totalRange 前,先将 String 转成了 NSString,这是因为此处 NSRange 中的 length 需要的是 UTF-16 长度,也就是与 NSString 的 length 定义一致,而 Swift 中的 String 没有 length 只有 count,指的是字符数,当文本中出现 emoji 表情时,二者就不一致了。

当然,也有一些其他办法来处理,如:

let lengthA = inputTextView.textStorage.length
let lengthB = inputTextView.attributedText.string.utf16.count

2.点击事件

实现高亮部分的点击事件,目前有3种实现方案:

  • 直接给UITextView添加点击事件
  • 通过设置LinkAttribute,利用超文本链接的点击实现
  • 重写UITextViewtouches...方法

其中,第二种只限于在非编辑状态(即 textView.isEditable = false)下的点击,故排除,①、③均可,本文采用第一种,主要实现如下:

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 {
            // 此处响应点击事件
            MBProgressHUD.showOnlyText(to: self.view, title: "美好时光")
            return
        }
    }
    inputTextView.becomeFirstResponder()
}

@提及用户

  • 编辑过程中 @提及用户 实时高亮,且只允许选取的用户名高亮,手动输入不高亮;

  • 点击删除键的时候,一次性删除整个高亮部分

1.高亮

  • 记录位置

本来准备用正则匹配的,但因为只允许选取的用户名高亮,纯手动输入的不高亮,所以使用正则匹配就不合理了,这里采用实时记录、更新已选取用户名位置的方式实现。

/// 用来保存已选取用户信息的结构体
struct UserInfo {
    /// 用户名
    var userName: String
    /// 位置信息
    var range: NSRange
    /// 用于临时替换的等长字符串
    var placeholder: String
}
  • 临时替换

因为#话题#@提及用户可以同时存在,所以需要考虑可能互相影响的问题,比如@提及用户中间可能出现#,导致前后话题的正则匹配发生错乱。

解决方案是:先使用一个@开头且与@提及用户等长的字符串替换@提及用户,再执行#话题#的正则匹配,最后再换回来。

2.整体删除

删除操作分为两步:第一次点删除仅选中整个用户名(提醒用户是整体删除);第二次点删除才真的删除文本。

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
            }
        }
    }
}

到此这篇关于iOS UITextView 实现类似微博的话题、提及功能的文章就介绍到这了,更多相关iOS UITextView微博话题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-06-04

iOS开发中Swift3 监听UITextView文字改变的方法(三种方法)

在项目中使用文本输入框出UITextField之外还会经常使用 UITextView ,难免会有需求监听UITextView文本框内文本数量.下面介绍在swift3中两种常用方式 方式一: 全局通知 1.注册通知 在合适位置注册监听UITextView文本变化的全局通知 //UITextView 监听开始输入的两种方法 //方法一:通知 NotificationCenter.default.addObserver(self, selector: #selector(ComposeVC.textV

iOS中的UITextView文字输入光标使用技巧小结

1.创建并初始化 @property (nonatomic, strong) UITextView *textView; // 创建 self.textView = [[UITextView alloc] initWithFrame:self.view.frame]; // 设置textview里面的字体颜色 self.textView.textColor = [UIColor blackColor]; // 设置字体名字和字体大小 self.textView.font = [UIFont fo

iOS应用开发中的文字选中操作控件UITextView用法讲解

1.创建并初始化 创建UITextView的文件,并在.h文件中写入如下代码: 复制代码 代码如下: #import <UIKit/UIKit.h>        @interface TextViewController : UIViewController <UITextViewDelegate>    {                  UITextView *textView;    }        @property (nonatomic, retain) UITex

iOS UITextView 首行缩进 撤销输入 反撤销输入的实现代码

最近公司涉及到作家助手的功能,能撤销输入的文字,并能反撤销被撤销掉的文字. 该功能类似ios系统的摇一摇撤销输入. 当时也特迷茫,不知道从何下手,后来搜索了大量的资料,终于完成了这个功能,现在就将该功能的实现写出来,共勉. 这个功能涉及到ios原生类:NSUndomanager.这个类挺强大.废话不多说,直接上代码. #import "ViewController.h" @interface ViewController ()<UITextViewDelegate>{ UI

iOS UITextField、UITextView只限输入中文、英文、数字及实时限制字符个数的封装实现代码

引言需求:(输入框限制输入多少字符) 1.一个字母.符号.数字相当于一个字符 2.一个汉字相当于两个字符 3.不能输入特殊字符 4.不能输入emoji表情 直接上代码 一.注册通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldChange:) name:UITextFieldTextDidChangeNotification object:nil]; [[NSNotifica

使用UITextField限制只可输入中,英文,数字的方法

前言 本文主要介绍使用UITextField限制只可输入中,英文,数字,我们可以使用NSPredicate正则表达式可以过滤,下面看看详细的步骤方法 首先设置UItextField的代理 实现如下方法: - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{ if ([self isInputRuleA

JavaScript控制输入框中只能输入中文、数字和英文的方法【基于正则实现】

本文实例讲述了JavaScript控制输入框中只能输入中文.数字和英文的方法.分享给大家供大家参考,具体如下: 1.问题背景: 遇到这样一个问题:有一个输入框,要求只能输入中文.数字和英文(即过滤特殊字符串) 2.JS代码: function checkUsername() { //正则表达式 var reg = new RegExp("^[A-Za-z0-9\u4e00-\u9fa5]+$"); //获取输入框中的值 var username = document.getElemen

JAVA 统计字符串中中文,英文,数字,空格,特殊字符的个数

引言 可以根据各种字符在Unicode字符编码表中的区间来进行判断,如数字为'0'~'9'之间,英文字母为'a'~'z'或'A'~'Z'等,Java判断一个字符串是否有中文是利用Unicode编码来判断,因为中文的编码区间为:0x4e00--0x9fbb, 但通用区间来判断中文也不非常精确,因为有些中文的标点符号利用区间判断会得到错误的结果.所以通过Character.UnicodeBlock来进行判断.代码如下: package cn.csrc.base.count; public class

php 判断是否是中文/英文/数字示例代码

复制代码 代码如下: $str='asb天水市12'; if (preg_match("/^[\x7f-\xff]+$/", $str)){ echo '全部是汉字'; }else { echo '不全是汉字'; } /** PHP自带的判断是否是中文, eregi('[^\x00-\x7F]', $str ) //中文 eregi('[0-9]', $str) //数字 eregi('[a-zA-Z]', $str)//英文 */ if (eregi('[^\x00-\x7F]',

js控制文本框只能输入中文、英文、数字与指定特殊符号的实现代码

JS 控制文本框只能输入数字 <input onkeyup="value=value.replace(/[^0-9]/g,'')"onpaste="value=value.replace(/[^0-9]/g,'')" oncontextmenu ="value=value.replace(/[^0-9]/g,'')"> JS 控制文本框只能输入数字.小数点 <inputonkeyup="value=value.repl

利用正则表达式(只录入中文,数字,英文)

只能输入中文:<input id="input1" type="text" onkeyup="value=value.replace(/[^\u4E00-\u9FA5]/g,'')"> 只能输入全角字符:<input id="input2" type="text" onkeyup="value=value.replac(/[^\u4E00-\u9FA5]/g,'')"&

IOS中UITextView或UITextField字数限制的实现

IOS中UITextView或UITextField字数限制的实现 UITextView或UITextField字数限制,输入时的限制,复制粘贴时的限制 字数限制有三种方法 在代理方法 "- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string" 或 "- (BOOL)textView:

IOS 中UITextField,UITextView,UILabel 根据内容来计算高度

IOS 中UITextField,UITextView,UILabel 根据内容来计算高度 在开发的过程中,常常遇到根据内容来决定控件的高度的情况,常见的就是UITextField,UITextView,UILabel这三个控件,下面一UITextView 为例来说明一下: 首先新新建一个textView. 设施text,font UITextView *textView = [[UITextView alloc] init]; textView.text = @"2015-01-19 14:0

IOS中用正则表达式判断输入的内容为8-16位且同时包含数字和字母

今天在项目中需要用到判断用户输入的用户名长度为8-16位且同时包含数字和字母,在网上搜了一下正则表达式的用法,然后参考这篇文章,完美解答了问题.记录一下: 密码有如下要求:由数字和字母组成,并且要同时含有数字和字母,且长度要在8-16位之间. 如何分析需求?拆分!这就是软件设计的一般思路了.于是乎,拆分需求如下: 1,不能全部是数字 2,不能全部是字母 3,必须是数字或字母 只要能同时满足上面3个要求就可以了,写出来如下: ^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z