72307


以下のコード( **TestViewController.swift** )で、UITableViewCellにUISwitchを埋め込みました。

**TestViewController.swift**

[code]

    import UIKit
    import Foundation
    
    class TestViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
    {
        var sections = ["section1","section2","section2.5","section3"]
        var values = [["value1-1","value1-2","value1-3"],["value2-1","value2-2","value2-3"],["value2.5-1","value2.5-2","value2.5-3"],["value3-1","value3-2","value3-3","value3-4","value3-5","value3-6","value3-7"]]
        
        
        @IBOutlet weak var tableView: UITableView!
        
        override func viewDidLoad()
        {
            super.viewDidLoad()
        }
        
        @objc func switchChanged(_ sender : UISwitch!)
        {
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
        {
            return values[section].count
        }
    
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
        {
            let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "testTableViewCells", for: indexPath)
            
            if values[indexPath.section][indexPath.row] == "value1-1"
            {
                print("1-1")
                 let switchView = UISwitch(frame: .zero)
                 switchView.setOn(true, animated: true)
                 switchView.tag = 0
                 switchView.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
                 cell.accessoryView = switchView
            }
            else
            {
                cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
            }
            
            cell.textLabel!.text = values[indexPath.section][indexPath.row]
            return cell;
        }
        
        func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
            let label : UILabel = UILabel()
            
            label.text = sections[section]
            label.alpha = 0.5
            
            return label
        }
        
        //高さを指定
        func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
            return 40
        }
    
        //cellがタップされた時の処理
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
        {
            print(values[indexPath.section][indexPath.row])
        }
        
        func numberOfSections(in tableView: UITableView) -> Int {
            
            return sections.count
        }
        
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    }
    
[/code]

このコードでは、value1-1のCellのみでUISwitchを表示するコードになっているのですが、TableViewを下にスクロールしていくと、value3-6や、value3-7のCellなど、意図しない位置にもUISwitchが表示されてしまいます。

[![画像の説明をここに入力](https://i.stack.imgur.com/nIb92.gif)](https://i.stack.imgur.com/nIb92.gif)

UITableView,UITableViewCellはStoryboard上で配置しており、以下のような構造になっています。  
UIViewにぴったりくっつけず、上部に余白を残してUITableViewを配置しています。  
(UITableViewの上に、UINavigationBarを配置する都合などでこのようにしています。)

[![画像の説明をここに入力](https://i.stack.imgur.com/bGQvl.png)](https://i.stack.imgur.com/bGQvl.png)

上部に余白を残さずにUITableViewを配置した場合、同様の問題は起きないので、余白がこの問題を引き起こしていると考えています。この問題を解決する方法はありますか？


CC BY-SA 4.0
2020-12-01T00:22:48.767
72302
2020-12-01T02:14:33.210
36446
question
swift
UITableViewを上部まで埋めないと、UISwitchが正しく表示されない


> 上部に余白を残さずにUITableViewを配置した場合、同様の問題は起きないので、余白がこの問題を引き起こしていると考えています。

問題が起きやすくなる条件は見つけられたようですが、それがこの問題の直接の原因とは限りません。

`tableView(_:cellForRowAt:)`を以下のように書き換えて、同じ問題が発生するかどうか確かめてみてください。

[code]

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "testTableViewCells", for: indexPath)
            
            if values[indexPath.section][indexPath.row] == "value1-1" {
                print("1-1")
                let switchView = UISwitch(frame: .zero)
                switchView.isOn = true
                switchView.tag = 0
                switchView.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
                cell.accessoryView = switchView
                cell.accessoryType = .none //<-
            } else {
                cell.accessoryView = nil //<-
                cell.accessoryType = .disclosureIndicator
            }
            
            cell.textLabel!.text = values[indexPath.section][indexPath.row]
            return cell
        }
    
[/code]

(細かいところで趣味に合わない書き方を修正してありますが、`<-`を付けたところ以外は本質的なものではありません。)

根本的な問題点は、あなたの`tableView(_:cellForRowAt:)`がセルの再利用を考慮した実装になっていないことにあります。

一度`UISwitch`を置いたセルがスクロール等によって、再利用された場合に、あなたの`tableView(_:cellForRowAt:)`はその`UISwitch`を消すようなことはしていませんから、予測不可能なある状況の場合には、それがそのまま表示されることになります。

「余白」はセルの再利用が発生しやすくなる条件であり、根本的な原因ではありません。

* * *

iOSにおいて、viewの生成は比較的重い処理なので、`tableView(_:cellForRowAt:)`の中で毎回動的にview(今の場合は`UISwitch`)を生成すると言うプログラミングは推奨されません。

見かけの違う2種類のセルを使うなら、Reuse Identifierを変えて管理することにより、再利用による問題も発生しにくくなり、応答性も良くなります。

[code]

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            
            let cell: UITableViewCell
            if values[indexPath.section][indexPath.row] == "value1-1" {
                print("1-1")
                //`"value1-1"`の場合は、異なるReuse Identifierを使用する
                //storyboardでこのIdentifier用のセルを別に準備しておく
                cell = tableView.dequeueReusableCell(withIdentifier: "switchTableViewCells", for: indexPath)
                //...
            } else {
                cell = tableView.dequeueReusableCell(withIdentifier: "testTableViewCells", for: indexPath)
                //...
            }
            
            cell.textLabel!.text = values[indexPath.section][indexPath.row]
            return cell
        }
    
[/code]


CC BY-SA 4.0
2020-12-01T02:14:33.210
72307
2020-12-01T02:14:33.210
13972
72302
answer