

collectionViewを使ったアプリを作っています。  
セルを長押しすると長押しされたセルが削除される機能を実装したいのですが、実機で長押しをするとデバックエリアに`libc++abi.dylib:
terminating with uncaught exception of type NSException`と表示されてしまい動きません。  
調べてみたところ接続の問題のようだったので、collectionViewのoutlet接続をし直したりしたのですが解決しませんでした。  
コードがどこか間違っているのでしょうか・・？  
教えていただけたら嬉しいです。何卒よろしくお願いいたします。

[code]

    import UIKit
    
    class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIGestureRecognizerDelegate {
    
        @IBOutlet weak var collectionView: UICollectionView!
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell : UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "coinCell", for: indexPath)
            cell.backgroundColor = .init(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 0.6)
    
            return cell
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            _ = UINib(nibName: "UICollectionElementKindCell", bundle: nil)
    
            let layout = UICollectionViewFlowLayout()
            layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
            layout.itemSize = CGSize(width: 100,height: 100)
            collectionView.collectionViewLayout = layout
    
            let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: Selector(("cellLongPressed")))
            longPressGestureRecognizer.delegate = self
            longPressGestureRecognizer.allowableMovement = 15
            longPressGestureRecognizer.minimumPressDuration = 0.6
            collectionView.addGestureRecognizer(longPressGestureRecognizer)
        }
    
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 1
        }
    
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int)-> Int {
            return 365
        }
    
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int)-> UIEdgeInsets {
            return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        }
    
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            let horizontalSpace : CGFloat = 500
            let cellSize : CGFloat = self.view.bounds.width / 5 - horizontalSpace
            return CGSize(width: cellSize, height: cellSize)
            // gyou saishou yohaku
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        func cellLongPressed(sender: UILongPressGestureRecognizer){
            let point: CGPoint = sender.location(in: collectionView)
            let indexPath = collectionView.indexPathForItem(at: point)
    
            if sender.state == UIGestureRecognizer.State.began{
                collectionView.deleteItems(at: [indexPath!])
                print("longPressed")
            }
        }
    
    }
    
[/code]


CC BY-SA 4.0
2019-07-09T15:01:36.550
56522
2019-07-10T01:19:04.267
2019-07-10T01:19:04.267
3060
34757
question
swift ios xcode
terminating with uncaught exception of type NSExceptionの解決方法


セルの上で長押しをした時、タイトルに挙げられたメッセージ以外にもっと色々なメッセージがデバッグコンソールに表示されたのではありませんか?

少し長くなりましたが、表題のエラーを解決してもまた次が、と言う状態なので、最後までじっくり読んでいただければと思います。

あなたのコードを手元の実機で実行して、長押しするとこんなメッセージが表示されました。(ちなみにシミュレータでもほとんど同じメッセージが表示されました。)

>
[code]

>     2019-07-10 01:58:03.642096+0900 AppName[11357:2184859]
> -[AppName.ViewController cellLongPressed]: unrecognized selector sent to
> instance 0x10140e6c0
>     2019-07-10 01:58:03.643874+0900 AppName[11357:2184859] *** Terminating
> app due to uncaught exception 'NSInvalidArgumentException', reason:
> '-[AppName.ViewController cellLongPressed]: unrecognized selector sent to
> instance 0x10140e6c0'
>     *** First throw call stack:
>     (0x18796527c 0x186b3f9f8 0x187881ab8 0x1b42caf60 0x18796aac4 0x18796c75c
> 0x1b3ec8868 0x1b3ed0a70 0x1b3ece414 0x1b3ecd940 0x1b3ec1a1c 0x1878f65f8
> 0x1878f1320 0x1878f189c 0x1878f10b0 0x189af179c 0x1b429f978 0x100f24170
> 0x1873b68e0)
>     libc++abi.dylib: terminating with uncaught exception of type NSException
>     (lldb)
>  
[/code]

この出力には大変重要な情報が含まれています。アプリがテスト中に異常終了した場合、原因になる操作をした時に出力されるメッセージには、すべて気を使ってチェックして下さい。また、ご自身で解決できずご質問を書かれる場合には、その全てのメッセージをご質問本文中に含めて下さい。

今回は、目視でもすぐに怪しい部分があちこちに見つかる上に、必ず再現するエラーだったので、原因はすぐにわかりましたが、これらのメッセージがないと解決が困難になる場合も多いです。

一番肝心なところは、ここです。

>
[code]

>     -[AppName.ViewController cellLongPressed]: unrecognized selector sent to
> instance
>  
[/code]

>
> 認識不可能なセレクタがインスタンスに送られた。(クラスはアプリの`ViewController`クラス、セレクタは`cellLongPressed`)

つまり、あなたの`ViewController`には(Objective-
C的な視点で)`cellLongPressed`なんてメソッドがないぞ、と言うことで実行時エラーになっているわけです。これは「接続の問題」で発生することもありますが、そんなものは数多く考えられる原因のうちの一つでしかありません。`unrecognized
selector sent to instance`の部分で検索してみたでしょうか? うまく調べられるようにしましょう。

一番まずいのはこの行です。

[code]

        let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: Selector(("cellLongPressed")))
    
[/code]

Swiftでは、 **`Selector(("文字定数"))`と言う構文は使ってはいけない** と覚えておいて下さい。

太古の時代のSwiftコードを参考にすると **Xcodeがこんな書き方を勧めてくることもあるんですが、はいそうですかと鵜呑みにしないでください**
。そもそも **そんな太古の時代のコードを参考にしないようにしましょう** 。

`func cellLongPressed(sender:
UILongPressGestureRecognizer)`と言うヘッダで宣言されるメソッドはObjective-
C的には、`cellLongPressedWithSender:`と言うセレクタになります。最後のコロンもメソッド名の一部で必須です。

こんな変換、Swiftから入られてObjective-Cの知識のない人間には普通はできないので、「Swift的メソッド名」を「Objective-
C的セレクタ」に変換してくれる構文があります。

### さっきの1行を次のように書き換えて下さい。

[code]

        let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(cellLongPressed))
    
[/code]

`#selector(cellLongPressed)`の部分は`#selector(cellLongPressed(sender:))`とか`#selector(ViewController.cellLongPressed)`とか`#selector(self.cellLongPressed)`とかいろんな書き方ができるんですが、`target:`に`self`を指定する時には、`self.`は要りません。

`#selector`構文を使うと、わかりにくいObjective-Cセレクタへの変換をSwiftがやってくれる上に、もしそんなメソッドがないと
**コンパイル時に** エラーを表示してくれます。

で、上の書き換えを行うと、こんな感じのエラーメッセージがFix Itのサジェスチョンと一緒に表示されます。

>
[code]

>     Argument of '#selector' refers to instance method
> 'cellLongPressed(sender:)' that is not exposed to Objective-C
>  
>     Add '@objc' to expose this instance method to Objective-C
>  
[/code]

### ここではXcodeのおすすめ修正に従って下さい。

さっきのメソッドヘッダーの前に`@objc`が追加されます。

[code]

        @objc func cellLongPressed(sender: UILongPressGestureRecognizer){
    
[/code]

* * *

## しかし、あなたのコードにはまだおかしいところがあります。

それは、こちら。

[code]

        @objc func cellLongPressed(sender: UILongPressGestureRecognizer){
            let point: CGPoint = sender.location(in: collectionView)
            let indexPath = collectionView.indexPathForItem(at: point)
            if sender.state == UIGestureRecognizer.State.began{
    
                collectionView.deleteItems(at: [indexPath!]) //<-
                print("longPressed")
    
            }
        }
    
[/code]

あなたのコードでは`collectionView`に対し、`deleteItems(at:)`を呼んで、「要素が削除されたよ」と伝えているのに、`collectionView(_:numberOfItemsInSection:)`は、ずーっと`365`を返しています。

そこでiOSは、「削除したっつったのに、減ってねーじゃねーか!」と言う意味で、`NSInternalInconsistencyException`なんて実行時エラーを報告してきます。

`deleteItems(at:)`を呼んだら、`collectionView(_:numberOfItemsInSection:)`の数も減るようになるまでは、`collectionView.deleteItems(at:
[indexPath!])`はコメントアウトしておく、なんてことが必要になるでしょう。

* * *

## その他ちょっと

### この行は何のためにあるのでしょう?

[code]

        _ = UINib(nibName: "UICollectionElementKindCell", bundle: nil)
    
[/code]

これが無いと、思った動作にならないなら、他の部分に何かうまく構成できていない部分があります。バッサリ削除して、「他の部分」を修正したほうがいいでしょう。もちろんバッサリ削除してもなんともなかったら、そのまま削除しておきましょう。

### 最新のSwiftでの乱数の求め方

この行:

[code]

            cell.backgroundColor = .init(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 0.6)
    
[/code]

は、現在のSwiftならこんな風に書き換えられます。

[code]

            cell.backgroundColor = .init(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: 0.6)
    
[/code]

文字数的には長くなりますが、`drand()`なんてよくわからないC言語用の関数を使う必要がなくなります。

(`cell.backgroundColor = .init(red: .random(in: 0...1), green: .random(in:
0...1), blue: .random(in: 0...1), alpha: 0.6)`なんて書き方もできますが。)

ただ、`collectionView(_:cellForItemAt:)`の中で毎回乱数を使っていると、スクロールした時や、セルを削除して再描画されるときなど、色がランダムに変わってしまいます。

例えば、  
セルの色を表すインスタンス変数を用意して、

[code]

        var colorArray: [UIColor] = []
    
[/code]

それを`viewDidLoad()`の中で初期化します。

[code]

        override func viewDidLoad() {
            super.viewDidLoad()
    
            //...
    
            for _ in 0..<365 {
                colorArray.append(UIColor(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: 0.6))
            }
        }
    
[/code]

すると`collectionView(_:numberOfItemsInSection:)`をこんな風に書き換えられます。

[code]

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "coinCell", for: indexPath)
    
            cell.backgroundColor = colorArray[indexPath.item] //<-
    
            return cell
        }
    
[/code]

さらに

[code]

        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int)-> Int {
            return colorArray.count //<-
        }
    
        //...
    
        @objc func cellLongPressed(sender: UILongPressGestureRecognizer){
            let point: CGPoint = sender.location(in: collectionView)
            let indexPath = collectionView.indexPathForItem(at: point)
            if sender.state == UIGestureRecognizer.State.began{
    
                colorArray.remove(at: indexPath!.item) //<-
                collectionView.deleteItems(at: [indexPath!])
                print("longPressed")
    
            }
        }
    
[/code]

と言う具合にすると、`deleteItems(at:)`とその他の部分のつじつまが合うようになるので、「長押しで削除」がうまくいくか確かめられるかと思います。


CC BY-SA 4.0
2019-07-09T18:18:57.520
56526
2019-07-09T19:01:29.653
2019-07-09T19:01:29.653
13972
13972
56522
answer