140
ios/Sources/Support.swift
Normal file
140
ios/Sources/Support.swift
Normal file
@@ -0,0 +1,140 @@
|
||||
import UIKit
|
||||
|
||||
func errorAlert(_ message: String) -> UIAlertController {
|
||||
let alert = UIAlertController(title: "Something went wrong", message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
||||
return alert
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
func show(error: Error) {
|
||||
present(errorAlert(error.localizedDescription), animated: true)
|
||||
}
|
||||
|
||||
func beginNavigationLoading(_ spinner: UIActivityIndicatorView) {
|
||||
guard navigationItem.rightBarButtonItem == nil else { return }
|
||||
spinner.startAnimating()
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner)
|
||||
}
|
||||
|
||||
func endNavigationLoading(_ spinner: UIActivityIndicatorView) {
|
||||
spinner.stopAnimating()
|
||||
if navigationItem.rightBarButtonItem?.customView === spinner {
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class RefreshingTableViewController: UITableViewController {
|
||||
private let spinner = UIActivityIndicatorView(style: .medium)
|
||||
var loadingTask: Task<Void, Never>?
|
||||
|
||||
init() {
|
||||
super.init(style: .plain)
|
||||
tableView.backgroundColor = .systemGroupedBackground
|
||||
tableView.separatorInset = .zero
|
||||
tableView.refreshControl = UIRefreshControl()
|
||||
tableView.refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged)
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
||||
|
||||
deinit { loadingTask?.cancel() }
|
||||
|
||||
func loadContent(refreshing: Bool) {}
|
||||
|
||||
func beginLoading(refreshing: Bool) {
|
||||
if !refreshing { beginNavigationLoading(spinner) }
|
||||
}
|
||||
|
||||
func endLoading() {
|
||||
endNavigationLoading(spinner)
|
||||
refreshControl?.endRefreshing()
|
||||
}
|
||||
|
||||
@objc private func refreshRequested() {
|
||||
loadContent(refreshing: true)
|
||||
}
|
||||
}
|
||||
|
||||
final class EmptyBackgroundView: UIView {
|
||||
init(title: String, detail: String) {
|
||||
super.init(frame: .zero)
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = title
|
||||
titleLabel.font = .preferredFont(forTextStyle: .title2)
|
||||
titleLabel.textAlignment = .center
|
||||
let detailLabel = UILabel()
|
||||
detailLabel.text = detail
|
||||
detailLabel.font = .preferredFont(forTextStyle: .body)
|
||||
detailLabel.textColor = .secondaryLabel
|
||||
detailLabel.textAlignment = .center
|
||||
detailLabel.numberOfLines = 0
|
||||
let stack = UIStackView(arrangedSubviews: [titleLabel, detailLabel])
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 8
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(stack)
|
||||
NSLayoutConstraint.activate([
|
||||
stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 28),
|
||||
stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -28),
|
||||
stack.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -40),
|
||||
])
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
convenience init?(hex: String) {
|
||||
let value = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
|
||||
guard value.count == 6, let rgb = Int(value, radix: 16) else { return nil }
|
||||
self.init(
|
||||
red: CGFloat((rgb >> 16) & 0xff) / 255,
|
||||
green: CGFloat((rgb >> 8) & 0xff) / 255,
|
||||
blue: CGFloat(rgb & 0xff) / 255,
|
||||
alpha: 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func markdown(_ source: String, textStyle: UIFont.TextStyle = .body) -> NSAttributedString {
|
||||
let attributed = (try? AttributedString(
|
||||
markdown: source,
|
||||
options: .init(interpretedSyntax: .full)
|
||||
)) ?? AttributedString(source)
|
||||
var mutable = attributed
|
||||
mutable.font = .preferredFont(forTextStyle: textStyle)
|
||||
mutable.foregroundColor = UIColor.label
|
||||
return NSAttributedString(mutable)
|
||||
}
|
||||
|
||||
func configureTextCell(_ cell: UITableViewCell, title: String, detail: String, image: UIImage? = nil) {
|
||||
var content = cell.defaultContentConfiguration()
|
||||
content.text = title
|
||||
content.secondaryText = detail
|
||||
content.secondaryTextProperties.numberOfLines = 2
|
||||
content.image = image
|
||||
content.imageProperties.tintColor = .tintColor
|
||||
cell.contentConfiguration = content
|
||||
cell.backgroundColor = .systemBackground
|
||||
}
|
||||
|
||||
func symbolText(_ symbol: String, text: String, font: UIFont, color: UIColor) -> NSAttributedString {
|
||||
let output = NSMutableAttributedString()
|
||||
if let image = UIImage(systemName: symbol)?.withTintColor(color, renderingMode: .alwaysOriginal) {
|
||||
let attachment = NSTextAttachment(image: image)
|
||||
attachment.bounds = CGRect(x: 0, y: -2, width: font.pointSize, height: font.pointSize)
|
||||
output.append(NSAttributedString(attachment: attachment))
|
||||
output.append(NSAttributedString(string: " "))
|
||||
}
|
||||
output.append(NSAttributedString(string: text, attributes: [
|
||||
.font: font,
|
||||
.foregroundColor: color,
|
||||
]))
|
||||
return output
|
||||
}
|
||||
Reference in New Issue
Block a user