TM-SGNL-iOS/SignalUI/Views/TextFieldWithPlaceholder.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

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
}
}