Remade project structure
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
import UIKit
|
||||
import Messages
|
||||
|
||||
class GIFCollectionViewController: UIViewController {
|
||||
|
||||
private var collectionView: UICollectionView!
|
||||
private let addButton = UIButton(type: .system)
|
||||
private let emptyStateLabel = UILabel()
|
||||
|
||||
private let reuseIdentifier = "GIFCell"
|
||||
private var gifs: [GIF] = []
|
||||
|
||||
var onSelectGIF: ((GIF) -> Void)?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupCollectionView()
|
||||
setupUI()
|
||||
setupGestureRecognizers()
|
||||
loadGIFs()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
loadGIFs()
|
||||
}
|
||||
|
||||
private func setupCollectionView() {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .vertical
|
||||
layout.minimumLineSpacing = 10
|
||||
layout.minimumInteritemSpacing = 10
|
||||
|
||||
// Calculate cell size to fit 2 cells per row with spacing
|
||||
let cellWidth = (view.bounds.width - 40) / 2 // 40 = padding (10 + 10) + spacing between cells (10) + extra margins (10)
|
||||
layout.itemSize = CGSize(width: cellWidth, height: cellWidth)
|
||||
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
||||
|
||||
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
collectionView.backgroundColor = .systemBackground
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
collectionView.register(GIFCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
|
||||
collectionView.alwaysBounceVertical = true
|
||||
|
||||
if #available(iOS 14.0, *) {
|
||||
// Use collection view's built-in contextual menu support
|
||||
// This is set up in collectionView(_:contextMenuConfigurationForItemAt:point:)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
view.backgroundColor = .systemBackground
|
||||
|
||||
// Add Collection View
|
||||
view.addSubview(collectionView)
|
||||
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
// Setup Add Button
|
||||
addButton.setImage(UIImage(systemName: "plus.circle.fill"), for: .normal)
|
||||
addButton.tintColor = .systemBlue
|
||||
addButton.contentHorizontalAlignment = .fill
|
||||
addButton.contentVerticalAlignment = .fill
|
||||
addButton.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
|
||||
view.addSubview(addButton)
|
||||
addButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
// Setup Empty State Label
|
||||
emptyStateLabel.text = "No GIFs saved yet. Add your first GIF!"
|
||||
emptyStateLabel.textAlignment = .center
|
||||
emptyStateLabel.textColor = .systemGray
|
||||
emptyStateLabel.numberOfLines = 0
|
||||
view.addSubview(emptyStateLabel)
|
||||
emptyStateLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
||||
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
|
||||
|
||||
addButton.widthAnchor.constraint(equalToConstant: 44),
|
||||
addButton.heightAnchor.constraint(equalToConstant: 44),
|
||||
addButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
||||
addButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
|
||||
|
||||
emptyStateLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
emptyStateLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
emptyStateLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32),
|
||||
emptyStateLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32)
|
||||
])
|
||||
|
||||
updateEmptyState()
|
||||
}
|
||||
|
||||
func loadGIFs() {
|
||||
gifs = GIFStorageService.shared.fetchGIFs()
|
||||
collectionView.reloadData()
|
||||
updateEmptyState()
|
||||
}
|
||||
|
||||
private func updateEmptyState() {
|
||||
emptyStateLabel.isHidden = !gifs.isEmpty
|
||||
}
|
||||
|
||||
@objc private func addButtonTapped() {
|
||||
let addGIFVC = AddGIFViewController()
|
||||
addGIFVC.onSaveGIF = { [weak self] urlString, gifData in
|
||||
GIFStorageService.shared.saveGIF(data: gifData, fromURL: urlString) { _ in
|
||||
DispatchQueue.main.async {
|
||||
self?.loadGIFs()
|
||||
}
|
||||
}
|
||||
}
|
||||
addGIFVC.onCancel = { [weak self] in
|
||||
self?.dismiss(animated: true)
|
||||
}
|
||||
|
||||
let navController = UINavigationController(rootViewController: addGIFVC)
|
||||
navController.modalPresentationStyle = .formSheet
|
||||
present(navController, animated: true)
|
||||
}
|
||||
|
||||
private func setupGestureRecognizers() {
|
||||
// For iOS versions earlier than 14, we'll use a long press gesture recognizer
|
||||
if #unavailable(iOS 14.0) {
|
||||
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
|
||||
collectionView.addGestureRecognizer(longPressGesture)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
|
||||
if gesture.state == .began {
|
||||
let point = gesture.location(in: collectionView)
|
||||
|
||||
guard let indexPath = collectionView.indexPathForItem(at: point) else { return }
|
||||
|
||||
// Show action sheet for pre-iOS 14 devices
|
||||
showDeleteActionSheet(for: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
private func showDeleteActionSheet(for indexPath: IndexPath) {
|
||||
let gif = gifs[indexPath.item]
|
||||
|
||||
let alertController = UIAlertController(
|
||||
title: "GIF Options",
|
||||
message: "What would you like to do with this GIF?",
|
||||
preferredStyle: .actionSheet
|
||||
)
|
||||
|
||||
alertController.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
|
||||
self?.deleteGIF(at: indexPath)
|
||||
})
|
||||
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
|
||||
present(alertController, animated: true)
|
||||
}
|
||||
|
||||
private func deleteGIF(at indexPath: IndexPath) {
|
||||
let gif = gifs[indexPath.item]
|
||||
GIFStorageService.shared.deleteGIF(with: gif.id)
|
||||
|
||||
// Remove from local array and update collection view
|
||||
gifs.remove(at: indexPath.item)
|
||||
collectionView.deleteItems(at: [indexPath])
|
||||
updateEmptyState()
|
||||
}
|
||||
}
|
||||
|
||||
extension GIFCollectionViewController: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return gifs.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? GIFCollectionViewCell else {
|
||||
return UICollectionViewCell()
|
||||
}
|
||||
|
||||
let gif = gifs[indexPath.item]
|
||||
cell.configure(with: gif)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let gif = gifs[indexPath.item]
|
||||
onSelectGIF?(gif)
|
||||
}
|
||||
|
||||
// MARK: - Context Menu Support (iOS 14+)
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
let gif = gifs[indexPath.item]
|
||||
|
||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
|
||||
let deleteAction = UIAction(
|
||||
title: "Delete",
|
||||
image: UIImage(systemName: "trash"),
|
||||
attributes: .destructive
|
||||
) { [weak self] _ in
|
||||
self?.deleteGIF(at: indexPath)
|
||||
}
|
||||
|
||||
return UIMenu(title: "", children: [deleteAction])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user