【swift 5】imagePickerの使い方~アプリのバックグラウンドにユーザーの選んだ画像を設定する~

## 環境
Xcode 11.3
swift 5.1.3
CocoaPods 1.8.4

## 要件
### 機能要件
アプリのバックグラウンドに、ユーザーの選んだ画像を設定する機能

## 仕様
- 「背景に画像を設定するボタン」をタップ
- 背景画像設定画面の表示
- カメラロール内の画像一覧を表示
- 画像をタップすることで、拡大表示
- オッケーボタンを押すことで、画像がアプリ内部に保存される
- その段階で、アプリの背景に選択した画像が表示される
- 設定完了後、「背景に画像を設定するボタン」が表示されているもとのページに戻る

## 設計
### 写真選択
背景に画像を設定するボタンタップ後、背景画像設定用の画面を表示。
カメラロール内の画像一覧を表示し、画像を選択させる。
選択後、選択した画像が背景に拡大表示されるようにする。

#### storyboard上の設定
**TopViewControllerに対して**

- UIButtonを追加
- storyboardIDを、`topView`に設定

<img width="274" alt="スクリーンショット 2020-01-13 19.02.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/5ba89552-be6d-2328-fac1-4018a9e690ab.png">

**SetBackgroundImageViewControllerに対して**

- 背景画像設定用の新規コントローラーの作成(SetBackgroundImageViewController)
- UIButton,imageViewを設置
- storyboardIDを`backgroundImageView`に設定

<img width="276" alt="スクリーンショット 2020-01-15 21.11.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/91105a54-570d-8d11-6a40-a9e7685043ab.png">

#### コードの記述

```swift:TopViewController.swift
import UIKit

class TopViewController: UIViewController {
    
    // 背景image設定ページへ
    // Btnが押されたときのaction
    @IBAction func showImageSelection(_ sender: Any) {
        // コントローラーの指定
        let backgroundImageController = self.storyboard?.instantiateViewController(withIdentifier: "backgroundImageView") as! SetBackgroundImageViewController
        
        // 全画面表示で画面遷移
        backgroundImageController.modalPresentationStyle = UIModalPresentationStyle(rawValue: 0)!
        self.present(backgroundImageController, animated: false, completion: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("user view did load")
    }
}

```

```swift:SetBackgroundImageViewController
import UIKit

class SetBackgroundImageViewController: UIViewController {
    
    // 背景設定
    @IBOutlet weak var backgroundImageView: UIImageView!
    var backgroundImage:UIImage!
    var themaColor:UIColor!
    
    // ボタンのoutlet接続およびaction接続
    // 戻るボタン
    @IBOutlet weak var rerturnBtn: boundButton!
    @IBAction func returnBtnPushed(_ sender: Any) {
    }
    // 画像選択ボタン
    @IBOutlet weak var selectBtn: UIButton!
    @IBAction func selectBtnPushed(_ sender: Any) {
    }
    // 決定ボタン
    @IBOutlet weak var setBtn: UIButton!
    @IBAction func setBtnPushed(_ sender: Any) {
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // imagePickerを表示
        addImagePickerView()  
    }
}

// imagePickerViewの設定用
extension SetBackgroundImageViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
    func addImagePickerView() {
        print("pushed image!")
        //imagePickerViewを表示する
        let pickerController = UIImagePickerController()
        pickerController.sourceType = .photoLibrary
        pickerController.delegate = self
        self.present(pickerController, animated: true, completion: nil)
    }
    
    // pickerの選択がキャンセルされた時の処理
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
         dismiss(animated: true, completion: nil)
    }
}


```

### 選択画像表示

画像が選択されたら、コントローラーのimageViewに表示されるようにする。

```swift:SetBackgroundImageViewController.swift
// imagePickerViewの設定用
extension SetBackgroundImageViewController　{
    // 以下の関数を追加
    // 写真が選択された時の処理
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
        // 選択されたimageを取得
        guard let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage? else {return}

        // imageをimageViewに設定
        backgroundImage = selectedImage
        backgroundImageView.image = backgroundImage
        
        // imagePickerの削除
        self.dismiss(animated: true, completion: nil)
    }
}
```


### 選択画像保存
決定ボタン押下時、選択されている画像をアプリ内に保存。
処理終了後に、topページに戻る。

```swift:SetBackgroundViewController.swift
import UIKit

class SetBackgroundImageViewController: UIViewController {
    
    // 背景設定
    @IBOutlet weak var backgroundImageView: UIImageView!
    var backgroundImage:UIImage!
    var themaColor:UIColor!
    
    // ボタン接続
    // 戻るボタン
    @IBOutlet weak var rerturnBtn: boundButton!
    @IBAction func returnBtnPushed(_ sender: Any) {
    }
    // もう一度選ぶボタン
    @IBOutlet weak var selectBtn: UIButton!
    @IBAction func selectBtnPushed(_ sender: Any) {
    }
    // 確定ボタン
    @IBOutlet weak var setBtn: UIButton!
    @IBAction func setBtnPushed(_ sender: Any) {
        // ここから追記
        //　backgroundImage.pngという名前で保存
        let imagePath = self.fileInDocumentsDirectory(filename: "backgroundImage.png")
        if saveImage(image: backgroundImage, path: imagePath) {
            // 画像を設定
            let topVC = self.presentingViewController as! TopViewController
            topVC.backgroundImage.image = backgroundImage

            // もとの画面(TopViewController)に戻る
            self.dismiss(animated: false, completion: nil)
            
        } else {
            backgroundImage = nil
        }
        // ここまで追記
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // imagePickerを表示
        addImagePickerView()  
    }
}

// ここから追記
// imageの保存関数
extension BackgroundImageViewController {
    // DocumentディレクトリのfileURLを取得
    func getDocumentsURL() -> NSURL {
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL
        return documentsURL
    }

    // ディレクトリのパスにファイル名をつなげてファイルのフルパスを作る
    func fileInDocumentsDirectory(filename: String) -> String {
        let fileURL = getDocumentsURL().appendingPathComponent(filename)
        return fileURL!.path
    }
    
    // ファイルに書き込み
    func saveImage (image: UIImage, path: String ) -> Bool {
        let pngImageData = image.pngData()
        do {
            try pngImageData!.write(to: URL(fileURLWithPath: path), options: .atomic)
        } catch {
            print(error)
            return false
        }
        return true
    }
}
// ここまで追記

// imagePickerViewの設定用
extension SetBackgroundImageViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
    func addImagePickerView() {
        print("pushed image!")
        //imagePickerViewを表示する
        let pickerController = UIImagePickerController()
        pickerController.sourceType = .photoLibrary
        pickerController.delegate = self
        self.present(pickerController, animated: true, completion: nil)
    }
    
    // pickerの選択がキャンセルされた時の処理
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
         dismiss(animated: true, completion: nil)
    }

    // 写真が選択された時の処理
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
        // 選択されたimageを取得
        guard let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage? else {return}

        // imageをimageViewに設定
        backgroundImage = selectedImage
        backgroundImageView.image = backgroundImage
        
        // imagePickerの削除
        self.dismiss(animated: true, completion: nil)
    }
}


```

### 保存画像のロード・表示
以下の関数を用意しておき、背景画像を表示させたいところで呼び出す。

```swift:
// imageをload
func loadBackgroundImage() -> UIImage? {
    let image = UIImage(contentsOfFile: fileInDocumentsDirectory(filename: "backgroundImage.png"))
    if image == nil {
        print("backgroundImage is missing")
    }
    return image
}
```

## 参考ページ
[teratail : エラー：which is already presenting (null)の対処法](https://teratail.com/questions/220865)
[qiita : 【Swift4】URL先の画像をアプリ内に保存＆ロードする](https://qiita.com/orimomo/items/a22ab921decd41df7dfa)
[HatenaBlog : iOSでアプリ内部（Document）に画像（UIImage）を保存する方法【iOSアプリ開発】](https://develop.hateblo.jp/entry/iosapp-uiimage-save)