iOSiOS之框架架构IOS文章收集

TextKit入门? 表情键盘? 图文混排? --看我就够了

2016-09-17  本文已影响3815人  呆子是只喵

前言

现在市场上大部分信息展示类应用都有用到图文混排,我之前对这块也是一知半解,最近放假正好深入了解一下这块,在这里对此做一个总结,废话不多说,先上 demo 地址
TextKit入门 demo地址
精仿手工课 demo地址
精仿手工课博客地址

背景知识

在正式开始学习之前,我们先来了解一下图文混排在 iOS 上面的发展,抛弃 coretext (涉及到一些底层,这里不讨论)不说,苹果在 iOS6给出了*** NSMutableAttributedString这个类,简单的说就是带属性的字符串,用它可以实现图文混排,在 iOS7苹果则给出了更加强大的 API--textKit,用 textKit***可以实现更加复杂的界面,接下来让我们开始浪起来吧

1.gif

富文本(NSMutableAttributedString)

NSMutableAttributedString和数组一样分为可变字符串和不可变字符串, NSAttributedString就是不可变字符串
NSMutableAttributedString简单的说就是一个带属性的字符串,因此它的使用非常简单,

 // 设置颜色等
    NSMutableDictionary *arrDic = [NSMutableDictionary dictionary];
    arrDic[NSForegroundColorAttributeName] = [UIColor purpleColor];
    arrDic[NSBackgroundColorAttributeName] = [UIColor greenColor];
    arrDic[NSKernAttributeName] = @10;
    arrDic[NSUnderlineStyleAttributeName] = @1;
    
    NSMutableAttributedString *attriOneStr = [[NSMutableAttributedString alloc]initWithString:@"来呀,快活呀,反正有大把时光" attributes:arrDic];
    self.oneLabel.attributedText = attriOneStr;
    
    // 简单的图文混排
    NSMutableAttributedString *arrTwoStr = [[NSMutableAttributedString alloc]init];
    NSMutableAttributedString *TwoChildStr = [[NSMutableAttributedString alloc]initWithString:@"你好啊"];
    [arrTwoStr appendAttributedString:TwoChildStr];
    
    NSTextAttachment *attachMent = [[NSTextAttachment alloc]init];
    attachMent.image = [UIImage imageNamed:@"2"];
    attachMent.bounds = CGRectMake(0, -5, 20, 20);
    NSAttributedString *picStr = [NSAttributedString attributedStringWithAttachment:attachMent];
    [arrTwoStr appendAttributedString:picStr];
    
    NSAttributedString *TwooStr = [[NSAttributedString alloc]initWithString:@"我是小菜鸟"];
    [arrTwoStr appendAttributedString:TwooStr];
    self.twoLabel.attributedText = arrTwoStr;

效果

简单.png

表情键盘,富文本实现图文混排

表情键盘

Snip20160917_5.png
平常大家用的 QQ,微信等APP中随处可见表情键盘,在做表情键盘前,先来了解一下什么是表情.
日常生活中,所用到的表情一般为两种--图片表情,emoji表情
emoji表情的本质就是字符串,比如 0x1f601:,在显示的时候我们需要将字符串转成表情
图片表情就是一张图片,比如这是一个表情信息,我们根据png来加载缓存在本地的图片,并显示.
图片表情.png

实现思路

考虑到表情的两种表现形式,决定用 Button 来实现,这样可以方便的显示字体或者图片

表情键盘代码###

由于代码量较大,这里上一些核心代码,具体代码可以下载,下来再看TextKit入门 demo地址
表情工具类加载所需表情

+ (NSArray *)defaultEmotions
{
    if (!_defaultEmotions) {
        NSString *plist = [[NSBundle mainBundle] pathForResource:@"EmotionIcons/default/info.plist" ofType:nil];
        _defaultEmotions = [GPEmotion mj_objectArrayWithFile:plist];
        [_defaultEmotions makeObjectsPerformSelector:@selector(setDirectory:) withObject:@"EmotionIcons/default"];
    }
    return _defaultEmotions;
}

+ (NSArray *)emojiEmotions
{
    if (!_emojiEmotions) {
        NSString *plist = [[NSBundle mainBundle] pathForResource:@"EmotionIcons/emoji/info.plist" ofType:nil];
        _emojiEmotions = [GPEmotion mj_objectArrayWithFile:plist];
        [_emojiEmotions makeObjectsPerformSelector:@selector(setDirectory:) withObject:@"EmotionIcons/emoji"];
    }
    return _emojiEmotions;
}

+ (NSArray *)lxhEmotions
{
    if (!_lxhEmotions) {
        NSString *plist = [[NSBundle mainBundle] pathForResource:@"EmotionIcons/lxh/info.plist" ofType:nil];
        _lxhEmotions = [GPEmotion mj_objectArrayWithFile:plist];
        [_lxhEmotions makeObjectsPerformSelector:@selector(setDirectory:) withObject:@"EmotionIcons/lxh"];
    }
    return _lxhEmotions;
}

+ (NSArray *)recentEmotions
{
    if (!_recentEmotions) {
        _recentEmotions = [NSKeyedUnarchiver unarchiveObjectWithFile:GPRecentFilepath];
        if (!_recentEmotions) {
            _recentEmotions = [NSMutableArray array];
        }
    }
    return _recentEmotions;
}

表情数据传递

  - (void)setEmtiontype:(GPEmtionType)type
{
    switch (type) {
        case GPEmotionTypeRecent: {
            self.listView.emotions = [GPEmtionTool recentEmotions];
            break;
        }
        case GPEmotionTypeDefault: {
            self.listView.emotions = [GPEmtionTool defaultEmotions];
            
            break;
        }
        case GPEmotionTypeEmoji: {
            self.listView.emotions = [GPEmtionTool emojiEmotions];
            break;
        }
        case GPEmotionTypeLxh: {
            self.listView.emotions = [GPEmtionTool lxhEmotions];
            break;
        }
    }
}
  - (void)setEmotions:(NSArray *)emotions
{
    _emotions = emotions;
    
    NSInteger totlas = (emotions.count + GPEmotionMaxCountPerPage - 1) / GPEmotionMaxCountPerPage;
    NSInteger currrentGridViewCount = self.scrollView.subviews.count;
    
    self.pageControl.numberOfPages = totlas;
    self.pageControl.currentPage = 0;
    GPEmottionGridView *gridView = nil;
    for (NSInteger i = 0; i < totlas; i ++) 
        if (i >= currrentGridViewCount) {
            gridView = [[GPEmottionGridView alloc]init];
            [self.scrollView addSubview:gridView];
        } else {
            gridView = self.scrollView.subviews[i]
        }
        NSInteger loc = i * GPEmotionMaxCountPerPage;
        NSInteger len = GPEmotionMaxCountPerPage;
        if (loc + len > emotions.count) {
            len = emotions.count - loc;
        }
        NSRange range = NSMakeRange(loc, len);
        NSArray *gridViewemotionS = [emotions subarrayWithRange:range];
        gridView.emotions = gridViewemotionS;
        gridView.hidden = NO
    
    for (NSInteger i = totlas; i<currrentGridViewCount; i++) {
        GPEmottionGridView *gridView = self.scrollView.subviews[i];
        gridView.hidden = YES;
    
       [self setNeedsLayout];
    self.scrollView.contentOffset = CGPointZero
  - (void)setEmotions:(NSArray *)emotions
{
    _emotions = emotions;
    
    NSInteger count = emotions.count;
    NSInteger currentEmotionViewCount = self.emotionViews.count;
    for (int i = 0; i<count; i++) {
        GPEmotionView *emotionView = nil;
        
        if (i >= currentEmotionViewCount) {
            emotionView = [[GPEmotionView alloc] init];
            [emotionView addTarget:self action:@selector(emotionClick:)           forControlEvents:UIControlEventTouchUpInside];
            [self addSubview:emotionView];
            [self.emotionViews addObject:emotionView];
        } else {
            emotionView = self.emotionViews[i];
        }
        // 传递模型数据
        emotionView.emotion = emotions[i];
       
      for NSInteger i = count; i<currentEmotionViewCount; i++) 
        UIButton *emotionView = self.emotionViews[i];
        emotionView.hidden = YES;
    ```
* Btn 展示表情
###图文混排实现


![Uploading Snip20160917_5_983709.png . . .]](http:https://img.haomeiwen.com/i694552/193ab88b3b973702.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

**实现思路**
* 获得后台的字符串,然后用正则表达式将字符串分割为表情和非表情两部分,然后将其转换为**富文本字符串**,并在匹配到超链接时候给其绑定一个 key, 随后将分割后的结果按顺序排好,

* 将之前转换后的富文本字符串赋值为 `UITextView` 的`attributedText`属性

* 点击超链接,判断当前点是否在链接范围之内,若在就打开链接

**图文混排核心代码**

* 普通字符串转换为富文本字符串
         - (NSAttributedString *)creatArrtext:(NSString *)text
      { NSArray *regexResults = [GPEmtionTool regexResultsWithText:text];
      NSMutableAttributedString *attributedString =   [[NSMutableAttributedString alloc] init];
       [regexResults enumerateObjectsUsingBlock:^(GPRegexResult *result,   NSUInteger idx, BOOL *stop) {
        GPEmotion *emotion = nil;
        if (result.isEmotion) { // 表情
            emotion = [GPEmtionTool emotionWithDesc:result.string];
        }
        
        if (emotion) { // 如果有表情
            // 创建附件对象
            GPEmotionAttachment *attach = [[GPEmotionAttachment alloc] init];
            
            // 传递表情
            attach.emotion = emotion;
            attach.bounds = CGRectMake(0, -3, GPStatusOrginalTextFont.lineHeight, GPStatusOrginalTextFont.lineHeight);
            
            // 将附件包装成富文本
            NSAttributedString *attachString = [NSAttributedString attributedStringWithAttachment:attach];
            [attributedString appendAttributedString:attachString];
        } else { // 非表情(直接拼接普通文本)
            NSMutableAttributedString *substr = [[NSMutableAttributedString alloc] initWithString:result.string];
        
            // 匹配超链接
            NSString *httpRegex = @"http(s)?://([a-zA-Z|\\\\d]+\\\\.)+[a-zA-Z|\\\\d]+(/[a-zA-Z|\\\\d|\\\\-|\\\\+|_./?%&=]*)?";
            [result.string enumerateStringsMatchedByRegex:httpRegex usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
                [substr addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:*capturedRanges];
                
                // 绑定一个key
                [substr addAttribute:GPLinkText value:*capturedStrings range:*capturedRanges];
            }];
            
            [attributedString appendAttributedString:substr];
        }
      }];
    
      // 设置字体
      [attributedString addAttribute:NSFontAttributeName   value:GPStatusOrginalTextFont range:NSMakeRange(0,   attributedString.length)];
    
      return attributedString;
      }

* 点击链接跳转
if (touchingLink) {
    [[NSNotificationCenter defaultCenter] postNotificationName:GPLinkDidSelectedNotification object:nil userInfo:@{GPLinkText : touchingLink.text}];
}

[self touchesCancelled:touches withEvent:event];

}

# TextKit
![TextKit.png](http:https://img.haomeiwen.com/i694552/21b677318e171048.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
接下来,我们来看今天的重头戏` TextKit`,首先来看看`textKit` 的类
* `NSTextStorage`: 平时用到的字符串 **string**,这个类里面包含着 string的属性,比如颜色,大小等都是这个类来管理
* `NSLayoutManager`: 这个就是管理中心,负责布局渲染
* `NSTextContainer` : 就是可以渲染的范围
* `UITextView` :就是我们平时用的控件,用来给用户展示数据
那么,我么可以用这些类来做哪些事情呢,看一些实例
### 阅读排版

![阅读排版.gif](http:https://img.haomeiwen.com/i694552/9463e4514a639b67.gif?imageMogr2/auto-orient/strip)


**阅读排版实现思路**
* 创建n 个 `textView`,共用一套`NSLayoutManager`

**阅读排版核心代码**

}

### 高亮文字,URL, 保持 URL 在一行是一个整体

**高亮文字,URL实现思路**
* 可以自定义`NSTextStorage`,这里注意`NSTextStorage`继承自`NSMutableAttributedString`,所以必须实现以下方法:
`  - (NSString *)string;`
 ` - (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range;`
 ` - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;`
 ` - (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range;`

* 在`- (void)processEditing`中匹配高亮字符,匹配 URL, 并高亮
* 在 `NSLayoutManagerDelegate`实现 url 保持整体不换行
**高亮文字,URL核心代码**


**机智的你一定发现,其实利用 TextKit也可以实现图文混排,机智的你自己试一试**
###环绕布局

![环绕布局.png](http:https://img.haomeiwen.com/i694552/93d9a2037da33412.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
如图所示;大家用 Word 的时候插入图片,使得文字环绕在图片周围,哈哈,好消息来了, iOS 也可以实现哦
**环绕布局实现思路**
* 记得`textContainer`,我们可以直接给其属性赋值一个` path`, 就相当于在一张纸上裁剪掉一部分,那么自然就不会在那部分渲染文字了
**环绕布局核心代码**
#总结
这只是一些简单的使用,机智的你肯定有更多想法,互相学习吧

>参考
[obJc,初始 textKit](https://objccn.io/issue-5-1/)
[MJ](https://github.com/CoderMJLee/MJExtension)
上一篇 下一篇

猜你喜欢

热点阅读