時隔半個月,終於來更新第二篇了,大家有木有很期待!
一、一個強大的UITextView分類,讓系統TextView自帶placeholder屬性、自動高度、支援輸入圖片三大功能
1、效果:
功能一、讓系統TextView自帶placeholder屬性
功能二、自動改變高度,類似聊天輸入框
功能三、支援輸入圖片
2、使用方法:
將UITextView+WZB.h和UITextView+WZB.m拖入工程
只需要在需要使用的地方直接匯入標頭檔案UITextView+WZB.h,你的UITextView就擁有了這三大功能
1 2 3 |
// 直接設定placeholder屬性即可 textView.placeholder = @"i love you"; [self.view addSubview:textView]; |
如果想要計算高度,只需要呼叫這個方法即可,你需要在block回撥裡手動更改textView的高度
1 2 |
/* 自動高度的方法,maxHeight:最大高度, textHeightDidChanged:高度改變的時候呼叫 */ - (void)autoHeightWithMaxHeight:(CGFloat)maxHeight textViewHeightDidChanged:(textViewHeightDidChangedBlock)textViewHeightDidChanged; |
如圖
插入圖片的方法如下:
1 2 3 4 5 6 7 8 9 10 11 |
/* 新增一張圖片 image:要新增的圖片 */ - (void)addImage:(UIImage *)image; /* 新增一張圖片 image:要新增的圖片 size:圖片大小 */ - (void)addImage:(UIImage *)image size:(CGSize)size; /* 插入一張圖片 image:要新增的圖片 size:圖片大小 index:插入的位置 */ - (void)insertImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index; /* 新增一張圖片 image:要新增的圖片 multiple:放大/縮小的倍數 */ - (void)addImage:(UIImage *)image multiple:(CGFloat)multiple; /* 插入一張圖片 image:要新增的圖片 multiple:放大/縮小的倍數 index:插入的位置 */ - (void)insertImage:(UIImage *)image multiple:(CGFloat)multiple index:(NSInteger)index; |
註釋寫的很清楚,效果如下:
3、實現大致原理:
使用runtime為textView新增如下屬性
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 佔位文字 static const void *WZBPlaceholderViewKey = &WZBPlaceholderViewKey; // 佔位文字顏色 static const void *WZBPlaceholderColorKey = &WZBPlaceholderColorKey; // 最大高度 static const void *WZBTextViewMaxHeightKey = &WZBTextViewMaxHeightKey; // 高度變化的block static const void *WZBTextViewHeightDidChangedBlockKey = &WZBTextViewHeightDidChangedBlockKey; // 動態新增屬性的本質是: 讓物件的某個屬性與值產生關聯 objc_setAssociatedObject(self, WZBPlaceholderViewKey, placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, WZBTextViewMaxHeightKey, [NSString stringWithFormat:@"%lf", maxHeight], OBJC_ASSOCIATION_COPY_NONATOMIC); objc_setAssociatedObject(self, WZBTextViewHeightDidChangedBlockKey, textViewHeightDidChanged, OBJC_ASSOCIATION_COPY_NONATOMIC); objc_setAssociatedObject(self, WZBPlaceholderColorKey, placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
監聽
1 2 3 4 5 6 7 8 |
// 監聽文字改變 [[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]; } |
當文字發生變化的時候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (void)textViewTextChange { self.placeholderView.hidden = (self.attributedText.length > 0 && self.attributedText); if (self.maxHeight >= self.bounds.size.height) { // 計算高度 NSInteger currentHeight = ceil([self sizeThatFits:CGSizeMake(self.bounds.size.width, MAXFLOAT)].height); NSInteger lastheight = ceil(self.maxHeight + self.textContainerInset.top + self.textContainerInset.bottom); // 如果高度有變化,呼叫block if (currentHeight != lastheight) { self.scrollEnabled = currentHeight >= self.maxHeight; if (self.textViewHeightDidChanged) { self.textViewHeightDidChanged((currentHeight >= self.maxHeight ? self.maxHeight : currentHeight)); } } } } |
新增圖片是用的NSTextAttachment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
- (void)addImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index multiple:(CGFloat)multiple { 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.0f) { textAttachment.image = [UIImage imageWithCGImage:textAttachment.image.CGImage scale:multiple orientation:UIImageOrientationUp]; } else { 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]; } NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment]; [attributedString replaceCharactersInRange:NSMakeRange(index, 0) withAttributedString:attrStringWithImage]; self.attributedText = attributedString; // 記得走下這兩個方法 [self textViewTextChange]; [self refreshPlaceholderView]; } |
4、GitHub原始碼地址:UITextView-WZB
二、一句話實現一個可以自動計算年齡和星座的DatePicker
1、效果:
2、使用方法:
將WZBDatePicker.h和WZBDatePicker.m拖入工程
只需要在需要使用的地方直接匯入標頭檔案WZBDatePicker.h,然後呼叫下邊這個方法即可
1 2 3 4 |
/* 顯示方法 view:如果WZBDatePickerInputView傳擁有inputView的控制元件,其他傳普通的view,pickerType:顯示方式 resultDidChange:滑動picker的時候呼叫 block引數age:年齡,constellation:星座 **/ [WZBDatePicker showToView:self.view pickerType:WZBDatePickerNormal resultDidChange:^(NSString *age, NSString *constellation){ // to do }]; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
typedef NS_ENUM(NSInteger, WZBDatePickerType) { WZBDatePickerNormal = 0, // 普通的顯示在介面上 WZBDatePickerInputView = 1, // 做鍵盤顯示 WZBDatePickerBottomPop // 從檢視底部彈出 }; @interface WZBDatePicker : UIView /* 顯示方法 view:如果WZBDatePickerInputView傳擁有inputView的空間,其他傳普通的view,pickerType:顯示方式 resultDidChange:滑動picker的時候呼叫 block引數age:年齡,constellation:星座 **/ + (instancetype)showToView:(UIView *)view pickerType:(WZBDatePickerType)pickerType resultDidChange:(void(^)(NSString *age, NSString *constellation))resultDidChange; /* 設定初始時間 **/ - (void)setCurrentDate:(NSDate *)date; /* 隱藏 **/ - (void)hidden; /* 是否有黑色半透明遮罩,預設沒有,在pickerType!=WZBDatePickerBottomPop時不起任何作用 **/ @property (nonatomic, assign, getter=isEnableDarkMask) BOOL enableDarkMask; |
h檔案中提供了這些方法和屬性,註釋寫的很清楚,可以直接使用。
如果做鍵盤顯示,需要這樣:
1 2 3 4 |
/* 顯示方法 view:如果WZBDatePickerInputView傳擁有inputView的空間,其他傳普通的view,pickerType:顯示方式 resultDidChange:滑動picker的時候呼叫 block引數age:年齡,constellation:星座 **/ [WZBDatePicker showToView:self.textField pickerType:WZBDatePickerInputView resultDidChange:^(NSString *age, NSString *constellation){ self.textField.text = [NSString stringWithFormat:@"%@--%@", age, constellation]; }]; |
注意,引數view,必須傳帶有inputView的控制元件,比如UITextField或UITextView等
3、實現大致原理:
裡邊是一個tableView,tableView的headerView是一個label,顯示資訊。tableView的footerView是個UIDatePicker,顯示日期。這樣寫是因為比較好控制中間兩行cell的顯示與隱藏,headerView和footerView也不用單獨管理size
監聽picker的滾動,並重新整理tableView或者呼叫block更新時間,如果是鍵盤,隱藏了中間兩行cell,所以不需要重新整理tableView
1 2 3 4 |
- (void)dateDidChanged:(UIDatePicker *)datePicker { self.checkedDate = datePicker.date; self.pickerType == WZBDatePickerInputView ? [self upData] : [self.tableView reloadData]; } |
如果enableDarkMask為yes,背景顏色變成半透明顏色,並且啟用單擊手勢
1 2 3 4 5 6 |
- (void)setEnableDarkMask:(BOOL)enableDarkMask { _enableDarkMask = enableDarkMask; if (self.pickerType == WZBDatePickerBottomPop) { self.backgroundColor = self.isEnableDarkMask ? [UIColor colorWithWhite:0 alpha:0.2] : [UIColor clearColor]; } } |
4、GitHub原始碼地址:WZBDatePicker
三、一句話實現一個帶有文字閃動漸變的標籤
1、效果:
2、使用方法:
將WZBGradualLabel.h和WZBGradualLabel.m拖入工程
只需要在需要使用的地方直接匯入標頭檔案WZBGradualLabel.h,然後呼叫下邊這個方法即可
1 2 3 4 5 6 7 8 |
/* * frame:label的frame * title:text * duration:時間間隔 * gradualWidth:漸變顏色的寬 * superView:label的父控制元件 **/ [WZBGradualLabel gradualLabelWithFrame:(CGRect){10, 450, WZBScreenWidth - 20, 40} title:@"123456789012345678901234567890" duration:2 superview:self.view]; |
下邊是提供的一些屬性和方法
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 |
/* /* 漸變顏色色值,有預設值 **/ @property (nonatomic, strong) NSArray *gradualColors; /* * frame:label的frame * title:text * duration:時間間隔 * gradualWidth:漸變顏色的寬 * superView:label的父控制元件 **/ + (instancetype)gradualLabelWithFrame:(CGRect)frame title:(NSString *)title duration:(NSTimeInterval)duration gradualWidth:(CGFloat)gradualWidth superview:(UIView *)superview; /* * frame:label的frame * title:text * superView:label的父控制元件 **/ + (instancetype)gradualLabelWithFrame:(CGRect)frame title:(NSString *)title superview:(UIView *)superview; /* * frame:label的frame * title:text * duration:時間間隔 * superView:label的父控制元件 **/ + (instancetype)gradualLabelWithFrame:(CGRect)frame title:(NSString *)title duration:(NSTimeInterval)duration superview:(UIView *)superview; /* * frame:label的frame * title:text * gradualWidth:漸變顏色的寬 * superView:label的父控制元件 **/ + (instancetype)gradualLabelWithFrame:(CGRect)frame title:(NSString *)title gradualWidth:(CGFloat)gradualWidth superview:(UIView *)superview; |
h檔案中提供了這些方法和屬性,註釋寫的很清楚,所有方法可以直接使用。colors是個陣列,可以設定漸變層的顏色,至少傳入兩個值
3、實現大致原理:
其實看到層級圖,它是有三層的,最底層的label就是你要顯示的label,中間有層紅色的部分,這個其實是漸變層view(截圖沒截好),最上層的label是漸變層的文字
1 |
self.gradientLayer.mask = self.label.layer; |
這句話是設定漸變圖層的mask為label圖層,這樣就可以用文字裁剪漸變圖層了,只保留文字的部分
至於動畫改變,我用的是一個UIView動畫,動態改變中間層View和漸變層label的x值,view是新增在底層label上的,上層label是新增在中間view上的,先讓view在父控制元件的最左邊,上層label在此view的右邊,說白了就是讓上層label和底層label完全重合,為了維持這一狀態,中間的view向右走,上層label必須以同樣的值向左走!具體請看程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (void)changX { __block CGRect frame1 = self.leftView.frame; __block CGRect frame2 = self.label.frame; frame1.origin.x = -self.gradualWidth; frame2.origin.x = self.gradualWidth; self.leftView.frame = frame1; self.label.frame = frame2; [UIView animateWithDuration:self.duration animations:^{ frame1.origin.x = self.bounds.size.width; self.leftView.frame = frame1; frame2.origin.x = -self.bounds.size.width; self.label.frame = frame2; }]; } |
kvo監聽這些屬性,也是為了維持上層label和底層label能完全重合的狀態,以便支援使用者對label進行文字對其、文字改變、文字換行、字型大小、frame、換行以及attributedText等操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// kvo監聽屬性 for (NSString *property in self.propertys) { [self addObserver:self forKeyPath:property options:NSKeyValueObservingOptionNew context:nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"text"]) { self.label.text = self.text; } else if ([keyPath isEqualToString:@"font"]) { self.label.font = self.font; } else if ([keyPath isEqualToString:@"textAlignment"]) { self.label.textAlignment = self.textAlignment; } else if ([keyPath isEqualToString:@"attributedText"]) { self.label.attributedText = self.attributedText; } else if ([keyPath isEqualToString:@"frame"]) { self.label.frame = (CGRect){self.label.frame.origin, self.frame.size}; } else if ([keyPath isEqualToString:@"numberOfLines"]) { self.label.numberOfLines = self.numberOfLines; } } |
4、GitHub原始碼地址:WZBGradualLabel
四、一行程式碼畫一個表格,UIView的分類,可以很簡單的畫擅長表格
1、效果:
很多app用到了這種html元素效果,寫了個demo
效果如下:
2、使用方法:
只需要一行程式碼 ,可以自定義文字顏色、背景顏色、合併單元格、點選事件等操作
1 2 3 4 5 6 7 |
/** * 建立一個表格 * line:列數 * columns:行數 * data:資料 */ [v1 wzb_drawListWithRect:v1.bounds line:4 columns:3 datas:@[@"", @"語文", @"數學", @"英語", @"王曉明", @"100.5", @"128", @"95", @"李小華", @"100.5", @"128", @"95", @"張愛奇", @"100.5", @"128", @"95"]]; |
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 |
/** * 建立一個表格 * line:列數 * columns:行數 * data:資料 */ - (void)wzb_drawListWithRect:(CGRect)rect line:(NSInteger)line columns:(NSInteger)columns datas:(NSArray *)datas; /** * 建立一個表格 * line:列數 * columns:行數 * data:資料 * lineInfo:行資訊,傳入格式:@{@"0" : @"3"}意味著第一行建立3個格子 */ - (void)wzb_drawListWithRect:(CGRect)rect line:(NSInteger)line columns:(NSInteger)columns datas:(NSArray *)datas lineInfo:(NSDictionary *)lineInfo; /** * 建立一個表格 * line:列數 * columns:行數 * data:資料 * colorInfo:顏色資訊,傳入格式:@{@"0" : [UIColor redColor]}意味著第一個格子文字將會變成紅色 */ - (void)wzb_drawListWithRect:(CGRect)rect line:(NSInteger)line columns:(NSInteger)columns datas:(NSArray *)datas colorInfo:(NSDictionary *)colorInfo; /** * 建立一個表格 * line:列數 * columns:行數 * data:資料 * colorInfo:顏色資訊,傳入格式:@{@"0" : [UIColor redColor]}意味著第一個格子文字將會變成紅色 * lineInfo:行資訊,傳入格式:@{@"0" : @"3"}意味著第一行建立3個格子 */ - (void)wzb_drawListWithRect:(CGRect)rect line:(NSInteger)line columns:(NSInteger)columns datas:(NSArray *)datas colorInfo:(NSDictionary *)colorInfo lineInfo:(NSDictionary *)lineInfo; /** * 建立一個表格 * line:列數 * columns:行數 * data:資料 * colorInfo:顏色資訊,傳入格式:@{@"0" : [UIColor redColor]}意味著第一個格子文字將會變成紅色 * lineInfo:行資訊,傳入格式:@{@"0" : @"3"}意味著第一行建立3個格子 * backgroundColorInfo:行資訊,傳入格式:@{@"0" : [UIColor redColor]}意味著第一個格子背景顏色變成紅色 */ - (void)wzb_drawListWithRect:(CGRect)rect line:(NSInteger)line columns:(NSInteger)columns datas:(NSArray *)datas colorInfo:(NSDictionary *)colorInfo lineInfo:(NSDictionary *)lineInfo backgroundColorInfo:(NSDictionary *)backgroundColorInfo; /** * 獲取第index個格子的label */ - (UILabel *)getLabelWithIndex:(NSInteger)index; /** * 畫一條線 * frame: 線的frame * color:線的顏色 * lineWidth:線寬 */ - (void)wzb_drawLineWithFrame:(CGRect)frame lineType:(WZBLineType)lineType color:(UIColor *)color lineWidth:(CGFloat)lineWidth; |
.h檔案中提供了這些方法,用法看註釋即可,getLabelWithIndex:方法是得到第index個格子,比如你有10個格子,[view getLabelWithIndex:0];是得到第一個格子,可以對其進行任何操作,比如我在第三個表格中拿到右邊三個label,新增單擊手勢進行跳轉
3、實現大致原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/** * 建立一個表格 * line:列數 * columns:行數 * data:資料 * colorInfo:顏色資訊,傳入格式:@{@"0" : [UIColor redColor]}意味著第一個格子文字將會變成紅色 * lineInfo:行資訊,傳入格式:@{@"0" : @"3"}意味著第一行建立3個格子 * backgroundColorInfo:行資訊,傳入格式:@{@"0" : [UIColor redColor]}意味著第一個格子背景顏色變成紅色 */ - (void)wzb_drawListWithRect:(CGRect)rect line:(NSInteger)line columns:(NSInteger)columns datas:(NSArray *)datas colorInfo:(NSDictionary *)colorInfo lineInfo:(NSDictionary *)lineInfo backgroundColorInfo:(NSDictionary *)backgroundColorInfo { NSInteger index = 0; CGFloat x = rect.origin.x; CGFloat y = rect.origin.y; CGFloat h = (1.0) * rect.size.height / columns; NSInteger newLine = 0; for (NSInteger i = 0; i |
這是核心程式碼,其中做了合併單元格、文字顏色、背景顏色、等判斷。畫線是用的貝塞爾曲線和CAShapeLayer,程式碼在下邊,每個單元格里都建立了label顯示文字。我還提供了方法畫一條直線,相信專案中很多頁面某些位置需要畫一條直線,
- (void)wzb_drawLineWithFrame:(CGRect)frame lineType:(WZBLineType)lineType color:(UIColor *)color lineWidth:(CGFloat)lineWidth
這個方法能夠很方便的實現這一需求
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 |
- (void)wzb_drawLineWithFrame:(CGRect)frame lineType:(WZBLineType)lineType color:(UIColor *)color lineWidth:(CGFloat)lineWidth { // 建立貝塞爾曲線 UIBezierPath *linePath = [[UIBezierPath alloc] init]; // 線寬 linePath.lineWidth = lineWidth; // 起點 [linePath moveToPoint:CGPointMake(0, 0)]; // 重點:判斷是水平方向還是垂直方向 [linePath addLineToPoint: lineType == WZBLineHorizontal ? CGPointMake(frame.size.width, 0) : CGPointMake(0, frame.size.height)]; // 建立CAShapeLayer CAShapeLayer *lineLayer = [CAShapeLayer layer]; // 顏色 lineLayer.strokeColor = color.CGColor; // 寬度 lineLayer.lineWidth = lineWidth; // frame lineLayer.frame = frame; // 路徑 lineLayer.path = linePath.CGPath; // 新增到layer上 [self.layer addSublayer:lineLayer]; } |
下邊畫了一下隨機顏色的線條,最終效果是這樣:
4、GitHub原始碼地址:UIView-WZB