🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1662閲覧

【swift】画像を並び替えると、画像が挿し変わってしまう

konakizzi_mk

総合スコア21

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2021/03/23 09:00

編集2021/03/23 09:50

実現したいこと

TwitterやInstagramのように複数枚画像を添付した際に、添付した画像を長押しで並び替えできるようにしたい。

##ソースコード

参考にしたサイト

Swift

1import UIKit 2import DKImagePickerController 3 4class PictureViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIGestureRecognizerDelegate { 5 6 var assets:[DKAsset]? 7 8 9 @IBOutlet weak var collectionView: UICollectionView! 10 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 addLongTapGesture() 15   16 } 17 @IBAction func tapButton(_ sender: Any) { 18 let pickerController = DKImagePickerController() 19 pickerController.maxSelectableCount = 4 - (assets?.count ?? 0) 20 pickerController.UIDelegate = CustumUIDelegate() 21 pickerController.didSelectAssets = { [unowned self] (assets: [DKAsset]) in 22 self.assets = assets 23 self.collectionView.reloadData() 24 25 } 26 present(pickerController, animated: true, completion: nil) 27 } 28 29 func addLongTapGesture() { 30 let longTapGesture = UILongPressGestureRecognizer(target:self,action:#selector(longTap(gesture:))) 31 collectionView.addGestureRecognizer(longTapGesture) 32 } 33 34 @objc func longTap(gesture:UILongPressGestureRecognizer){ 35 switch gesture.state{ 36 // ロングタップの開始時 37 case.began: 38 guard 39 let selectedIndexPath = collectionView.indexPathForItem(at:gesture.location(in:collectionView))else{ 40 break 41 } 42 collectionView.beginInteractiveMovementForItem(at:selectedIndexPath) 43 // セルの移動中 44 case.changed: 45 collectionView.updateInteractiveMovementTargetPosition(gesture.location(in:gesture.view)) 46 // セルの移動完了時 47 case.ended: 48 collectionView.endInteractiveMovement() 49 default: 50 collectionView.cancelInteractiveMovement() 51 } 52 } 53 54 55 56 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 57 return self.assets?.count ?? 0 58 } 59 60 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 61 let asset = self.assets![indexPath.row] 62 var cell: UICollectionViewCell? 63 var imageView: UIImageView? 64 65 if asset.type == .photo { 66 cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellImage", for: indexPath) 67 imageView = cell?.contentView.viewWithTag(1) as? UIImageView 68 } 69 //表示 70 if let imageView = imageView { 71 72 asset.fetchFullScreenImage(completeBlock: { image, info in 73 imageView.image = image 74 75 }) 76 } 77 return cell! 78 } 79 80 // 並び替えを可とする 81 func collectionView(_collectionView:UICollectionView, canMoveItemAt indexPath:IndexPath)-> Bool { 82 return true 83 } 84 85 func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 86 reorderCells(sourceIndex: sourceIndexPath.item, destinationIndex: destinationIndexPath.item) 87 } 88 89 // MARK: - PRIVATE METHODS 90 91 /// セルの移動 92 /// 93 /// - Parameters: 94 /// - sourceIndex: 移動元の位置 95 /// - destinationIndex: 移動先の位置 96 private func reorderCells(sourceIndex: Int, destinationIndex: Int) { 97 var imageView: [UIImageView]? 98 guard let n = imageView?.remove(at: sourceIndex) else { return } 99 imageView?.insert(n, at: destinationIndex) 100 } 101 102 103 104 105} 106

※画像のサイズがバラバラなのは見逃していただけると助かりますmm

並び替え前並び替え後(一番左の画像を左から2番目に移動)
並び替え前並び替え後

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

Swift

1 private func reorderCells(sourceIndex: Int, destinationIndex: Int) { 2 var imageView: [UIImageView]? 3 guard let n = imageView?.remove(at: sourceIndex) else { return } 4 imageView?.insert(n, at: destinationIndex) 5 }

の中で入れ替えているのは UIImageView ですが、collectionView(_:cellForItemAt:) の中では

Swift

1if let imageView = imageView { 2 3 asset.fetchFullScreenImage(completeBlock: { image, info in 4 imageView.image = image 5 6 }) 7 }

という具合に「並び替えていない」 asset から毎回データを呼び出しているのが原因の一つだと思います。

また、reorderCells(sourceIndex:destinationIndex:)は何もデータが入っていない [UIImageView]? を並び替えようとしているため、実質なにも行われません。

加えて、上記処理中の fetchFullScreenImage(completeBlock:) は非同期処理の上、セルの生成ごとに処理させると冗長な上、場合によっては通信も伴いセルの更新に影響が出るため、アセットの取り出しは別途行った方が効率も良いかと思います。

ざっと書き直した程度なのでこれも効率が良くない方法ではあるのですが、たとえば下記のようにする方法もあるかと思います(エラー処理やセルの生成など、細かい点では改善すべき点もいくつかあるのですが、それは今回のご質問とは直接関係しないためとりあえず修正しないで置いておきます)。

Swift

1import UIKit 2import DKImagePickerController 3 4class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIGestureRecognizerDelegate { 5 6 // DKAsset ではなく、UIImage で保存する 7 //var assets:[DKAsset]? 8 var images: [UIImage] = [] 9 10 @IBOutlet weak var collectionView: UICollectionView! 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 // 検証用 16 //collectionView.delegate = self 17 //collectionView.dataSource = self 18 19 addLongTapGesture() 20 } 21 22 @IBAction func tapButton(_ sender: Any) { 23 let pickerController = DKImagePickerController() 24 25 // 残り選択可能枚数は images.count を使う 26 //pickerController.maxSelectableCount = 4 - (assets?.count ?? 0) 27 pickerController.maxSelectableCount = 4 - (images.count) 28 29 pickerController.UIDelegate = CustumUIDelegate() 30 31 pickerController.didSelectAssets = { [unowned self] (assets: [DKAsset]) in 32 // ここであらかじめ画像を取り出しておく 33 // Assetsからデータを取り出し、images に UIImage という形で保存する 34 for asset in assets { 35 if asset.type == .photo { 36 37 // fetchFullScreenImage は非同期処理 38 asset.fetchFullScreenImage(completeBlock: { image, info in 39 guard let image = image else { 40 return 41 } 42 images.append(image) 43 // 追加ごとに reloadData() を実行する 44 self.collectionView.reloadData() 45 }) 46 } 47 } 48 49 // ここでは実行しない 50 // self.collectionView.reloadData() 51 } 52 53 present(pickerController, animated: true, completion: nil) 54 } 55 56 func addLongTapGesture() { 57 let longTapGesture = UILongPressGestureRecognizer(target:self,action:#selector(longTap(gesture:))) 58 collectionView.addGestureRecognizer(longTapGesture) 59 } 60 61 @objc func longTap(gesture:UILongPressGestureRecognizer){ 62 switch gesture.state{ 63 // ロングタップの開始時 64 case.began: 65 guard 66 let selectedIndexPath = collectionView.indexPathForItem(at:gesture.location(in:collectionView))else{ 67 break 68 } 69 collectionView.beginInteractiveMovementForItem(at:selectedIndexPath) 70 // セルの移動中 71 case.changed: 72 collectionView.updateInteractiveMovementTargetPosition(gesture.location(in:gesture.view)) 73 // セルの移動完了時 74 case.ended: 75 collectionView.endInteractiveMovement() 76 default: 77 collectionView.cancelInteractiveMovement() 78 } 79 } 80 81 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 82 // images.count を返す 83 //return self.assets?.count ?? 0 84 return images.count 85 } 86 87 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 88 //let asset = self.assets![indexPath.row] 89 var cell: UICollectionViewCell? 90 var imageView: UIImageView? 91 92 // Asset から取り出すときに .photo か否かをチェックしているので、ここでのチェックは不要 93 //if asset.type == .photo { 94 cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellImage", for: indexPath) 95 imageView = cell?.contentView.viewWithTag(1) as? UIImageView 96 //} 97 98 //表示 99 if let imageView = imageView { 100 // ここで Asset から取り出そうとすると、非同期処理のため思ったような処理にならないため、あらかじめ取り出しておいた画像を使う 101// asset.fetchFullScreenImage(completeBlock: { image, info in 102// imageView.image = image 103// 104// }) 105 106 imageView.image = images[indexPath.row] 107 } 108 109 return cell! 110 } 111 112 // 並び替えを可とする 113 func collectionView(_collectionView:UICollectionView, canMoveItemAt indexPath:IndexPath)-> Bool { 114 return true 115 } 116 117 func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 118 reorderCells(sourceIndex: sourceIndexPath.item, destinationIndex: destinationIndexPath.item) 119 120 collectionView.reloadData() 121 } 122 123 private func reorderCells(sourceIndex: Int, destinationIndex: Int) { 124 // imageView ではなく、データそのものを入れ替える 125 //var imageView: [UIImageView]? 126 127// guard let n = imageView?.remove(at: sourceIndex) else { return } 128// imageView?.insert(n, at: destinationIndex) 129 130 // 画像の入れ替え 131 let image = images.remove(at: sourceIndex) 132 images.insert(image, at: destinationIndex) 133 } 134}

投稿2021/03/23 11:16

TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

konakizzi_mk

2021/03/24 09:36

TsukubaDepotさんいつも分かりやすい回答をありがとうございます。 ご提示いただいたコードで想定通り動作すること、確認できましたmm 今回collectionViewを使用して画像の入れ替えを再現しましたが、以下URLのやりとりを拝見し、 UILongPressGestureRecognizerだけでも再現できるのかなと思い試行錯誤してみましたが、 長押しは検知できても画像を動かすことができず困っております。 そもそもUILongPressGestureRecognizerだけで画像入れ替えは可能なのでしょうか? https://teratail.com/questions/310568
TsukubaDepot

2021/03/24 10:17

CollectionViewを使わずすべてご自身で実装するという文脈であれば、UILongPressGestureRecognizer で実装できるかと思いますが、画像の並び替え、整列、その他さまざまな機能の実装を考慮すれば、今回参考にされているようなCollectionView を用いた実装を行った方が、堅牢性も保守性も良いのではないかと思います(ご自身でチャレンジしてみることそのものは否定しませんが、どこに注力したいのか次第であり、並び替えの実装に手間をかけたくないのであれば、あえて挑戦する話ではないと思います)。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問