Skip to content
Snippets Groups Projects
SearchComponent.swift 5.55 KiB
import UIKit
import Combine

public final class SearchComponent: UIView {
    let rightButton = UIButton()
    let leftImageView = UIImageView()
    let containerView = UIView()
    let inputField = UITextField()

    public var rightPublisher: AnyPublisher<Void, Never> {
        rightSubject.eraseToAnyPublisher()
    }

    public var textPublisher: AnyPublisher<String, Never> {
        textSubject.eraseToAnyPublisher()
    }

    private var rightImage = Asset.sharedScan.image {
        didSet {
            rightButton.setImage(rightImage, for: .normal)
        }
    }

    private var isEditing = false
    private var cancellables = Set<AnyCancellable>()
    private var rightSubject = PassthroughSubject<Void, Never>()
    private var textSubject = PassthroughSubject<String, Never>()

    public init() {
        super.init(frame: .zero)
        setup()
    }

    required init?(coder: NSCoder) { nil }

    public func set(
        placeholder: String? = nil,
        imageAtRight: UIImage? = nil,
        inputAccessibility: String? = nil,
        rightAccessibility: String? = nil
    ) {
        inputField.accessibilityIdentifier = inputAccessibility
        rightButton.accessibilityIdentifier = rightAccessibility

        if let placeholder = placeholder {
            let attrPlaceholder
                = NSAttributedString(
                    string: placeholder,
                    attributes: [
                        .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
                        .foregroundColor: Asset.neutralWeak.color
                    ])

            inputField.attributedPlaceholder = attrPlaceholder
        }

        if let image = imageAtRight {
            self.rightImage = image
        } else {
            rightButton.isHidden = true
        }
    }

    public func update(placeholder: String) {
        inputField.attributedPlaceholder = NSAttributedString(
            string: placeholder,
            attributes: [
                .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
                .foregroundColor: Asset.neutralWeak.color
        ])
    }

    public func abortEditing() {
        inputField.text = nil
        textSubject.send("")
        inputField.endEditing(true)
        isEditing = false
    }

    private func setup() {
        containerView.layer.cornerRadius = 25
        containerView.backgroundColor = Asset.neutralSecondary.color

        leftImageView.image = Asset.lens.image
        leftImageView.contentMode = .center
        leftImageView.tintColor = Asset.neutralDisabled.color

        rightButton.tintColor = Asset.neutralBody.color
        rightButton.setImage(rightImage, for: .normal)
        rightButton.setContentHuggingPriority(.required, for: .horizontal)
        rightButton.setContentCompressionResistancePriority(.required, for: .horizontal)

        inputField.delegate = self
        inputField.textColor = Asset.neutralActive.color
        inputField.font = Fonts.Mulish.regular.font(size: 16.0)

        let attrPlaceholder
            = NSAttributedString(
                string: Localized.Shared.Search.placeholder,
                attributes: [
                    .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
                    .foregroundColor: Asset.neutralWeak.color
                ])

        inputField.attributedPlaceholder = attrPlaceholder

        inputField.textPublisher
            .sink { [weak textSubject] in textSubject?.send($0) }
            .store(in: &cancellables)

        rightButton.publisher(for: .touchUpInside)
            .sink { [weak rightSubject, self] in
                if isEditing {
                    abortEditing()
                } else {
                    rightSubject?.send()
                }
            }.store(in: &cancellables)

        addSubview(containerView)
        containerView.addSubview(inputField)
        containerView.addSubview(leftImageView)
        containerView.addSubview(rightButton)

        setupConstraints()
        setupAccessibility()
    }

    private func setupConstraints() {
        containerView.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.left.equalToSuperview()
            make.right.equalToSuperview()
            make.bottom.equalToSuperview()
            make.height.equalTo(50)
        }

        leftImageView.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(10)
            make.left.equalToSuperview().offset(13)
            make.bottom.equalToSuperview().offset(-10)
            make.height.equalTo(leftImageView.snp.width)
        }

        inputField.snp.makeConstraints { make in
            make.top.bottom.equalToSuperview()
            make.left.equalTo(leftImageView.snp.right).offset(20)
            make.right.equalTo(rightButton.snp.left).offset(-32)
        }

        rightButton.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.right.equalToSuperview().offset(-13)
            make.bottom.equalToSuperview()
        }
    }

    private func setupAccessibility() {
        inputField.accessibilityIdentifier = Localized.Accessibility.Shared.Search.textField
        rightButton.accessibilityIdentifier = Localized.Accessibility.Shared.Search.rightButton
    }

    public func textFieldDidBeginEditing(_ textField: UITextField) {
        rightButton.setImage(Asset.sharedCross.image, for: .normal)
        isEditing = true
    }

    public func textFieldDidEndEditing(_ textField: UITextField) {
        rightButton.setImage(rightImage, for: .normal)
        isEditing = false
    }
}

extension SearchComponent: UITextFieldDelegate {}