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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

TableView

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

Swift

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

Q&A

解決済

1回答

838閲覧

【Swift】カスタムクラスの配列要素が正しく更新されない

ishiishiyay

総合スコア33

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

TableView

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

Swift

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

0グッド

0クリップ

投稿2019/09/16 12:47

本件で3週間詰まってしまっており、お力添え頂けますと大変幸いです。

【実現したいこと】
メッセージアプリを作っています。
基本的な処理の流れは下記の通りです。
1.各友達との個別のトーク画面を開く
2.Firebaseからその友達とのメッセージ情報を取得し、カスタムクラスの配列に代入
3-a.Collection Viewのセルをタップすると、カスタムクラスから該当の情報を取得し再生
3-b.iPhoneのProximity Sensorが発動した際も、3-aと同様に、画面に表示されているセルの音声を自動再生(1画面に表示されるセルは1つのみ。)

【問題点】
3-a.は問題なく処理できますが、3-b.でつっかかっています。アプリを起動し、最初に開いたトーク画面で処理をする分には上手くいきますが、一度個別トークルームを出てから、もう一度そのトークルーム、あるいは別の友達のトークルームに行ってからProximity Sensorを発動すると、正しいメッセージと、先ほどいたトークルームのメッセージの2つが同時に再生されてしまいます。message配列は個別トークルームを開くたびに作り直されると思っていましたが、なぜか前回入ったトークルームの情報を踏襲してしまっています。踏襲した上で、新しい方のトークルームの情報も追加されるという謎現象です。

Swift

1 @objc func proximitySensorStateChanged(){ 2 print(messages[1].url) 3 4 if (UIDevice.current.proximityState == true) { 5 do{ 6 let audioSession = AVAudioSession.sharedInstance() 7 do { 8 try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.none) 9 } catch let error as NSError { 10 print("audioSession error: (error.localizedDescription)") 11 } 12 let audioUrl = URL(string: messages[self.neo].url!)//self.neoには画面に現在表示されているセルのindexPath.rowの数字が入っています。(別箇所で取得済み)。 13 URLSession.shared.dataTask(with: audioUrl!, completionHandler: { (data, response, error) in 14 DispatchQueue.global().async{ 15 DispatchQueue.main.async{ 16 do { 17 self.audioPlayer = try AVAudioPlayer(data:data!) 18 self.audioPlayer.delegate = self 19 self.audioPlayer.play() 20 } 21 catch{} 22 } 23 } 24 }).resume() 25 } catch { 26 } 27 } 28 } 29

【試してみたこと】
一度入ったトークルームから戻るときに、removeAllでmessage配列を空にしてみました。

Swift

1 @IBAction func backBtn(_ sender: Any) { 2 messages.removeAll() 3 dismiss(animated: true, completion: nil); 4 }

この状態で、もう一度誰かのトークルームに入ると、 promximity sensorの上記コードで記載した一番最初のprint(messages[1].url)がエラーとなりクラッシュしてしまいます。一度messageを空にしても、今回新しいトークルームに入るタイミングでその情報を取得しmessageに入れているはずなのでnilにならないはずなのですが。。。
またこの状態で、以下didselectの中で同じ出力をしてもクラッシュしません。didselectで処理する分には、いろんなトークルームに出入りしようがバグは起きず、ちゃんと該当のメッセージが再生されます。

Swift

1 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 2 print(messages[1].url) 3} 4

なお、Firebaseから情報を取得してカスタムクラスに代入しているコードは以下です。

Swift

1 var messages = [Message]() 2 3//override func viewDidLoad()の中 4 Database.database().reference().child("user-messages").child(uid!).observe(.childAdded, with: { (snapshot) in 5 let messageId = snapshot.key 6 DBProvider.Instance.mediaMessagesRef.child(messageId).observe(.value, with: { (snapshot) in 7 if let d = snapshot.value as? [String:AnyObject]{ 8 if let url = d["url"] as? String { 9 let senderId = d["sender_id"] as? String 10 let toId = d["toID"] as? String 11 let childkey = d["childkey"] as? String 12 let listened = d["listened"] as? Bool 13 let timeStamp = d["timeStamp"] as? NSNumber 14 let newContact = Message(senderId: senderId!, url: url, toId: toId!, timeStamp: timeStamp! , childkey: childkey!, listened: listened!); 15 //自分が送ったボイスかつ、送り先がContactsVCで選んだ友達のとき。 16 if senderId == (Auth.auth().currentUser?.uid)! && toId! == self.toID { 17 self.messages.append(newContact); 18 } 19 //友達が送ったボイスかつ、送り先が自分 20 else if senderId == self.toID && toId == Auth.auth().currentUser?.uid { 21 self.messages.append(newContact); 22 } else { 23 } 24 //自分からそれ以外に送ったボイスや、友達が自分以外に送ったボイスはスルーする。 25 self.messages.sort(by: {Double($0.timeStamp!) < Double($1.timeStamp!)}) 26 } 27 } 28 }, withCancel: nil) 29 }, withCancel: nil) 30 31 32NotificationCenter.default.addObserver(self, selector: #selector(ChatVC.proximitySensorStateChanged), name: UIDevice.proximityStateDidChangeNotification, object: nil) 33

以上、お読みいただきありがとうございます。
アドバイス等ございましたらコメント頂けますと幸いです。よろしくお願いいたします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

ちょっと全体像がわかりませんが、
「message配列は個別トークルームを開くたびに作り直される」と書いてありますが、
viewDidLoadに記載しているとのことでしたよね?

print()を追記し、確認していただければわかると思いますが、
きっと、viewDidLoadはviewを生成する時(メモリに保持する時)のみで、
画面を表示するたびに実行されていないと思いますが、、、

通常表示を毎回読み込みたいコードを書くなら、
viewWillAppearやviewDidAppearではないですか???

投稿2019/09/16 23:47

hameji

総合スコア1380

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

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

ishiishiyay

2019/09/17 01:33

hamejiさん ご回答いただきありがとうございます。 はい、viewdidloadの中に記載しています。 夜帰宅後にコードを確認させていただぎす。
ishiishiyay

2019/09/17 16:00

hamejiさん 遅くなりすみません。 各友達とのトークルームのView ControllerのviewDidLoad内でprint()してみたのですが、各友達のトークルームに入るたびに出力されています。したがって、その友達とのメッセージもmessage配列に格納され、そのトークルーム内でセルをタップすると(didselect発動)正しい該当メッセージが再生されます。 ですが確かにご指摘の通り、本来viewdidloadは最初しか実行されないはずですので、なぜ上手く処理できているのか不可解です。 (viewdidloadの中でcollection viewをreloadしていますが、それもそもそもviewdidloadが実行されなければ関係ないと思いますので。) 何かお気付きの点ございましたらご教示頂けますと幸いです。
hameji

2019/09/19 16:23

messages.removeAll()後に入り直すと、クラッシュするとのことでしたので、 viewdidLoadではなく、viewwillappearやviewdidloadなどにした方が いいのかな?と提案してみましたが、そこは本題ではありませんね。 現状、目下の問題点は読み上げがどんどん増えていくことですよね。 読み上げるself.audioPlayer.play()のとこをみてみると、 self.audioPlayer = try AVAudioPlayer(data:data!) となっているので、data!の中身に問題ある気がします。 取得はURLSession.shared.dataTask(with: audioUrl!, completionHandler: { (data, response, error) in となっているので、 その値を毎回printするようにしてみてはどうですか? そこがどんどん追加されているようなら(きっとされていますよね) let audioUrl = URL(string: messages[self.neo].url!)がおかしいということですね。 collectionView didselectやでちゃんと動くprint(messages[1].url)ではなく、 messages[self.neo].url!としているのはなぜですか? ちゃんと確認したとありますが、 毎回要素1であるのと、表示されているセルのindexPath.rowである self.neoであるのは違う意味になりませんか? 3aでの指定の仕方もmessages[self.neo].url!となっているでしょうか? そもそもそこをmessages[1].urlと書けばいいのではないですか??? 根本的な構造・問題部をわかり易くするために 2つの方法で同じような処理をしているので、読み上げ部分をprivate funcに切り離し、 引数に読み上げるデータを渡すようにしてはどうでしょう? そうすれば、おかしいのは「読み上げデータの取得」というのに気づけて、 修正箇所がはっきりすると思います。
ishiishiyay

2019/09/23 05:19

hamejiさん 遅くなりすみません、丁寧にご回答いただき誠にありがとうございます。 以下インラインにて失礼させてください。 取得はURLSession.shared.dataTask(with: audioUrl!, completionHandler: { (data, response, error) inとなっているので、 その値を毎回printするようにしてみてはどうですか? そこがどんどん追加されているようなら(きっとされていますよね) let audioUrl = URL(string: messages[self.neo].url!)がおかしいということですね。 →はい、ご指摘の通りです。messages[self.neo].urlがおかしい理由は、複数の要素が代入されているからです。proximitysensorの箇所の一番最初の行でprint(messages[1].url)で出力していますが、ここをmessages[self.neo].urlに変えても、同様に複数のurlが出力されます。前回入室していたトークルームの情報を踏襲してしまっているのが原因であり、その理由が分からずです。。要はmessages[1]に二つの要素が入っています。再びトークルームから出て他の部屋に入ると、今度は三つの要素が入って3音声が同時に再生されます。 didselectの方で比較すると、print(messages[1].url)は複数のurlは出力されませんので正しく処理できています(該当のメッセージしか再生されません)。同じ処理をしているのに差が出るのが不可解です。 collectionView didselectやでちゃんと動くprint(messages[1].url)ではなく、 messages[self.neo].url!としているのはなぜですか? ちゃんと確認したとありますが、 毎回要素1であるのと、表示されているセルのindexPath.rowである self.neoであるのは違う意味になりませんか? 3aでの指定の仕方もmessages[self.neo].url!となっているでしょうか? →いえ、didselectのところではmessages[indexPath.row].urlです。選択した該当セルのindexPathを使っているだけです!(Table Viewなどでセルを選択するときの基本と同じです) そもそもそこをmessages[1].urlと書けばいいのではないですか??? →messages[1]というのはバグ発見のために一時的に使っているだけで、どのメッセージを開いても同じ中身が再生されてしまってはメッセージアプリの意味がなくなってしまいます。。。 根本的な構造・問題部をわかり易くするために 2つの方法で同じような処理をしているので、読み上げ部分をprivate funcに切り離し、 引数に読み上げるデータを渡すようにしてはどうでしょう? →検討してみます!! ありがとうございます。
ishiishiyay

2019/09/23 08:01

補足させてください。 Aくん、Bくんのの二人とのトークがあり、Aくんとのメッセージ数は18個、Bくんとのメッセージ数は10個です。 Aくん、BくんのそれぞれのトークルームのView ControllerのViewdidappearの中で、messages.countを出力すると、それぞれ18と10が出力され、collectionview上でも同数のセルが表示されています。 ここで、Aくんのトークルームに入った状態でiPhone上部のセンサーを手で隠し、proximitysensorを発動させます。selectorの中でprint(messages.count)と出力をすると、18個と出力されます。ここまでは問題ありません。一度Aくんのトークを出て、Bくんのトークに入りなおします。この状態でproximitysensorを発動させると、print(messages.count)は、18と10が連続で出力されます。(ここはてっきり1回で28が出力される思っていました。) 順番としてまず18を出力してから10を出力します。確かに音声を聞いても、先にAくんのメッセージが流れ初めて、途中ですぐかぶせる形でBくんの音声が流れ始めます。message配列の箱が二つ作成されるのかと思います。 最初にAくんの部屋に入ってから戻るときにmessages.removeAll()を加えると、次にB君の部屋に入ってproximitysensorを発動させたときにmessages.countが0となりself.neoのところでクラッシュします。Aくんとのメッセージ内容を全て取り除いても、Bくんの部屋に入ったらBくんとのメッセージ情報を取得するはずなので、ここで配列の中身が空になることが今まで不思議でした。 ですが上記から察するに、一度Aくんのmessagesを参照しに行ってから、次にBくんのmessagesを参照しに行っているので、Aくんのmessagesはremoveで0になると、たとえBくんのmessagesに情報が入っていたとしても、Aくんの箇所で先にクラッシュしてしまうのかと思います。 didselectやviewdidapperなどでmessages.countを出力する分には異常がないので、selectorを使用してselectorの中でmessagesにアクセスしに行こうとするとおかしいことになるのかと推察します。 なお、selectorを呼び出す下記を記載する位置を色々変えてみても結果は変わらずでした。 NotificationCenter.default.addObserver(self, selector: #selector(ChatVC.proximitySensorStateChanged), name: UIDevice.proximityStateDidChangeNotification, object: nil) もし何かお気付きでしたらご指摘頂けますと幸いです。 なければそのままベストアンサーとさせてください。 私の方でももう少し色々試してみます。
hameji

2019/09/28 13:23 編集

お話を聞くとさらに全体像をみてみたくなりました。 しかし、messageの作り方に問題があるのは確かです。 通常、Firebaseからデータを取得する場合、 当然ネットからの取得なので、非同期処理となります。 なので、backBtn でmessagesをremoveAll()とすると、 messagesは空配列になります。 そして、入り直した時、viewDidLoadでダウンロードの処理が開始されますが、 非同期なので、完了を待たずに、viewを作り出す事になります。 なので、collectionViewがmessagesなしの状態で作り始められてしまいます。 その結果、url は nilとなり、クラッシュします。 通常だと、待ってからcollectionViewを読み込むのは無理があるので、 まず、""や0などのデータがない状態にセットしておき、 firebaseからデータの読み込みが完了した時点で、 再度、collectionViewをreloadします。 numberOfItemsInSectionやcellForItemAtを走るように適当にうめたあとで、 各func の最初の行にprint("[***(function名)***]messages:", messages) firebaseのデータ読み込みの{}の中にprint("firebase完了")を書き込んでみたら、 読み込み完了のタイミングがわかると思います。 解決策としては、 AくんやBくんの各々のメッセージ表示画面の一つ上の階層(view)は ラインの「トーク」画面などのように 全部の会話が日付順にリスト表示されていると思います。 例) 2019/9/26 Aくん 2019/9/24 Bくん そのviewとかで自分が出した、自分宛にきたデータの全部をDLし、 個別メッセージを開くときはその中から該当の相手のデータのみをfilterし、 pushする方法にした方がいいのではないですか?
ishiishiyay

2019/09/28 14:02

hamejiさん ご丁寧にご指南いただきありがとうございます。 実は先ほど、下記方法でなんとか解決いたしました...! 戻るときにVCを解放できていなかったことが問題でした。 (ただ、didselectやviewdidappear内のprintでも同じはずなのに、なぜ問題がなかったかは謎ですが) https://teratail.com/questions/214257
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問