はじめに
初めまして、たなぼうと申します。
記事を書いている現在は学生であり、独学でiosアプリを開発をしています。
今回のテーマは
・カメラorアルバムから取得した画像をトリミングして表示したい!
私は「画像のトリミング」ってどうやるの?って悩んだので、共有する記事になります。
全コードは以下に載せてます。
https://github.com/RyotaLab/trimmingImage.git
完成イメージ
カメラorアルバムから画像を取得し、トリミングするアプリです。
今回のアプリではCropViewControllerというライブラリを使用しています。
1.下準備
コードを書く前に、以下の準備が必要です。
- ライブラリ:CropViewControllerをインストール
- Xcode上でinfoにPrivacy - Camera Usage Descriptionを追加
2.実装
ここからは実際のコードをもとに、意味やポイントについて解説していきます。
以下の3ファイルを順番に作成or更新します。
- CameraController
- imageCropper
- ContentView
2-1.カメラの準備
写真を撮影するviewを用意します。
image:UIImage?を引数に持ち、写真を撮影後、imageの内容を変更するというものです。
こちらに関してはテンプレが以下の記事に存在します。ありがたいですね。
【SwiftUI】カメラを使いたい
コードは以下の通りです。
━━━━━━━━━━━━━━━━━━┓
┃ ソースコードを表示(折りたたみ) ┃
┗━━━━━━━━━━━━━━━━━━┛
import Foundation
import SwiftUI
import SwiftUI
public struct CameraView: UIViewControllerRepresentable {
@Binding private var image: UIImage?
@Environment(\.dismiss) private var dismiss
public init(image: Binding<UIImage?>) {
self._image = image
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
public func makeUIViewController(context: Context) -> UIImagePickerController {
let viewController = UIImagePickerController()
viewController.delegate = context.coordinator
if UIImagePickerController.isSourceTypeAvailable(.camera) {
viewController.sourceType = .camera
}
return viewController
}
public func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}
extension CameraView {
public class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: CameraView
init(_ parent: CameraView) {
self.parent = parent
}
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let uiImage = info[.originalImage] as? UIImage {
self.parent.image = uiImage
}
self.parent.dismiss()
}
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.parent.dismiss()
}
}
}
CameraView(image: $UIImage?)
2-2.画像をトリミングする準備
画像をトリミングするviewを作成します。ここが肝ですね。
以下の引数を持ちます。
- image:UIimage?
- visible:Bool
- done:Void
imageはトリミングする画像です。
visibleはトリミングが終わった時、トリミングの画面を非表示する時に使用。
doneはトリミングが完了した時に呼ばれる自分で設定する関数です(後で解説)。
コードは以下の通り。
━━━━━━━━━━━━━━━━━━┓
┃ ソースコードを表示(折りたたみ) ┃
┗━━━━━━━━━━━━━━━━━━┛
import Foundation
import SwiftUI
import UIKit
import CropViewController
struct ImageCropper: UIViewControllerRepresentable{
@Binding var image: UIImage?
@Binding var visible: Bool
var done: (UIImage) -> Void
class Coordinator: NSObject, CropViewControllerDelegate{
let parent : ImageCropper
init(_ parent: ImageCropper){
self.parent = parent
}
//編集完了時(done)
func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
print("didcroped")
parent.done(image)
self.parent.visible = false
}
//cancel時
func cropViewController(_ cropViewController: CropViewController, didFinishCancelled cancelled: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation{
self.parent.visible = false
}
}
}
}//class
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
func makeUIViewController(context: Context) -> some UIViewController {
let img = self.image ?? UIImage()
let cropViewController = CropViewController(image:img)
cropViewController.delegate = context.coordinator
cropViewController.aspectRatioPreset = .presetSquare
cropViewController.aspectRatioPickerButtonHidden = true
cropViewController.aspectRatioLockEnabled = true
return cropViewController
}
}
余談ですが、トリミング方法やアスペクト比などを自分で設定できる場所があります。
cropViewController.aspectRatioPreset = .presetSquare
cropViewController.aspectRatioPickerButtonHidden = true
cropViewController.aspectRatioLockEnabled = true
設定できる点は以下の記事で網羅されているので、詳しく知りたい方は以下をご覧ください。
設定の参考:【Swift】画像をトリミングできるライブラリを使ってみた
SwiftUI実装の参考:https://github.com/TimOliver/TOCropViewController/issues/421
2-3.ContentViewで実装
━━━━━━━━━━━━━━━━━━┓
┃ ソースコードを表示(折りたたみ) ┃
┗━━━━━━━━━━━━━━━━━━┛
import SwiftUI
import PhotosUI
struct ContentView: View {
//アルバムから選択された画像
@State var selectedPhotos: [PhotosPickerItem] = []
//UIImageに変換したアイテムを格納する
@State var getUImage: UIImage?
//トリミングされた画像があれば表示
@State var CreppedUImage: UIImage?
//アルバムorカメラから画像が取得されれば、トリミングへ
@State private var showImageCropper = false
//カメラ表示
@State var showCameraSheet: Bool = false
var body: some View {
VStack {
//トリミング後に表示
if (CreppedUImage != nil) {
Image(uiImage: CreppedUImage!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:300, height: 300)
}
//アルバムから選択
PhotosPicker(selection: $selectedPhotos, maxSelectionCount: 1, matching: .images){
Text("getPhoto")
}
//カメラ表示
Button{
showCameraSheet = true
}label:{
Text("getCamera")
}
}
.padding()
//アルバムから取得した画像はuiImage型に変更する
.onChange(of: selectedPhotos){
Task{
guard let data = try? await selectedPhotos[0].loadTransferable(type: Data.self) else {return}
guard let uiImage = UIImage(data:data) else {return}
getUImage = uiImage
}
}
//写真を撮る時に表示
.fullScreenCover(isPresented:$showCameraSheet){
CameraView(image: $getUImage).ignoresSafeArea()
}
//画像を得た時
.onChange(of:getUImage){
showImageCropper = true
}
//得た画像を編集する時に表示
.sheet(isPresented: $showImageCropper) {
ImageCropper(image: self.$getUImage, visible: self.$showImageCropper, done: imageCropped)
}
}
//トリミングが終わった後に呼ばれる
func imageCropped(image: UIImage){
CreppedUImage = image
}
}
最後は今までに準備した、カメラ、画像トリミングに「アルバムから取得」を追加し、組み合わせるだけです。
型変換が少し面倒ですが、コードに記載したコメントを落ち着いて読めば、大体流れが掴めると思います。
.sheet(isPresented: $showImageCropper) {
ImageCropper(image: self.$getUImage, visible: self.$showImageCropper, done: imageCropped)
}
...
//トリミングが終わった後に呼ばれる
func imageCropped(image: UIImage){
CreppedUImage = image
}
画像をトリミングするImageCropperはトリミングが完了時、doneの引数に設定している関数を呼び出します。
今回はimageCropped(image: UIImage)が呼ばれますね。
終わりに
お疲れ様です。カメラorアルバムから画像を取得し、トリミングできるようになったと思います!
是非ご自身のアプリに取り込んでみてください!
カメラで撮った写真は保存されないので、保存機能を作る方はご自身で追加してください。
最後に宣伝です。
このような誕生日を通知するシンプルなデザインのiosアプリをリリースしています。
誕生日book-通知とメモをリストで管理
是非使ってみてください!