[iOS]Swiftでインスタのような加工アプリを作ってみた

Swiftで加工アプリを作ってみました！

概観は以下のGifの通りです。
![加工アプリ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/848369/2e6a317f-2b7b-20e1-8bfe-b92bfaa93e42.gif)

1. 画像をセットする

まずプロジェクトを作り、Main.storyboardに移動した後、View、UIImageView、ボタンを置いていきます。
最初にViewを上、右、左に0、高さを200。次にUIImageViewをViewの中に入れるのですが、別にレイアウトはお好きなようにお願いします。下のボタンは雰囲気で置いているだけなのでこちらもお好きなようにカスタマイズしてください。
それと、UIImageViewの上に雰囲気で画像を貼っているのですが、画像を何か設定しないと空白のように見えるので何かセットしておいた方が分かりやすいです。

![スクリーンショット 2020-11-03 10.19.18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/848369/20e5a192-0bcf-52cb-aa2b-4fdbfe74ba7f.jpeg)

次に、各部品をIBOutletで接続していきます。

```ruby:Viewcontroler.swift
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var photo: UIImageView!
    @IBOutlet weak var shareButton: UIButton!
    
    //選択した画像
    var selectedImage: UIImage?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleSelectPhoto))
        photo.addGestureRecognizer(tapGesture)
        photo.isUserInteractionEnabled = true
    }

    @IBAction func shareButton_TouchUpInside(_ sender: Any) {
        view.endEditing(true)
    }
    
    @objc func handleSelectPhoto() {
        let pickerController = UIImagePickerController()
        pickerController.delegate = self
        pickerController.allowsEditing = true
        present(pickerController, animated: true, completion: nil)
    }
    
}

extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let image = info[.editedImage] as? UIImage else { return }
        self.selectedImage = image
        photo.layer.masksToBounds = true
        photo.clipsToBounds = true
        photo.image = image
        dismiss(animated: true, completion: nil)
    }

}


```

UIImageViewをphotoと接続し、選択したときの画像を使いたいので

```ruby:Viewcontroller.swift
    //選択した画像
    var selectedImage: UIImage?
```

と定義します。後はviewDidLoadでタップ検知をし、UIImagePickerController()を呼んであげます。そしてextensionでdidFinishPickingMediaWithInfoで選択した画像をphotoにしまいます。ここら辺のコードはいろいろ調べてみてください。

2 . Filter画面を作る

写真をセットできるようになったらFilter(加工)を選択する画面を作っていきます。
新しいViewControllerを作り、FilterViewControllerとしてください。
そして、ViewcontrollerからFilterViewControllerへ Present Mordally にしてください。
できたセグエに対して「filter_segue」と定義してください。後で遷移するときに使います。
次に、FilterViewControllerに部品をおいていきます。
写真のように、まず上に新しくViewを置き、そのViewの上にそれぞれCancelボタン、Nextボタンを置いてください。
そして、上のViewの下にUIImageViewを置きます。これにViewControllerで選択した画像を反映させます。
最後に、UIImageViewの下にCollectionViewを置き、少しだけ離しておきます。このCollectionViewの中にまた
UIImageViewを置き、ここで加工する画像を選択します。
置いた後、CollectionViewのScroll Viewを見てください。
・Show Horizontal Indicator
・Show Vertical Indicator
の二つのチェックを外してください。これでよく加工アプリに使われている横スライドのUIを表現できます。
![スクリーンショット 2020-11-03 21.44.19.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/848369/72dd45cf-d52f-05e8-ab7b-72c38ca42966.jpeg)


必要な部品をおいた後、delegateとdatasourceを各自セットしてください。
そしてCollectionViewのセルに新たなクラスが必要なのでまた新しくUICollectionViewCellを継承した「FilterCollectionViewCell.swift」を作成します。
![スクリーンショット 2020-11-03 20.04.15.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/848369/040a8cd9-4f6f-001d-0385-9d16fdbb0e17.jpeg)

```ruby:FilterCollectionViewCell.swift
import UIKit

class FilterCollectionViewCell: UICollectionViewCell {
    
    @IBOutlet weak var filterPhoto: UIImageView!
}
```

ここではCollectionViewの中のUICollectionViewだけで大丈夫です。

3 . 加工アプリを作る![スクリーンショット 2020-11-03 20.38.24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/848369/92166900-05fa-13b4-e766-27ba042e6ad3.jpeg)

ここから実際に作っていきますが、とりあえず全コードをまず載せて置きますので、分かる方は全コピしてもらって構いませんが、分からない方のために上から順に解説していきます。

```ruby:FilterViewController.swift
import UIKit

protocol FilterViewControllerDelegate {
    func updatePhoto(image: UIImage)
}

class FilterViewController: UIViewController {
    
    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var filterPhoto: UIImageView!
    
    var delegate: FilterViewControllerDelegate?
    var selectedImage: UIImage!
    var CIFilterNames = [
        "CIPhotoEffectChrome",
        "CIPhotoEffectFade",
        "CIPhotoEffectInstant",
        "CIPhotoEffectNoir",
        "CIPhotoEffectProcess",
        "CIPhotoEffectTonal",
        "CIPhotoEffectTransfer",
        "CISepiaTone"
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        filterPhoto.image = selectedImage
        filterPhoto.contentMode = .scaleAspectFill
    }
    
    @IBAction func cancelBtn_TouchUpInside(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }
    
    @IBAction func nextBtn_TouchUpInside(_ sender: Any) {
        dismiss(animated: true, completion: nil)
        delegate?.updatePhoto(image: self.filterPhoto.image!)
    }
    
    func resizeImage(image: UIImage, newWidth: CGFloat) -> UIImage {
        let scale = newWidth / image.size.width
        let newHeight = image.size.height * scale
        UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
        image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }
    
}

extension FilterViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return CIFilterNames.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterCollectionViewCell", for: indexPath) as! FilterCollectionViewCell
        let context = CIContext(options: nil)
        let newImage = resizeImage(image: selectedImage, newWidth: 150)
        let ciImage = CIImage(image: newImage)
        let filter = CIFilter(name: CIFilterNames[indexPath.item])
        filter?.setValue(ciImage, forKey: kCIInputImageKey)
        if let filteredImage = filter?.value(forKey: kCIOutputImageKey) as? CIImage {
            let result = context.createCGImage(filteredImage, from: filteredImage.extent)
            cell.filterPhoto.image = UIImage(cgImage: result!)
        }
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let context = CIContext(options: nil)
        let ciImage = CIImage(image: selectedImage)
        let filter = CIFilter(name: CIFilterNames[indexPath.item])
        filter?.setValue(ciImage, forKey: kCIInputImageKey)
        if let filteredImage = filter?.value(forKey: kCIOutputImageKey) as? CIImage {
            let result = context.createCGImage(filteredImage, from: filteredImage.extent)
            self.filterPhoto.image = UIImage(cgImage: result!)
        }

    }
}

```

FilterViewControllerのOutlet接続は私は

```ruby:FilterCollectionView.swift
@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var filterPhoto: UIImageView!
@IBAction func cancelBtn_TouchUpInside(_ sender: Any) {
        dismiss(animated: true, completion: nil)
}

@IBAction func nextBtn_TouchUpInside(_ sender: Any) {
        dismiss(animated: true, completion: nil)
        delegate?.updatePhoto(image: self.filterPhoto.image!)
}
```

としておきました。CancelButtonは前の画面に戻り、NextButtonでは下のセルで選んだ画像をViewControllerに反映させます。

次に、FilterViewControllerで使うプロパティを定義します。

```ruby:FilterCollectionView.swift
    var selectedImage: UIImage!
    var CIFilterNames = [
        "CIPhotoEffectChrome",
        "CIPhotoEffectFade",
        "CIPhotoEffectInstant",
        "CIPhotoEffectNoir",
        "CIPhotoEffectProcess",
        "CIPhotoEffectTonal",
        "CIPhotoEffectTransfer",
        "CISepiaTone"
    ]
```

selectedImageはもう言わなくても分かると思いますが、下のCIFilterNamesは、一文字も小文字＆大文字を間違えないでください。今回は８種類だけ使います。ここに書いてあるCI~は、AppleのCore Image Filter Referenceというガイドで定義されています。これらをcellで使いたいので配列にしておきます。別に書いてあるもの以外でも構いませんし、８種類以上でも大丈夫です。各々おすきなように追加してください。
参照：[Core Image Filter Reference](https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/uid/TP40004346)

追加後、viewDidLoadにて前(ViewController)で選んだ画像を反映し、全て見えるようにしておきます。
そして、cellで使う、加工するための関数を作っておきます。

```ruby:FilterCollectionView.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        filterPhoto.image = selectedImage
        filterPhoto.contentMode = .scaleAspectFill
    }

    func resizeImage(image: UIImage, newWidth: CGFloat) -> UIImage {
        let scale = newWidth / image.size.width
        let newHeight = image.size.height * scale
        UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
        image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }
```

numberOfItemsInSectionではCIFilterNames分の数をカウントします。ここでは８個数えています。
cellForItemAtではFilterCollectionViewCellを使いながら設定していきます。newImageでは上で定義したresizeImage関数を用いて大きさを調整し、セルごとに新しいFilter画像を設定しています。ciImageではFilterするために選んだ画像をCIImageにしています。そしてfilterでCIFilterNamesの数だけciImageをセットしています。後は新たにCGImageを作り、UIImageにしてcellに設定しています。
didSelectItemAtも同じ処理です。

``` ruby:FilterCollectionView.swift
extension FilterViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return CIFilterNames.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterCollectionViewCell", for: indexPath) as! FilterCollectionViewCell
        let context = CIContext(options: nil)
        let newImage = resizeImage(image: selectedImage, newWidth: 150)
        let ciImage = CIImage(image: newImage)
        let filter = CIFilter(name: CIFilterNames[indexPath.item])
        filter?.setValue(ciImage, forKey: kCIInputImageKey)
        if let filteredImage = filter?.value(forKey: kCIOutputImageKey) as? CIImage {
            let result = context.createCGImage(filteredImage, from: filteredImage.extent)
            cell.filterPhoto.image = UIImage(cgImage: result!)
        }
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let context = CIContext(options: nil)
        let ciImage = CIImage(image: selectedImage)
        let filter = CIFilter(name: CIFilterNames[indexPath.item])
        filter?.setValue(ciImage, forKey: kCIInputImageKey)
        if let filteredImage = filter?.value(forKey: kCIOutputImageKey) as? CIImage {
            let result = context.createCGImage(filteredImage, from: filteredImage.extent)
            self.filterPhoto.image = UIImage(cgImage: result!)
        }

    }
}

```

ここまできたらもう終わったようなものなのですが、このままではViewControllerには加工して選んだ画像が反映されません。そこで新たにプロトコルを定義してあげます。

```ruby:FilterViewController.swift
protocol FilterViewControllerDelegate {
    func updatePhoto(image: UIImage)
}
```

このプロトコルを使うためにデリゲートプロパティを定義します。

```ruby:FilterViewController.swift
var delegate: FilterViewControllerDelegate?
```

このデリゲートは加工後の選択した画像に、ViewControllerに渡したいのでNextボタンでデリゲートを通してあげます。

```ruby:FilterViewController.swift
@IBAction func nextBtn_TouchUpInside(_ sender: Any) {
        dismiss(animated: true, completion: nil)
        delegate?.updatePhoto(image: self.filterPhoto.image!)
}
```

最後に、ViewController.swiftに以下の処理を追加します。
これで選択後の画像が反映されます。

```ruby:ViewController.swift
extension ViewController: FilterViewControllerDelegate {
    func updatePhoto(image: UIImage) {
        self.photo.image = image
    }
}

```

4 . 終わりに

今回が初投稿となりました。
この加工アプリの内容は下記のUdemyの講座から作成したものです。

https://www.udemy.com/course/build-instagram-to-master-swift-and-firebase/

海外の講座は難しいですがクオリティが非常に高いのでとてもおすすめです。
加工アプリの作り方は誰も載せていないので紹介させていただきました。
もっと改変すれば商用レベルのアプリができると思います。
意外とそんなに難しくないことはお分かりになられたはずです。
加工アプリの作り方が気になっている初心者さんなどにお役に立てれば幸いです。

これからも、まだ誰も投稿していないような内容があったらその都度アウトプットと同時にご紹介していきますので、
よろしくお願いします。