NetNewsWire/iOS/Article/ArticleSearchBar.swift
2024-05-03 23:01:35 -07:00

177 lines
5.0 KiB
Swift

//
// ArticleSearchBar.swift
// NetNewsWire
//
// Created by Brian Sanders on 5/8/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import UIKit
protocol SearchBarDelegate: AnyObject {
@MainActor func nextWasPressed(_ searchBar: ArticleSearchBar)
@MainActor func previousWasPressed(_ searchBar: ArticleSearchBar)
@MainActor func doneWasPressed(_ searchBar: ArticleSearchBar)
@MainActor func searchBar(_ searchBar: ArticleSearchBar, textDidChange: String)
}
@IBDesignable final class ArticleSearchBar: UIStackView {
var searchField: UISearchTextField!
var nextButton: UIButton!
var prevButton: UIButton!
var background: UIView!
weak private var resultsLabel: UILabel!
var resultsCount: UInt = 0 {
didSet {
updateUI()
}
}
var selectedResult: UInt = 1 {
didSet {
updateUI()
}
}
weak var delegate: SearchBarDelegate?
override var keyCommands: [UIKeyCommand]? {
return [UIKeyCommand(title: "Exit Find", action: #selector(donePressed(_:)), input: UIKeyCommand.inputEscape)]
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
layer.backgroundColor = UIColor(named: "barBackgroundColor")?.cgColor ?? UIColor.white.cgColor
isOpaque = true
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: UITextField.textDidChangeNotification, object: searchField)
}
private func updateUI() {
if resultsCount > 0 {
let format = NSLocalizedString("%d of %d", comment: "Results selection and count")
resultsLabel.text = String.localizedStringWithFormat(format, selectedResult, resultsCount)
} else {
resultsLabel.text = NSLocalizedString("No results", comment: "No results")
}
nextButton.isEnabled = selectedResult < resultsCount
prevButton.isEnabled = resultsCount > 0 && selectedResult > 1
}
@discardableResult override func becomeFirstResponder() -> Bool {
searchField.becomeFirstResponder()
}
@discardableResult override func resignFirstResponder() -> Bool {
searchField.resignFirstResponder()
}
override var isFirstResponder: Bool {
searchField.isFirstResponder
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
private extension ArticleSearchBar {
func commonInit() {
isLayoutMarginsRelativeArrangement = true
alignment = .center
spacing = 8
layoutMargins.left = 8
layoutMargins.right = 8
background = UIView(frame: bounds)
background.backgroundColor = .systemGray5
background.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(background)
let doneButton = UIButton()
doneButton.setTitle(NSLocalizedString("Done", comment: "Done"), for: .normal)
doneButton.setTitleColor(UIColor.label, for: .normal)
doneButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
doneButton.isAccessibilityElement = true
doneButton.addTarget(self, action: #selector(donePressed), for: .touchUpInside)
doneButton.isEnabled = true
addArrangedSubview(doneButton)
let resultsLabel = UILabel()
searchField = UISearchTextField()
searchField.autocapitalizationType = .none
searchField.autocorrectionType = .no
searchField.returnKeyType = .search
searchField.delegate = self
resultsLabel.font = .systemFont(ofSize: UIFont.smallSystemFontSize)
resultsLabel.textColor = .secondaryLabel
resultsLabel.text = ""
resultsLabel.textAlignment = .right
resultsLabel.adjustsFontSizeToFitWidth = true
searchField.rightView = resultsLabel
searchField.rightViewMode = .always
self.resultsLabel = resultsLabel
addArrangedSubview(searchField)
prevButton = UIButton(type: .system)
prevButton.setImage(UIImage(systemName: "chevron.up"), for: .normal)
prevButton.accessibilityLabel = "Previous Result"
prevButton.isAccessibilityElement = true
prevButton.addTarget(self, action: #selector(previousPressed), for: .touchUpInside)
addArrangedSubview(prevButton)
nextButton = UIButton(type: .system)
nextButton.setImage(UIImage(systemName: "chevron.down"), for: .normal)
nextButton.accessibilityLabel = "Next Result"
nextButton.isAccessibilityElement = true
nextButton.addTarget(self, action: #selector(nextPressed), for: .touchUpInside)
addArrangedSubview(nextButton)
}
}
private extension ArticleSearchBar {
@objc func textDidChange(_ notification: Notification) {
delegate?.searchBar(self, textDidChange: searchField.text ?? "")
if searchField.text?.isEmpty ?? true {
searchField.rightViewMode = .never
} else {
searchField.rightViewMode = .always
}
}
@objc func nextPressed() {
delegate?.nextWasPressed(self)
}
@objc func previousPressed() {
delegate?.previousWasPressed(self)
}
@objc func donePressed(_ _: Any? = nil) {
delegate?.doneWasPressed(self)
}
}
extension ArticleSearchBar: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
delegate?.nextWasPressed(self)
return false
}
}