29060


SecondViewControllerで配列にUIImageを保存したいです。  
まず、SecondViewControllerで写真を配列[imageFile]に保存します。

[code]

    import UIKit
        var imageFile = [UIImage]()
        class SecondViewController: UIViewController,UINavigationControllerDelegate,UIImagePickerControllerDelegate {
    
            override func viewDidLoad() {
                super.viewDidLoad()
    
                if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.PhotoLibrary){
                    let pho = UIImagePickerController()
                    pho.delegate = self
                    pho.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
                    self.presentViewController(pho, animated: true, completion: nil)
                }
            }
            var original:UIImage!
    
            @IBOutlet weak var imageView: UIImageView!
    
        func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
                if info[UIImagePickerControllerOriginalImage] != nil{
                    original = info[UIImagePickerControllerOriginalImage] as? UIImage
                    imageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
                }
                picker.dismissViewControllerAnimated(true, completion: nil)
            }
            override func didReceiveMemoryWarning() {
                super.didReceiveMemoryWarning()
                // Dispose of any resources that can be recreated.
            }
    
    
            @IBAction func save(sender: UIButton) {
                imageFile.append(original)
    
                NSUserDefaults.standardUserDefaults().setObject(imageFile, forKey: "Key")
            }
    
        }
    
[/code]

次にFirstViewControllerで保存したものを受け取りたいのですが  
SecondViewControllerのsaveボタンを押したらエラーが出ます。  
他のプログラムでも同じことをしましたができませんでした。  
文字の配列では、できました。  
NSUserDefaultsではUIImageの配列は扱えないのでしょうか？

[code]

    import UIKit
    
            class FirstViewController: UIViewController {
    
                override func viewDidLoad() {
                    super.viewDidLoad()
                    // Do any additional setup after loading the view, typically from a nib.
                    if NSUserDefaults.standardUserDefaults().arrayForKey("Key") != nil{
    
                        imageFile = NSUserDefaults.standardUserDefaults().arrayForKey("Key") as! [UIImage]
    
                    }
    
                }
    
                override func didReceiveMemoryWarning() {
                    super.didReceiveMemoryWarning()
                    // Dispose of any resources that can be recreated.
                }
                @IBOutlet weak var label: UILabel!
                @IBAction func load(sender: UIButton) {
                label.text! = String(imageFile.count)
                }
    
            }
    
[/code]


CC BY-SA 3.0
2016-09-22T07:08:36.490
29050
2016-09-22T12:52:11.967
18539
question
swift
NSUserDefaultsにUIImageの配列を保存できません


_(調子に乗ってまた長文になってしまいました。後半のコード例の付近まではさっと読み飛ばして、後で興味と必要に応じて読み直してください。)_

* * *

まず、一番簡単なのは、グローバル変数を使う方法でしょう。あなたの`SecondViewController`のclass定義の前に、`var
imageFile =
[UIImage]()`と言う変数宣言がありますが、これはどのclassにも所属していないので、グローバル変数と通称されるものになります。(Swiftの場合には、「トップレベルプロパティ」なんて言い方もありますが、「トップレベル」と言うのは、どのクラスにも所属していないことを表していると思ってください。)

グローバル変数は`private`などの修飾がない限り、同じプロジェクト内のどのファイルのどこからでも(別の同名変数に隠されていない限り)アクセスできます。また、インスタンスプロパティなどと違い、そのプロパティを現在持っているインスタンスはどれか、なんてことに気を使う必要もありません。

`SecondViewController`からアクセスしている`imageFile`は、このグローバル変数のようですから、`FirstViewController`からもそいつにアクセスしてやるだけで良いということになります。現在の`FirstViewController`内の`imageFile`もそのグローバル変数を参照しているだけのようですので、何も付け足す必要はないですね。

`SecondViewController`,
`FirstViewController`の両方から、`NSUserDefaults`が出てくる部分をそのまま削除してやってください。`FirstViewController`に戻って、`load(_:)`に結びつけたボタンを押せば、更新内容が反映されているがわかると思います。

* * *

私的には練習用やサンプルコードと割り切った「絶対に将来大規模化しないアプリ」でグローバル変数を使うのは有りなのですが、やはりアプリをいじっていると「やはり端末に保存できるようにしたい」「クラウドにも保存できると嬉しい」「サムネイルも表示できるようにしたい」「すぐメモリ落ちするのでメモリを節約したい」…と言ったいろいろな要求が出てくるものです。

グローバル変数は、そのお手軽さゆえ、放置するとあちらこちらで無制限に使われてしまい、いろいろな要求に対応する場合の修正点がどんどん膨らんでいってしまいます。

今回のように画面間の遷移に限ったデータの受け渡しの場合には、セグエやdelegateパターンなどを使えば解決できることも多いのですが、今回は「溜め込んだ画像データがこのアプリの根幹」であると仮定して、専用のモデルマネージャクラスを設ける例を挙げておきます。

まずは、こんな内容の`MyImageManager`クラスを作成します。

[code]

    import UIKit
    
    class MyImageManager {
        //UIImageの配列という特定のデータ表現には直接触れないようにすると、将来拡張しやすくなる
        private var imageFile: [UIImage] = []
        //いわゆる「シングルトン」のSwiftでの実現方法
        static let shared = MyImageManager()
    
        func addImage(image: UIImage) {
            self.imageFile.append(image)
        }
    
        var imageCount: Int {
            return imageFile.count
        }
    
        //その他必要に応じてメソッドを増やしていく
        //...
    }
    
[/code]

メソッドやなんかは必要に応じて増やしていけば良いんですが、その時に中身の`imageFile`と言ったものには、できるだけ直接触われないようにしておくのが、将来拡張しやすくするためのコツです。

`SecondViewController`の方では、`save(_:)`メソッドをこんな感じに修正します。

[code]

    @IBAction func save(sender: UIButton) {
        if let original = original {
            //シングルトンの取得
            let manager = MyImageManager.shared
            //画像の追加は直接配列にアクセスさせずにメソッドを通じて行う
            manager.addImage(original)
        }
    }
    
[/code]

次に`FirstViewController`の方では、`load(_:)`メソッドをこんな風に書くことになります。

[code]

    @IBAction func load(sender: UIButton) {
        //シングルトンの取得
        let manager = MyImageManager.shared
        //画像に関する情報の取得も、メソッドかプロパティを通じて行う
        label.text! = String(manager.imageCount)
    }
    
[/code]

実用アプリにするには、`MyImageManager`の機能はまだまだ足りないでしょうが、雰囲気だけは掴んでください。例えば「やっぱり端末にデータを保存できるようにしたい」と思ったら、`saveInDevice()`なんてメソッドを追加することになるでしょうし、「クラウドにも」と思ったら、`saveInCloud()`メソッドも作ることになるかもしれません。

実際のアプリの機能拡張はそう綺麗にはいかないものですが、無制限にグローバル変数を使うよりはすっきりさせることができるはずです。お試しください。


CC BY-SA 3.0
2016-09-22T12:52:11.967
29060
2016-09-22T12:52:11.967
13972
29050
answer