質問をすることでしか得られない、回答やアドバイスがある。

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

ただいまの
回答率

90.33%

  • Swift

    7683questions

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

  • Xcode

    4345questions

    Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

  • iOS

    4186questions

    iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

  • TableView

    127questions

    TableView(UITableView)とは、リスト形式で表示するコントロールで、ほとんどのアプリに使用されています。画面を「行」に分けて管理し、一般的には各行をタップした際に詳細画面に移動します。

【iOS】Collection Viewの特定セルのみの更新について

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 932

Ethan

score 2

前提・実現したいこと

ご覧いただき、ありがとうございます。
現在、Collection ViewのCell内に音声を再生するためのボタンを置き、
ストリーミングで再生する機能を作っています。

下記のコードでは、音声が再生されているかを調べ、
再生ボタンと停止ボタンを分岐させています。

ですが、このやり方ですと、コレクションビューをスクロールすると、全てのセルに反映されてしまい、再生している時は全てのセルで停止ボタンが表示されてしまいます。

音声再生中の特定セルのみの再生状態を読み取り、再生中のセルのボタンの状態を変化させるには、どうすればよいのでしょうか?
また、他にベストな方法があるのでしょうか?

宜しくお願いします。

cell.playButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
cell.playButton.setImage(UIImage(named: "stop"), for: UIControlState.selected)
cell.playButton.isSelected = false

    if !(mediaPlayer.rate == 0.0){
       cell.playButton.isSelected = true
    }else{
       cell.playButton.isSelected = false
    }

 cell.playButton.addTarget(self,action:#selector(playButtonDidTouch(sender:)), for: .touchUpInside)

該当のソースコード

class AudioViewController: UIViewController {

    var mediaPlayer = AVPlayer()

    override func viewDidLoad() {
        super.viewDidLoad()
    }

//コレクションビュー関連
extension AudioViewController : UICollectionViewDataSource {

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        cell.playButton.tag = indexPath.row
        cell.playButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
        cell.playButton.setImage(UIImage(named: "stop"), for: UIControlState.selected)
        cell.playButton.isSelected = false

        if !(mediaPlayer.rate == 0.0){
           cell.playButton.isSelected = true
        }else{
           cell.playButton.isSelected = false
        }

      cell.playButton.addTarget(self,action:#selector(playButtonDidTouch(sender:)), for: .touchUpInside)

      return cell
    }

    func playButtonDidTouch(sender:UIButton) {
        if (sender.isSelected){
            mediaPlayer.pause()
            sender.isSelected = false
        }
        else{
            let url = ""
            do { try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)}
            catch {print("エラー")}
            let playerItem = AVPlayerItem( url:NSURL( string:url! )! as URL )
            mediaPlayer = AVPlayer(playerItem:playerItem)
            mediaPlayer.rate = 1.0
            mediaPlayer.play()
            sender.isSelected = true
        }
    }

}

試したこと

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Storyboard.CellIdentifier, for: indexPath) as! AudioCollectionViewCell
        cell.playButton.tag = indexPath.row

        if let optionalIndexPath = currentSelectedIndexPath{
            if (indexPath == optionalIndexPath){
               cell.playButton.setImage(UIImage(named: "stop"), for: UIControlState.selected)
            }
            else{
               cell.playButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
            }
        }
        cell.playButton.addTarget(self,action:#selector(playButtonDidTouch(sender:)), for: .touchUpInside)
        return cell
    }

    func playButtonDidTouch(sender:UIButton) {

        let cell = sender.superview?.superview as! UICollectionViewCell
        let indexPath = collectionView.indexPath(for: cell)!
        currentSelectedIndexPath = indexPath

        if (sender.isSelected){
            mediaPlayer.pause()
            sender.isSelected = false
            sender.setImage(UIImage(named: "play"), for: UIControlState.normal)
        }
        else{
            let row = sender.tag
            let url = ""
            do { try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)}
            catch {print("エラー")}
            let playerItem = AVPlayerItem( url:NSURL( string:url! )! as URL )
            mediaPlayer = AVPlayer(playerItem:playerItem)
            mediaPlayer.rate = 1.0
            mediaPlayer.play()
            sender.isSelected = true
            sender.setImage(UIImage(named: "stop"), for: UIControlState.normal)
        }
    }

補足情報(言語/FW/ツール等のバージョンなど)

Swift 3での実装

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+3

再利用対策を実施したとのことですが、どのような再利用対策を実施したのでしょうか?
それが提示されていないので、どこまで理解されているのかわかりませんが、そのあたりの再利用対策が理解されていないような気がします。

通常の再利用対策は、collectionViewのセルに表示するデータを別途配列のプロパティで持っておき、cellForItemAtのメソッドでその配列内のデータをもとにセルの内容を再セットします。そうすれば、再利用前にセットされていたセルの表示は、正しい状態に再セットされます。

今回なら、セル毎のデータとして、ボタンの状態とか音声のURL(またはそれをもとに生成した音声データ)があるはずです。

ただ、今回の場合は、音声再生とのことですので、おそらくたくさんあるセルの中で最後にタップされたセルだけ再生状態にする(以前に再生していたセルの音声は停止し、ボタンも非選択状態にもどす)ようなことを期待しているような気がします。

もしそうであれば、配列データを持たなくても、「現在再生中のセルの番号」をプロパティで持っておき、cellForItemAtでセルの内容を再セットする際に、
・再生中のセル番号と一致していればセルを再生中の表示に設定する。
・一致していなければセルを非再生の表示に設定する。
といったようなことをすればよさそうに思います。

また、この場合、playButtonDidTouchの処理でどのセル番号のボタンがタップされたかの情報を取得して、「現在再生中のセルの番号」を更新する必要がありますが、
その処理は

let cell = sender.superview?.superview as! UICollectionViewCell
let indexPath = collectionView.indexPath(for: cell)!


のようにすれば、ボタンがタップされたセルの番号を簡単に取得できます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/04/20 14:38

    TakeOneさま
    丁寧なご回答、まことにありがとうございます。
    上記を試したところ、全てのセルには反映されなくなったのですが、
    1つ目のセルの音声を再生すると、5個目と9個目のボタンも1つ目のボタンと連動して、変化してしまいます。連動させないようにするにはどうすれば良いのでしょうか...?
    また、音声を再生させながら前の画面に戻って、(タップされた変数をグローバル変数として持っています)、再度コレクションビュー内に戻ると、ボタンが全て停止ボタンでは無く、再生ボタンとして表示されてしまいます。


    再利用対策で行ったのは、UICollectionCell内で、PrepareForReuseメソッド内において、
    ボタンにnilを加え、初期化させました。

    質問を続けてしまい、大変恐縮ですが、何卒宜しくお願いいたします。

    キャンセル

  • 2017/04/20 15:44 編集

    > 1つ目のセルの音声を再生すると、5個目と9個目のボタンも1つ目のボタンと連動して、変化してしまいます。連動させないようにするにはどうすれば良いのでしょうか...?

    どのように修正されたのかコードを見せてもらわないとわかりません。
    修正したコードを追記するときは、質問欄に追記してしてください。

    キャンセル

  • 2017/04/20 17:59

    TakeOneさま
    大変、失礼いたしました。
    『試したこと』の欄に追加したコードを追記しました。
    ご確認宜しくお願い致します。

    キャンセル

  • 2017/04/21 01:05

    currentSelectedIndexPathがどのように定義されているのか
    肝心のところがありませんが、おそらく
    var currentSelectedIndexPath:IndexPath!
    という定義になっているのだろうと推測して見てみました。

    ざっと見た限り、以下の問題があると思います。
    1.cellForItemAtでoptionalIndexPathを見て
    setImageでstopかplayの画像を設定する処理がありますが、
    ここで選択状態(cell.playButton.isSelected)の再設定が必要だと思います。
    2.cellForItemAtで`if let optionalIndexPath = currentSelectedIndexPath`
    のオプショナルバインディング処理がありますが、これのelseルートは
    まだ1回もボタンを押してない状態の処理だと思うので、
    cell.playButton.isSelectedとsender.setImageが必要だと思います。
    3. playButtonDidTouchの最後に
    collectionView.reloadData()が必要だと思います。

    キャンセル

  • 2017/04/21 10:41

    TakeOneさま

    cellForItemAtで、選択状態(cell.playButton.isSelected)の再設定をしたところ、
    期待通りの動きが得られました。情報不足で大変ご迷惑をおかけしました。
    ありがとうございました!

    キャンセル

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

  • ただいまの回答率 90.33%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Swift

    7683questions

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

  • Xcode

    4345questions

    Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

  • iOS

    4186questions

    iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

  • TableView

    127questions

    TableView(UITableView)とは、リスト形式で表示するコントロールで、ほとんどのアプリに使用されています。画面を「行」に分けて管理し、一般的には各行をタップした際に詳細画面に移動します。