

Swiftにてカスタムセル 二つ それぞれちがうVC から追加したいのですが

大元を Tab - Navi - List ( UIVC ) ほかの二つを Post Fol とします どちらも UIVC です

そのさい カスタムセル を表示する List にはどのように書けばよろしいでしょうか ？

[code]

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return mainArray.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let postcell = tableView.dequeueReusableCell(withIdentifier: "postcell")!
            postcell.textLabel?.text = mainArray[indexPath.row]
            return postcell
        } 
    
[/code]

だと ひとつしか 登録できない とおもうのですが

**追記**

大元のVC

[code]

    class List: UIViewController, UITableViewDataSource, UITableViewDelegate {
        
        var mainArray: [String] = []
        let initArray: [String] = []
        
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.dataSource = self
            tableView.delegate = self
            
            // Do any additional setup after  the view.
        }
        
        @IBOutlet weak var tableView: UITableView!
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return mainArray.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let postcell = tableView.dequeueReusableCell(withIdentifier: "postcell")!
            postcell.textLabel?.text = mainArray[indexPath.row]
            return postcell
        }
        
        func tableView(_ tableView: UITableView, didSelectRowAt className: UITableViewCell) {
            
            switch className {
            case is PostCell:
                
                guard let viewControlelr = storyboard?.instantiateViewController(withIdentifier: "Post") as? Post else {
                    return
                }; navigationController?.pushViewController(viewControlelr, animated: true)
            
                
            case is FolderCell:
                
                guard let list = storyboard?.instantiateViewController(withIdentifier: "List") as? List else {
                    return
                }; navigationController?.pushViewController(list, animated: true)
                
            default:
                return
            }
            
        }
    
    }
    
[/code]

投稿するVC Post

[code]

    class Post: UIViewController {
    
        var postString: String = ""
        
        @IBOutlet weak var postTextField: UITextField!
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Do any additional setup after loading the view.
        }
        
        @IBAction func postBack(_ sender: Any) {
        self.navigationController?.popToRootViewController(animated: true)
            
            postTextField.text = postString
            
            guard let list = tabBarController?.viewControllers?[0] as? List else {
                return
            }
            
            let postString = postTextField.text ?? ""
            
            list.tableView?.beginUpdates()
            list.tableView?.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
            list.tableView?.endUpdates()
        } 
    
    } 
    
[/code]

Folder用のセルの追加のVC Fol

まだコードはないのですが Post とほぼ同じで 追加するカスタムセル が FolderCell なだけです

**追記**

List に protcol を追加すると

[code]

    Protocol 'ListEntryDelegate' cannot be nested inside another declaration
    
[/code]

と、extention では

[code]

    Declaration is only valid at file scope 
    
    Use of undeclared type 'ListEntryDelegate' 
    
[/code]

というエラーが出ます

なにかちがうのでしょうか ？

追記 1

[code]

    struct ListEntry {
        enum CellType: String {
            case post   = "postcell"
            case folder = "foldercell"
        }
        /// 種類
        var cellType: CellType
        
        /// テキスト
        var text: String
        
        var array: [String]
        
        // 一つのセルに表示する情報が増えればプロパティを増やす
        //...
    }
    
[/code]

に Post また Fol から

[code]

    listView.mainArray.insert(folString, at: 0) 
    
[/code]

しようとすると

[code]

    Cannot convert value of type 'String' to expected argument type 'ListEntry' 
    
[/code]

が出ます

**追記 2**

PostCell をタップしたときはPostにpostStringを入れて
FolderCellをタップしたときはListのmainArrayにFolCellの配列folArrayを入れてNavigation
Controllerで遷移したいのです

**追記**

Post でButtonを押した場合は PostCell を追加 Fol でButtonを押した場合は FolCell というのは Post
のInsartになにを付け足せばいいのでしょうか ？


CC BY-SA 4.0
2020-06-30T08:53:55.330
68142
2020-08-27T12:46:46.670
2020-08-27T12:46:46.670
32986
39481
question
swift
Swift カスタムセル 二つ


更新後のご質問内容を読んで、以下のように理解しました。

  * 2種類のカスタムセルを表示したい`UITableView`は`List`(ViewController)に一つだけ
  * その`UITableView`では、セクション0だけを使用する
  * そのセクション0内では、post用のセル、folder用のセルが混在して表示される

何か認識間違いがあれば、早めにお知らせください。

(動作に支障があるわけではないんですが、viewControllerに`List`なんて名前をつけるのは、あまりiOSアプリにはないのでコードが読みづらいですね。`ListViewController`か、せめて`ListVC`くらいにしておいた方が良いと思います。作りかけのアプリのクラス名を変えるといろいろ本質的でないところで苦労する場合があるため、以下では`List`(や`Post`)のままでいきます。)

* * *

さて、一つの`UITableView`に「2種類のカスタムセルを表示したい」なんてことをするとなれば、「どこかにセルの種類を区別するための情報が必要」と言うことになります。

`mainArray`とは別の配列を用意してそちらに情報を入れると言う方法は、アプリが複雑になるうちにどんどん処理が煩雑になっていずれ破綻するのでやめておきます。

「一つのセル」に対応する情報は、「一つの型」で表すようにしましょう。

### ListEntry.swift

[code]

    import Foundation
    
    struct ListEntry {
        enum CellType: String {
            case post   = "postcell"
            case folder = "foldercell"
        }
        /// 種類
        var cellType: CellType
        
        /// テキスト
        var text: String
        
        // 一つのセルに表示する情報が増えればプロパティを増やす
        //...
    }
    
[/code]

`List`の`mainArray`は、`String`の配列ではなく、この`ListEntry`の配列にします。

[code]

    class List: UIViewController, UITableViewDataSource, UITableViewDelegate {
        
        var mainArray: [ListEntry] = []
        //let initArray: [String] = []
        
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.dataSource = self
            tableView.delegate = self
            
            // Do any additional setup after  the view.
            //以下はstoryboardで設定しているなら不要
            tableView.register(PostCell.self, forCellReuseIdentifier: ListEntry.CellType.post.rawValue)
            tableView.register(FolderCell.self, forCellReuseIdentifier: ListEntry.CellType.folder.rawValue)
        }
    
        //...
    }    
    
[/code]

`register(_:forCellReuseIdentifier:)`の呼び出しは、storyboardでやっているのなら不要ですが、やっていないなら、セルの種類全てについて必ず呼び出しておいて下さい。

* * *

さて、実際にセルの種類によって処理を分けないといけないところは、`ListEntry`の`cellType`プロパティの値をもとに場合分けしてやります。

例えば、`tableView(_:cellForRowAt:)`はこんな感じにしないといけないでしょう。

[code]

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let listEntry = mainArray[indexPath.row]
            switch listEntry.cellType {
            case .post:
                let postcell = tableView.dequeueReusableCell(withIdentifier: listEntry.cellType.rawValue, for: indexPath) as! PostCell
                //実際には`textLabel`でなく、`PostCell`の専用プロパティを使用する必要があると思われる
                postcell.textLabel?.text = listEntry.text
                return postcell
            case .folder:
                let foldercell = tableView.dequeueReusableCell(withIdentifier: listEntry.cellType.rawValue, for: indexPath) as! FolderCell
                //実際には`textLabel`でなく、`FolderCell`の専用プロパティを使用する必要があると思われる
                foldercell.textLabel?.text = listEntry.text
                return foldercell
            }
        }
    
[/code]

(ちなみに、危険とされている`as!`を使っているのは、「設定間違えてたらクラッシュしてくれ」と言う意図でわざとです。)

* * *

さて、`tableView(_:didSelectRowAt:)`の方ですが、「種類による場合わけ」が間違った形で書かれているので、大幅に書き直してもらわないと予測不能の障害が起こります。

どうせ大幅に書き換えるならと言うことで、`Post`(ViewController)内で`List`にアクセスしている部分を書き換えておきます。現在のコードですと、どのタブにどのVCを表示するのか、と言うデザイン上の変更があると、コードまで書き換えないといけなくなります。ある程度は仕方ないのですが、できるだけコードとデザインの分離をする、と言うのが良い設計と言うことになっています。

今のところ`Post`でやっているのは要素の追加だけのようなので、次のようなプロトコルを用意して、`List`クラスにそのプロトコルを実装させておきます。

(実際には「要素の編集」も必要な気がしますが、その辺り「2種類のセルを使い分ける」と言う主題から離れすぎるので、この回答ではこれ以上深入りしないことにします。)

(`List`と同じファイルに追加すること想定。)

[code]

    protocol ListEntryDelegate: class {
        func addEntry(_ entry: ListEntry, at index: Int)
    }
    
    extension List: ListEntryDelegate {
        func addEntry(_ entry: ListEntry, at index: Int) {
            mainArray.insert(entry, at: index)
            tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
        }
    }
    
[/code]

上記のデリゲートを使用する前提の`Post`は次のような感じになるでしょう。

[code]

    class Post: UIViewController {
        
        weak var delegate: ListEntryDelegate? //<- 追加
        
        var postString: String = ""
        
        @IBOutlet weak var postTextField: UITextField!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // Do any additional setup after loading the view.
        }
        
        @IBAction func postBack(_ sender: Any) {
            self.navigationController?.popToRootViewController(animated: true)
            
            let postString = postTextField.text ?? ""
            
            //↓自前で`List`のメソッドを呼び出したりせず、デリゲートのメソッドを呼び出す
            let newEntry = ListEntry(cellType: .post, text: postString)
            delegate?.addEntry(newEntry, at: 0) //<- いつでも先頭に追加で良い?
        }
    }
    
[/code]

さて、`Fol`(ViewController)側(VCのクラス名なんて、そうなんども書く必要があるわけでもないので、もう少しわかりやすい名前にした方が良いと思います)にも同様の変更を施すと、先ほどの`List`クラスの`tableView(_:didSelectRowAt:)`は次のような感じになるでしょう。

[code]

        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            let listEntry = mainArray[indexPath.row]
            switch listEntry.cellType {
            case .post:
                guard let postVC = storyboard?.instantiateViewController(withIdentifier: "Post") as? Post else {
                    return
                }
                postVC.delegate = self
                postVC.postString = listEntry.text
                //その他`postVC`に伝えるべき情報があればここで設定
                //postVC.〜 = 〜
                navigationController?.pushViewController(postVC, animated: true)
            case .folder:
                guard let folderVC = storyboard?.instantiateViewController(withIdentifier: "Folder") as? Fol else {
                    return
                }
                folderVC.delegate = self
                //その他`folderVC`に伝えるべき情報があればここで設定
                //folderVC.〜 = 〜
                navigationController?.pushViewController(folderVC, animated: true)
            }
        }
    
[/code]

`tableView(_:didSelectRowAt:)`の`didSelectRowAt:`に渡されるのは`IndexPath`型の値です。それを`UITableViewCell`型で宣言すると、実行時に予測不可能な障害が発生する可能性があります。

(実際にはSwiftコンパイラが「これは`UITableViewDelegate`の`tableView(_:didSelectRowAt:)`じゃない！」と判断して、呼び出しさえされないはずですが。)

* * *

変更が多岐にわたって少しみにくいかと思いますが、どのクラスのどの部分を修正するのかをよく読み取ってお試しいただければと思います。

なにぶん読みにくくなってしまったので、わからない点やうまくいかない点など出てくると思いますが、

  * 「2種類のカスタムセルを表示したい」→この質問へのコメント
  * それ以外の事柄 →新規の質問を立てる

と言った使い分けをしてもらえると、後からこのスレを読まれる方がこれ以上混乱せずに済むかと思います。

* * *

### ご質問の「追記」について → `List`クラスのまとめ。

説明の都合を優先して、コードをバラバラに小出しにしてしまったので、混乱させてしまったようです。`List`クラスを記載するファイル(List.swift
?)に上記のすべての修正を適用すると、以下のような感じになります。

[code]

    import UIKit
    
    class List: UIViewController, UITableViewDataSource, UITableViewDelegate {
        
        var mainArray: [ListEntry] = []
        //let initArray: [String] = []
        
        override func viewDidLoad() {
            super.viewDidLoad()
            tableView.dataSource = self
            tableView.delegate = self
            
            // Do any additional setup after  the view.
            //以下はstoryboardで設定しているなら不要
            tableView.register(PostCell.self, forCellReuseIdentifier: ListEntry.CellType.post.rawValue)
            tableView.register(FolderCell.self, forCellReuseIdentifier: ListEntry.CellType.folder.rawValue)
        }
        
        @IBOutlet weak var tableView: UITableView!
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return mainArray.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let listEntry = mainArray[indexPath.row]
            switch listEntry.cellType {
            case .post:
                let postcell = tableView.dequeueReusableCell(withIdentifier: listEntry.cellType.rawValue, for: indexPath) as! PostCell
                //実際には`textLabel`でなく、`PostCell`の専用プロパティを使用する必要があると思われる
                postcell.textLabel?.text = listEntry.text
                return postcell
            case .folder:
                let foldercell = tableView.dequeueReusableCell(withIdentifier: listEntry.cellType.rawValue, for: indexPath) as! FolderCell
                //実際には`textLabel`でなく、`FolderCell`の専用プロパティを使用する必要があると思われる
                foldercell.textLabel?.text = listEntry.text
                return foldercell
            }
        }
        
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            let listEntry = mainArray[indexPath.row]
            switch listEntry.cellType {
            case .post:
                guard let postVC = storyboard?.instantiateViewController(withIdentifier: "Post") as? Post else {
                    return
                }
                postVC.delegate = self
                postVC.postString = listEntry.text
                //その他`postVC`に伝えるべき情報があればここで設定
                //postVC.〜 = 〜
                navigationController?.pushViewController(postVC, animated: true)
            case .folder:
                guard let folderVC = storyboard?.instantiateViewController(withIdentifier: "Folder") as? Fol else {
                    return
                }
                folderVC.delegate = self
                //その他`folderVC`に伝えるべき情報があればここで設定
                //folderVC.〜 = 〜
                navigationController?.pushViewController(folderVC, animated: true)
            }
        }
    } //<- ここで`List`のクラス定義は一旦終わっている
    
    protocol ListEntryDelegate: class {
        func addEntry(_ entry: ListEntry, at index: Int)
    }
    
    extension List: ListEntryDelegate {
        func addEntry(_ entry: ListEntry, at index: Int) {
            mainArray.insert(entry, at: index)
            tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
        }
    }
    
[/code]

`ListEntryDelegate`や`ListEntryDelegate`を記載する場所が、やってみたのと違っているのではないでしょうか?
(わかりにくくてすいませんでした。)この内容で再度お試しください。


CC BY-SA 4.0
2020-06-30T13:13:59.117
68152
2020-07-01T14:22:33.397
2020-07-01T14:22:33.397
13972
13972
68142
answer