Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

이미지 Crop 구현 및 TextField 위치 조정, CustomAlbum ViewModel 구조 리팩토링 #66

Merged
merged 17 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import MHCore
import UIKit
import Photos
import Combine

final class CustomAlbumViewController: UIViewController {
// MARK: - Properties
private let imagePicker = UIImagePickerController()
// MARK: - UI Components
private lazy var albumCollectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
let cellSize = (self.view.bounds.inset(by: self.view.safeAreaInsets).width - 6) / 3
Expand All @@ -16,7 +17,11 @@ final class CustomAlbumViewController: UIViewController {

return collectionView
}()

// MARK: - Properties
private let viewModel: CustomAlbumViewModel
private let input = PassthroughSubject<CustomAlbumViewModel.Input, Never>()
private var cancellables = Set<AnyCancellable>()

// MARK: - Initializer
init(viewModel: CustomAlbumViewModel) {
Expand All @@ -31,20 +36,31 @@ final class CustomAlbumViewController: UIViewController {
super.init(nibName: nil, bundle: nil)
}

// MARK: - ViewDidLoad
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}

// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()

setup()
configureConstraints()
configureNavagationBar()
viewModel.action(.viewDidLoad)
configureNavigationBar()
bind()
input.send(.viewDidLoad)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

configureNavigationAppearance()
}

// MARK: - Setup & Configure
private func setup() {
view.backgroundColor = .baseBackground
imagePicker.delegate = self
PHPhotoLibrary.shared().register(self)
albumCollectionView.delegate = self
albumCollectionView.dataSource = self
albumCollectionView.register(
Expand All @@ -58,11 +74,13 @@ final class CustomAlbumViewController: UIViewController {
albumCollectionView.fillSuperview()
}

private func configureNavagationBar() {
private func configureNavigationBar() {
// TODO: - 추후 삭제 필요
navigationController?.navigationBar.isHidden = false
navigationItem.title = "사진 선택"
navigationController?.navigationBar.titleTextAttributes = [
NSAttributedString.Key.font: UIFont.ownglyphBerry(size: 17),
NSAttributedString.Key.foregroundColor: UIColor.mhTitle]

// TODO: - 추후 Convenience 생성자로 수정 필요
// Left Bar BarButton
let closeAction = UIAction { [weak self] _ in
guard let self else { return }
self.navigationController?.popViewController(animated: true)
Comment on lines 84 to 86
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3
제가 이번에 올린 PR에 네비게이션 아이템 만드는 편의 생성자 Extension 추가해둬서
영현님이 보시기에 가독성 증가 필요 시 적용해보셔도 좋을 것 같습니다 !

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

효준님 PR 머지되고 나서 다음 제 PR에서 수정하겠습니당 .ᐟ.ᐟ 효준님께서 만들어주신 편의 생성자 정말 좋은 것 같아요 .ᐟ.ᐟ

Expand All @@ -76,13 +94,78 @@ final class CustomAlbumViewController: UIViewController {
navigationItem.leftBarButtonItem = leftBarButton
}

// MARK: - Open Camera
private func configureNavigationAppearance() {
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.backgroundColor = .baseBackground
navigationBarAppearance.titleTextAttributes = [
NSAttributedString.Key.font: UIFont.ownglyphBerry(size: 17),
NSAttributedString.Key.foregroundColor: UIColor.mhTitle
]
navigationController?.navigationBar.standardAppearance = navigationBarAppearance
navigationController?.navigationBar.compactAppearance = navigationBarAppearance
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: - Camera
private func checkCameraAuthorization() {
let authorization = AVCaptureDevice.authorizationStatus(for: .video)

switch authorization {
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { @Sendable granted in
if granted {
Task { [weak self] in
await self?.openCamera()
}
} else {
// TODO: 카메라 권한 설정 페이지로 이동
MHLogger.info("카메라 권한 거부")
}
}
case .authorized:
openCamera()
case .restricted, .denied:
// TODO: 카메라 권한 설정 페이지로 이동
MHLogger.info("카메라 권한 거부")
default:
MHLogger.error(authorization)
}
}

private func openCamera() {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .camera
navigationController?.show(imagePicker, sender: nil)
} else {
// TODO: - 카메라 접근 권한 Alert
}
}
}
Expand All @@ -94,14 +177,14 @@ extension CustomAlbumViewController: UICollectionViewDelegate {
didSelectItemAt indexPath: IndexPath
) {
if indexPath.item == 0 {
self.openCamera()
self.checkCameraAuthorization()
} else {
guard let asset = viewModel.photoAsset?[indexPath.item - 1] else { return }
Task {
await LocalPhotoManager.shared.requestImage(with: asset) { [weak self] image in
guard let self else { return }
let editViewController = EditPhotoViewController()
editViewController.setPhoto(image: image)
editViewController.setPhoto(image: image, date: asset.creationDate)
self.navigationController?.pushViewController(editViewController, animated: true)
}
}
Expand Down Expand Up @@ -149,11 +232,20 @@ extension CustomAlbumViewController: UIImagePickerControllerDelegate, UINavigati
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
dismiss(animated: true, completion: nil)
dismiss(animated: true)
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
let editViewController = EditPhotoViewController()
editViewController.setPhoto(image: image)
editViewController.setPhoto(image: image, date: .now)
self.navigationController?.pushViewController(editViewController, animated: true)
}
}
}

// MARK: - PHPhotoLibraryChangeObserver
extension CustomAlbumViewController: PHPhotoLibraryChangeObserver {
nonisolated func photoLibraryDidChange(_ changeInstance: PHChange) {
Task { @MainActor in
input.send(.photoDidChanged(to: changeInstance))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,33 @@ import MHFoundation
import Photos
import Combine

final class CustomAlbumViewModel {
final class CustomAlbumViewModel: ViewModelType {
enum Input {
case viewDidLoad
case photoDidChanged(to: PHChange)
}
// MARK: - Properties
@Published private(set) var photoAsset: PHFetchResult<PHAsset>?

func action(_ input: CustomAlbumViewModel.Input) {
switch input {
case .viewDidLoad:
fetchPhotoAssets()
enum Output {
case fetchAssets
case changedAssets(with: PHFetchResultChangeDetails<PHAsset>)
}

private let output = PassthroughSubject<Output, Never>()
private var cancellables = Set<AnyCancellable>()
private(set) var photoAsset: PHFetchResult<PHAsset>?

func transform(input: AnyPublisher<Input, Never>) -> AnyPublisher<Output, Never> {
input.sink { [weak self] event in
switch event {
case .viewDidLoad:
self?.fetchPhotoAssets()
case .photoDidChanged(let changeInstance):
self?.updatePhotoAssets(changeInstance)
}
}
.store(in: &cancellables)

return output.eraseToAnyPublisher()
}

private func fetchPhotoAssets() {
Expand All @@ -22,5 +37,17 @@ final class CustomAlbumViewModel {
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

photoAsset = PHAsset.fetchAssets(with: fetchOptions)
output.send(.fetchAssets)
}

private func updatePhotoAssets(_ changeInstance: PHChange) {
guard let asset = self.photoAsset,
let changes = changeInstance.changeDetails(for: asset) else { return }

self.photoAsset = changes.fetchResultAfterChanges

if changes.hasIncrementalChanges {
output.send(.changedAssets(with: changes))
}
}
}
Loading
Loading