Полезности и нужна помощь))


#1

Доброго времени суток))

Нужна помощь!

В общем пытаюсь сделать UITextView на максималках, с нижним и верхним плейсхолдером и пока все работает, но потерялся в анимации перехода плейсхолдера из текста в текствью на верх!

Код что ниже сыроват)) И очень буду благодарен за правки по фрейма, и за советы или может за доработку анимации, всем спасибо)))

@IBDesignable class CustomTextView: UITextView, UITextViewDelegate {
    
    @IBInspectable var textColor_: UIColor = .black
    
    //set text indents
    @IBInspectable var fieldTopIndent: CGFloat = 4
    
    @IBInspectable var fieldBottomIndent: CGFloat = 4
    
    @IBInspectable var fieldRightIndent: CGFloat = 4
    
    @IBInspectable var fieldLeftIndent: CGFloat {
        get { return textContainerInset.left }
        set { textContainerInset.left = newValue }
    }
    
    
    
    //set top placeholder label style parameters
    
    @IBInspectable var placeholderText: String {
        get { return placeholderLabel.text ?? "" }
        set { placeholderLabel.text = newValue }
    }
    
    @IBInspectable var placeholderTextColor: UIColor {
        get { return placeholderLabel.textColor }
        set { placeholderLabel.textColor = newValue }
    }
    
    @IBInspectable var placeholderFont: UIFont {
        get { return placeholderLabel.font }
        set { placeholderLabel.font = newValue }
    }
    
    
    //set bottom placeholder label parameters
    
    @IBInspectable var bottomPlaceholderText: String {
        get { return bottomPlaceholderLabel.text ?? "" }
        set { bottomPlaceholderLabel.text = newValue }
    }
    
    @IBInspectable var bottomPlaceholderTextColor: UIColor {
        get { return bottomPlaceholderLabel.textColor }
        set { bottomPlaceholderLabel.textColor = newValue }
    }
    
    @IBInspectable var bottomPlaceholderFont: UIFont {
        get { return bottomPlaceholderLabel.font }
        set { bottomPlaceholderLabel.font = newValue }
    }
    
    @IBInspectable var bottomPlaceholderTopIndent: CGFloat = 4
    
    
    //set separator style
    
    @IBInspectable var separatorHeight: CGFloat = 0
    
    @IBInspectable var separatorColor: UIColor? {
        get { return separator.backgroundColor }
        set { separator.backgroundColor = newValue }
    }
    
    
    //set clear button image
    @IBInspectable var clearButtonHeight: CGFloat = 8
    @IBInspectable var clearButtonRigthIndent: CGFloat = 4
    @IBInspectable var withClearButton: Bool = true
    
    @IBInspectable var clearButtonImage: UIImage? {
        get { return clearButton.imageView?.image }
        set { clearButton.setImage(newValue, for: []) }
    }
    
    private var clearImage: UIImage? {
        guard
            let data = Data(base64Encoded: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAAGXcA1uAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAGKADAAQAAAABAAAAGAAAAADiNXWtAAABf0lEQVRIDa2TS04EMQxEBzYsucFwHm4ViQOxA4m7MOdgB36ZLsupdOYjYSnyr6qcT/fhUOxhi3/xj6XRw3cV6DzF6jA4BJ+xugGj8HFOe9yRW56FDtAUEEhpC+hP1lQBLU3Fz96kcaQoTTGyxnAVX6iGKT+0SDqdalg2SH62gg5JTQaQ1VTAA1QD7zm1Oi3Ss90MBF4neDxstRm4jvaJw1PkLTPObADSe401FTdS23w6wNqzkwCrlwQV5EWqYHonMQAILP9mtW+B5fdIIk/gS6QlGFKLJdXqdSYwaS2iCvJ4IDmY29g7U5KqWl7dDmn4niBVcKTdNGkAq7nyuRUH8InUP28JNGKLXMcinqwKCyjP1leDWvSEc08vDQH/r5xQB7XAe7/m3PUx1mS3DKpCHi+FfdK9g/j8d3fswp77H+o7Jv+KtXoj18u8RbQndqlW3yiFPLgmzFWw47sHXRP2x7vljaYT7Q1x4dj8YKtBk3hltUiuCVc8sQZdFHbSv+V/XSgDfYCuWLoAAAAASUVORK5CYII=", options: .ignoreUnknownCharacters),
            let image = UIImage(data: data)
            else { return nil }
            return image
    }
    
    
    private var isPlaceholder: Bool {
        return self.textColor == placeholderTextColor
    }
    
    private let separator               = UIView()
    private let placeholderLabel        = UILabel()
    private let bottomPlaceholderLabel  = UILabel()
    private let clearButton             = UIButton()
    
    var height: ((CGFloat) -> ())?
    
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        commonInit()
    }
    
    
    override func layoutSubviews() {
        super.layoutSubviews()
        self.separator.layer.cornerRadius = separatorHeight / 2
        self.setFrames()
        self.height?(prefferedHeight)
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        checkPlaceholder()
        print("super.draw(rect)")
    }
    
    func textViewDidChange(_ textView: UITextView) {
        
        self.height?(prefferedHeight)
        clearButton.isHidden = text.isEmpty
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
        if isPlaceholder {
            text = ""
            textColor = textColor_
            placeholderLabel.alpha = 1
            layoutSubviews()
        }
        clearButton.isHidden = text.isEmpty
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        checkPlaceholder()
        clearButton.isHidden = true
    }
    
    
    
}

private extension CustomTextView {
    
    func commonInit() {
        
        textContainer.lineFragmentPadding = 0
        textContainerInset = .zero
        delegate = self
        
        addSubview(placeholderLabel)
        addSubview(bottomPlaceholderLabel)
        addSubview(separator)
        addSubview(clearButton)
        
        placeholderLabel.numberOfLines = 0
        bottomPlaceholderLabel.numberOfLines = 0
        
        clearButton.setImage(clearImage, for: [])
        clearButton.tintColor = .black
        clearButton.addTarget(self, action: #selector(clearButtonDidTap), for: .touchUpInside)
        clearButton.isHidden = true
        
        fieldTopIndent = 4
        fieldBottomIndent = 4
        fieldLeftIndent = 4
        fieldRightIndent = 4
        
    }
    
    
    func checkPlaceholder() {
        placeholderLabel.alpha = text.isEmpty ? 0 : 1
        textColor = text.isEmpty ? placeholderTextColor : textColor_
        if text.isEmpty { text = placeholderText }
        
        layoutSubviews()
    }
    
    
    
    var prefferedHeight: CGFloat {
        let size = CGSize(width: bounds.width, height: .infinity)
        let estimatedHeight = sizeThatFits(size).height
        return estimatedHeight
    }
    
    
    var placeholderHeight: CGFloat {
        let width = frame.width - (fieldLeftIndent + fieldRightIndent)
        let text = placeholderText
        let height = text.isEmpty ? 0 : text.height(width: width, font: placeholderFont)
        return height
    }
    
    var bottomPlaceholderHeight: CGFloat {
        let width = frame.width - (fieldLeftIndent + fieldRightIndent)
        let text = bottomPlaceholderText
        let height = text.isEmpty ? 0 : text.height(width: width, font: bottomPlaceholderFont)
        return height
    }
    
    func setFrames() {
        setPlacehoderFrame()
        setBottomElementFrames()
        clearImageFrame()
    }
    
    var placeholderFrame: CGRect {
        let placeholderHeight = self.placeholderHeight
        
        let x = fieldLeftIndent
        let y = CGFloat(0)
        let width = self.frame.width - (fieldLeftIndent + fieldRightIndent)
        let height = placeholderHeight
        let frame = CGRect(x: x, y: y, width: width, height: height)
        return frame
    }
    
    func setPlacehoderFrame() {
        placeholderLabel.frame = placeholderFrame
        textContainerInset.top = placeholderHeight > 0 ? placeholderHeight + fieldTopIndent : 0
    }
    
    func setBottomElementFrames() {
        
        let sepX = CGFloat(0)
        let sepY = (frame.height + fieldBottomIndent) - textContainerInset.bottom
        let sepWidth = frame.width
        let sepHeight = separatorHeight
        let sepFrame = CGRect(x: sepX, y: sepY, width: sepWidth, height: sepHeight)
        
        self.separator.frame = sepFrame
        
        let plX = fieldLeftIndent
        let plY = sepFrame.maxY + bottomPlaceholderTopIndent
        let plWidth = self.frame.width - (fieldLeftIndent + fieldRightIndent)
        let plHeight = bottomPlaceholderHeight
        let plFrame = CGRect(x: plX, y: plY, width: plWidth, height: plHeight)
        
        bottomPlaceholderLabel.frame = plFrame
        
        let bottomLabelHeight = bottomPlaceholderHeight
        
        let bottomPLaceholderSpace = bottomLabelHeight > 0 ? bottomLabelHeight + bottomPlaceholderTopIndent : 0
                
        let bottomInset = fieldBottomIndent + separatorHeight + bottomPLaceholderSpace
        
        textContainerInset.bottom = bottomInset
    }
    
    func clearImageFrame() {
        
        guard withClearButton else {
            clearButton.frame = .zero
            textContainerInset.right = fieldRightIndent
            return
        }
        
        let x = frame.width - clearButtonHeight - clearButtonRigthIndent
        let textMinY = textContainerInset.top - fieldTopIndent
        let textMaxY = frame.height - textContainerInset.bottom + fieldBottomIndent
        let y = textMinY + (textMaxY - textMinY) / 2
            
        let frame = CGRect(x: x, y: y, width: clearButtonHeight, height: clearButtonHeight)
        clearButton.frame = frame
        textContainerInset.right = fieldRightIndent + clearButtonHeight + clearButtonRigthIndent
    }
    
    @objc func clearButtonDidTap() {
        text = ""
        clearButton.isHidden = true
        layoutSubviews()
    }
    
}



extension String {
    
    func height(width: CGFloat, font: UIFont) -> CGFloat {
        
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
        
        return ceil(boundingBox.height)
        
    }
    
    func height(indent: CGFloat, font: UIFont) -> CGFloat {
        let width = UIScreen.main.bounds.width - indent
        
        return height(width: width, font: font)
    }
    
    func width(height: CGFloat, font: UIFont) -> CGFloat {
        
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
        
        return ceil(boundingBox.width)
        
    }
}

#2

Подделал немножко, теперь можно спокойно перенести на textField

Помогите плиз анимацию перехода плейсхолдера сделать!!!

//Стиль
    struct FieldStyle {
        
        var fieldFont: UIFont = .systemFont(ofSize: 16, weight: .regular)
        
        var fieldTopIndent:      CGFloat = 8
        var fieldBottomIndent:   CGFloat = 16
        var fieldRightIndent:    CGFloat = 12
        var fieldLeftIndent:     CGFloat = 8
        
        var placeholderTextColor:   UIColor = .lightGray
        var placeholderFont:        UIFont = .systemFont(ofSize: 12, weight: .regular)
        
        var bottomPlaceholderTextColor: UIColor = .lightGray
        var bottomPlaceholderFont:      UIFont = .systemFont(ofSize: 12, weight: .regular)
        var bottomPlaceholderTopIndent: CGFloat = 4
        
        var separatorHeight:        CGFloat = 2
        var separatorColor:         UIColor? = .lightGray
        
        var clearButtonHeight:      CGFloat = 8
        var clearButtonRigthIndent: CGFloat = 4
        var withClearButton:        Bool = true
        
    }

    protocol FieldType: class {
        
        var font:                       UIFont?     { get set }
        
        var fieldTopIndent:             CGFloat     { get set }
        var fieldBottomIndent:          CGFloat     { get set }
        var fieldRightIndent:           CGFloat     { get set }
        var fieldLeftIndent:            CGFloat     { get set }
        
        var placeholderText:            String      { get set }
        var placeholderTextColor:       UIColor     { get set }
        var placeholderFont:            UIFont      { get set }
        
        var bottomPlaceholderText:      String      { get set }
        var bottomPlaceholderTextColor: UIColor     { get set }
        var bottomPlaceholderFont:      UIFont      { get set }
        var bottomPlaceholderTopIndent: CGFloat     { get set }
        var separatorHeight:            CGFloat     { get set }
        var separatorColor:             UIColor?    { get set }
        
        var clearButtonHeight:          CGFloat     { get set }
        var clearButtonRigthIndent:     CGFloat     { get set }
        var withClearButton:            Bool        { get set }
        var clearButtonImage:           UIImage?    { get set }
        
        
        var placeholderHeight:          CGFloat         { get }
        var bottomPlaceholderHeight:    CGFloat         { get }
        var padding:                    UIEdgeInsets    { get }
        var prefferedHeight:            CGFloat         { get }
        
        var style: FieldStyle?  { get set }
        func setStyle(_ style: FieldStyle)
        
        func setFrames()
        func configure(text: String?, placeholder: String?, bottomPlaceholder: String?)
    }

    extension FieldType where Self: UIView {
        
        var clearImage: UIImage? {
            guard
                let data = Data(base64Encoded: "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAAGXcA1uAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAGKADAAQAAAABAAAAGAAAAADiNXWtAAABf0lEQVRIDa2TS04EMQxEBzYsucFwHm4ViQOxA4m7MOdgB36ZLsupdOYjYSnyr6qcT/fhUOxhi3/xj6XRw3cV6DzF6jA4BJ+xugGj8HFOe9yRW56FDtAUEEhpC+hP1lQBLU3Fz96kcaQoTTGyxnAVX6iGKT+0SDqdalg2SH62gg5JTQaQ1VTAA1QD7zm1Oi3Ss90MBF4neDxstRm4jvaJw1PkLTPObADSe401FTdS23w6wNqzkwCrlwQV5EWqYHonMQAILP9mtW+B5fdIIk/gS6QlGFKLJdXqdSYwaS2iCvJ4IDmY29g7U5KqWl7dDmn4niBVcKTdNGkAq7nyuRUH8InUP28JNGKLXMcinqwKCyjP1leDWvSEc08vDQH/r5xQB7XAe7/m3PUx1mS3DKpCHi+FfdK9g/j8d3fswp77H+o7Jv+KtXoj18u8RbQndqlW3yiFPLgmzFWw47sHXRP2x7vljaYT7Q1x4dj8YKtBk3hltUiuCVc8sQZdFHbSv+V/XSgDfYCuWLoAAAAASUVORK5CYII=", options: .ignoreUnknownCharacters),
                let image = UIImage(data: data)
                else { return nil }
                return image
        }
        
        func setStyle(_ style: FieldStyle) {
            
            self.font = style.fieldFont
            
            self.fieldTopIndent = style.fieldTopIndent
            self.fieldLeftIndent = style.fieldLeftIndent
            self.fieldBottomIndent = style.fieldBottomIndent
            self.fieldRightIndent = style.fieldRightIndent
            
            self.placeholderTextColor = style.placeholderTextColor
            self.placeholderFont = style.placeholderFont
            
            self.bottomPlaceholderFont = style.bottomPlaceholderFont
            self.bottomPlaceholderTextColor = style.bottomPlaceholderTextColor
            self.bottomPlaceholderTopIndent = style.bottomPlaceholderTopIndent
            
            self.separatorColor = style.separatorColor
            self.separatorHeight = style.separatorHeight
            
            self.clearButtonRigthIndent = style.clearButtonRigthIndent
            self.withClearButton = style.withClearButton
            
        }
        
        var placeholderHeight: CGFloat {
            let width = frame.width - (fieldLeftIndent + fieldRightIndent)
            let text = placeholderText
            let height = text.isEmpty ? 0 : text.height(width: width, font: placeholderFont)
            return height
        }
        
        var bottomPlaceholderHeight: CGFloat {
            let width = frame.width - (fieldLeftIndent + fieldRightIndent)
            let text = bottomPlaceholderText
            let height = text.isEmpty ? 0 : text.height(width: width, font: bottomPlaceholderFont)
            return height
        }
        
        var clearButtonWidth: CGFloat {
            return withClearButton && clearButtonHeight > 0 ? clearButtonHeight + clearButtonRigthIndent : 0
        }
        
        var bottomPlaceholderHeightWithIndent: CGFloat {
            return bottomPlaceholderHeight > 0 ? bottomPlaceholderHeight + bottomPlaceholderTopIndent : 0
        }
        
        var padding: UIEdgeInsets {
            let top = placeholderHeight + fieldTopIndent
            let left = fieldLeftIndent
            let bottom = bottomPlaceholderHeightWithIndent + separatorHeight + fieldBottomIndent
            let right = fieldRightIndent + clearButtonHeight
            return UIEdgeInsets(top: top, left: left, bottom: bottom, right: right)
        }
        
        var clearButtonFrame: CGRect {
            guard withClearButton && clearButtonHeight > 0 else { return .zero }
            let x = self.frame.width - (clearButtonHeight + clearButtonRigthIndent)
            let padding = self.padding
            let y = padding.top + (self.frame.height - (padding.top + padding.bottom + clearButtonHeight)) / 2
            return CGRect(x: x, y: y, width: clearButtonHeight, height: clearButtonHeight)
        }
        
        var placeholderFrame: CGRect {
            let x = fieldLeftIndent
            let y = CGFloat(0)
            let width = self.frame.width - (fieldLeftIndent + fieldRightIndent)
            return CGRect(x: x, y: y, width: width, height: placeholderHeight)
        }
        
        var bottomPlaceholderFrame: CGRect {
            let x = fieldLeftIndent
            let bottomPlaceholderHeight = self.placeholderHeight
            let y = frame.height - bottomPlaceholderHeight
            let width = self.frame.width - (fieldLeftIndent + fieldRightIndent)
            return CGRect(x: x, y: y, width: width, height: bottomPlaceholderHeight)
        }
        
        var separatorFrame: CGRect {
            let y = self.frame.height - padding.bottom + fieldBottomIndent
            return CGRect(x: 0, y: y, width: frame.width, height: separatorHeight)
        }
        
        var prefferedHeight: CGFloat {
            let size = CGSize(width: bounds.width, height: .infinity)
            let estimatedHeight = sizeThatFits(size).height
            return estimatedHeight
        }
        
        func configure(text: String?, placeholder: String?) {
            self.configure(text: text, placeholder: placeholder, bottomPlaceholder: nil)
        }
        
        func configure(placeholder: String?) {
            self.configure(text: nil, placeholder: placeholder)
        }
    }


    override func layoutSubviews() {
            super.layoutSubviews()
            self.separator.layer.cornerRadius = separatorHeight / 2
            
            self.textContainerInset = self.padding
            
            self.setFrames()
            
            self.height?(prefferedHeight)
        }

#3

ближе к финалочке)) Мало-ли))

Простите за настырность)


#4

вроде сделал

для конфигурации нужно
func configure(text: String?, placeholder: String?, bottomPlaceholder: String?)

для стилей

**protocol** FieldStyleType {

**var** fieldFont: UIFont { **get** **set** }

**var** fieldTopIndent: CGFloat { **get** **set** }

**var** fieldBottomIndent: CGFloat { **get** **set** }

**var** fieldRightIndent: CGFloat { **get** **set** }

**var** fieldLeftIndent: CGFloat { **get** **set** }

**var** fieldActiveTextColor: UIColor { **get** **set** }

**var** fieldInactiveTextColor: UIColor { **get** **set** }

**var** fieldErrorTextColor: UIColor { **get** **set** }

**var** placeholderFont: UIFont { **get** **set** }

**var** placeholderInactiveTextColor: UIColor { **get** **set** }

**var** placeholderActiveTextColor: UIColor { **get** **set** }

**var** placeholderErrorTextColor: UIColor { **get** **set** }

**var** bottomPlaceholderFont: UIFont { **get** **set** }

**var** bottomPlaceholderInactiveTextColor: UIColor { **get** **set** }

**var** bottomPlaceholderActiveTextColor: UIColor { **get** **set** }

**var** bottomPlaceholderErrorTextColor: UIColor { **get** **set** }

**var** bottomPlaceholderTopIndent: CGFloat { **get** **set** }

**var** separatorHeight: CGFloat { **get** **set** }

**var** separatorInactiveColor: UIColor { **get** **set** }

**var** separatorActiveColor: UIColor { **get** **set** }

**var** separatorErrorColor: UIColor { **get** **set** }

**var** withClearButton: Bool { **get** **set** }

**var** clearButtonHeight: CGFloat { **get** **set** }

**var** clearButtonRigthIndent: CGFloat { **get** **set** }

}

Для textView есть комилетишен с высотой

Буду рад любой критике и правкам)