diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/BookCover/ViewModel/ModifyBookCoverViewModel.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/BookCover/ViewModel/ModifyBookCoverViewModel.swift index a8c571d8..06b341ec 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/BookCover/ViewModel/ModifyBookCoverViewModel.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/BookCover/ViewModel/ModifyBookCoverViewModel.swift @@ -128,6 +128,7 @@ final class ModifyBookCoverViewModel: ViewModelType { return } let newBookCover = BookCover( + id: bookID, order: bookOrder, title: bookTitle, imageData: bookImageData, diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumCollectionViewCell.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumCollectionViewCell.swift index 98c0969e..2e06c669 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumCollectionViewCell.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumCollectionViewCell.swift @@ -9,6 +9,7 @@ final class CustomAlbumCollectionViewCell: UICollectionViewCell { return imageView }() + var representedAssetIdentifier: String? // MARK: - Initialize override init(frame: CGRect) { diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumViewController.swift index 389c5012..0755559e 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/View/CustomAlbumViewController.swift @@ -68,10 +68,10 @@ final class CustomAlbumViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + bind() setup() configureConstraints() configureNavigationBar() - bind() checkThumbnailAuthorization() } @@ -81,6 +81,32 @@ final class CustomAlbumViewController: UIViewController { configureNavigationAppearance() } + // MARK: - Binding + private func bind() { + let output = viewModel.transform(input: input.eraseToAnyPublisher()) + + output.receive(on: DispatchQueue.main) + .sink { [weak self] event in + switch event { + case .fetchAssets: + self?.albumCollectionView.reloadData() + case .changedAssets(let changes): + self?.albumCollectionView.performBatchUpdates { + if let inserted = changes.insertedIndexes, !inserted.isEmpty { + self?.albumCollectionView.insertItems( + at: inserted.map({ IndexPath(item: $0 + 1, section: 0) }) + ) + } + if let removed = changes.removedIndexes, !removed.isEmpty { + self?.albumCollectionView.deleteItems( + at: removed.map({ IndexPath(item: $0 + 1, section: 0) }) + ) + } + } + } + }.store(in: &cancellables) + } + // MARK: - Setup & Configure private func setup() { view.backgroundColor = .baseBackground @@ -99,7 +125,6 @@ final class CustomAlbumViewController: UIViewController { } private func configureNavigationBar() { - // TODO: - 추후 삭제 필요 navigationController?.navigationBar.isHidden = false if mediaType == .image { navigationItem.title = "사진 선택" @@ -139,33 +164,6 @@ final class CustomAlbumViewController: UIViewController { navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance } - // MARK: - Binding - private func bind() { - let output = viewModel.transform(input: input.eraseToAnyPublisher()) - - output.receive(on: DispatchQueue.main) - .sink { [weak self] event in - switch event { - case .fetchAssets: - self?.albumCollectionView.reloadData() - case .changedAssets(let changes): - self?.albumCollectionView.performBatchUpdates { - if let inserted = changes.insertedIndexes, !inserted.isEmpty { - self?.albumCollectionView.insertItems( - at: inserted.map({ IndexPath(item: $0 + 1, section: 0) }) - ) - } - if let removed = changes.removedIndexes, !removed.isEmpty { - self?.albumCollectionView.deleteItems( - at: removed.map({ IndexPath(item: $0 + 1, section: 0) }) - ) - } - } - } - } - .store(in: &cancellables) - } - // MARK: - Media private func checkThumbnailAuthorization() { let authorization = PHPhotoLibrary.authorizationStatus() @@ -229,6 +227,7 @@ final class CustomAlbumViewController: UIViewController { navigationController?.show(imagePicker, sender: nil) } } + private func moveToEditPhotoView(image: UIImage?, creationDate: Date) { guard let photoSelectCompletionHandler else { return } var editPhotoViewController: EditPhotoViewController @@ -281,7 +280,8 @@ extension CustomAlbumViewController: UICollectionViewDelegate { private func handleImageSelection(with asset: PHAsset) { Task { await LocalPhotoManager.shared.requestThumbnailImage(with: asset) { [weak self] image in - guard let self = self, let image = image else { return } + guard let self, + let image else { return } self.moveToEditPhotoView(image: image, creationDate: asset.creationDate ?? .now) } } @@ -322,10 +322,13 @@ extension CustomAlbumViewController: UICollectionViewDataSource { cell.setPhoto(.photo) } else { guard let asset = viewModel.photoAsset?[indexPath.item - 1] else { return cell } + cell.representedAssetIdentifier = asset.localIdentifier let cellSize = cell.bounds.size Task { await LocalPhotoManager.shared.requestThumbnailImage(with: asset, cellSize: cellSize) { image in - cell.setPhoto(image) + if cell.representedAssetIdentifier == asset.localIdentifier { + cell.setPhoto(image) + } } } } diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/ViewModel/LocalPhotoManager.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/ViewModel/LocalPhotoManager.swift index fb9fedcb..8b7c5f40 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/ViewModel/LocalPhotoManager.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/CustomAlbum/ViewModel/LocalPhotoManager.swift @@ -4,10 +4,12 @@ import Photos actor LocalPhotoManager { static let shared = LocalPhotoManager() - private let imageManager = PHImageManager() + private let imageManager = PHCachingImageManager() private let imageRequestOptions: PHImageRequestOptions = { let options = PHImageRequestOptions() options.isSynchronous = true + options.isNetworkAccessAllowed = true + options.deliveryMode = .highQualityFormat return options }() @@ -29,6 +31,13 @@ actor LocalPhotoManager { resultHandler: { image, _ in Task { await completion(image) } }) + + imageManager.startCachingImages( + for: [asset], + targetSize: cellSize, + contentMode: .aspectFill, + options: nil + ) } func requestVideoURL( diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/Design/MHPolaroidPhotoView.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/Design/MHPolaroidPhotoView.swift index 8d43fae9..e50e2f79 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/Design/MHPolaroidPhotoView.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/Design/MHPolaroidPhotoView.swift @@ -64,11 +64,15 @@ final class MHPolaroidPhotoView: UIView { private func configureConstraints() { photoImageView.setAnchor(top: topAnchor, constantTop: 25, - width: 280, height: 210) - photoImageView.setCenterX(view: self) + leading: leadingAnchor, constantLeading: 17, + trailing: trailingAnchor, constantTrailing: 17) + NSLayoutConstraint.activate([ + photoImageView.heightAnchor.constraint(equalTo: photoImageView.widthAnchor, multiplier: 0.75) + ]) captionLabel.setAnchor(top: photoImageView.bottomAnchor, constantTop: 12, - leading: photoImageView.leadingAnchor) + leading: photoImageView.leadingAnchor, + trailing: photoImageView.trailingAnchor) creationDateLabel.setAnchor(bottom: bottomAnchor, constantBottom: 7, trailing: trailingAnchor, constantTrailing: 12) diff --git a/MemorialHouse/MHPresentation/MHPresentation/Source/EditPhoto/EditPhotoViewController.swift b/MemorialHouse/MHPresentation/MHPresentation/Source/EditPhoto/EditPhotoViewController.swift index 45756727..1cb6141e 100644 --- a/MemorialHouse/MHPresentation/MHPresentation/Source/EditPhoto/EditPhotoViewController.swift +++ b/MemorialHouse/MHPresentation/MHPresentation/Source/EditPhoto/EditPhotoViewController.swift @@ -100,7 +100,7 @@ final class EditPhotoViewController: UIViewController { configureNavigationBar() configureAddSubView() configureConstraints() - configureButtonAction() + configureAction() } override func viewWillAppear(_ animated: Bool) { @@ -213,7 +213,7 @@ final class EditPhotoViewController: UIViewController { dividedLine2.setBottom(anchor: editButtonStackView.topAnchor, constant: 11) captionTextField.setAnchor( leading: view.leadingAnchor, constantLeading: 13, - trailing: view.trailingAnchor, + trailing: view.trailingAnchor, constantTrailing: 13, height: 30 ) captionTextFieldBottomConstraint = captionTextField.bottomAnchor.constraint( @@ -269,18 +269,26 @@ final class EditPhotoViewController: UIViewController { ) } - // MARK: - Add Button Action - private func configureButtonAction() { + private func configureAction() { let rotateButtonAction = UIAction { [weak self] _ in guard let self else { return } let image = self.photoImageView.image self.photoImageView.image = image?.rotate(radians: .pi / 2) } + rotateButton.addAction(rotateButtonAction, for: .touchUpInside) + let drawButtonAction = UIAction { _ in // TODO: - Draw Action } - rotateButton.addAction(rotateButtonAction, for: .touchUpInside) drawButton.addAction(drawButtonAction, for: .touchUpInside) + + let textFieldAction = UIAction { [weak self] _ in + guard let self else { return } + if self.captionTextField.text?.count ?? 0 > 50 { + self.captionTextField.text = String(self.captionTextField.text?.prefix(50) ?? "") + } + } + captionTextField.addAction(textFieldAction, for: .editingChanged) } // MARK: - Keyboard Appear & Hide