单军华
2018-07-31 21d3023a9b7b6aff68c1170e345951396b1c6cfd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
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