22601


swift初心者です、現在既存のコードを編集し、わからない部分を調べつつswiftの学習をしています。

<https://sites.google.com/a/gclue.jp/swift-docs/>

こちらのサイトの「015 UIViewControllerで画面遷移する」のプログラムをベースに

<http://swift-salaryman.com/nsuserdefaults.php>

こちらのサイトのArrayの項目を参考にし`FirstViewController`で保存した配列データを`SecondViewController`のラベルに表示するというプログラムを作成しようとしたところ、`outputLabel.text
= arr[0][0]` の部分で

> Type 'AnyObject!' has no subscript members

のエラーが出てきました。詳しい方ご教授お願いします。

[code]

    import UIKit
    
    var dictionary : [[String]] = [["0"],["1"]]
    class FirstViewController: UIViewController,UITextFieldDelegate {
    
        var saveField: UITextField!//saveField
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.view.backgroundColor = UIColor.grayColor() // 背景色をGreenに
            // nextボタンの生成
            let nextButton: UIButton = UIButton(frame: CGRectMake(0,0,120,50))
            nextButton.backgroundColor = UIColor.redColor();
            nextButton.layer.masksToBounds = true
            nextButton.setTitle("Next", forState: .Normal)
            nextButton.layer.cornerRadius = 20.0
            nextButton.layer.position = CGPoint(x: self.view.bounds.width/2 , y:self.view.bounds.height-50)
                //ボタンアクション
            nextButton.addTarget(self, action: "onClickMyButton:", forControlEvents: .TouchUpInside)
            //saveボタンの生成
            let saveButton: UIButton = UIButton(frame: CGRectMake(0,0,100,25))
            saveButton.backgroundColor = UIColor.redColor();
            saveButton.layer.masksToBounds = true
            saveButton.setTitle("保存", forState: .Normal)
            saveButton.layer.cornerRadius = 3.0
            saveButton.layer.position = CGPoint(x: (self.view.bounds.width/2)+70 , y:self.view.bounds.height-530)
                //ボタンアクション
            saveButton.addTarget(self, action: "onClickSaveButton:", forControlEvents: .TouchUpInside)
            //saveFieldの作成
            saveField = UITextField(frame: CGRectMake(0,0,250,30))
            saveField.delegate = self        // Delegateの設定
            saveField.borderStyle = UITextBorderStyle.RoundedRect        // 枠を表示する.
            saveField.layer.position = CGPoint(x:self.view.bounds.width/2,y:100)    //位置決め
    
            // Viewに追加
            self.view.addSubview(saveField)
            self.view.addSubview(nextButton)
            self.view.addSubview(saveButton)
        }
    
        //Nextボタン処理
        internal func onClickMyButton(sender: UIButton){
            let mySecondViewController: UIViewController = SecondViewController()        // SecondViewControllerに遷移
            mySecondViewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve        // アニメーション設定
            self.presentViewController(mySecondViewController, animated: true, completion: nil)        // Viewの移動
        }
        //saveボタン処理
        func onClickSaveButton(sender: UIButton){
            dictionary[0][0] = String(saveField.text)   //現在の値をdictionaryに格納
            NSUserDefaults.standardUserDefaults().setObject(dictionary, forKey:"dictionarySaveDate")   //保存処理
            NSUserDefaults.standardUserDefaults().synchronize()    //書いたほうがいいらしい
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    }
    
    //////////////////////////////////    SecondViewController    ////////////////////////////////////////////////////
    
    class SecondViewController: UIViewController,UITextFieldDelegate {
    
        override func viewDidLoad() {
            //保存データの読み込み
            let arr: AnyObject! = NSUserDefaults.standardUserDefaults().arrayForKey("dictionarySaveDate")
            // 背景色を設定.
            self.view.backgroundColor = UIColor.whiteColor()
            // ボタンを作成.
            let backButton: UIButton = UIButton(frame: CGRectMake(0,0,120,50))
            backButton.backgroundColor = UIColor.redColor();
            backButton.layer.masksToBounds = true
            backButton.setTitle("Back", forState: .Normal)
            backButton.layer.cornerRadius = 20.0
            backButton.layer.position = CGPoint(x: self.view.bounds.width/2 , y:self.view.bounds.height-50)
                //ボタンアクション
            backButton.addTarget(self, action: "onClickMyButton:", forControlEvents: .TouchUpInside)
            self.view.addSubview(backButton)
            // ラベル作成
            let outputLabel: UILabel = UILabel(frame: CGRectMake(0,0,200,50))
            outputLabel.backgroundColor = UIColor.orangeColor()
            outputLabel.layer.masksToBounds = true
            outputLabel.layer.cornerRadius = 20.0
    
            outputLabel.text = arr[0][0]    //ERROR   Type 'AnyObject!' has no subscript members
    
            outputLabel.textColor = UIColor.whiteColor()
            outputLabel.textAlignment = NSTextAlignment.Center
            outputLabel.layer.position = CGPoint(x: self.view.bounds.width/2,y: 200)
            self.view.addSubview(outputLabel)
        }
    
    
        internal func onClickMyButton(sender: UIButton){
            let myViewController: UIViewController = FirstViewController()  // 遷移するViewを定義
            myViewController.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal   // アニメーションを設定
            self.presentViewController(myViewController, animated: true, completion: nil)   // Viewの移動
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    }
    
[/code]


CC BY-SA 3.0
2016-02-27T09:35:41.610
22599
2016-02-27T13:16:42.457
2016-02-27T13:16:42.457
10492
14580
question
swift
swiftでの多次元配列のデータ保存、呼び出しについて


動作確認がしにくかったので、FirstViewControllerの方を少し修正させていただきました。

[code]

        //saveボタン処理
        func onClickSaveButton(sender: UIButton){
            saveField.resignFirstResponder() //->他にUITextFieldがなければキーボードが隠れる
            dictionary[0][0] = String(saveField.text)   //現在の値をdictionaryに格納
            NSUserDefaults.standardUserDefaults().setObject(dictionary, forKey:"dictionarySaveDate")   //保存処理
            NSUserDefaults.standardUserDefaults().synchronize()    //書いたほうがいいらしい
        }
    
[/code]

テストの際は外部キーボードを有効にしているのだと思いますが、実際のアプリでは外部キーボードを接続しているユーザはほとんどいません。この辺り「学習」を進めて実アプリを構築される場合までに実アプリでの定番の処理を覚えていってください。

さて **本題**
です。Swiftでデータ型がらみのエラーが出た場合には、エラーが発生している行をより細かく分解していくと、どこが誤りなのかが分かりやすくなります。

[code]

        let innerArr = arr[0]
        let value = innerArr[0]
        outputLabel.text = value //->error: cannot assign value of type 'AnyObject!' to type 'String?'
    
[/code]

どうやらSwiftが本当に報告すべき内容は「`outputLabel.text`のデータ型は`String?`なので、`AnyObject!`型の値は代入できません」と言うことのようです。

Swiftは現在発展途上の言語で、特にデータ型が絡んだエラー診断メッセージについては時折理解不能なことを言うことがあります。(今回のは、英語でバグ報告が書けるなら、Appleかswift.orgにバグ報告を書いてもらった方が良いレベルです。)今のところは慣れてもらって、「何かしらのデータ型にまつわるエラーがありますよ」と言う報告だと思って読むようにしてください。

さて上記のコードで出るエラーは3行目を次のように修正すると解消することができます。

[code]

        outputLabel.text = (value as! String)
    
[/code]

カッコは中途半端にお節介なSwiftの診断メッセージを黙らせるために必要なのですが、これは置いて、あなたのもとの`outputLabel.text =
arr[0][0]`にも`as! String`等で明示的な型変換を記載してやれば、とりあえずエラーは解消できるはずです。

ただし、それはあまり正しい解決方法とは言えません。あなたのSecondViewControllerでは、こんな行でarrを定義しています。

[code]

        let arr: AnyObject! = NSUserDefaults.standardUserDefaults().arrayForKey("dictionarySaveDate")
    
[/code]

あなたのコード全体が正常に動作していれば、`NSUserDefaults.standardUserDefaults().arrayForKey("dictionarySaveDate")`の値は`[[String]]`であることがわかっているのに(arrayForKey(_:)の戻り値の型よりさらに情報の少ない)AnyObject型として`arr`を宣言してしまっています。

ここではやはり`arr`は`[[String]]`型で宣言するべきでしょう。また、もし異なるデータ型であればプログラムのどこかに誤りがあるので、簡単なチェックをした上でおかしい場合は開発者にわかるようなメッセージを出した方が良いでしょう。最新のSwiftにはguard文というのがあるので、そちらの出番になります。  
`let arr: AnyObject! = ...`の1行を次のように書き換えてみてください。

[code]

            guard let arr = NSUserDefaults.standardUserDefaults().arrayForKey("dictionarySaveDate") as? [[String]] else {
                print("Error in dictionarySaveDate")
                return
            }
    
[/code]

これで`arr`は`[[String]]`型として宣言されたことになり、それ以外のデータ型であればメッセージを表示して処理を中断することになります。この変更により`outputLabel.text
= arr[0][0]`の部分は元のあなたのコードのままでエラーが出なくなるはずです。

一般的に、データ型がはっきりとわかっているのに変数のデータ型をAnyObjectとしてしまうと、データ型に起因する謎のエラーに悩まされることが多くなります。できるだけ使用は避けた方が無難でしょう。

なんで次のViewControllerにデータ渡したいだけでNSUserDefaultsなんかつこてるねん?とかなんで配列やのに変数名がdictionaryやねん?とか、突っ込みどころはたくさんあるのですが、「学習」から実アプリを意識したコーディングに移るまでの間にしっかり改善していってください。(学習用のコードは時折、説明したいポイントになることを示すために実アプリではやらないようなコードになっていることもよくあります。お気をつけて。)


CC BY-SA 3.0
2016-02-27T10:45:14.497
22601
2016-02-27T10:45:14.497
13972
22599
answer