#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的更多相关文章

  1. 自定义TextView 调用ttf格式字体

    自定义TextView 调用ttf格式字体 1.<strong>将ttf格式文件存放在assets/fonts/下</strong> 注:PC系统字体存放在C:\Windows ...

  2. [原创]Android秒杀倒计时自定义TextView

    自定义TextView控件TimeTextView代码: import android.content.Context; import android.content.res.TypedArray; ...

  3. ios开发之自定义textView

    自定义textView,从理论上讲很简单,根据需求自定义,比如我在开发中的需求就是现实一个字数的限制以及根据输入的文字改变提示剩余字数,那么开始我的基本思路就是自定义一个View,而里面包含一个子控件 ...

  4. 安卓自定义TextView实现自动滚动

    xml文件代码 <com.mobile.APITest.ScrollEditText android:id="@+id/statusEditText" android:lay ...

  5. Android开发学习笔记-自定义TextView属性模版

    如果项目中有很多个控件使用的是同一种样式,则为了方便,可以将样式设置到系统中去,这样使用的时候会方便很多. 下面是自定义样式模版的方法. 1.在style.xml文件中添加自己要设置的样式内容 < ...

  6. 自定义TextView带有各类.ttf字体的TextView

    最近项目遇到了将普通文字转化为带有字体样式的文字,这里就涉及到了.ttf文件,我上网百度了不少资料最终终于实现了,现在想想其实并不复杂 1,你需要下载一种.ttf字体文件,你可以从网上找到一种字体的. ...

  7. Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果

    一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...

  8. 练习,自定义TextView(1.1)

    重新自定义TextView是非常有趣的事情,跟着Android4高级编程,通过自定义TextView,来敲一下代码: 这个是那么的简单,自定义TextView,新建CustomTextView继承Te ...

  9. [置顶] android 自定义TextView

    系统自带的控件TextView有时候没满一行就换行了,为了解决这个问题,自定义了一个TextView,只有一行显示不完全的情况下才会去换行显示,代码如下: package com.open.textv ...

  10. Android自定义View(一、初体验自定义TextView)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51454685 本文出自:[openXu的博客] 目录: 继承View重写onDraw方法 自 ...

随机推荐

  1. [c/c++] programming之路(20)、字符串(一)

    一.字符串 #include<stdio.h> #include<stdlib.h> void main(){ ]="notepad"; printf(&q ...

  2. 剑指offer(60)把二叉树打印成多行

    题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行. 题目分析 从上到下打印二叉树我们知道用队列可以实现,但是如果多行打印怎么做呢? 我们需要分割,在行与行之间进行分割.如何分割 ...

  3. JS设计模式(3)代理模式

    什么是代理模式? 情景:小明追女生 A 非代理模式:小明 =花=> 女生A 代理模式:小明 =花=> 让女生A的好友B帮忙 =花=> 女生A 定义:为其他对象提供一种代理以控制对这个 ...

  4. gulp的安装与使用【附配置代码】

    备忘 1.配置 下载安装node.js node -v //检查nodejs版本   npm(nodejs package manager)nodejs包管理工具   nodejs完毕在命令行输入np ...

  5. https连接器

    非对称性加密:A生成一份公私钥,将公钥交给需要进行数据传输的B,B发送数据时先用公钥对数据进行加密,然后发送给A,再由A使用私钥进行解密. 但存在漏洞即B如何确认公钥是由A提供的.因此需要一个第三方机 ...

  6. spring boot 2整合mybatis

    mybatis-spring-boot-starter主要有两种解决方案,一种是使用注解,一种是使用XML. 参考这篇文章动手跑了一个例子,稍微不同之处,原文是spring boot,这里改成了spr ...

  7. HDFS数据节点DataNode未启动解决方法

    在解决这个问题的过程中,我又是积累了不少经验... 首先让我搞了很久的问题是,书上说进程全部启动的命令是/bin/start-all.sh,但是当我执行的时候显示command not found.后 ...

  8. PHP isset 和 array_key_exists 对比

    经常使用 isset 判断变量或数组中的键是否存在,但是数组中可以使用 array_key_exists 这个函数,那么这两个 哪一个更优呢? 官方文档这样定义两者: isset:语言构造器,用于检测 ...

  9. Springboot解决war包放到Tomcat服务器上404的特殊情况

    Springboot解决war包放到Tomcat服务器上404的特殊情况 原文链接:https://www.cnblogs.com/blog5277/p/9330577.html 原文作者:博客园-- ...

  10. 对啊英语音标---二、ghywr这些辅音怎么发音

    对啊英语音标---二.ghywr这些辅音怎么发音 一.总结 一句话总结:对比法,和汉语拼音做对比 对比法,和汉语拼音做对比 1.清辅音和浊辅音的区别是什么? 清辅音-[不需要声带震动]就能发出的音节: ...