自定义textview
#import <UIKit/UIKit.h>
@class FSTextView;
typedef void(^FSTextViewHandler)(FSTextView *textView);
IB_DESIGNABLE
@interface FSTextView : UITextView
/**
便利构造器.
*/
+ (instancetype)textView;
/**
设定文本改变Block回调. (切记弱化引用, 以免造成内存泄露.)
*/
- (void)addTextDidChangeHandler:(FSTextViewHandler)eventHandler;
/**
设定达到最大长度Block回调. (切记弱化引用, 以免造成内存泄露.)
*/
- (void)addTextLengthDidMaxHandler:(FSTextViewHandler)maxHandler;
/**
最大限制文本长度, 默认为无穷大, 即不限制, 如果被设为 0 也同样表示不限制字符数.
*/
@property (nonatomic, assign) IBInspectable NSUInteger maxLength;
/**
圆角半径.
*/
@property (nonatomic, assign) IBInspectable CGFloat cornerRadius;
/**
边框宽度.
*/
@property (nonatomic, assign) IBInspectable CGFloat borderWidth;
/**
边框颜色.
*/
@property (nonatomic, strong) IBInspectable UIColor *borderColor;
/**
placeholder, 会自适应TextView宽高以及横竖屏切换, 字体默认和TextView一致.
*/
@property (nonatomic, copy) IBInspectable NSString *placeholder;
/**
placeholder文本颜色, 默认为#C7C7CD.
*/
@property (nonatomic, strong) IBInspectable UIColor *placeholderColor;
/**
placeholder文本字体, 默认为UITextView的默认字体.
*/
@property (nonatomic, strong) UIFont *placeholderFont;
/**
是否允许长按弹出UIMenuController, 默认为YES.
*/
@property (nonatomic, assign, getter=isCanPerformAction) BOOL canPerformAction;
/**
该属性返回一个经过处理的 `self.text` 的值, 去除了首位的空格和换行.
*/
@property (nonatomic, readonly) NSString *formatText;
@end
#import "FSTextView.h"
CGFloat const kFSTextViewPlaceholderVerticalMargin = 8.0; ///< placeholder垂直方向边距
CGFloat const kFSTextViewPlaceholderHorizontalMargin = 6.0; ///< placeholder水平方向边距
@interface FSTextView ()
@property (nonatomic, copy) FSTextViewHandler changeHandler; ///< 文本改变Block
@property (nonatomic, copy) FSTextViewHandler maxHandler; ///< 达到最大限制字符数Block
@property (nonatomic, strong) UILabel *placeholderLabel; ///< placeholderLabel
@end
@implementation FSTextView
#pragma mark - Override
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
_changeHandler = NULL;
_maxHandler = NULL;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (!(self = [super initWithCoder:aDecoder])) return nil;
if ([[[UIDevice currentDevice] systemVersion] compare:@"10.0" options:NSNumericSearch] != NSOrderedAscending) {
[self layoutIfNeeded];
}
[self initialize];
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame])) return nil;
[self initialize];
return self;
}
- (BOOL)becomeFirstResponder
{
BOOL become = [super becomeFirstResponder];
// 成为第一响应者时注册通知监听文本变化
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:UITextViewTextDidChangeNotification object:nil];
return become;
}
- (BOOL)resignFirstResponder
{
BOOL resign = [super resignFirstResponder];
// 注销第一响应者时移除文本变化的通知, 以免影响其它的`UITextView`对象.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:nil];
return resign;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
BOOL result = [super canPerformAction:action withSender:sender];
if (result) {
if (![self respondsToSelector:action]) {
result = NO;
} else {
result = _canPerformAction;
}
}
return result;
}
#pragma mark - Private
- (void)initialize
{
// 基本配置 (需判断是否在Storyboard中设置了值)
_canPerformAction = YES;
if (_maxLength == 0 || _maxLength == NSNotFound) {
_maxLength = NSUIntegerMax;
}
if (!_placeholderColor) {
_placeholderColor = [UIColor colorWithRed:0.780 green:0.780 blue:0.804 alpha:1.000];
}
// 基本设定 (需判断是否在Storyboard中设置了值)
if (!self.backgroundColor) {
self.backgroundColor = [UIColor whiteColor];
}
if (!self.font) {
self.font = [UIFont systemFontOfSize:15.f];
}
// placeholderLabel
self.placeholderLabel.font = self.font;
self.placeholderLabel.text = _placeholder; // 可能在Storyboard中设置了Placeholder
self.placeholderLabel.textColor = _placeholderColor;
[self addSubview:self.placeholderLabel];
// constraint
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:kFSTextViewPlaceholderVerticalMargin]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderLabel
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:kFSTextViewPlaceholderHorizontalMargin]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:-kFSTextViewPlaceholderHorizontalMargin*2]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.placeholderLabel
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:-kFSTextViewPlaceholderVerticalMargin*2]];
}
#pragma mark - Getter
/// 返回一个经过处理的 `self.text` 的值, 去除了首位的空格和换行.
- (NSString *)formatText
{
return [[super text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // 去除首尾的空格和换行.
}
- (UILabel *)placeholderLabel
{
if (!_placeholderLabel) {
_placeholderLabel = [[UILabel alloc] init];
_placeholderLabel.numberOfLines = 0;
_placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO;
}
return _placeholderLabel;
}
#pragma mark - Setter
- (void)setText:(NSString *)text
{
[super setText:text];
self.placeholderLabel.hidden = [@(text.length) boolValue];
// 手动模拟触发通知
NSNotification *notification = [NSNotification notificationWithName:UITextViewTextDidChangeNotification object:self];
[self textDidChange:notification];
}
- (void)setFont:(UIFont *)font
{
[super setFont:font];
self.placeholderLabel.font = font;
}
- (void)setMaxLength:(NSUInteger)maxLength
{
_maxLength = fmax(0, maxLength);
self.text = self.text;
}
- (void)setCornerRadius:(CGFloat)cornerRadius
{
_cornerRadius = cornerRadius;
self.layer.cornerRadius = _cornerRadius;
}
- (void)setBorderColor:(UIColor *)borderColor
{
if (!borderColor) return;
_borderColor = borderColor;
self.layer.borderColor = _borderColor.CGColor;
}
- (void)setBorderWidth:(CGFloat)borderWidth
{
_borderWidth = borderWidth;
self.layer.borderWidth = _borderWidth;
}
- (void)setPlaceholder:(NSString *)placeholder
{
if (!placeholder) return;
_placeholder = [placeholder copy];
if (_placeholder.length > 0) {
self.placeholderLabel.text = _placeholder;
}
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
if (!placeholderColor) return;
_placeholderColor = placeholderColor;
self.placeholderLabel.textColor = _placeholderColor;
}
- (void)setPlaceholderFont:(UIFont *)placeholderFont
{
if (!placeholderFont) return;
_placeholderFont = placeholderFont;
self.placeholderLabel.font = _placeholderFont;
}
#pragma mark - NSNotification
- (void)textDidChange:(NSNotification *)notification
{
// 通知回调的实例的不是当前实例的话直接返回
if (notification.object != self) return;
// 根据字符数量显示或者隐藏 `placeholderLabel`
self.placeholderLabel.hidden = [@(self.text.length) boolValue];
// 禁止第一个字符输入空格或者换行
if (self.text.length == 1) {
if ([self.text isEqualToString:@" "] || [self.text isEqualToString:@"\n"]) {
self.text = @"";
}
}
// 只有当maxLength字段的值不为无穷大整型也不为0时才计算限制字符数.
if (_maxLength != NSUIntegerMax && _maxLength != 0 && self.text.length > 0) {
if (!self.markedTextRange && self.text.length > _maxLength) {
!_maxHandler ?: _maxHandler(self); // 回调达到最大限制的Block.
self.text = [self.text substringToIndex:_maxLength]; // 截取最大限制字符数.
[self.undoManager removeAllActions]; // 达到最大字符数后清空所有 undoaction, 以免 undo 操作造成crash.
}
}
// 回调文本改变的Block.
!_changeHandler ?: _changeHandler(self);
}
#pragma mark - Public
+ (instancetype)textView
{
return [[self alloc] init];
}
- (void)addTextDidChangeHandler:(FSTextViewHandler)changeHandler
{
_changeHandler = [changeHandler copy];
}
- (void)addTextLengthDidMaxHandler:(FSTextViewHandler)maxHandler
{
_maxHandler = [maxHandler copy];
}
@end
自定义textview的更多相关文章
- 自定义TextView 调用ttf格式字体
自定义TextView 调用ttf格式字体 1.<strong>将ttf格式文件存放在assets/fonts/下</strong> 注:PC系统字体存放在C:\Windows ...
- [原创]Android秒杀倒计时自定义TextView
自定义TextView控件TimeTextView代码: import android.content.Context; import android.content.res.TypedArray; ...
- ios开发之自定义textView
自定义textView,从理论上讲很简单,根据需求自定义,比如我在开发中的需求就是现实一个字数的限制以及根据输入的文字改变提示剩余字数,那么开始我的基本思路就是自定义一个View,而里面包含一个子控件 ...
- 安卓自定义TextView实现自动滚动
xml文件代码 <com.mobile.APITest.ScrollEditText android:id="@+id/statusEditText" android:lay ...
- Android开发学习笔记-自定义TextView属性模版
如果项目中有很多个控件使用的是同一种样式,则为了方便,可以将样式设置到系统中去,这样使用的时候会方便很多. 下面是自定义样式模版的方法. 1.在style.xml文件中添加自己要设置的样式内容 < ...
- 自定义TextView带有各类.ttf字体的TextView
最近项目遇到了将普通文字转化为带有字体样式的文字,这里就涉及到了.ttf文件,我上网百度了不少资料最终终于实现了,现在想想其实并不复杂 1,你需要下载一种.ttf字体文件,你可以从网上找到一种字体的. ...
- Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果
一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...
- 练习,自定义TextView(1.1)
重新自定义TextView是非常有趣的事情,跟着Android4高级编程,通过自定义TextView,来敲一下代码: 这个是那么的简单,自定义TextView,新建CustomTextView继承Te ...
- [置顶] android 自定义TextView
系统自带的控件TextView有时候没满一行就换行了,为了解决这个问题,自定义了一个TextView,只有一行显示不完全的情况下才会去换行显示,代码如下: package com.open.textv ...
- Android自定义View(一、初体验自定义TextView)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51454685 本文出自:[openXu的博客] 目录: 继承View重写onDraw方法 自 ...
随机推荐
- 剑指offer(32)把数组排成最小的数
题目描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323. 题目分析 主 ...
- 对负载均衡的理解及nginx负载均衡的配置
https://blog.csdn.net/qq_28602957/article/details/61615876
- 彻底删除windows残留启动引导
在win7/Win8系统下安装其他系统或者一键重装系统后,安装的系统删除或者一键重装文件删除了,在windows启动管理器中还残留了启动引导选项,影响开机效率. 在系统配置中有些"引导&qu ...
- Vue提供操作DOM的方法
<div ref="wrapper"> Vue.js 提供了我们一个获取 DOM 对象的接口—— vm.$refs.在这里,我们通过了 this.$refs.wrapp ...
- docker从容器里面拷文件到宿主机或从宿主机拷文件到docker容器里面
1.从容器里面拷文件到宿主机? 答:在宿主机里面执行以下命令 docker cp 容器名:要拷贝的文件在容器里面的路径 要拷贝到宿主机的相应路径 示例: 假设容器名为testtomcat, ...
- vue--vux框架的使用
<1>. 在项目里安装vux npm install vux --save <2>. 安装vux-loader npm install vux-loader --save-de ...
- 力扣(LeetCode)605. 种花问题
假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有.可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去. 给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花 ...
- leecode第二百零六题(反转链表)
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode ...
- Android中使用Thread线程与AsyncTask异步任务的区别
最近和几个朋友交流Android开发中的网络下载问题时,谈到了用Thread开启下载线程时会产生的Bug,其实直接用子线程开启下载任务的确是很Low的做法,那么原因究竟如何,而比较高大上的做法是怎样? ...
- Annotaion——深入理解注解类型
什么是注解? 对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解.它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metada ...