내비게이션 아이템은 내비게이션바의 콘텐츠를 표시하는 객체로, 뷰 컨트롤러가 전환될 때마다 내비게이션바는 하나의 공동 객체지만 내비게이션 아이템은 각각의 뷰 컨트롤러가 가지고 있는 프로퍼티이다.
즉, 내비게이션바가 내비게이션 컨트롤러와 연관이 있다면 내비게이션 아이템은 해당 뷰 컨트롤러와 연관이 있으며 보통 내비게이션바에서 보여지는 중앙 타이틀, 좌측 바 버튼, 우측 바 버튼 등이 내비게이션 아이템의 프로퍼티이다.
주요 프로퍼티
title : 내비게이션바에 표시되는 내비게이션 아이템의 제목. 기본값은 nil
vartitle:String?{getset}
backBarButtonItem : 내비게이션바에서 뒤로 버튼이 필요할 때 사용할 바 버튼 항목
varbackBarButtonItem:UIBarButtonItem?{getset}
hidesBackButton : 뒤로 버튼이 숨겨져 있는지를 결정하는 부울 값
varhidesBackButton:Bool{getset}
주요 메서드
// setHidesBackButton(_:animated:) : 뒤로 버튼이 숨겨져 있는지를 설정하고, 전환 애니메이션 효과 적용funcsetHidesBackButton(_hidesBackButton:Bool,animated:Bool)
내비게이션 아이템 커스터마이징
내비게이션 아이템은 크게 좌, 우 바 버튼 아이템과 중앙 타이틀 영역이 있다.
좌측 바 버튼, 우측 바 버튼 아이템의 경우 타입은 UIBarButtonItem 이며 상황에 따라서 커스텀 뷰를 넣어서 구현할 수도 있다.
프로퍼티
// titleView : 중앙 타이틀 영역의 뷰vartitleView:UIView?{getset}// Tip : 중앙 타이틀 영역에 뷰를 상속받은 (UILabel, UIView, UIImageView 등등) 클래스들을 표현할 수 있습니다.leftBarButtonItems:좌측아이템영역의UIBarButtonItem의바버튼아이템배열varleftBarButtonItems:[UIBarButtonItem]?{getset}// leftBarButtonItem : 좌측 아이템 영역의 UIBarButtonItem 중에 가장 좌측 바 버튼 아이템varleftBarButtonItem:UIBarButtonItem?{getset}// rightBarButtonItems : 우측 아이템 영역의 UIBarButtonItem의 바 버튼 아이템 배열varrightBarButtonItems:[UIBarButtonItem]?{getset}// rightBarButtonItem : 우측 아이템 영역의 UIBarButtonItem 중에 가증 우측 바 버튼 아이템varrightBarButtonItem:UIBarButtonItem?{getset}
메서드
// setLeftBarButtonItems(_:animated:) : 좌측 아이템 영역에 UIBarButtonItem 타입의 객체들을 순차적으로 설정하고 애니메이션 효과를 적용funcsetLeftBarButtonItems(_items:[UIBarButtonItem]?,animated:Bool)// setLeftBarButton(_:animated:) : 좌측 아이템 영역에 UIBarButtonItem 타입의 객체를 설정하고 애니메이션 효과를 적용funcsetLeftBarButton(_item:UIBarButtonItem?,animated:Bool)// setRightBarButtonItems(_:animated:) : 우측 내비게이션 아이템 영역에 UIBarButtonItem 타입의 객채들을 순차적으로 설정하고 애니메이션 효과를 적용funcsetRightBarButtonItems(_items:[UIBarButtonItem]?,animated:Bool)//setRightBarButton(_:animated:) : 우측 아이템 영역에 UIBarButtonItem 타입의 객체를 설정하고 애니메이션 효과를 적용funcsetRightBarButton(_item:UIBarButtonItem?,animated:Bool)
바 버튼 아이템
바 버튼 아이템은 UIToolbar 또는 UINavigationBar (backBarButtonItem,leftBarButtonItem,rightBarButtonItem등)에 배치할 수 있는 특수한 버튼이다. 제목이나 이미지를 보여줄 수 있고 미리 UIBarButtonItem.SystemItem 열거형에 정의된 여러 스타일 중 하나의 스타일로 선택할 수도 있다.
Tip: iOS 11에서는 UIBarButtonItem을 오토레이아웃 제약 없이 내비게이션 아이템으로 추가하면 바 버튼 아이템의 프레임이 예상치 못한 크기로 나올 수 있다.
이럴 땐 UIBarButtonItem 객체에 적절한 오토레이아웃 제약을 추가한 후 내비게이션 아이템으로 설정하면 설정한 제약에 따라 알맞은 크기로 볼 수 있다.
주요 프로퍼티
title : 아이템에 표시되는 제목
vartitle:String?{getset}
image : 아이템에 표시되는 이미지
varimage:UIImage?{getset}
style : 아이템의 스타일
varstyle:UIBarButtonItem.Style{getset}
width : 아이템의 너비 값
varwidth:CGFloat{getset}
tintColor : 아이템에 적용할 색상
vartintColor:UIColor?{getset}
주요 상수
UIBarButtonItem.Style : 아이템 스타일 정의
UIBarButtonItem.SystemItem : 바 버튼 아이템에 대한 시스템 제공 스타일
importUIKitimportPhotosclassImageZoomViewController:UIViewController,UIScrollViewDelegate{// 전화면에서 받아올 asset 프로퍼티varasset:PHAsset!// imageManager를 통해 이미지 요청letimageManager:PHCachingImageManager=PHCachingImageManager()@IBOutletweakvarimageView:UIImageView!// scrollView delegate 메서드funcviewForZooming(inscrollView:UIScrollView)->UIView?{// scrollview가 줌을 해줄 대상이 누구? = imageViewreturnself.imageView}overridefuncviewDidLoad(){super.viewDidLoad()// 메모리에 화면이 로드가 되면 호출될 이미지의 사이즈 등..imageManager.requestImage(for:asset,targetSize:CGSize(width:asset.pixelWidth,height:asset.pixelHeight),contentMode:.aspectFill,options:nil,resultHandler:{image,_inself.imageView.image=image})// Do any additional setup after loading the view.}}
스토리보드로 넘어서 새로운 뷰컨트롤러를 만들어주고 이전에 만들어놨던 뷰컨트롤러는 네비게이션 컨트롤러 임베드를 해준다.
그리고 스크롤뷰의 delegate는 ImageZoomViewController가 되고 ImageZoomViewController의 imageView는 스토리보드에 생성해놓은 imageView로 연결해준다.
그리고 스크롤뷰와 이미지뷰의 셋팅을 위와같이 설정해준다.
그리고 ViewController로 넘어와 네비게이션 컨트롤러를 통해 전달해줄 데이터 세그를 작성해준다.
스크롤뷰는 스크롤뷰 안에 포함된 뷰를 상,하,좌,우로 스크롤 할 수 있고 확대 및 축소할 수 있는 뷰를 의미한다.
그리고 스크롤뷰를 상속받아 활용되는 뷰로는 UITableView, UICollectionView, UITextView등 여러 UIKit 클래스가 있다.
스크롤뷰 상호작용
주요 프로퍼티
delegate : 스크롤뷰 객체의 델리게이트
weakvardelegate:UIScrollViewDelegate?{getset}
UIScrollViewDelegate 프로토콜에 의해 선언된 메소드 델리게이트가 UIScrollView 클래스의 메시지에 응답
콘텐츠 크기 및 오프셋 관리
주요 프로퍼티
contentSize : 콘텐츠뷰의 크기
varcontentSize:CGSize{getset}
contentOffset : 콘텐츠뷰의 원점이 스크롤뷰의 원점에서 오프셋 된 지점
varcontentOffset:CGPoint{getset}
주요 메서드
// setContentOffset(_:animated:) : 스크롤뷰의 원점에 대한 콘텐츠뷰의 오프셋 설정funcsetContentOffset(_contentOffset:CGPoint,animated:Bool)
콘텐츠 삽입 동작 관리
주요 프로퍼티
contentInset : 콘텐츠뷰와 안전 영역 또는 스크롤뷰 가장자리에 간격
varcontentInset:UIEdgeInsets{getset}
스크롤뷰 구성
주요 프로퍼티
// isScrollEnabled : 스크롤링이 사용 가능한지 아닌지를 결정하는 부울 값varisScrollEnabled:Bool{getset}// isDirectionalLockEnabled : 스크롤이 특정 방향으로 고정할지를 결정하는 부울 값varisDirectionalLockEnabled:Bool{getset}//isPagingEnabled : 스크롤뷰에서 페이징을 사용할 수 있는 여부를 결정하는 부울 값varisPagingEnabled:Bool{getset}//scrollsToTop : 스크롤 할 수 있는 제스처를 사용할지를 결정하는 부울 값varscrollsToTop:Bool{getset}// bounces : 스크롤뷰가 가장자리를 통과해서 다시 튀어나오는지 제어하는 부울 값varbounces:Bool{getset}// alwaysBounceVertical : 세로 스크롤이 콘텐츠뷰의 끝에 도달할 때 튀어 오르기가 항상 발생하는지를 결정하는 부울 값varalwaysBounceVertical:Bool{getset}// alwaysBounceHorizontal : 가로 스크롤이 콘텐츠뷰의 끝에 도달할 때 튀어 오르기가 항상 발생하는지를 결정하는 부울 값varalwaysBounceHorizontal:Bool{getset}
스크롤링 상태 가져오기
주요 프로퍼티
// isTracking : 사용자가 스크롤을 시작하기 위해 콘텐츠를 터치한 여부를 반환varisTracking:Bool{get}// isDragging : 사용자가 콘텐츠를 스크롤하고 있는지 나타내는 부울 값varisDragging:Bool{get}// isDecelerating : 사용자가 손가락을 떼었을 때 콘텐츠가 스크롤뷰에서 움직이지 않고 있는지를 반환varisDecelerating:Bool{get}// decelerationRate : 사용자가 손가락을 뗀 후의 감속도를 결정하는 부동 소수점 값vardecelerationRate:CGFloat{getset}
스크롤 인디케이터 및 새로고침 제어 관리
주요 프로퍼티
// indicatorStyle : 스크롤 인디케이터의 스타일varindicatorStyle:UIScrollViewIndicatorStyle{getset}// showsHorizontalScrollIndicator : 가로 스크롤 바 표시 여부를 제어하는 부울 값varshowsHorizontalScrollIndicator:Bool{getset}// showsVerticalScrollIndicator : 세로 스크롤 바 표시 여부를 제어하는 부울 값varshowsVerticalScrollIndicator:Bool{getset}
특정 위치로 스크롤 하기
주요 메서드
// scrollRectToVisible(_:animated:) : 콘텐츠의 특정 위치로 스크롤 하여 화면에 표시funcscrollRectToVisible(_rect:CGRect,animated:Bool)
확대 및 축소
주요 프로퍼티
// panGestureRecognizer : 팬 제스처를 제어하기 위한 제스처 인스턴스varpanGestureRecognizer:UIPanGestureRecognizer{get}// pinchGestureRecognizer : 핀치 제스처를 제어하기 위한 제스처 인스턴스varpinchGestureRecognizer:UIPinchGestureRecognizer?{get}// zoomScale : 스크롤뷰 콘텐츠에 적용되는 현재 배율varzoomScale:CGFloat{getset}// maximumZoomScale : 스크롤뷰 콘텐츠에 적용되는 최대 배율varmaximumZoomScale:CGFloat{getset}// minimumZoomScale : 스크롤뷰 콘텐츠에 적용되는 최소 배율varminimumZoomScale:CGFloat{getset}// isZoomBouncing : 확대 및 축소가 지정한 배율 제한을 초과했음을 나타내는 부울 값varisZoomBouncing:Bool{get}// isZooming : 콘텐츠뷰가 현재 확대 또는 축소되어 있는지를 나타내는 부울 값varisZooming:Bool{get}// bouncesZoom : 크기 조정이 최대 또는 최소 제한을 초과할 때 튀어 오르는 애니메이션을 보여줄지 결정하는 부울 값varbouncesZoom:Bool{getset}
주요 메서드
// zoom(to:animated:) : 콘텐츠 특정 영역 확대funczoom(torect:CGRect,animated:Bool)// setZoomScale(_:animated:) : 현재 배율을 지정funcsetZoomScale(_scale:CGFloat,animated:Bool)
키보드 관리
주요 프로퍼티
// keyboardDismissMode : 스크롤뷰에서 드래그가 시작될 때 키보드가 해제되는 방식varkeyboardDismissMode:UIScrollViewKeyboardDismissMode{getset}
UIScrollViewDelegate 프로토콜
스크롤 및 드래그
// scrollViewDidScroll(_:) : 콘텐츠뷰를 스크롤 할 때 델리게이트에 알림optionalfuncscrollViewDidScroll(_scrollView:UIScrollView)// scrollViewWillBeginDragging(_:) : 스크롤뷰에서 콘텐츠 스크롤을 시작할 시점을 델리게이트에 알림optionalfuncscrollViewWillBeginDragging(_scrollView:UIScrollView)// scrollViewWillEndDragging(_:withVelocity:targetContentOffset:) : 스크롤뷰의 드래그가 끝나기 직전에 델리게이트에 알림optionalfuncscrollViewWillEndDragging(_scrollView:UIScrollView,withVelocityvelocity:CGPoint,targetContentOffset:UnsafeMutablePointer<CGPoint>)// scrollViewDidEndDragging(_:willDecelerate:) : 스크롤뷰의 드래그가 끝났을 때 델리게이트에 알림optionalfuncscrollViewDidEndDragging(_scrollView:UIScrollView,willDeceleratedecelerate:Bool)// scrollViewShouldScrollToTop(_:) : 스크롤뷰가 콘텐츠의 맨 위로 스크롤 해야 하는 경우 델리게이트에 동작 여부를 물어봄optionalfuncscrollViewShouldScrollToTop(_scrollView:UIScrollView)->Bool// scrollViewDidScrollToTop(_:) : 스크롤뷰가 콘텐츠의 맨 위로 스크롤 되었음을 델리게이트에 알림optionalfuncscrollViewDidScrollToTop(_scrollView:UIScrollView)// scrollViewWillBeginDecelerating(_:) : 스크롤링 동작이 감속되기 시작하고 있다고 델리게이트에 알림optionalfuncscrollViewWillBeginDecelerating(_scrollView:UIScrollView)// scrollViewDidEndDecelerating(_:) : 스크롤링 동작이 감속이 끝났을 때 델리게이트에 알림// optional func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
확대 및 축소
//viewForZooming(in:) : 스크롤뷰에서 확대 및 축소를 할 때 확대 및 축소를 할 뷰 인스턴스를 요청optionalfuncviewForZooming(inscrollView:UIScrollView)->UIView?// scrollViewWillBeginZooming(_:with:) : 스크롤뷰의 콘텐츠 확대가 시작될 때 델리게이트에 알림optionalfuncscrollViewWillBeginZooming(_scrollView:UIScrollView,withview:UIView?)// scrollViewDidEndZooming(_:with:atScale:) : 스크롤뷰의 콘텐츠 확대가 완료될 때 델리게이트에 알림optionalfuncscrollViewDidEndZooming(_scrollView:UIScrollView,withview:UIView?,atScalescale:CGFloat)// scrollViewDidZoom(_:) : 스크롤뷰의 확대 및 축소 배율이 변경될 때 델리게이트에 알림optionalfuncscrollViewDidZoom(_scrollView:UIScrollView)
프로세서는 하드웨어적인 측면에서 컴퓨터 내에서 프로그램을 수행하는 하드웨어 유닛이다. 대표적으로 중앙처리장치(Central Processing Unit - CPU)가 이에 속한다. 한 컴퓨터가 여러 개의 프로세서를 갖는다면 멀티 프로세서라고 말하고, 듀얼 프로세서라고 한다면 한 컴퓨터에 두 개의 프로세서가 운용되는 것을 의미한다.
코어
프로세서에서 코어는 주요 연산회로이다. 싱글코어는 말 그대로 하나의 연산회로가 내장되어있는 것이고 듀얼코어는 두 개의 연산회로가 내장된 것을 뜻한다. 또, 여러 개의 코어를 가진 프로세서를 멀티 프로세서라고 한다.
프로그램(Program)과 프로세스(Process)
프로그램은 일반적으로 보조기억 장치에 저장된 실행코드 즉, 생명이 없는 상태를 말한다. 프로세스는 프로그램을 구동하여 프로그램 자체와 프로그램의 상태가 메모리상에서 실행되는 작업 단위를 말한다. 동시에 여러 개의 프로세스를 운용하는 시분할 방식을 멀티태스킹이라고 하며 이러한 프로세스 관리는 운영체제에서 담당한다.
스레드(Thread)
스레드는 하나의 프로세스 내에서 실행되는 작업흐름의 단위를 말한다. 보통 한 프로세스는 하나의 스레드를 가지고 있지만, 프로세스 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 방식을 멀티스레딩이라고 한다. 그리고 프로그램 실행이 시작될 때부터 동작하는 스레드를 메인 스레드라 하고 그 외에 나중에 생성된 스레드를 서브 스레드 또는 세컨더리 스레드라고 한다.
비동기(Asynchronous) 프로그래밍
프로그램의 주 실행 흐름을 멈추어서 기다리는 부분 없이 바로 다음 작업을 실행할 수 있게 하는 방식이다.
즉, 코드의 실행 결과 처리를 별도의 공간에 맡겨둔 뒤 결과를 기다리지 않고 바로 다음 코드를 실행하는 병렬처리 방식을 의미한다. 비동기 프로그래밍은 언어 및 프레임워크에서 지원하는 여러 방법으로 구현할 수 있다.
동시성(Concurrency) 프로그래밍
논리적인 용어로 동시에 실행되는 것처럼 보이는 것을 의미한다.
싱글 코어(멀티 코어에서도 가능)에서 멀티스레드를 동작시키기 위한 방식으로 멀티 태스킹을 위해 여러 개의 스레드가 번갈아 가면서 실행되는 방식이다. 동시성을 이용한 싱글 코어의 멀티 태스킹은 각 스레드들이 병렬적으로 실행되는 것처럼 보이지만 사실은 서로 번갈아 가면서 실행되고 있는 방식이다.
병렬성(Parallelism) 프로그래밍
물리적으로 동시에 정확히 동시에 실행되는 것을 의미한다.
멀티 코어에서 멀티 스레드를 동작시키는 방식으로 데이터 병렬성(Data Parallelism)과 작업 병렬성(Task Parallelism)으로 구분된다.
데이터 병렬성: 전체 데이터를 나누어 서브 데이터들로 만든 뒤, 서브 데이터들을 병렬 처리해서 작업을 빠르게 수행하는 방법
작업 병렬성 : 서로 다른 작업을 병렬 처리하는 방법
동시성(Concurrecny)과 병렬성(Parallelism) 차이
동시성 프로그래밍과 병렬성 프로그래밍 모두 비동기(Asynchronous) 동작을 구현할 수 있지만, 그 동작 원리가 다르다
동시성(Concurrecny) : 통장을 만들러 온 N개의 대기열과 한 명 이상의 은행직원
병렬성(Parallelism) : 통장을 만들러 온 N개의 대기열과 N명의 은행직원
즉, 동시성은 싱글코어 및 멀티코어에서 모두 구현할 수 있지만, 병렬성은 멀티 코어에서만 구현할 수 있다.
iOS 환경 동시성 프로그래밍 지원 종류
Grand Central Dispatch (GCD) : 멀티 코어와 멀티 프로세싱 환경에서 최적화된 프로그래밍을 할 수 있도록 애플이 개발한 기술
연산 대기열 (Operation Queue) : 비동기적으로 실행되어야 하는 작업을 객체 지향적인 방법으로 사용
importUIKitimportPhotosclassViewController:UIViewController,UITableViewDataSource{// 사진 목록을 테이블뷰에 보여줄 것@IBOutletweakvartableView:UITableView!varfetchResult:PHFetchResult<PHAsset>!// 가져온 에셋을 가지고 이미지를 로드해옴letimageManager:PHCachingImageManager=PHCachingImageManager()letcellIdentifier:String="cell"funcrequestCollection(){// 사진찍으면 사진이 저장되는 카메라롤의 컬렉션을 가져옴letcameraRoll:PHFetchResult<PHAssetCollection>=PHAssetCollection.fetchAssetCollections(with:.smartAlbum,subtype:.smartAlbumUserLibrary,options:nil)guardletcameraRollColletction=cameraRoll.firstObjectelse{return}letfetchOptions=PHFetchOptions()// 최신순으로 사진을 sortfetchOptions.sortDescriptors=[NSSortDescriptor(key:"creationDate",ascending:false)]// 그 결과를 fetchResult라는 프로퍼티로 가져옴self.fetchResult=PHAsset.fetchAssets(in:cameraRollColletction,options:fetchOptions)}overridefuncviewDidLoad(){super.viewDidLoad()// Do any additional setup after loading the view.letphotoAuthorizationStatus=PHPhotoLibrary.authorizationStatus()switchphotoAuthorizationStatus{case.authorized:print("접근 허가됨")self.requestCollection()self.tableView.reloadData()case.denied:print("접근 불허")case.notDetermined:print("아직 응답하지 않음")PHPhotoLibrary.requestAuthorization({(status)inswitchstatus{case.authorized:print("사용자가 허용함")self.requestCollection()self.tableView.reloadData()case.denied:print("사용자가 불허함")default:break}})case.restricted:print("접근 제한")}}functableView(_tableView:UITableView,numberOfRowsInSectionsection:Int)->Int{returnself.fetchResult?.count??0}functableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{// cell에 이미지를 하나씩 넣어줌letcell:UITableViewCell=tableView.dequeueReusableCell(withIdentifier:self.cellIdentifier,for:indexPath)// asset은 fetchresult에서 index에 해당letasset:PHAsset=fetchResult.object(at:indexPath.row)// imageManager를 통해 실질적인 이미지를 요청imageManager.requestImage(for:asset,targetSize:CGSize(width:30,height:30),contentMode:.aspectFill,options:nil,resultHandler:{image,_incell.imageView?.image=image})returncell}}
이렇게 코드를 진행하게 되면 에러가 발생할 것이다.
reloadData는 메인 쓰레드에서만 동작해야한다. » operation queue 사용
overridefuncviewDidLoad(){super.viewDidLoad()// Do any additional setup after loading the view.letphotoAuthorizationStatus=PHPhotoLibrary.authorizationStatus()switchphotoAuthorizationStatus{case.authorized:print("접근 허가됨")self.requestCollection()self.tableView.reloadData()case.denied:print("접근 불허")case.notDetermined:print("아직 응답하지 않음")PHPhotoLibrary.requestAuthorization({(status)inswitchstatus{case.authorized:print("사용자가 허용함")self.requestCollection()OperationQueue.main.addOperation{self.tableView.reloadData()}case.denied:print("사용자가 불허함")default:break}})case.restricted:print("접근 제한")}}
사진첩에서 사진 삭제하기
importUIKitimportPhotosclassViewController:UIViewController,UITableViewDataSource,UITableViewDelegate,PHPhotoLibraryChangeObserver{// PHphotoobserver는 library에 변화가 생기면 감지를 하는 프로토콜// 사진 목록을 테이블뷰에 보여줄 것@IBOutletweakvartableView:UITableView!varfetchResult:PHFetchResult<PHAsset>!// 가져온 에셋을 가지고 이미지를 로드해옴letimageManager:PHCachingImageManager=PHCachingImageManager()letcellIdentifier:String="cell"funcrequestCollection(){// 사진찍으면 사진이 저장되는 카메라롤의 컬렉션을 가져옴letcameraRoll:PHFetchResult<PHAssetCollection>=PHAssetCollection.fetchAssetCollections(with:.smartAlbum,subtype:.smartAlbumUserLibrary,options:nil)guardletcameraRollColletction=cameraRoll.firstObjectelse{return}letfetchOptions=PHFetchOptions()// 최신순으로 사진을 sortfetchOptions.sortDescriptors=[NSSortDescriptor(key:"creationDate",ascending:false)]// 그 결과를 fetchResult라는 프로퍼티로 가져옴self.fetchResult=PHAsset.fetchAssets(in:cameraRollColletction,options:fetchOptions)}overridefuncviewDidLoad(){super.viewDidLoad()// Do any additional setup after loading the view.letphotoAuthorizationStatus=PHPhotoLibrary.authorizationStatus()switchphotoAuthorizationStatus{case.authorized:print("접근 허가됨")self.requestCollection()self.tableView.reloadData()case.denied:print("접근 불허")case.notDetermined:print("아직 응답하지 않음")PHPhotoLibrary.requestAuthorization({(status)inswitchstatus{case.authorized:print("사용자가 허용함")self.requestCollection()OperationQueue.main.addOperation{self.tableView.reloadData()}case.denied:print("사용자가 불허함")default:break}})case.restricted:print("접근 제한")}// 아래 한줄을 써줌으로써 photoLibrary가 변화될때마다 delegate메서드가 호출됨PHPhotoLibrary.shared().register(self)}functableView(_tableView:UITableView,canEditRowAtindexPath:IndexPath)->Bool{// row를 편집할수 있게 해주는 메서드, tableview row를 밀어서 삭제하는 행위를 가능하게 함returntrue}functableView(_tableView:UITableView,commiteditingStyle:UITableViewCell.EditingStyle,forRowAtindexPath:IndexPath){// 삭제 모드로 들어왔을때(편집을 하려할때)ifeditingStyle==.delete{// 삭제라면 asset을 가지고 delete를 하면letasset:PHAsset=self.fetchResult[indexPath.row]PHPhotoLibrary.shared().performChanges({PHAssetChangeRequest.deleteAssets([asset]asNSArray)},completionHandler:nil)// 실제 삭제를 하게되면 나오게 되는 팝업창}}funcphotoLibraryDidChange(_changeInstance:PHChange){// observer 프로토콜을 따른 메서드guardletchanges=changeInstance.changeDetails(for:fetchResult)else{return}// 어떤게 바꼈는지fetchResult=changes.fetchResultAfterChanges// 바꼈다면 tableview를 다시 불러달라OperationQueue.main.addOperation{self.tableView.reloadSections(IndexSet(0...0),with:.automatic)}}functableView(_tableView:UITableView,numberOfRowsInSectionsection:Int)->Int{returnself.fetchResult?.count??0}functableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{// cell에 이미지를 하나씩 넣어줌letcell:UITableViewCell=tableView.dequeueReusableCell(withIdentifier:self.cellIdentifier,for:indexPath)// asset은 fetchresult에서 index에 해당letasset:PHAsset=fetchResult.object(at:indexPath.row)// imageManager를 통해 실질적인 이미지를 요청imageManager.requestImage(for:asset,targetSize:CGSize(width:30,height:30),contentMode:.aspectFill,options:nil,resultHandler:{image,_incell.imageView?.image=image})returncell}}