Navivation Item, Bar Item

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


내비게이션 아이템(NavigationItem)

내비게이션 아이템은 내비게이션바의 콘텐츠를 표시하는 객체로, 뷰 컨트롤러가 전환될 때마다 내비게이션바는 하나의 공동 객체지만 내비게이션 아이템은 각각의 뷰 컨트롤러가 가지고 있는 프로퍼티이다. 즉, 내비게이션바가 내비게이션 컨트롤러와 연관이 있다면 내비게이션 아이템은 해당 뷰 컨트롤러와 연관이 있으며 보통 내비게이션바에서 보여지는 중앙 타이틀, 좌측 바 버튼, 우측 바 버튼 등이 내비게이션 아이템의 프로퍼티이다.

주요 프로퍼티

  • title : 내비게이션바에 표시되는 내비게이션 아이템의 제목. 기본값은 nil
    var title: String? { get set }
    
  • backBarButtonItem : 내비게이션바에서 뒤로 버튼이 필요할 때 사용할 바 버튼 항목
     var backBarButtonItem: UIBarButtonItem? { get set }
    
  • hidesBackButton : 뒤로 버튼이 숨겨져 있는지를 결정하는 부울 값
    var hidesBackButton: Bool { get set }
    

주요 메서드

// setHidesBackButton(_:animated:) : 뒤로 버튼이 숨겨져 있는지를 설정하고, 전환 애니메이션 효과 적용
func setHidesBackButton(_ hidesBackButton: Bool, animated: Bool)

내비게이션 아이템 커스터마이징

내비게이션 아이템은 크게 좌, 우 바 버튼 아이템과 중앙 타이틀 영역이 있다.
좌측 바 버튼, 우측 바 버튼 아이템의 경우 타입은 UIBarButtonItem 이며 상황에 따라서 커스텀 뷰를 넣어서 구현할 수도 있다.

프로퍼티

// titleView : 중앙 타이틀 영역의 뷰
 var titleView: UIView? { get set }

// Tip : 중앙 타이틀 영역에 뷰를 상속받은 (UILabel, UIView, UIImageView 등등) 클래스들을 표현할 수 있습니다.
leftBarButtonItems : 좌측 아이템 영역의 UIBarButtonItem의  버튼 아이템 배열
var leftBarButtonItems: [UIBarButtonItem]? { get set }

// leftBarButtonItem : 좌측 아이템 영역의 UIBarButtonItem 중에 가장 좌측 바 버튼 아이템
var leftBarButtonItem: UIBarButtonItem? { get set }

// rightBarButtonItems : 우측 아이템 영역의 UIBarButtonItem의 바 버튼 아이템 배열
var rightBarButtonItems: [UIBarButtonItem]? { get set }

// rightBarButtonItem : 우측 아이템 영역의 UIBarButtonItem 중에 가증 우측 바 버튼 아이템
var rightBarButtonItem: UIBarButtonItem? { get set }

메서드

// setLeftBarButtonItems(_:animated:) : 좌측 아이템 영역에 UIBarButtonItem 타입의 객체들을 순차적으로 설정하고 애니메이션 효과를 적용
func setLeftBarButtonItems(_ items: [UIBarButtonItem]?, animated: Bool)

// setLeftBarButton(_:animated:) : 좌측 아이템 영역에 UIBarButtonItem 타입의 객체를 설정하고 애니메이션 효과를 적용
func setLeftBarButton(_ item: UIBarButtonItem?, animated: Bool)

// setRightBarButtonItems(_:animated:) : 우측 내비게이션 아이템 영역에 UIBarButtonItem 타입의 객채들을 순차적으로 설정하고 애니메이션 효과를 적용
func setRightBarButtonItems(_ items: [UIBarButtonItem]?, animated: Bool)

//setRightBarButton(_:animated:) : 우측 아이템 영역에 UIBarButtonItem 타입의 객체를 설정하고 애니메이션 효과를 적용
func setRightBarButton(_ item: UIBarButtonItem?, animated: Bool)

바 버튼 아이템

바 버튼 아이템은 UIToolbar 또는 UINavigationBar (backBarButtonItem,leftBarButtonItem,rightBarButtonItem등)에 배치할 수 있는 특수한 버튼이다. 제목이나 이미지를 보여줄 수 있고 미리 UIBarButtonItem.SystemItem 열거형에 정의된 여러 스타일 중 하나의 스타일로 선택할 수도 있다.

Tip: iOS 11에서는 UIBarButtonItem을 오토레이아웃 제약 없이 내비게이션 아이템으로 추가하면 바 버튼 아이템의 프레임이 예상치 못한 크기로 나올 수 있다.
이럴 땐 UIBarButtonItem 객체에 적절한 오토레이아웃 제약을 추가한 후 내비게이션 아이템으로 설정하면 설정한 제약에 따라 알맞은 크기로 볼 수 있다.

주요 프로퍼티

  • title : 아이템에 표시되는 제목
    var title: String? { get set }
    
  • image : 아이템에 표시되는 이미지
    var image: UIImage? { get set }
    
  • style : 아이템의 스타일
    var style: UIBarButtonItem.Style { get set }
    
  • width : 아이템의 너비 값
    var width: CGFloat { get set }
    
  • tintColor : 아이템에 적용할 색상
    var tintColor: UIColor? { get set }
    

주요 상수

  • UIBarButtonItem.Style : 아이템 스타일 정의
  • UIBarButtonItem.SystemItem : 바 버튼 아이템에 대한 시스템 제공 스타일

Scroll View를 통해 이미지 확대해보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Scroll View를 통해 이미지 확대해보기

우선 ImageZoomViewController 파일 하나를 생성해준다.

import UIKit
import Photos

class ImageZoomViewController: UIViewController, UIScrollViewDelegate {

    // 전화면에서 받아올 asset 프로퍼티
    var asset: PHAsset!
    // imageManager를 통해 이미지 요청
    let imageManager: PHCachingImageManager = PHCachingImageManager()

    @IBOutlet weak var imageView: UIImageView!

    // scrollView delegate 메서드
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        // scrollview가 줌을 해줄 대상이 누구? = imageView
        return self.imageView
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        // 메모리에 화면이 로드가 되면 호출될 이미지의 사이즈 등..
        imageManager.requestImage(for: asset, targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight), contentMode: .aspectFill, options: nil, resultHandler: { image, _ in self.imageView.image = image})

        // Do any additional setup after loading the view.
    }
}

스토리보드로 넘어서 새로운 뷰컨트롤러를 만들어주고 이전에 만들어놨던 뷰컨트롤러는 네비게이션 컨트롤러 임베드를 해준다.

그리고 스크롤뷰의 delegate는 ImageZoomViewController가 되고 ImageZoomViewController의 imageView는 스토리보드에 생성해놓은 imageView로 연결해준다.

그리고 스크롤뷰와 이미지뷰의 셋팅을 위와같이 설정해준다.

그리고 ViewController로 넘어와 네비게이션 컨트롤러를 통해 전달해줄 데이터 세그를 작성해준다.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let nextViewController: ImageZoomViewController = segue.destination as? ImageZoomViewController else { return }
    guard let cell: UITableViewCell = sender as? UITableViewCell else { return }
    guard let index: IndexPath = self.tableView.indexPath(for: cell) else { return }
    nextViewController.asset = self.fetchResult[index.row]
}

Scroll View

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Scroll View

스크롤뷰는 스크롤뷰 안에 포함된 뷰를 상,하,좌,우로 스크롤 할 수 있고 확대 및 축소할 수 있는 뷰를 의미한다.

그리고 스크롤뷰를 상속받아 활용되는 뷰로는 UITableView, UICollectionView, UITextView등 여러 UIKit 클래스가 있다.

스크롤뷰 상호작용

주요 프로퍼티

delegate : 스크롤뷰 객체의 델리게이트

weak var delegate: UIScrollViewDelegate? { get set }

UIScrollViewDelegate 프로토콜에 의해 선언된 메소드 델리게이트가 UIScrollView 클래스의 메시지에 응답

콘텐츠 크기 및 오프셋 관리

주요 프로퍼티

contentSize : 콘텐츠뷰의 크기

var contentSize: CGSize { get set }

contentOffset : 콘텐츠뷰의 원점이 스크롤뷰의 원점에서 오프셋 된 지점

var contentOffset: CGPoint { get set }

주요 메서드

// setContentOffset(_:animated:) : 스크롤뷰의 원점에 대한 콘텐츠뷰의 오프셋 설정
func setContentOffset(_ contentOffset: CGPoint, animated: Bool)

콘텐츠 삽입 동작 관리

주요 프로퍼티

contentInset : 콘텐츠뷰와 안전 영역 또는 스크롤뷰 가장자리에 간격

var contentInset: UIEdgeInsets { get set }

스크롤뷰 구성

주요 프로퍼티

// isScrollEnabled : 스크롤링이 사용 가능한지 아닌지를 결정하는 부울 값
var isScrollEnabled: Bool { get set }

// isDirectionalLockEnabled : 스크롤이 특정 방향으로 고정할지를 결정하는 부울 값
var isDirectionalLockEnabled: Bool { get set }

//isPagingEnabled : 스크롤뷰에서 페이징을 사용할 수 있는 여부를 결정하는 부울 값
var isPagingEnabled: Bool { get set }

//scrollsToTop :  스크롤 할 수 있는 제스처를 사용할지를 결정하는 부울 값
var scrollsToTop: Bool { get set }

// bounces : 스크롤뷰가 가장자리를 통과해서 다시 튀어나오는지 제어하는 부울 값
var bounces: Bool { get set }

// alwaysBounceVertical : 세로 스크롤이 콘텐츠뷰의 끝에 도달할 때 튀어 오르기가 항상 발생하는지를 결정하는 부울 값
var alwaysBounceVertical: Bool { get set }

// alwaysBounceHorizontal : 가로 스크롤이 콘텐츠뷰의 끝에 도달할 때 튀어 오르기가 항상 발생하는지를 결정하는 부울 값
var alwaysBounceHorizontal: Bool { get set }

스크롤링 상태 가져오기

주요 프로퍼티

// isTracking : 사용자가 스크롤을 시작하기 위해 콘텐츠를 터치한 여부를 반환
var isTracking: Bool { get }

// isDragging : 사용자가 콘텐츠를 스크롤하고 있는지 나타내는 부울 값
var isDragging: Bool { get }

// isDecelerating : 사용자가 손가락을 떼었을 때 콘텐츠가 스크롤뷰에서 움직이지 않고 있는지를 반환
var isDecelerating: Bool { get }

// decelerationRate : 사용자가 손가락을 뗀 후의 감속도를 결정하는 부동 소수점 값
var decelerationRate: CGFloat { get set }

스크롤 인디케이터 및 새로고침 제어 관리

주요 프로퍼티

// indicatorStyle : 스크롤 인디케이터의 스타일
var indicatorStyle: UIScrollViewIndicatorStyle { get set }

// showsHorizontalScrollIndicator : 가로 스크롤 바 표시 여부를 제어하는 부울 값
var showsHorizontalScrollIndicator: Bool { get set }

// showsVerticalScrollIndicator : 세로 스크롤 바 표시 여부를 제어하는 부울 값
var showsVerticalScrollIndicator: Bool { get set }

특정 위치로 스크롤 하기

주요 메서드

// scrollRectToVisible(_:animated:) : 콘텐츠의 특정 위치로 스크롤 하여 화면에 표시
func scrollRectToVisible(_ rect: CGRect,  animated: Bool)

확대 및 축소

주요 프로퍼티

// panGestureRecognizer : 팬 제스처를 제어하기 위한 제스처 인스턴스
var panGestureRecognizer: UIPanGestureRecognizer { get }

// pinchGestureRecognizer : 핀치 제스처를 제어하기 위한 제스처 인스턴스
var pinchGestureRecognizer: UIPinchGestureRecognizer? { get }

// zoomScale : 스크롤뷰 콘텐츠에 적용되는 현재 배율
var zoomScale: CGFloat { get set }

// maximumZoomScale :  스크롤뷰 콘텐츠에 적용되는 최대 배율
var maximumZoomScale: CGFloat { get set }

// minimumZoomScale : 스크롤뷰 콘텐츠에 적용되는 최소 배율
var minimumZoomScale: CGFloat { get set }

// isZoomBouncing : 확대 및 축소가 지정한 배율 제한을 초과했음을 나타내는 부울 값
var isZoomBouncing: Bool { get }

// isZooming : 콘텐츠뷰가 현재 확대 또는 축소되어 있는지를 나타내는 부울 값
var isZooming: Bool { get }

// bouncesZoom : 크기 조정이 최대 또는 최소 제한을 초과할 때 튀어 오르는 애니메이션을 보여줄지 결정하는 부울 값
var bouncesZoom: Bool { get set }

주요 메서드

// zoom(to:animated:) : 콘텐츠 특정 영역 확대
func zoom(to rect: CGRect, animated: Bool)

// setZoomScale(_:animated:) : 현재 배율을 지정
func setZoomScale(_ scale: CGFloat, animated: Bool)

키보드 관리

주요 프로퍼티

// keyboardDismissMode : 스크롤뷰에서 드래그가 시작될 때 키보드가 해제되는 방식
var keyboardDismissMode: UIScrollViewKeyboardDismissMode { get set }

UIScrollViewDelegate 프로토콜

스크롤 및 드래그

// scrollViewDidScroll(_:) : 콘텐츠뷰를 스크롤 할 때 델리게이트에 알림
optional func scrollViewDidScroll(_ scrollView: UIScrollView)

// scrollViewWillBeginDragging(_:) : 스크롤뷰에서 콘텐츠 스크롤을 시작할 시점을 델리게이트에 알림
optional func scrollViewWillBeginDragging(_ scrollView: UIScrollView)

// scrollViewWillEndDragging(_:withVelocity:targetContentOffset:) : 스크롤뷰의 드래그가 끝나기 직전에 델리게이트에 알림
optional func scrollViewWillEndDragging(_ scrollView: UIScrollView,
                        withVelocity velocity: CGPoint,
                 targetContentOffset: UnsafeMutablePointer<CGPoint>)

// scrollViewDidEndDragging(_:willDecelerate:) : 스크롤뷰의 드래그가 끝났을 때 델리게이트에 알림
optional func scrollViewDidEndDragging(_ scrollView: UIScrollView,
                     willDecelerate decelerate: Bool)

// scrollViewShouldScrollToTop(_:) : 스크롤뷰가 콘텐츠의 맨 위로 스크롤 해야 하는 경우 델리게이트에 동작 여부를 물어봄
optional func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool

// scrollViewDidScrollToTop(_:) : 스크롤뷰가 콘텐츠의 맨 위로 스크롤 되었음을 델리게이트에 알림
optional func scrollViewDidScrollToTop(_ scrollView: UIScrollView)

// scrollViewWillBeginDecelerating(_:) : 스크롤링 동작이 감속되기 시작하고 있다고 델리게이트에 알림
optional func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView)

// scrollViewDidEndDecelerating(_:) : 스크롤링 동작이 감속이 끝났을 때 델리게이트에 알림

// optional func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)

확대 및 축소

//viewForZooming(in:) : 스크롤뷰에서 확대 및 축소를 할 때 확대 및 축소를 할 뷰 인스턴스를 요청
optional func viewForZooming(in scrollView: UIScrollView) -> UIView?

// scrollViewWillBeginZooming(_:with:) : 스크롤뷰의 콘텐츠 확대가 시작될 때 델리게이트에 알림
optional func scrollViewWillBeginZooming(_ scrollView: UIScrollView,
                                 with view: UIView?)

// scrollViewDidEndZooming(_:with:atScale:) : 스크롤뷰의 콘텐츠 확대가 완료될 때 델리게이트에 알림
optional func scrollViewDidEndZooming(_ scrollView: UIScrollView,
                              with view: UIView?,
                           atScale scale: CGFloat)

// scrollViewDidZoom(_:) : 스크롤뷰의 확대 및 축소 배율이 변경될 때 델리게이트에 알림
optional func scrollViewDidZoom(_ scrollView: UIScrollView)

스크롤 애니메이션

scrollViewDidEndScrollingAnimation(_:) : 스크롤뷰의 스크롤 애니메이션이 끝날  델리게이트에 알림
optional func scrollViewDidEndScrollingAnimation(_ 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) : 비동기적으로 실행되어야 하는 작업을 객체 지향적인 방법으로 사용
  • Thread : 멀티스레드 프로그래밍을 위한 애플에서 제공하는 스레드 클래스

사진첩에서 사진을 가져오고 삭제해보기(실습)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


사진첩에서 사진 가져오기


import UIKit
import Photos

class ViewController: UIViewController, UITableViewDataSource {

    // 사진 목록을 테이블뷰에 보여줄 것
    @IBOutlet weak var tableView: UITableView!
    var fetchResult: PHFetchResult<PHAsset>!
    // 가져온 에셋을 가지고 이미지를 로드해옴
    let imageManager: PHCachingImageManager = PHCachingImageManager()
    let cellIdentifier: String = "cell"

    func requestCollection() {
        // 사진찍으면 사진이 저장되는 카메라롤의 컬렉션을 가져옴
        let cameraRoll: PHFetchResult<PHAssetCollection> = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil)

        guard let cameraRollColletction = cameraRoll.firstObject else {
            return
        }
        let fetchOptions = PHFetchOptions()
        // 최신순으로 사진을 sort
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        // 그 결과를 fetchResult라는 프로퍼티로 가져옴
        self.fetchResult = PHAsset.fetchAssets(in: cameraRollColletction, options: fetchOptions)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let photoAuthorizationStatus = PHPhotoLibrary.authorizationStatus()

        switch photoAuthorizationStatus {
        case .authorized:
            print("접근 허가됨")
            self.requestCollection()
            self.tableView.reloadData()
        case .denied:
            print("접근 불허")
        case .notDetermined:
            print("아직 응답하지 않음")
            PHPhotoLibrary.requestAuthorization({ (status) in
                switch status {
                case .authorized:
                    print("사용자가 허용함")
                    self.requestCollection()
                    self.tableView.reloadData()
                case .denied:
                    print("사용자가 불허함")
                default: break
                }
            })
        case .restricted:
            print("접근 제한")
        }
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.fetchResult?.count ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // cell에 이미지를 하나씩 넣어줌
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
        // asset은 fetchresult에서 index에 해당
        let asset: PHAsset = fetchResult.object(at: indexPath.row)
        // imageManager를 통해 실질적인 이미지를 요청
        imageManager.requestImage(for: asset, targetSize: CGSize(width: 30, height: 30), contentMode: .aspectFill, options: nil, resultHandler: { image, _ in cell.imageView?.image = image})
        return cell
    }
}

이렇게 코드를 진행하게 되면 에러가 발생할 것이다.

reloadData는 메인 쓰레드에서만 동작해야한다. » operation queue 사용

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    let photoAuthorizationStatus = PHPhotoLibrary.authorizationStatus()

    switch photoAuthorizationStatus {
    case .authorized:
        print("접근 허가됨")
        self.requestCollection()
        self.tableView.reloadData()
    case .denied:
        print("접근 불허")
    case .notDetermined:
        print("아직 응답하지 않음")
        PHPhotoLibrary.requestAuthorization({ (status) in
            switch status {
            case .authorized:
                print("사용자가 허용함")
                self.requestCollection()
                OperationQueue.main.addOperation {
                    self.tableView.reloadData()
                }
            case .denied:
                print("사용자가 불허함")
            default: break
            }
        })
    case .restricted:
        print("접근 제한")
    }
}

사진첩에서 사진 삭제하기

import UIKit
import Photos

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, PHPhotoLibraryChangeObserver {
    // PHphotoobserver는 library에 변화가 생기면 감지를 하는 프로토콜

    // 사진 목록을 테이블뷰에 보여줄 것
    @IBOutlet weak var tableView: UITableView!
    var fetchResult: PHFetchResult<PHAsset>!
    // 가져온 에셋을 가지고 이미지를 로드해옴
    let imageManager: PHCachingImageManager = PHCachingImageManager()
    let cellIdentifier: String = "cell"

    func requestCollection() {
        // 사진찍으면 사진이 저장되는 카메라롤의 컬렉션을 가져옴
        let cameraRoll: PHFetchResult<PHAssetCollection> = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil)

        guard let cameraRollColletction = cameraRoll.firstObject else {
            return
        }
        let fetchOptions = PHFetchOptions()
        // 최신순으로 사진을 sort
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        // 그 결과를 fetchResult라는 프로퍼티로 가져옴
        self.fetchResult = PHAsset.fetchAssets(in: cameraRollColletction, options: fetchOptions)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let photoAuthorizationStatus = PHPhotoLibrary.authorizationStatus()

        switch photoAuthorizationStatus {
        case .authorized:
            print("접근 허가됨")
            self.requestCollection()
            self.tableView.reloadData()
        case .denied:
            print("접근 불허")
        case .notDetermined:
            print("아직 응답하지 않음")
            PHPhotoLibrary.requestAuthorization({ (status) in
                switch status {
                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)
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {  // row를 편집할수 있게 해주는 메서드, tableview row를 밀어서 삭제하는 행위를 가능하게 함
        return true
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        // 삭제 모드로 들어왔을때(편집을 하려할때)
        if editingStyle == .delete { // 삭제라면 asset을 가지고 delete를 하면
            let asset: PHAsset = self.fetchResult[indexPath.row]
        PHPhotoLibrary.shared().performChanges({PHAssetChangeRequest.deleteAssets([asset] as NSArray)}, completionHandler: nil)
            // 실제 삭제를 하게되면 나오게 되는 팝업창
        }
    }

    func photoLibraryDidChange(_ changeInstance: PHChange) {
        // observer 프로토콜을 따른 메서드

        guard let changes = changeInstance.changeDetails(for: fetchResult) else {
            return
        }
        // 어떤게 바꼈는지
        fetchResult = changes.fetchResultAfterChanges
        // 바꼈다면 tableview를 다시 불러달라
        OperationQueue.main.addOperation {
            self.tableView.reloadSections(IndexSet(0...0), with: .automatic)
        }
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.fetchResult?.count ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // cell에 이미지를 하나씩 넣어줌
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
        // asset은 fetchresult에서 index에 해당
        let asset: PHAsset = fetchResult.object(at: indexPath.row)
        // imageManager를 통해 실질적인 이미지를 요청
        imageManager.requestImage(for: asset, targetSize: CGSize(width: 30, height: 30), contentMode: .aspectFill, options: nil, resultHandler: { image, _ in cell.imageView?.image = image})
        return cell
    }
}