在花了太多时间尝试为简单的 textField 设置 MDCTextField 之后,我决定开发自己的子类,其中 i) 上边缘的动画占位符,ii) 信息标签和 iii) 底部的动画线。
它是 UITextField 的子类,因此使用 UITextFieldDelegate 方法一切正常
使用textField.placehoderText = "Placeholder"
和
textfield.infolabel.text = "Warning"
设置辅助文本。动画是自动的。
passwordTextField = AGTextField()
passwordTextField.delegate = self
passwordTextField.autocapitalizationType = .none
passwordTextField.keyboardType = UIKeyboardType.default
passwordTextField.isSecureTextEntry = true
passwordTextField.placehoderText = "Password"
self.view.addSubview(passwordTextField)
passwordTextField.addAnchorsAndCenter(centerX: true, centerY: nil, width: width, height: 30, left: nil, top: 30, right: nil, bottom: nil, withAnchor: .top, relativeToView: emailTextField)
附加子类和自动布局助手
import UIKit
class AGTextField: UITextField {
var animationDuration: TimeInterval = 0.3
var placehoderText: String = "" {
didSet {
placeholderLabel.text = placehoderText
}
}
var placeholderLabel: UILabel!
var infoLabel: UILabel!
var activeColor: UIColor = .gray {
didSet {
line.backgroundColor = activeColor
}
}
private let placeholderColor = UIColor.gray
private let infoColor = UIColor.red
private var line: UIView!
convenience init() {
self.init(frame: CGRect.zero)
self.tintColor = activeColor
self.font = UIFont(name: Fonts.regular, size: 16)
line = UIView()
line.backgroundColor = activeColor
self.addSubview(line)
line.addAnchorsAndSize(width: nil, height: 1.5, left: 0, top: nil, right: 0, bottom: 0)
placeholderLabel = UILabel()
placeholderLabel.font = UIFont(name: Fonts.regular, size: 16)
placeholderLabel.textColor = .lightGray
self.addSubview(placeholderLabel)
Timer.scheduledTimer(withTimeInterval: 0.01, repeats: false) { (nil) in
self.placeholderLabel.frame = self.bounds
}
infoLabel = UILabel()
infoLabel.font = UIFont(name: Fonts.regular, size: 12)
infoLabel.textColor = .red
infoLabel.isHidden = true
self.addSubview(infoLabel)
infoLabel.addAnchors(left: 0, top: 8, right: nil, bottom: nil, withAnchor: .top, relativeToView: line)
NotificationCenter.default.addObserver(self, selector: #selector(textFieldDidBeginEditing(notification:)), name: NSNotification.Name.UITextFieldTextDidBeginEditing, object: self)
NotificationCenter.default.addObserver(self, selector: #selector(textFieldDidEndEditing(notification:)), name: NSNotification.Name.UITextFieldTextDidEndEditing, object: self)
}
@objc func textFieldDidBeginEditing(notification: Notification) {
UIView.animate(withDuration: animationDuration) {
self.placeholderLabel.frame.origin.y = self.line.frame.origin.y - 50.0
self.placeholderLabel.font = UIFont(name: Fonts.regular, size: 12)
self.line.backgroundColor = .gray
}
line.backgroundColor = Colors.accent
infoLabel.isHidden = true
}
@objc func textFieldDidEndEditing(notification: Notification) {
if self.text?.count == 0 {
UIView.animate(withDuration: animationDuration) {
self.placeholderLabel.frame = self.bounds
self.placeholderLabel.font = UIFont(name: Fonts.regular, size: 16)
}
}
self.line.backgroundColor = .gray
}
func showInfo(_ text: String) {
infoLabel.text = text
infoLabel.isHidden = false
}
func hideInfo() {
infoLabel.isHidden = true
}
}
我还添加了我的自动布局 UIView 扩展
import UIKit
enum Anchor { case left, top, right, bottom }
extension UIView {
/**
Description: Centers the view in the superview, using the superview's **size** and **XYAxis** position, not the left, top, right, bottom anchors to avoid issues with the *UISCrollViews*
Parameters: None
*/
func addAnchorsCenterAndFillContainer() {
self.translatesAutoresizingMaskIntoConstraints = false
self.widthAnchor.constraint(equalTo: self.superview!.widthAnchor).isActive = true
self.heightAnchor.constraint(equalTo: self.superview!.heightAnchor).isActive = true
self.centerXAnchor.constraint(equalTo: self.superview!.centerXAnchor).isActive = true
self.centerYAnchor.constraint(equalTo: self.superview!.centerYAnchor).isActive = true
}
/**
Adds 2 optional alignment parameterts (**centerX**, **centerY**), 2 optional size dimensions (**width** and **height**) and up to 4 border anchors **.left**, **.top**, **.right** and **.bottom**. One of them (defined in **withAnchor** can be relative to another view
- Parameter centerX: **Bool** value (or *nil*) to define if the view should be centered **horizontally** to the superview. (optional)
- Parameter centerY: **Bool** value (or *nil*) to define if the view should be centered **vertically** to the superview. (optional)
- Parameter width: The **width** of the view (optional)
- Parameter width: The **width** of the view (optional)
- Parameter height: The **height** of the view (optional)
- Parameter left: The **left** margin to the superview
- Parameter top: The **top** margin to the superview
- Parameter right: The **right** margin to the superview. *Magniture adjusted to be positive for margins inside the view*
- Parameter bottom: The **bottom** margin to the superview. *Magniture adjusted to be positive for margins inside the view*
- Parameter withAnchor: The **Anchor** type that is relative to the **relativeToView** view. *This parameter can be omited*
- Parameter relativeToView: The **UIView** object that is the reference for the **withAnchor** anchor. *This parameter can be omited*
- Returns: None
*/
func addAnchorsAndCenter(centerX: Bool?, centerY: Bool?, width: CGFloat?, height: CGFloat?, left: CGFloat?, top: CGFloat?, right: CGFloat?, bottom: CGFloat?, withAnchor: Anchor? = nil, relativeToView: UIView? = nil) {
self.translatesAutoresizingMaskIntoConstraints = false
if centerX != nil {
if centerX! == true {
self.centerXAnchor.constraint(equalTo: self.superview!.centerXAnchor).isActive = true
}
}
if centerY != nil {
if centerY! == true {
self.centerYAnchor.constraint(equalTo: self.superview!.centerYAnchor).isActive = true
}
}
self.addAnchorsAndSize(width: width, height: height, left: left, top: top, right: right, bottom: bottom, withAnchor: withAnchor, relativeToView: relativeToView)
}
/**
Adds 2 optional size dimensions (**width** and **height**) and up to 4 border anchors **.left**, **.top**, **.right** and **.bottom**. One of them (defined in **withAnchor** can be relative to another view
- Parameter width: The **width** of the view (optional)
- Parameter height: The **height** of the view (optional)
- Parameter left: The **left** margin to the superview
- Parameter top: The **top** margin to the superview
- Parameter right: The **right** margin to the superview. *Magniture adjusted to be positive for margins inside the view*
- Parameter bottom: The **bottom** margin to the superview. *Magniture adjusted to be positive for margins inside the view*
- Parameter withAnchor: The **Anchor** type that is relative to the **relativeToView** view. *This parameter can be omited*
- Parameter relativeToView: The **UIView** object that is the reference for the **withAnchor** anchor. *This parameter can be omited*
- Returns: None
*/
func addAnchorsAndSize(width: CGFloat?, height: CGFloat?, left: CGFloat?, top: CGFloat?, right: CGFloat?, bottom: CGFloat?, withAnchor: Anchor? = nil, relativeToView: UIView? = nil) {
self.translatesAutoresizingMaskIntoConstraints = false
if width != nil {
self.widthAnchor.constraint(equalToConstant: width!).isActive = true
}
if height != nil {
self.heightAnchor.constraint(equalToConstant: height!).isActive = true
}
self.addAnchors(left: left, top: top, right: right, bottom: bottom, withAnchor: withAnchor, relativeToView: relativeToView)
}
/**
Adds up to 4 border anchors **.left**, **.top**, **.right** and **.bottom**. One of them (defined in **withAnchor** can be relative to another view
- Parameter left: The **left** margin to the superview
- Parameter top: The **top** margin to the superview
- Parameter right: The **right** margin to the superview. *Magniture adjusted to be positive for margins inside the view*
- Parameter bottom: The **bottom** margin to the superview. *Magniture adjusted to be positive for margins inside the view*
- Parameter withAnchor: The **Anchor** type that is relative to the **relativeToView** view. *This parameter can be omited*
- Parameter relativeToView: The **UIView** object that is the reference for the **withAnchor** anchor. *This parameter can be omited*
- Returns: None
*/
func addAnchors(left: CGFloat?, top: CGFloat?, right: CGFloat?, bottom: CGFloat?, withAnchor: Anchor? = nil, relativeToView: UIView? = nil) {
self.translatesAutoresizingMaskIntoConstraints = false
let superView = self.superview!
if withAnchor != nil && relativeToView != nil {
/**
* Anchors relative to oposite anchors of reference view
**/
switch withAnchor! {
case .left:
if left != nil {
self.leftAnchor.constraint(equalTo: relativeToView!.rightAnchor, constant: left!).isActive = true
}
case .top:
if top != nil {
self.topAnchor.constraint(equalTo: relativeToView!.bottomAnchor, constant: top!).isActive = true
}
case .right:
if right != nil {
self.rightAnchor.constraint(equalTo: relativeToView!.leftAnchor, constant: -right!).isActive = true
}
case .bottom:
if bottom != nil {
self.bottomAnchor.constraint(equalTo: relativeToView!.topAnchor, constant: -bottom!).isActive = true
}
}
}
/**
* Anchors relative to same anchors of superview
**/
if let _anchor = withAnchor {
if _anchor == .left {
if top != nil {
self.topAnchor.constraint(equalTo: superView.topAnchor, constant: top!).isActive = true
}
if right != nil {
self.rightAnchor.constraint(equalTo: superView.rightAnchor, constant: -right!).isActive = true
}
if bottom != nil {
self.bottomAnchor.constraint(equalTo: superView.bottomAnchor, constant: -bottom!).isActive = true
}
}
if _anchor == .top {
if left != nil {
self.leftAnchor.constraint(equalTo: superView.leftAnchor, constant: left!).isActive = true
}
if right != nil {
self.rightAnchor.constraint(equalTo: superView.rightAnchor, constant: -right!).isActive = true
}
if bottom != nil {
self.bottomAnchor.constraint(equalTo: superView.bottomAnchor, constant: -bottom!).isActive = true
}
}
if _anchor == .right {
if left != nil {
self.leftAnchor.constraint(equalTo: superView.leftAnchor, constant: left!).isActive = true
}
if top != nil {
self.topAnchor.constraint(equalTo: superView.topAnchor, constant: top!).isActive = true
}
if bottom != nil {
self.bottomAnchor.constraint(equalTo: superView.bottomAnchor, constant: -bottom!).isActive = true
}
}
if _anchor == .bottom {
if left != nil {
self.leftAnchor.constraint(equalTo: superView.leftAnchor, constant: (left!)).isActive = true
}
if top != nil {
self.topAnchor.constraint(equalTo: superView.topAnchor, constant: top!).isActive = true
}
if right != nil {
self.rightAnchor.constraint(equalTo: superView.rightAnchor, constant: -right!).isActive = true
}
}
} else {
if left != nil {
self.leftAnchor.constraint(equalTo: superView.leftAnchor, constant: left!).isActive = true
}
if top != nil {
self.topAnchor.constraint(equalTo: superView.topAnchor, constant: top!).isActive = true
}
if right != nil {
self.rightAnchor.constraint(equalTo: superView.rightAnchor, constant: -right!).isActive = true
}
if bottom != nil {
self.bottomAnchor.constraint(equalTo: superView.bottomAnchor, constant: -bottom!).isActive = true
}
}
}
}