0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TableViewからTableViewへの画面遷移時に表示内容を変更する

Last updated at Posted at 2021-02-22

環境

・OS: macOS Catalina バージョン10.15.7
・Swift 5.3.1
・Xcode Version12.2
・Firebase(Firebase Realtime Database)

前提知識

・Swift5
・CRUDの知識 ※主にFirebase
・XCodeの使い方
・TableView, TableViewCell,TextField,Buttonの知識

概要

ezgif-3-be4fb662b80d.gif

上の動画のように、TableViewからTableViewに画面遷移をする際に、押下されたセルによって画面遷移後の内容を変更したいことはありませんか?

私はあります!!

根元のViewControllerから派生していく感じ、、、なんかいいですよね。
今回は私が考えたこと、リサーチしたことや実装していくまでの過程を実践ベースで記したいと思います。

仕様概要

スクリーンショット 2021-02-22 0.10.06.png
↑今回のStoryboardです。

①画面遷移前のTableViewにあるcellのindexPathを取得し、投稿内容と同時にFirebaseのRealtime Databaseに保存する
              ↓
②Firebaseから情報をReadしてくる際に、①で保存したindexPathの値と、画面遷移後のcellのindexPathを比較する。
              ↓
③もし②で比較したindexPathの値が同値であったら、配列に保存する。

雑な説明ですみません。ここで上記の内容がわかった人は、今すぐXCodeを開いて試してみましょう!
「この説明、よくわからんわ!!」って人は一緒に以下で実装方法を考えてみましょう!

完成形

ezgif-3-be4fb662b80d.gif
この記事を読み終わった時には、このような仕様が実装できるようになります。

今回使用したコード(初期段階)

そのコードが以下です。見るのが面倒な人は、飛ばしてください。

AppDelegate.swift

import UIKit
import Firebase

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        FirebaseApp.configure()
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}
SceneDelegate.swift
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
    }


}

ViewController.swift
import UIKit
import Firebase

class ViewController: UIViewController {
    
    //    tableview定義
    @IBOutlet var sampleTableView: UITableView!  
    //    tableViewに表示する内容を配列に格納
    var testArray = [testDataModel]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        sampleTableView.delegate = self
        sampleTableView.dataSource = self
        //        カスタムセルの登録
        let nib = UINib(nibName: "sampleTableViewCell", bundle: Bundle.main)
        sampleTableView.register(nib, forCellReuseIdentifier: "cell")
        //        不要な線を消す
        sampleTableView.tableFooterView = UIView()
        //        セルの高さを指定
        sampleTableView.rowHeight = 100
        observeInfoFromFirebase()
    }
    
    private func observeInfoFromFirebase(){
        let ref = Database.database().reference()
        ref.child("testComment").observe(.value) { (snapshot) in
            //            配列を初期化
            self.testArray = []
            for data in snapshot.children{
                let snapData = data as! DataSnapshot
                let dicData = snapData.value as! [String:Any]
                
                let testData = testDataModel(dic: dicData)
                self.testArray.append(testData)
            }
            self.sampleTableView.reloadData()
        }
        
    }
    
    
}

extension ViewController: UITableViewDelegate, UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return testArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! sampleTableViewCell
        let testPost = testArray[indexPath.row]
        //        cellの内容
        let testText = cell.viewWithTag(1) as! UILabel
        testText.text = testPost.testText
        
        let commentText = cell.viewWithTag(2) as! UILabel
        commentText.text = testPost.commentText
        
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //        セルを選択した後に選択状態を解除する
        tableView.deselectRow(at: indexPath as IndexPath, animated: true)
        self.performSegue(withIdentifier: "toDetail", sender: nil)
    }
}

destinationViewController.swift
import UIKit
import Firebase

class destinationViewController: UIViewController {
  
    @IBOutlet var destinationTableView: UITableView!
    var sampleArray = [testDataModel]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //カスタムセルの登録
        let nib = UINib(nibName: "sampleTableViewCell", bundle: Bundle.main)
        destinationTableView.dataSource = self
        destinationTableView.delegate = self
        destinationTableView.register(nib, forCellReuseIdentifier: "cell")
        //        不要な線を消す
        destinationTableView.tableFooterView = UIView()
        //        セルの高さを指定
        destinationTableView.rowHeight = 100
        observeInfoFromFirebase()
    }
    
    private func observeInfoFromFirebase(){
        let ref = Database.database().reference()
        ref.child("sampleComment").observe(.value) { (snapshot) in
            //            配列を初期化
            self.sampleArray = []
            for data in snapshot.children{
                let snapData = data as! DataSnapshot
                let dicData = snapData.value as! [String:Any]
                
                let testData = testDataModel(dic: dicData)
                self.sampleArray.append(testData)
            }
            self.destinationTableView.reloadData()
        }
    }
}

extension destinationViewController:UITableViewDelegate,UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sampleArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! sampleTableViewCell
        let samplePost = sampleArray[indexPath.row]
        //        cellの内容
        let testText = cell.viewWithTag(1) as! UILabel
        testText.text = samplePost.testText
        
        let commentText = cell.viewWithTag(2) as! UILabel
        commentText.text = samplePost.commentText
        
        return cell
    }
    
    
}
testDataModel.swift
import Foundation
import UIKit

class testDataModel{
    //    cellに包含する内容の定義
    var testText: String
    var commentText: String
    
    //    testDataModelを初期化
    init(dic:[String:Any]) {
        self.testText = dic["text"] as! String
        self.commentText = dic["comment"] as! String
    }
    
}
sampleTableViewCell.swift
import UIKit

class sampleTableViewCell: UITableViewCell {

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
}

スクリーンショット 2021-02-22 1.15.48.png
↑ sampleTableViewCell.swiftに対応するtableViewCellです。

createDestinationPageViewController.swift
import UIKit
import Firebase

class createDestinationPageViewController: UIViewController {
    
    @IBOutlet var testTextField: UITextField!
    @IBOutlet var commentTextField: UITextField!
    
    @IBOutlet var sendButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    @IBAction func sendButtonAction(_ sender: Any) {
        setFirebase()
        self.navigationController?.popViewController(animated: true)
    }
    
    private func setFirebase(){
        let ref = Database.database().reference()
        let postModel:[String:Any] = [
            "text": testTextField.text,
            "comment": commentTextField.text
        ]
        ref.child("sampleComment").childByAutoId().setValue(postModel)
    }
}

extension createDestinationPageViewController: UITextFieldDelegate{
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
    }
}
createPageViewController.swift
import UIKit
import Firebase

class createPageViewController: UIViewController {
    
    @IBOutlet var testTextField: UITextField!
    @IBOutlet var commentTextField: UITextField!
    
    @IBOutlet var sendButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    @IBAction func sendButtonAction(_ sender: Any) {
        setFirebase()
        self.navigationController?.popViewController(animated: true)
    }
    
    private func setFirebase(){
        let ref = Database.database().reference()
        let postModel:[String:Any] = [
            "text": testTextField.text,
            "comment": commentTextField.text
        ]
        ref.child("testComment").childByAutoId().setValue(postModel)
    }
}

extension createPageViewController: UITextFieldDelegate{
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
    }
}

今回使用したコード(最終段階)

完成形のコードは以下です。
https://github.com/Sho-max/tableViewSample

今回の問題点

①どのcellを押下して画面遷移をしても、画面遷移後の表示内容が同一。
       ezgif-2-cfc87235ca33.gif

②対策法をググる
でも、この問題を解決するものが見当たらない。

③解決すべくあらゆる手段を思案
・配列をたくさん作る → 配列の数は有限であるから、実用的ではない。
・Storyboardをたくさん作る → これも配列同様、実用的ではない。
・indexPathを使用して識別 → 採用💮

④では、今回採用のindexPathを使用したコードをゴリゴリ書いていきましょう!!

解決策

今回、変更したコードは以下です。

destinationTestDataModel.swift
//遷移後に使用するデータモデルを "testDataModel.swift"から"destinationTestDataModel.swift"に変更しました。
import Foundation
import UIKit

class destinationTestDataModel{
    //    cellに包含する内容の定義
    var testText: String
    var commentText: String
    
    //    追加した変数:取得したindexPathを格納する
    var passedIndexPath: Int
    
    //    testDataModelを初期化
    init(dic:[String:Any]) {
        self.testText = dic["text"] as! String
        self.commentText = dic["comment"] as! String
        //        追加した変数を初期化する
        self.passedIndexPath = dic["passedIndexPath"] as! Int
    }
    
}

ViewController.swift
import UIKit
import Firebase

class ViewController: UIViewController {

  //    tableview定義
    @IBOutlet var sampleTableView: UITableView!
    
    //    indexpathを取得するための変数定義
    var passedIndexPath: Int!
   
                            省略

 private func observeInfoFromFirebase(){
        let ref = Database.database().reference()
        ref.child("testComment").observe(.value) { (snapshot) in
            //            配列を初期化
            self.testArray = []
            for data in snapshot.children{
                let snapData = data as! DataSnapshot
                let dicData = snapData.value as! [String:Any]
                
                let testData = testDataModel(dic: dicData)
                //                didselectRowAtから送られ、passedIndexPathに格納された値とtestData内のpassedIndexPathを比較。同値だったらtestArrayに追加する。
                if self.passedIndexPath == testData.passedIndexPath{
                    self.testArray.append(testData)
                }
            }
            self.sampleTableView.reloadData()
        }
        
    }
                             省略

extension ViewController: UITableViewDelegate, UITableViewDataSource{

                             省略

 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //        追加したコード。didselectRowAtが起動した時(cellが押された時)に、上で定義した変数にindexPathを格納する。
        passedIndexPath = Int(indexPath.row)
        //        セルを選択した後に選択状態を解除する
        sampleTableView.deselectRow(at: indexPath as IndexPath, animated: true)
        self.performSegue(withIdentifier: "toDetail", sender: nil)
    }
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        //        segueが成功した時に起動
        if segue.identifier == "toDetail"{
            let DestinationVC = segue.destination as! destinationViewController
            //    遷移後画面の変数のrecievedIndexPathにこの画面で格納したpassedIndexPathを値渡しする
            DestinationVC.recievedIndexPath = passedIndexPath
        }
    }
}
destinationViewController.swift
import UIKit
import Firebase

class destinationViewController: UIViewController {
    
    @IBOutlet var destinationTableView: UITableView!
    
    //    遷移前の画面から渡されたindexPath
    var recievedIndexPath: Int!
    //  配列をdestinationTestDataModel型に変更
    var sampleArray = [destinationTestDataModel]()

                    省略

private func observeInfoFromFirebase(){
        let ref = Database.database().reference()
        ref.child("sampleComment").observe(.value) { (snapshot) in
            //      配列を初期化
            self.sampleArray = []
            for data in snapshot.children{
                let snapData = data as! DataSnapshot
                let dicData = snapData.value as! [String:Any]
                
                let testData = destinationTestDataModel(dic: dicData)
            //   遷移前画面から渡されたindexPathとtestDataに含まれるindexPathを比較し、同値であったらsampleArray配列にappendする。
                if testData.passedIndexPath == self.recievedIndexPath{
                    self.sampleArray.append(testData)
                }
            }
            self.destinationTableView.reloadData()
        }
    }
}
extension destinationViewController:UITableViewDelegate,UITableViewDataSource{

                    省略

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
             if segue.identifier == "toCreatePage"{
                let createDestinationPageVC = segue.destination as! createDestinationPageViewController
            //   画面遷移後の変数であるrecievedIndexPathにこの画面のrecievedIndexPathを入れる、
                createDestinationPageVC.recievedIndexPath = recievedIndexPath
          }
      }   
} 

このように変更してみると、思ったように動きました!!

まとめ

この仕様は使えるものだと思いますが、意外と実践ベースのソースがないので記してみました。
ファイルの名前の付け方など初心者丸出しの部分は多々ありますが、温かい目で読んでくれると幸いです。
また気づいた点などがあれば投稿したいと思います。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?