225 lines
6.9 KiB
Swift
225 lines
6.9 KiB
Swift
import Messages
|
|
import UIKit
|
|
|
|
final class StickerBrowserViewController: MSStickerBrowserViewController {
|
|
private struct Pack {
|
|
let savedPack: SavedPack
|
|
let thumbnail: UIImage?
|
|
let stickers: [MSSticker]
|
|
}
|
|
|
|
private var packs: [Pack] = []
|
|
private var selectedPackIndex: Int? // nil = all packs
|
|
private var displayedStickers: [MSSticker] = []
|
|
|
|
private var tabCollectionView: UICollectionView!
|
|
|
|
private let tabSize: CGFloat = 36
|
|
private let tabSpacing: CGFloat = 6
|
|
private let tabInset: CGFloat = 8
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
init() {
|
|
super.init(stickerSize: .small)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) is not supported")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
setupTabBar()
|
|
}
|
|
|
|
// MARK: - Data
|
|
|
|
func reloadStickers() {
|
|
packs = StickerStore.shared.savedPacks
|
|
.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending }
|
|
.compactMap { pack in
|
|
let urls = StickerStore.shared.stickerFileURLs(forPack: pack.name)
|
|
let stickers = urls.compactMap { url in
|
|
try? MSSticker(contentsOfFileURL: url, localizedDescription: pack.title)
|
|
}
|
|
guard !stickers.isEmpty else { return nil }
|
|
let thumbnail = StickerStore.shared.thumbnail(forPack: pack.name)
|
|
return Pack(savedPack: pack, thumbnail: thumbnail, stickers: stickers)
|
|
}
|
|
|
|
if let index = selectedPackIndex, index >= packs.count {
|
|
selectedPackIndex = nil
|
|
}
|
|
|
|
updateDisplayedStickers()
|
|
tabCollectionView?.reloadData()
|
|
}
|
|
|
|
private func updateDisplayedStickers() {
|
|
if let index = selectedPackIndex {
|
|
displayedStickers = packs[index].stickers
|
|
} else {
|
|
displayedStickers = packs.flatMap(\.stickers)
|
|
}
|
|
stickerBrowserView.reloadData()
|
|
}
|
|
|
|
// MARK: - Tab bar
|
|
|
|
private func setupTabBar() {
|
|
let tabBarHeight = tabSize + tabInset * 2
|
|
|
|
let layout = UICollectionViewFlowLayout()
|
|
layout.scrollDirection = .horizontal
|
|
layout.minimumInteritemSpacing = tabSpacing
|
|
layout.minimumLineSpacing = tabSpacing
|
|
layout.sectionInset = UIEdgeInsets(top: 0, left: tabInset, bottom: 0, right: tabInset)
|
|
layout.itemSize = CGSize(width: tabSize, height: tabSize)
|
|
|
|
tabCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
|
tabCollectionView.translatesAutoresizingMaskIntoConstraints = false
|
|
tabCollectionView.backgroundColor = .secondarySystemBackground
|
|
tabCollectionView.showsHorizontalScrollIndicator = false
|
|
tabCollectionView.dataSource = self
|
|
tabCollectionView.delegate = self
|
|
tabCollectionView.register(TabCell.self, forCellWithReuseIdentifier: TabCell.reuseID)
|
|
|
|
let separator = UIView()
|
|
separator.translatesAutoresizingMaskIntoConstraints = false
|
|
separator.backgroundColor = .separator
|
|
|
|
view.addSubview(tabCollectionView)
|
|
view.addSubview(separator)
|
|
|
|
NSLayoutConstraint.activate([
|
|
tabCollectionView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
tabCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
tabCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
tabCollectionView.heightAnchor.constraint(equalToConstant: tabBarHeight),
|
|
|
|
separator.topAnchor.constraint(equalTo: tabCollectionView.bottomAnchor),
|
|
separator.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
separator.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
separator.heightAnchor.constraint(equalToConstant: 1.0 / UIScreen.main.scale),
|
|
])
|
|
|
|
additionalSafeAreaInsets = UIEdgeInsets(top: tabBarHeight, left: 0, bottom: 0, right: 0)
|
|
}
|
|
|
|
private func selectPack(at index: Int?) {
|
|
guard selectedPackIndex != index else { return }
|
|
selectedPackIndex = index
|
|
updateDisplayedStickers()
|
|
tabCollectionView.reloadData()
|
|
}
|
|
|
|
// MARK: - MSStickerBrowserViewDataSource
|
|
|
|
override func numberOfStickers(in stickerBrowserView: MSStickerBrowserView) -> Int {
|
|
displayedStickers.count
|
|
}
|
|
|
|
override func stickerBrowserView(_ stickerBrowserView: MSStickerBrowserView, stickerAt index: Int) -> MSSticker {
|
|
displayedStickers[index]
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDataSource
|
|
|
|
extension StickerBrowserViewController: UICollectionViewDataSource {
|
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
packs.count + 1 // +1 for "All" tab
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TabCell.reuseID, for: indexPath) as! TabCell
|
|
if indexPath.item == 0 {
|
|
let isSelected = selectedPackIndex == nil
|
|
cell.configureAsAll(isSelected: isSelected)
|
|
} else {
|
|
let pack = packs[indexPath.item - 1]
|
|
let isSelected = selectedPackIndex == indexPath.item - 1
|
|
cell.configure(image: pack.thumbnail, isSelected: isSelected)
|
|
}
|
|
return cell
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDelegate
|
|
|
|
extension StickerBrowserViewController: UICollectionViewDelegate {
|
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
if indexPath.item == 0 {
|
|
selectPack(at: nil)
|
|
} else {
|
|
selectPack(at: indexPath.item - 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - TabCell
|
|
|
|
private final class TabCell: UICollectionViewCell {
|
|
static let reuseID = "TabCell"
|
|
|
|
private let imageView = UIImageView()
|
|
private let label = UILabel()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
contentView.layer.cornerRadius = 8
|
|
contentView.clipsToBounds = true
|
|
|
|
imageView.contentMode = .scaleAspectFit
|
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.addSubview(imageView)
|
|
|
|
label.font = .systemFont(ofSize: 11, weight: .medium)
|
|
label.textAlignment = .center
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.isHidden = true
|
|
contentView.addSubview(label)
|
|
|
|
let inset: CGFloat = 4
|
|
NSLayoutConstraint.activate([
|
|
imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: inset),
|
|
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -inset),
|
|
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: inset),
|
|
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -inset),
|
|
|
|
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
|
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
|
])
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) is not supported")
|
|
}
|
|
|
|
func configure(image: UIImage?, isSelected: Bool) {
|
|
imageView.image = image
|
|
imageView.isHidden = false
|
|
label.isHidden = true
|
|
contentView.backgroundColor = isSelected ? .systemGray5 : .clear
|
|
imageView.alpha = isSelected ? 1.0 : 0.5
|
|
}
|
|
|
|
func configureAsAll(isSelected: Bool) {
|
|
imageView.isHidden = true
|
|
label.isHidden = false
|
|
label.text = "All"
|
|
label.textColor = isSelected ? .label : .secondaryLabel
|
|
contentView.backgroundColor = isSelected ? .systemGray5 : .clear
|
|
}
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
imageView.image = nil
|
|
imageView.isHidden = false
|
|
label.isHidden = true
|
|
}
|
|
}
|