134 lines
3.8 KiB
Swift
134 lines
3.8 KiB
Swift
//
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import SignalServiceKit
|
|
|
|
public protocol TextFieldWithPlaceholderDelegate: AnyObject {
|
|
|
|
func textFieldDidBeginEditing(_ textField: UITextField)
|
|
|
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public class TextFieldWithPlaceholder: UIView {
|
|
|
|
// MARK: - Public Properties
|
|
|
|
public weak var delegate: TextFieldWithPlaceholderDelegate?
|
|
|
|
public var placeholderText: String = "" {
|
|
didSet {
|
|
placeholderLabel.text = placeholderText
|
|
textfield.accessibilityLabel = placeholderText
|
|
}
|
|
}
|
|
|
|
public func acceptAutocorrectSuggestion() {
|
|
textfield.acceptAutocorrectSuggestion()
|
|
}
|
|
|
|
public var text: String? {
|
|
get { textfield.text?.nilIfEmpty }
|
|
set {
|
|
textfield.text = newValue
|
|
updatePlaceholderVisibility()
|
|
}
|
|
}
|
|
|
|
@discardableResult
|
|
public override func becomeFirstResponder() -> Bool {
|
|
textfield.becomeFirstResponder()
|
|
}
|
|
|
|
@discardableResult
|
|
public override func resignFirstResponder() -> Bool {
|
|
textfield.resignFirstResponder()
|
|
}
|
|
|
|
public override var canBecomeFirstResponder: Bool {
|
|
textfield.canBecomeFirstResponder
|
|
}
|
|
|
|
public override var isFirstResponder: Bool {
|
|
textfield.isFirstResponder
|
|
}
|
|
|
|
public var font: UIFont? {
|
|
get { textfield.font }
|
|
set { textfield.font = newValue }
|
|
}
|
|
|
|
// MARK: - Private Properties
|
|
|
|
private lazy var textfield = UITextField()
|
|
private lazy var placeholderLabel = UILabel()
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
applyTheme()
|
|
|
|
textfield.delegate = self
|
|
placeholderLabel.isUserInteractionEnabled = false
|
|
|
|
// The placeholderLabel is perfectly aligned with the textView to allow for us to easily
|
|
// hide/show placeholder text without needing to manipulate the text property of our primary
|
|
// text view. This makes VoiceOver navigation by dragging a bit tricky, since a user won't be
|
|
// able to find the placeholder text. Let's disable it in VoiceOver. Instead, placeholderText
|
|
// will be an accessibility label on the primary text view.
|
|
placeholderLabel.accessibilityElementsHidden = true
|
|
|
|
// Layout + Constraints
|
|
for subview in [textfield, placeholderLabel] {
|
|
addSubview(subview)
|
|
subview.autoPinEdgesToSuperviewEdges()
|
|
subview.setCompressionResistanceHigh()
|
|
}
|
|
|
|
textfield.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(applyTheme), name: .themeDidChange, object: nil)
|
|
|
|
updatePlaceholderVisibility()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
@objc
|
|
private func textFieldDidChange(sender: UITextField) {
|
|
updatePlaceholderVisibility()
|
|
}
|
|
|
|
private func updatePlaceholderVisibility() {
|
|
placeholderLabel.isHidden = text != nil
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
@objc
|
|
private func applyTheme() {
|
|
placeholderLabel.textColor = UIColor.Signal.secondaryLabel
|
|
textfield.textColor = UIColor.Signal.label
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
extension TextFieldWithPlaceholder: UITextFieldDelegate {
|
|
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
|
delegate?.textFieldDidBeginEditing(textField)
|
|
}
|
|
|
|
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
delegate?.textField(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true
|
|
}
|
|
}
|