Issue #325
- Use UILabelas placeholder and move it
- When label is moved up, scale it down 80%. It means it has 10% padding on the left and right when shrinked, so offsetXfor translation is 10%
- Translation transform should happen before scale
- Ideally we can animate font and color change using CATextLayer, but withUILabelwe can useUIView.transition
final class MaterialInputView: UIView {
    lazy var label: UILabel = {
        return UILabel()
    }()
    lazy var textField: UITextField = {
        let textField = UITextField()
        textField.tintColor = R.color.primary
        textField.textColor = R.color.lightText
        textField.font = R.customFont.medium(16)
        textField.autocapitalizationType = .none
        textField.autocorrectionType = .no
        return textField
    }()
    lazy var line: UIView = {
        let line = UIView()
        line.backgroundColor = R.color.primary
        return line
    }()
    // Whether label should be moved to top
    private var isUp: Bool = false {
        didSet {
            styleLabel(isUp: isUp)
            moveLabel(isUp: isUp)
        }
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }
    private func setup() {
        addSubviews([textField, label, line])
        textField.delegate = self
        NSLayoutConstraint.on([
            textField.leftAnchor.constraint(equalTo: leftAnchor, constant: 16),
            textField.rightAnchor.constraint(equalTo: rightAnchor, constant: -16),
            textField.topAnchor.constraint(equalTo: topAnchor, constant: 16),
            label.leftAnchor.constraint(equalTo: textField.leftAnchor),
            label.centerYAnchor.constraint(equalTo: textField.centerYAnchor),
            line.leftAnchor.constraint(equalTo: textField.leftAnchor),
            line.rightAnchor.constraint(equalTo: textField.rightAnchor),
            line.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 8),
            line.heightAnchor.constraint(equalToConstant: 2)
        ])
        styleLabel(isUp: false)
    }
    private func styleLabel(isUp: Bool) {
        UIView.transition(
            with: label,
            duration: 0.15,
            options: .curveEaseInOut,
            animations: {
                if isUp {
                    self.label.font = R.customFont.regular(12)
                    self.label.textColor = R.color.primary
                } else {
                    self.label.font = R.customFont.medium(16)
                    self.label.textColor = R.color.grayText
                }
            },
            completion: nil
        )
    }
    private func moveLabel(isUp: Bool) {
        UIView.animate(
            withDuration: 0.15,
            delay: 0,
            options: .curveEaseInOut,
            animations: {
                if isUp {
                    let offsetX = self.label.frame.width * 0.1
                    let translation = CGAffineTransform(translationX: -offsetX, y: -24)
                    let scale = CGAffineTransform(scaleX: 0.8, y: 0.8)
                    self.label.transform = translation.concatenating(scale)
                } else {
                    self.label.transform = .identity
                }
            },
            completion: nil
        )
    }
}
extension MaterialInputView: UITextFieldDelegate {
    func textFieldDidBeginEditing(_ textField: UITextField) {
        if !isUp {
            isUp = true
        }
    }
    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        guard let text = textField.text else {
            return false
        }
        if isUp && text.isEmpty {
            isUp = false
        }
        return true
    }
}
Start the conversation