

現在コレクションビューでリストアプリを作っています。  
セルをクリックした遷移先（inputViewController）で入力した情報を、コレクションビューのセル上のラベルに表示したいのですが、入力した情報が全てのセルに反映されてしまい困っています。  
遷移先のテキストフィールドで取得した日時を遷移元（viewContoroller）に表示したいです。  
画面の遷移はstoryboad上のsegueにて、コレクションビューセルからinputviewcontrollerに繋いでいます。  
選択されたセル毎に反映させる方法をご存知でしたら教えていただけたら嬉しいです。

はじめての質問なので不慣れな点もあるかと思いますがどうかご教示のほどお願いいたします。

* * *

[![コントローラー](https://i.stack.imgur.com/tWt6U.png)](https://i.stack.imgur.com/tWt6U.png)

[code]

    class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
        //コレクションビューのレイアウト
        @IBOutlet weak var collectionView: UICollectionView!
    
        var text1: String?
    
        let toDos = ["１","２","３","４","５","６"]
    
        private let itemsPerRow: CGFloat = 3
    
        override func viewDidLoad() {
            super.viewDidLoad(）
    
            let layout = UICollectionViewFlowLayout()
            layout.itemSize = CGSize(width: 100 , height: 100)
            collectionView.collectionViewLayout = layout
        }
    
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return 6//表示するセルの数
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
            //セルの色
            cell.backgroundColor = .white
    
            let label = cell.contentView.viewWithTag(2) as! UILabel
            label.text = toDos[indexPath.row]
            let dateLabel = cell.contentView.viewWithTag(3) as! UILabel
            //dateLabel.text = text1[indexPath.row]
            dateLabel.text = text1
    
            return cell
        }
    
        func collectionview(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let horizontalSpace : CGFloat = 20
            let cellSize : CGFloat = self.view.bounds.width / 3 - horizontalSpace
            return CGSize(width: cellSize, height: cellSize)
        }
    }
    
[/code]

[code]

    import UIKit
    
    class inputViewController: UIViewController,UITextFieldDelegate{
    
        @IBOutlet weak var textField: UITextField!
    
        var datePicker: UIDatePicker = UIDatePicker()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            datePicker.datePickerMode = UIDatePicker.Mode.date
            datePicker.timeZone = NSTimeZone.local
            datePicker.locale = Locale(identifier: "ja")
            textField.inputView = datePicker
    
    
            let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 35))
            let spaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: #selector(done))
            let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done)); toolBar.setItems([spaceItem,doneItem],animated: true)
    
            textField.inputView = datePicker
            textField.inputAccessoryView = toolBar
    
        }
    
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    
            textField.resignFirstResponder()
    
            return true
        }
    
        @ objc func done(){
            textField.endEditing(true)
    
            let formatter = DateFormatter()
    
            formatter.dateFormat = "yyyy年MM月dd日"
    
            textField.text = "\(formatter.string(from: datePicker.date))"
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        @IBAction func finishButton(_ sender: Any) {
    
            let viewController: ViewController =
            self.storyboard?.instantiateViewController(withIdentifier: "finish") as! ViewController
            viewController.text1 = textField.text
            self.navigationController?.pushViewController (viewController, animated: true)
    
        }
    
    }
    
    
[/code]

[code]

    import UIKit
    
    protocol inputViewControllerDelegate: class {
        func inputViewController(_ inputVC: inputViewController, didFinishWithText text: String?)
    }
    
    class inputViewController: UIViewController,UITextFieldDelegate {
    
        @IBOutlet weak var textField: UITextField!
    
        weak var delegate: inputViewControllerDelegate?
    
        var datePicker: UIDatePicker = UIDatePicker()
    
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
    
            datePicker.datePickerMode = UIDatePicker.Mode.date
            datePicker.timeZone = NSTimeZone.local
            datePicker.locale = Locale(identifier: "ja")
            textField.inputView = datePicker
    
    
            let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 35))
            let spaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: #selector(done))
            let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done)); toolBar.setItems([spaceItem,doneItem],animated: true)
    
            textField.inputView = datePicker
            textField.inputAccessoryView = toolBar
    
    
        }
    
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    
            textField.resignFirstResponder()
    
            return true
        }
    
        @ objc func done(){
            textField.endEditing(true)
    
            let formatter = DateFormatter()
    
            formatter.dateFormat = "yyyy年MM月dd日"
    
            textField.text = "\(formatter.string(from: datePicker.date))"
    
    
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning() 
    
        }
    
    
    
    
        @IBAction func finishBtn(_ sender: Any){
    
    
        delegate?.inputViewController(self, didFinishWithText: textField.text)
            self.dismiss(animated: true, completion: nil)
    
    
    }
    
    
    
    }
    
[/code]


CC BY-SA 4.0
2019-06-16T14:00:27.107
55853
2021-01-14T05:04:00.380
2019-06-21T13:21:11.270
34757
34757
question
swift ios xcode
collectionViewにてcell毎に処理をする方法


※ご自身でも色々試されているかもしれませんが、以下の説明・コードはご質問本文中のコードを前提に書かれています。長文ですが、その点にもご留意して最後まで見ていって下さい。

コメントに示したように`text1`(日付を表示する部分でしょうからもっと適切な名前をつけましょう)はセルの数だけ用意されていませんから、「セル毎に反映させる」ためにはセルごとに異なる情報はセルの数だけ用意すべきでしょう。

但し、コメントに書いたようにバラバラの配列をたくさん持つことはお勧めできません。セル表示用(もっと他の用途にも使って構いませんが)にデータ型を定義しておくべきでしょう。

ViewController.swiftの最初の方にでも、以下のデータ型宣言を追加して下さい。  
(`class ViewController ...`より前。)

[code]

    //1つのセルに表示される項目は1つのデータ型にまとめる
    struct MyTodoItem {
        var title: String
        var dateString: String? //「日付」を保持する項目にはそのことがわかるような名前をつける
        //画像、背景色...
    }
    
[/code]

本当は日付を表すのに`String`型を使うといろいろ難しいことが出てくるので、`Date`型などに変えておきたいところですが、問題の解決に本質的でない部分の修正は避けておきます。「選択されたセル毎に反映させる」だけでも、まだまだ修正が必要なので…。

そうして、現在の`text1`と`toDos`の代わりに以下のような配列を保持しておきます。

[code]

        var todos: [MyTodoItem] = [
            MyTodoItem(title: "１", dateString: nil),
            MyTodoItem(title: "２", dateString: nil),
            MyTodoItem(title: "３", dateString: nil),
            MyTodoItem(title: "４", dateString: nil),
            MyTodoItem(title: "５", dateString: nil),
            MyTodoItem(title: "６", dateString: nil),
        ]
    
[/code]

`collectionView(_:cellForItemAt:)`の実装は、それに合わせて少し書き換えてやります。

[code]

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
            let item = todos[indexPath.row]  //`item`は`MyTodoItem`型
            //セルの色
            //cell.backgroundColor = .white
    
            let label = cell.contentView.viewWithTag(2) as! UILabel
            label.text = item.title //<-
            let dateLabel = cell.contentView.viewWithTag(3) as! UILabel
            dateLabel.text = item.dateString //<-
    
            return cell
        }
    
[/code]

* * *

で、ここまではまだ大したことはないんですが、「セル毎に反映させる」だけでも、まだまだ考えないといけないことがあります。

  * `InputViewController`(現在は`inputViewController`になっているかもしれませんが、Swiftでは「型名は大文字で始める」と言う大変よく守られているルールがあるので、以下`InputViewController`で統一しておきます。)でテキストの編集が終わった後、(適切なインスタンスの)`todos`の適切なインデックスの`dateString`を書き換えないといけない。

  * `todos`の中身を書き換えたら、`UICollectionView`のreload系メソッドを呼んでやらないと、変更を確実に表示に反映させることができない

と言うわけで、せっかく配列をちゃんと作っても`InputViewController`は配列のインデックスもわかっていません…。

この辺のことを、元の`ViewController`側に丸投げするためには、デリゲートパターンなんてものがよく使われます。(delegateを動詞で使うと、「委任する」「委譲する」あたりの意味になりますが、場面によっては「丸投げする」の方が近いでしょう…。)

InputViewController.swiftの中にでも、次のようなプロトコルを追加してやります。

[code]

    protocol InputViewControllerDelegate: class {
        func inputViewController(_ inputVC: InputViewController, didFinishWithText text: String?)
    }
    
[/code]

(`text`は日付を表していることが命名に反映されていませんが、長くなりすぎるので…。)

`InputViewController`の中には上記のプロトコル型の`delegate`プロパティを追加します。

[code]

    class InputViewController: UIViewController, UITextFieldDelegate {
    
        @IBOutlet weak var textField: UITextField!
    
        weak var delegate: InputViewControllerDelegate? //<-
    
        var datePicker: UIDatePicker = UIDatePicker()
    
        //...
    }
    
[/code]

後は、編集が完了した後の処理をこの`delegate`に丸投げするように`finishButton(_:)`の処理を書き換えてやります。  
**修正** この部分は、セグエの種類(Kind)として`Show`を選んで、
**`InputViewController`も`UINavigationController`内に表示されている** 場合のコードです。

[code]

        @IBAction func finishButton(_ sender: Any) {
            delegate?.inputViewController(self, didFinishWithText: textField.text)
            self.navigationController?.popViewController(animated: true)
        }
    
[/code]

(元コードから`UINavigationController`を使っているものと推測してます。もし違うなら、「元の画面に戻る」処理は変更しないといけませんが、決して「新しい画面を表示する」ようなコードにしてはいけません。`UINavigationController`を使っているなら「戻る」ボタンが表示されるかと思いますが、話を単純にするため、そっちはキャンセル扱いで何もしないことにしておきます。)

セグエの種類にPresent Modallyを選んだりして、
**`InputViewController`が`UINavigationController`を隠している** ような場合はこちら:

[code]

        @IBAction func finishButton(_ sender: Any) {
            delegate?.inputViewController(self, didFinishWithText: textField.text)
            //print(self.navigationController,self.presentingViewController)
            self.dismiss(animated: true, completion: nil)
        }
    
[/code]

* * *

ここまででも解説を挟みながらだとそこそこの長さになってしまいましたが、ここまでの修正を入れても`InputViewController`での編集結果は全く元の画面に反映されません、`delegate`に丸投げしているのに、「俺が丸投げされてやる」と、誰かが`delegate`を引き受けてやる部分がないからです。

  * `InputViewController`への画面遷移の際に`delegate`を設定してやる

  * その`delegate`に必要なメソッドを実装してやる

と言う部分が必要になります。

上記1点目、セルから直接セグエを引いてしまっているので、`prepare(for:sender:)`メソッドの中でやらせることになるでしょう。

**該当のセグエには、Identifierを以下のコードと整合するように(`ToInput`)付けておいて下さい。**

`delegate`役は`ViewController`にやらせるので、以下は`ViewController`クラスに追加することになります。

[code]

        var editingItem: Int? //<- 編集中要素のインデックスを覚えておくためのプロパティを追加
    
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if segue.identifier == "ToInput" {
                //セルからセグエを引いたら`sender`がセルになってこのメソッドが呼ばれる
                let cell = sender as! UICollectionViewCell
                //そのセルの`todos`配列内のインデックスをどこか(インスタンスプロパティに)覚えておく
                if let indexPath = collectionView.indexPath(for: cell) {
                    editingItem = indexPath.item
                } else {
                    editingItem = nil
                }
                //このセグエでは遷移先は`InputViewController`
                let destVC = segue.destination as! InputViewController
                //その`delegate`を引き受けるのは俺(`self`)だと設定する
                destVC.delegate = self
            }
        }
    
[/code]

このままだと最後の`destVC.delegate =
self`にエラーが表示されるはずですが、`ViewController`が`InputViewControllerDelegate`に適合していないせいなので、クラス宣言の後ろにこんな宣言を付け加えてやります。

[code]

    extension ViewController: InputViewControllerDelegate {
        func inputViewController(_ inputVC: InputViewController, didFinishWithText text: String?) {
            //`self.editingItem`がnilのままだと何かの間違いなんで一応チェック
            if let editingItem = self.editingItem {
                //`todos`配列の適切な要素を更新
                todos[editingItem].dateString = text
                //更新を確実に画面に反映させる
                collectionView.reloadItems(at: [IndexPath(item: editingItem, section: 0)])
            }
        }
    }
    
[/code]

* * *

さてずいぶん長くなってしまいましたが、これでも「選択されたセル毎に反映」するのに必要な事柄以外はほとんど無視したつもりです。お試しの上、うまくいかない点、わかりにくい点があればお知らせください。


CC BY-SA 4.0
2019-06-18T21:11:35.670
55907
2019-06-20T16:30:38.273
2019-06-20T16:30:38.273
13972
13972
55853
answer