swift ์ค์ต - 5 : Photo Gallery App
์ค๋ ํ๊ฑด ๊ฐค๋ฌ๋ฆฌ์์ ์ฌ์ง์ ๊ฐ์ ธ์์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ ๋ํด์ ๋ฐฐ์ ๋ค.
์์ฑ๋ ์ฑ์ ์คํํ๋ฉด์ด๋ค.
1. UICollectionView, UICollectionViewCell๋ก ๋ ์ด์์ ๋ง๋ค๊ธฐ
2. ๋ค๋น๊ฒ์ด์ ๋ฐ์ ๋ฒํผ ์ถ๊ฐ
3. ๊ฐค๋ฌ๋ฆฌ ์ ๊ทผ ๊ถํ ์ฃผ๊ธฐ
4. ๊ฐ์ ธ์จ ์ฌ์ง ํ์ ๋ฐ ๋ก๋ฉํ๊ธฐ
์ด ์์๋๋ก ๋ฐฐ์ด ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด๋ ค๊ณ ํ๋ค.
1. ๋ ์ด์์ ๋ง๋ค๊ธฐ
์คํ ๋ฆฌ๋ณด๋์์ UICollectionView๋ฅผ ์ถ๊ฐํด์ ํ๋ฉด์ ๊ฝ ์ฐจ๋๋ก ์ค์ ํด๋๊ณ
UICollectionViewCell์ ์ถ๊ฐํ๋ค.
๋ทฐ ์ ์ ๋ทฐ๋ง ์ค์ ํด๋ ํ๋๊ฐ ์๋ก ์๊ธฐ๊ณ , ๊ทธ ์์ ์ด๋ฏธ์ง๋ทฐ๋ฅผ ๋ฃ์ด์ ๊ทธ ์ ์์ ๊ฝ ์ฐจ๋๋ก ์ค์ ์ ํ๋ฉด ๋๋ค.
์ ์ ๋ํด์๋ PhotoCell.swift ํ์ผ์ ์๋ก ์์ฑํ๊ณ , identifier์ class๋ชจ๋ PhotoCell๋ก ์ค์ ํด๋๋ค.
2. ๋ค๋น๊ฒ์ด์ ๋ฐ์ ๋ฒํผ ์ถ๊ฐ
Editor > Embed In > Navigation Controller
๋ฅผ ๋๋ฌ์ ๋ค๋น๊ฒ์ด์ ์ปจํธ๋กค๋ฌ๋ฅผ ์ถ๊ฐํ๋ค.
๊ทธ ์์ ํ์ดํ๊ณผ ๋ฒํผ์ ๋ํ ์ค์ ์
๋ฉ์ธ ViewController์ viewDidLoad์์
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Photo Gallery App"
makeNavigationItem()
}
์ด๋ ๊ฒ ์ค์ ํด๋๋ค.
๋ฒํผ์ ์์ฑํ๋ ์ฝ๋๋ ๊ธธ์ด์ ธ์
makeNavigationItem() ํจ์๋ก ๋นผ๋จ๋ค.
func makeNavigationItem(){
let photoItem = UIBarButtonItem(image: UIImage(systemName: "photo"), style: .done, target: self, action: #selector(checkPermission))
let refreshItem = UIBarButtonItem(image: UIImage(systemName: "arrow.clockwise"), style: .done, target: self, action: #selector(refresh))
photoItem.tintColor = .black.withAlphaComponent(0.7)
self.navigationItem.rightBarButtonItem = photoItem
self.navigationItem.leftBarButtonItem = refreshItem
}
photoItem์ด ์ฌ์ง ๋ชจ์ ์์ด์ฝ ๋ฒํผ์ด๊ณ refreshItem์ด ์๋ก๊ณ ์นจ์ ํ๋ ๋ฒํผ์ด๋ค.
๋๋ค ์ฌ์ง ์์ด์ฝ์ธ๋ฐ, SF Symbols๋ฅผ ์ฌ์ฉํ์ฌ ์์คํ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์จ๋ค.
photoItem์ withAlphaComponent๋ฅผ ์ฌ์ฉํ์ฌ ํ์์ผ๋ก ์ค์ ํด๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ๊ฐ์ ๋ฒํผ์ด ํ๋ ์ผ์ UIBarButtonItem์ ์ฌ์ฉํ ๋ #selector๋ก objc ๋ฐํ์ ํจ์๋ฅผ ๋ฃ์ด์คฌ๋ค.
refresh ๋ฒํผ์ ๋ํด์๋ ๊ทธ๋ฅ ๋ฐ์ดํฐ๋ฅผ reload ํด์ค๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์
@objc func refresh(){
self.photoCollectionView.reloadData()
}
์ด๋ ๊ฒ ์ค์ ์ ํด์คฌ๊ณ ,
์ฌ์ง ์ด๋ฏธ์ง ๋ฒํผ์ ๋ํด์๋ ๋จผ์ ๊ฐค๋ฌ๋ฆฌ์ ์ ๊ทผ๊ถํ์ด ํ์ํ๊ธฐ ๋๋ฌธ์
์ด๋ฅผ ์ฒดํฌํ๊ณ ๊ฐ๊ฐ ๊ถํ ํ์ฉ ์ค์ ์ํ์ ๋ฐ๋ผ ์ฒ๋ฆฌํด์ผ ํ ์ผ์ ์ฝ๋ฉํด์ค๋ค.
3. ๊ฐค๋ฌ๋ฆฌ ์ ๊ทผ ๊ถํ ์ฃผ๊ธฐ
๊ฐค๋ฌ๋ฆฌ ์ ๊ทผ๊ถํ์ ๋ํ ์ํ๋ ํฌ๊ฒ 5๊ฐ์ง๊ฐ ์๋ค.
- .authorized : ํ์ฉ๋ ์ํ
- .limited : ๋ถ๋ถ ํ์ฉ๋ ์ํ
- .denied : ๊ฑฐ๋ถ๋ ์ํ
- .notDetermined : ๊ถํ ์ค์ ์ ์์ฒญ๋ฐ์ง ์์ ์ํ
- .restricted : ์ด๋ค ์์ธ์์๋ ์ง ๊ถํ ์ค์ ์ ํ ์ ์๋ ์ํ
์ฌ๊ธฐ์ ๋ง์ง๋ง restricted๋ ๊ฐ๋ฐ ๋จ๊ณ์์ ์ด๋ป๊ฒ ํด๊ฒฐํ ์ ์๋ ๋ฌธ์ ์ด๊ธฐ ๋๋ฌธ์ ์ ์ธํ๊ณ ,
๋๋จธ์ง 4๊ฐ์ง์ ๋ํด์ ์ค์ ํ๋ค.
@objc func checkPermission() {
if PHPhotoLibrary.authorizationStatus() == .authorized || PHPhotoLibrary.authorizationStatus() == .limited {
DispatchQueue.main.async {
self.showGallery()
}
}else if PHPhotoLibrary.authorizationStatus() == .denied{
DispatchQueue.main.async {
self.showAuthorizationAlert()
}
}else if PHPhotoLibrary.authorizationStatus() == .notDetermined{
PHPhotoLibrary.requestAuthorization { status in
print(status)
self.checkPermission()
}
}
}
๋ค๋น๊ฒ์ด์ ๋ฐ์ ์ฌ์ง ์์ด์ฝ ๋ฒํผ์์ ์ฌ์ฉํ ํจ์์ด๊ธฐ ๋๋ฌธ์ object-c runtime์ผ๋ก ์ ์ธํด์ค๋ค.
์ฒซ๋ฒ์งธ๋ก authorized๋ limited์ ๊ฒฝ์ฐ์๋ ๊ถํ์ด ํ์ฉ๋ ๊ฒ์ด๋ฏ๋ก showGallery() ํจ์๋ฅผ ํธ์ถํด์ ๊ฐค๋ฌ๋ฆฌ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด ๋๋ค.
๋๋ฒ์งธ denied์์๋ ๊ถํ์ด ๊ฑฐ์ ๋ ๊ฒ์ด๋ฏ๋ก, ๊ถํ์ ํ์ฑํ ํ ๊ฒ์ ์๊ตฌํ๋ showAuthorizationAlert ํจ์๋ฅผ ํธ์ถํ๋ค.
๋ง์ง๋ง notDetermined์์๋ ๊ถํ ์ค์ ์ ๋ฌป์ง๋ ์์ ์ํ์ด๊ธฐ ๋๋ฌธ์ ๊ถํ ์ค์ ์ ํ๋ ํจ์ checkPermission์ ๋ค์ ํธ์ถํ๋ค.
์ฌ๊ธฐ์ ์ฌ์ฉํ๋ ํจ์ showGallery์ showAuthorizationAlert์ ์ดํด๋ณด๋ฉด
func showAuthorizationAlert(){
let alert = UIAlertController(title: "ํฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๊ทผ ๊ถํ์ ํ์ฑํ ํด์ฃผ์ธ์.", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "๋ซ๊ธฐ", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "์ค์ ์ผ๋ก ๊ฐ๊ธฐ", style: .default, handler: {
action in
guard let url = URL(string: UIApplication.openSettingsURLString) else{
return
}
if UIApplication.shared.canOpenURL(url){
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}))
self.present(alert, animated: true, completion: nil)
}
๊ถํ ์ค์ ์ ์ํด์ alert์ฐฝ์ ๋์ฐ๊ธฐ ์ํด UIAlertController๋ก ์์ฑํ๋ค.
์ค์ ์ผ๋ก ๊ฐ๋๋ก ํ๊ธฐ ์ํด์ URL๋ก ์ค์ ํด์ค๋ค.
์ฌ๊ธฐ์ ๊ฐค๋ฌ๋ฆฌ ์ฌ์ฉ์ ์ํด ๋จ๋ ๊ฒฝ๊ณ ์ฐฝ์
์ฌ์ง๋ค์ด ์ ํ์ํ์ง ์ ์ด๋์์ผ ๋์ค์ ์ฑ์ ์ถ์ํ ๋ ์ฌ์ฌ์ ํต๊ณผํ ์ ์๋ค๊ณ ํ๋ค.
๊ทธ๊ฑธ ์ค์ ํ๋ ค๋ฉด Info.plist ํ์ผ์ ๋ค์ด๊ฐ์
4. ๊ฐ์ ธ์จ ์ฌ์ง ํ์ ๋ฐ ๋ก๋ฉํ๊ธฐ
(์ฌ๊ธฐ๊ฐ ์กฐ๊ธ ์ด๋ ต๋ค,,, ์ ๋๋ก ์ดํดํ๋์ง๋ ๋ชจ๋ฅด๊ฒ ๋ค. ๊ทธ๋ฅ ์ดํดํ ๋งํผ ์ ๋ฆฌํด๋๋ ๊ฑธ๋ก^^)
๋จผ์ PhotosUI๋ฅผ import ํด์ผํ๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐค๋ฌ๋ฆฌ์์ ์ฌ์ง์ ์ ํํ๊ณ ๋ ๋ค ์ผ์ด๋์ผ ํ ์ผ๋ค์ ๋ํด์๋ PHPickerViewControllerDelegate๋ฅผ ๊ตฌํํด์ผํ๋ค.
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
let identifiers = results.map{
$0.assetIdentifier ?? ""
}
self.fetchResults = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil)
self.photoCollectionView.reloadData()
self.dismiss(animated: true, completion: nil)
}
๊ตฌํํด์ผํ ํจ์๋ ์ด๋ ๊ฒ ์๊ฒผ๋๋ฐ, ์ค์ํ๊ฑด photo data๋ค์ด ์ฌ์ง ํ์ผ ๊ทธ ์์ฒด๋ก ์ ๋ฌ๋๋ ๊ฒ์ด ์๋๋ผ
๋๋ฒ๊น ๋ถ๋ถ์ ๋ณด๋ฉด
assetIdentifier๋ผ๊ณ ์ ์ฝ๋๋ก ์ ๋ฌ์ด ๋๋ค. ๊ทธ๋์ ์ ์ฝ๋๋ฅผ ๊ฐ์ ธ์์ ์ ์ฅํด์
asset ํํ๋ก ์ ์ฅํ๊ณ ํ๋ฉด์ ๋ฟ๋ ค์ค์ผ ํ๋ค.
identifier ์์์ 0๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก๋ถํฐ ๊ฐ์ ธ์จ assetIdentifier๋ฅผ ์ ์ฅํ๊ณ ,
์ ์ญ๋ณ์๋ก ์ ์ฅ๋ PHFetchResult<PHAsset> ํํ์ ๋ฐฐ์ด์ธ fetchResults์ PHAsset.fetchAssets๋ฅผ ์ฌ์ฉํ์ฌ identifier๋ฅผ ๋ณํํ์ฌ ๋ฃ์ด์ฃผ๊ณ collectionView๋ฅผ reloadํ๊ฒ ํ ๋ค, dismissํ์ฌ ๋ฉ์ธ์ผ๋ก ๋์๊ฐ๋ค.
ํ๋ฉด์ ๋ฟ๋ ค์ฃผ๋ ๋ถ๋ถ์ ๋ง์ฐฌ๊ฐ์ง๋ก
CollectionView์์ ๊ผญ ๊ตฌํํด์ผ ํ๋ UICollectionViewDataSource ์์ ๋๊ฐ์ง ํจ์์์ ์งํํ๋ค.
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.fetchResults?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
if let asset = self.fetchResults?[indexPath.row]{
cell.loadImage(asset: asset)
}
return cell
}
}
์ฒซ๋ฒ์งธ ํจ์์์๋ fetchResults์ ๊ธธ์ด๋ฅผ ๊ณ์ฐํ์ฌ ๋ช๊ฐ์ ์ ์ ๋ณด์ฌ์ค๊ฑด์ง ๊ฒฐ์ ํ๊ณ ,
๋๋ฒ์ค ํจ์์์๋ asset์์ ์์๋ค์ ๊ฐ์ ธ์์ PhotoCell์ ์ ์ธํ loadImageํจ์์ ํ๋ผ๋ฏธํฐ๋ก ๋ณด๋ด ํ๋ฉด์ ๋ฟ๋ ค์ฃผ๊ฒ ํ๋ค.
PhotoCell์ loadImage ํจ์๋
func loadImage(asset: PHAsset){
let imageManager = PHImageManager()
let scale = UIScreen.main.scale
let imageSize = CGSize(width: 150 * scale, height: 150 * scale)
let options = PHImageRequestOptions()
options.deliveryMode = .opportunistic
self.photoImageView.image = nil
imageManager.requestImage(for: asset, targetSize: imageSize, contentMode: .aspectFill, options: options, resultHandler: {
image, info in
// if (info?[PHImageResultIsDegradedKey] as? Bool) == true {
// //์ ํ์ง
// }else{
// //๊ณ ํ์ง
// }
self.photoImageView.image = image
})
}
์ด๋ ๊ฒ ์๊ฒผ๋ค.
PHImageManager๋ฅผ ์ธ์คํด์คํ ํ๊ณ , ์ฌ์ง์ ์ฌ์ด์ฆ๋ฅผ ์ค์ ํ๊ธฐ ์ํ ์ฌ๋ฌ๊ฐ์ง ์์ ๋ค์ ํ๋ค,
imageManager.requestImage๋ฅผ ์ฌ์ฉํ์ฌ, ์ ๋ฌ๋ฐ์ asset์ ์ฌ์ด์ฆ, ํ์ง์ ๋ง๊ฒ ์ค์ ์ ํด์ฃผ๊ณ
์ด๋ฏธ์ง๋ฅผ ๋ฐํํ์ฌ ์ด๋ฏธ์ง ๋ทฐ์ ๋ฟ๋ฆฐ๋ค.
์ด๋ ๊ฒ ํ๋ฉด ๋~~~~~~