单军华
2018-07-31 21d3023a9b7b6aff68c1170e345951396b1c6cfd
screendisplay/screendisplay/Classes/Category/ThirdCategory/UITextView+WZB.m
New file
@@ -0,0 +1,337 @@
//
//  UITextView+WZB.m
//  WZBTextView-demo
//
//  Created by normal on 2016/11/14.
//  Copyright © 2016年 WZB. All rights reserved.
//
#import "UITextView+WZB.h"
#import <objc/runtime.h>
// 占位文字
static const void *WZBPlaceholderViewKey = &WZBPlaceholderViewKey;
// 占位文字颜色
static const void *WZBPlaceholderColorKey = &WZBPlaceholderColorKey;
// 最大高度
static const void *WZBTextViewMaxHeightKey = &WZBTextViewMaxHeightKey;
// 最小高度
static const void *WZBTextViewMinHeightKey = &WZBTextViewMinHeightKey;
// 高度变化的block
static const void *WZBTextViewHeightDidChangedBlockKey = &WZBTextViewHeightDidChangedBlockKey;
// 存储添加的图片
static const void *WZBTextViewImageArrayKey = &WZBTextViewImageArrayKey;
// 存储最后一次改变高度后的值
static const void *WZBTextViewLastHeightKey = &WZBTextViewLastHeightKey;
@interface UITextView ()
// 存储添加的图片
@property (nonatomic, strong) NSMutableArray *wzb_imageArray;
// 存储最后一次改变高度后的值
@property (nonatomic, assign) CGFloat lastHeight;
@end
@implementation UITextView (WZB)
#pragma mark - Swizzle Dealloc
+ (void)load {
    // 交换dealoc
    Method dealoc = class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc"));
    Method myDealloc = class_getInstanceMethod(self.class, @selector(myDealloc));
    method_exchangeImplementations(dealoc, myDealloc);
}
- (void)myDealloc {
    // 移除监听
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
    // 如果有值才去调用,这步很重要
    if (placeholderView) {
        NSArray *propertys = @[@"frame", @"bounds", @"font", @"text", @"textAlignment", @"textContainerInset"];
        for (NSString *property in propertys) {
            @try {
                [self removeObserver:self forKeyPath:property];
            } @catch (NSException *exception) {}
        }
    }
    [self myDealloc];
}
#pragma mark - set && get
- (UITextView *)wzb_placeholderView {
    // 为了让占位文字和textView的实际文字位置能够完全一致,这里用UITextView
    UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
    if (!placeholderView) {
        // 初始化数组
        self.wzb_imageArray = [NSMutableArray array];
        placeholderView = [[UITextView alloc] init];
        // 动态添加属性的本质是: 让对象的某个属性与值产生关联
        objc_setAssociatedObject(self, WZBPlaceholderViewKey, placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        placeholderView = placeholderView;
        // 设置基本属性
        placeholderView.scrollEnabled = placeholderView.userInteractionEnabled = NO;
//        self.scrollEnabled = placeholderView.scrollEnabled = placeholderView.showsHorizontalScrollIndicator = placeholderView.showsVerticalScrollIndicator = placeholderView.userInteractionEnabled = NO;
        placeholderView.textColor = [UIColor lightGrayColor];
        placeholderView.backgroundColor = [UIColor clearColor];
        [self refreshPlaceholderView];
        [self addSubview:placeholderView];
        // 监听文字改变
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewTextChange) name:UITextViewTextDidChangeNotification object:self];
        // 这些属性改变时,都要作出一定的改变,尽管已经监听了TextDidChange的通知,也要监听text属性,因为通知监听不到setText:
        NSArray *propertys = @[@"frame", @"bounds", @"font", @"text", @"textAlignment", @"textContainerInset"];
        // 监听属性
        for (NSString *property in propertys) {
            [self addObserver:self forKeyPath:property options:NSKeyValueObservingOptionNew context:nil];
        }
    }
    return placeholderView;
}
- (void)setWzb_placeholder:(NSString *)placeholder
{
    // 为placeholder赋值
    [self wzb_placeholderView].text = placeholder;
}
- (NSString *)wzb_placeholder
{
    // 如果有placeholder值才去调用,这步很重要
    if (self.placeholderExist) {
        return [self wzb_placeholderView].text;
    }
    return nil;
}
- (void)setWzb_placeholderColor:(UIColor *)wzb_placeholderColor
{
    // 如果有placeholder值才去调用,这步很重要
    if (!self.placeholderExist) {
        NSLog(@"请先设置placeholder值!");
    } else {
        self.wzb_placeholderView.textColor = wzb_placeholderColor;
        // 动态添加属性的本质是: 让对象的某个属性与值产生关联
        objc_setAssociatedObject(self, WZBPlaceholderColorKey, wzb_placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}
- (UIColor *)wzb_placeholderColor
{
    return objc_getAssociatedObject(self, WZBPlaceholderColorKey);
}
- (void)setWzb_maxHeight:(CGFloat)wzb_maxHeight
{
    CGFloat max = wzb_maxHeight;
    // 如果传入的最大高度小于textView本身的高度,则让最大高度等于本身高度
    if (wzb_maxHeight < self.frame.size.height) {
        max = self.frame.size.height;
    }
    objc_setAssociatedObject(self, WZBTextViewMaxHeightKey, [NSString stringWithFormat:@"%lf", max], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CGFloat)wzb_maxHeight
{
    return [objc_getAssociatedObject(self, WZBTextViewMaxHeightKey) doubleValue];
}
- (void)setWzb_minHeight:(CGFloat)wzb_minHeight
{
    objc_setAssociatedObject(self, WZBTextViewMinHeightKey, [NSString stringWithFormat:@"%lf", wzb_minHeight], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CGFloat)wzb_minHeight
{
    return [objc_getAssociatedObject(self, WZBTextViewMinHeightKey) doubleValue];
}
- (void)setWzb_textViewHeightDidChanged:(textViewHeightDidChangedBlock)wzb_textViewHeightDidChanged
{
    objc_setAssociatedObject(self, WZBTextViewHeightDidChangedBlockKey, wzb_textViewHeightDidChanged, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (textViewHeightDidChangedBlock)wzb_textViewHeightDidChanged
{
    void(^textViewHeightDidChanged)(CGFloat currentHeight) = objc_getAssociatedObject(self, WZBTextViewHeightDidChangedBlockKey);
    return textViewHeightDidChanged;
}
- (NSArray *)wzb_getImages
{
    return self.wzb_imageArray;
}
- (void)setLastHeight:(CGFloat)lastHeight {
    objc_setAssociatedObject(self, WZBTextViewLastHeightKey, [NSString stringWithFormat:@"%lf", lastHeight], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CGFloat)lastHeight {
    return [objc_getAssociatedObject(self, WZBTextViewLastHeightKey) doubleValue];
}
- (void)setWzb_imageArray:(NSMutableArray *)wzb_imageArray {
    objc_setAssociatedObject(self, WZBTextViewImageArrayKey, wzb_imageArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableArray *)wzb_imageArray {
    return objc_getAssociatedObject(self, WZBTextViewImageArrayKey);
}
- (void)wzb_autoHeightWithMaxHeight:(CGFloat)maxHeight
{
    [self wzb_autoHeightWithMaxHeight:maxHeight textViewHeightDidChanged:nil];
}
// 是否启用自动高度,默认为NO
static bool autoHeight = NO;
- (void)wzb_autoHeightWithMaxHeight:(CGFloat)maxHeight textViewHeightDidChanged:(textViewHeightDidChangedBlock)textViewHeightDidChanged
{
    autoHeight = YES;
    [self wzb_placeholderView];
    self.wzb_maxHeight = maxHeight;
    if (textViewHeightDidChanged) self.wzb_textViewHeightDidChanged = textViewHeightDidChanged;
}
#pragma mark - addImage
/* 添加一张图片 */
- (void)wzb_addImage:(UIImage *)image
{
    [self wzb_addImage:image size:CGSizeZero];
}
/* 添加一张图片 image:要添加的图片 size:图片大小 */
- (void)wzb_addImage:(UIImage *)image size:(CGSize)size
{
    [self wzb_insertImage:image size:size index:self.attributedText.length > 0 ? self.attributedText.length : 0];
}
/* 插入一张图片 image:要添加的图片 size:图片大小 index:插入的位置 */
- (void)wzb_insertImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index
{
    [self wzb_addImage:image size:size index:index multiple:-1];
}
/* 添加一张图片 image:要添加的图片 multiple:放大/缩小的倍数 */
- (void)wzb_addImage:(UIImage *)image multiple:(CGFloat)multiple
{
    [self wzb_addImage:image size:CGSizeZero index:self.attributedText.length > 0 ? self.attributedText.length : 0 multiple:multiple];
}
/* 插入一张图片 image:要添加的图片 multiple:放大/缩小的倍数 index:插入的位置 */
- (void)wzb_insertImage:(UIImage *)image multiple:(CGFloat)multiple index:(NSInteger)index
{
    [self wzb_addImage:image size:CGSizeZero index:index multiple:multiple];
}
/* 插入一张图片 image:要添加的图片 size:图片大小 index:插入的位置 multiple:放大/缩小的倍数 */
- (void)wzb_addImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index multiple:(CGFloat)multiple {
    if (image) [self.wzb_imageArray addObject:image];
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
    NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
    textAttachment.image = image;
    CGRect bounds = textAttachment.bounds;
    if (!CGSizeEqualToSize(size, CGSizeZero)) {
        bounds.size = size;
        textAttachment.bounds = bounds;
    } else if (multiple <= 0) {
        CGFloat oldWidth = textAttachment.image.size.width;
        CGFloat scaleFactor = oldWidth / (self.frame.size.width - 10);
        textAttachment.image = [UIImage imageWithCGImage:textAttachment.image.CGImage scale:scaleFactor orientation:UIImageOrientationUp];
    } else {
        bounds.size = image.size;
        textAttachment.bounds = bounds;
    }
    NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];
    [attributedString replaceCharactersInRange:NSMakeRange(index, 0) withAttributedString:attrStringWithImage];
    self.attributedText = attributedString;
    [self textViewTextChange];
    [self refreshPlaceholderView];
}
#pragma mark - KVO监听属性改变
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    [self refreshPlaceholderView];
    if ([keyPath isEqualToString:@"text"]) [self textViewTextChange];
}
// 刷新PlaceholderView
- (void)refreshPlaceholderView {
    UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
    // 如果有值才去调用,这步很重要
    if (placeholderView) {
        self.wzb_placeholderView.frame = self.bounds;
        if (self.wzb_maxHeight < self.bounds.size.height) self.wzb_maxHeight = self.bounds.size.height;
        self.wzb_placeholderView.font = self.font;
        self.wzb_placeholderView.textAlignment = self.textAlignment;
        self.wzb_placeholderView.textContainerInset = self.textContainerInset;
        self.wzb_placeholderView.hidden = (self.text.length > 0 && self.text);
    }
}
// 处理文字改变
- (void)textViewTextChange {
    UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
    // 如果有值才去调用,这步很重要
    if (placeholderView) {
        self.wzb_placeholderView.hidden = (self.text.length > 0 && self.text);
    }
    // 如果没有启用自动高度,不执行以下方法
    if (!autoHeight) return;
    if (self.wzb_maxHeight >= self.bounds.size.height) {
        // 计算高度
        NSInteger currentHeight = ceil([self sizeThatFits:CGSizeMake(self.bounds.size.width, MAXFLOAT)].height);
        // 如果高度有变化,调用block
        if (currentHeight != self.lastHeight) {
            // 是否可以滚动
            self.scrollEnabled = currentHeight >= self.wzb_maxHeight;
            CGFloat currentTextViewHeight = currentHeight >= self.wzb_maxHeight ? self.wzb_maxHeight : currentHeight;
            // 改变textView的高度
            if (currentTextViewHeight >= self.wzb_minHeight) {
                CGRect frame = self.frame;
                frame.size.height = currentTextViewHeight;
                self.frame = frame;
                // 调用block
                if (self.wzb_textViewHeightDidChanged) self.wzb_textViewHeightDidChanged(currentTextViewHeight);
                // 记录当前高度
                self.lastHeight = currentTextViewHeight;
            }
        }
    }
    if (!self.isFirstResponder) [self becomeFirstResponder];
}
// 判断是否有placeholder值,这步很重要
- (BOOL)placeholderExist {
    // 获取对应属性的值
    UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
    // 如果有placeholder值
    if (placeholderView) return YES;
    return NO;
}
@end