質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.62%

ユーザーデータの保存法

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 628

sam3457

score 52

以下のような、プロフィール情報(名前・アイコン)を設定して、それらの情報とともにカメラロールから選んだ画像1枚だけを投稿できるインスタの劣化版パクリアプリを作っています。(プロジェクト名は気にしないでください、、)
イメージ説明

現在はstoryboard下方のプロフィール設定画面(今の所firebaseにプロフィールのデータを送るところまで)とユーザーのモデル定義クラスを作っています。
イメージ説明

プロフィール設定画面のクラス

import UIKit
import Firebase

class EditProfile: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    @IBOutlet var iconImageView: UIImageView!
    @IBOutlet var nameTextField: UITextField!

    var ref: DatabaseReference!



    override func viewDidLoad() {
        super.viewDidLoad()

        nameTextField.delegate = self


    }

    @IBAction func changeIcon(_ sender: UITapGestureRecognizer) {

        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {

            let pickerView = UIImagePickerController()
            pickerView.sourceType = .photoLibrary
            pickerView.delegate = self
            self.present(pickerView, animated: true)

        }
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        let image = info[UIImagePickerControllerOriginalImage] as! UIImage
        iconImageView.image = image
        dismiss(animated: true)

    }

    @IBAction func changeProfile(_ sender: Any) {

        let user = User()
        user.name = nameTextField.text!
        user.icon = iconImageView.image!

        var data: Data = Data()
        if let image = iconImageView.image {
            data = UIImageJPEGRepresentation(image,0.5)! as Data
        }
        let encodedImageData = data.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters) as String

        let myProfile = ["myName": user.name, "myIcon": encodedImageData]
        ref.childByAutoId().setValue(myProfile)

    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        nameTextField.resignFirstResponder()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

ユーザーを定義したクラス

import UIKit

class User: NSObject {

    var name: String = "匿名"
    var icon: UIImage = UIImage(named: "user.png")!

}

今まではUdemyという学習サイトを見ながら、Firebaseなどのライブラリを使い、一応形だけサーバと連携して動くSNSアプリをなんとか作れるレベルだったのですがそのアプリでは、サーバから取って来た投稿をタイムラインに表示するのみで、名前をタップしてそのユーザーの詳しいプロフィールを見たり、そのユーザーの過去の投稿一覧などをみる機能はついていませんでした。
(そのアプリはユーザーモデルや投稿モデルを独自クラスで定義していなく、ばらばらのデータを配列に入れてそれをサーバに飛ばしていました。)

こんな感じです

let databaseRef = Database.database().reference()

        //ユーザー名
        let username = myProfileLabel.text

        //コメント
        let message = commentTexfView.text

        //投稿画像
        var data: NSData = NSData()
        if let image = imageView.image {
            data = UIImageJPEGRepresentation(image,1.0)! as NSData
        }
        let base64String = data.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) as String

        //プロフィール画像
        var data2: NSData = NSData()
        if let image2 = myProfileImageView.image {
            data2 = UIImageJPEGRepresentation(image2,1.0)! as NSData
        }
        let base64String2 = data2.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) as String

        //サーバーに飛ばす箱
        let user: NSDictionary = ["username": username!, "comment": message!, "postImage": base64String, "profileImage": base64String2]
        databaseRef.child("posts").childByAutoId().setValue(user)

・名前をタップしてそのユーザーの詳しいプロフィールを見る機能
・そのユーザーの過去の投稿一覧などをみる機能

をつけるにはまず、ユーザーモデルや投稿モデルを独自クラスで定義してそれを利用することが必要だと講師の方に言われました。

モデル定義について調べながら作ったものが冒頭で説明したものです。

ですが、独自クラスで作った意味あるのかコレ状態になってしまいました。(結局配列で同じように送ってる、、)

上記の二つの機能を追加することを見越したユーザーモデルの定義、またそれをどのような形でfirebaseに送ればいいのでしょうか?
皆さまならどのように書いていますでしょうか?

ご存知の方がいらっしゃいましたらよろしくお願いいたします。

<追記>
変更後

import UIKit

struct User {
    let name: String
    let icon: UIImage
}

extension User {
    // このdictionaryはfirebaseから取得してきたデータ
    init(dictionary: [String: Any]) {
        self.name = dictionary["myName"] as! String
        do {
            let url = dictionary["iconImage"] as! String
            let data =  try Data(contentsOf: url)
            let image = UIImage(data: data)!
            self.icon = image
        } catch {
            // 何かしらのエラー処理
        }
    }
}


間違っているのはわかるのですが、どうしてもたどり着けませんでした、、EditProfile.swiftです。

override func viewDidLoad() {
        super.viewDidLoad()

        nameTextField.delegate = self

        ref.queryLimited(toLast: 1).observe(DataEventType.childAdded, with: { (snapshot) in
            let user = User(name: "userName", icon: "userIcon")
            nameTextField.text = user.name
            iconImageView.image = user.icon

        })


    }


こちらはEditProfile.swiftのプロフィール変更決定ボタンです。キーを変えました。

@IBAction func changeProfile(_ sender: Any) {

        let userName = nameTextField.text

        var data: Data = Data()
        if let image = iconImageView.image {
            data = UIImageJPEGRepresentation(image,0.5)! as Data
        }
        let encodedImageData = data.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters) as String

        let user = ["userName": userName, "userIcon": encodedImageData]
        ref.childByAutoId().setValue(user)

    }

<追記2>
newmtさまにお教え頂いた様に記述したところエラーが出たのですが、修正できなかったので私の知っている、String>UIImageにする処理に直してみました。
イメージ説明

extension User {
    // このdictionaryはfirebaseから取得してきたデータ
    init(dictionary: [String: Any]) {
        self.name = dictionary["myName"] as! String
        do {
            /*
            let url = dictionary["iconImage"] as! String
            let data =  Data(contentsOf: url)
            let image = UIImage(data: data)!
            self.icon = image
            */

            let decodeData = (base64Encoded: dictionary["iconImage"])
            let decodedData = Data(base64Encoded: decodeData as! String, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)
            let decodedIcon = UIImage(data: decodedData! as Data)
            self.icon = decodedIcon!

        } catch {
            // 何かしらのエラー処理
        }
    }
}


ビルドしてeditProfile.swiftの対応する画面のタブをタップしたところ、editProfile.swiftのviewDidLoadでエラーがおきました。
as!周りをアンラップしろ。ということまではわかったのですが、この場合どう修正するのでしょうか?
私の修正のせいかもしれません、、
イメージ説明
調べながらやってはいるのですが、どんどん質問が増えてしまって申し訳ありません、、

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

あくまで個人的な意見として述べさせて頂きます。

firebaseに送る方法としてはson様と同じような形で送ると思います。

独自クラスを使うメリットとしてはデータを参照する場合にあると考えています。
例えば、プロフィール設定画面に初期値としてfirebaseから既存のデータを設定する場合、Dictionaryを使用していますと、

myProfileLabel.text = user["userName"] as? String


という形で設定できますが、

                      // ↓本当はuserName    
myProfileLabel.text = user["userNema"] as? String


といった間違いをした場合にアプリ実行中に落ちる可能性があります。

これを独自クラスを使用することで

myProfileLabel.text = user.username


といった形で使用でき、仮に変数名が間違っている場合はビルド時にエラーとなるため、事前にエラーを防ぐことができます。またすでに型がわかっているため、キャストをする必要がありません。
クラス内のデータの数が少ない場合は、まだ良いかもしれませんが、数が多くなっていくに連れて、キー名を間違える可能性も高くなります。

個人的にはUserクラスは下記のようにstructで定義してfirebaseから取得したデータで初期化できるようにするかなと思いました。structとclassの違いは検索すればすぐに出てくると思います。

struct User {
    let name: String
    let icon: UIImage
}

extension User {
    // このdictionaryはfirebaseから取得してきたデータ
    init(dictionary: [String: Any]) {
        self.username = dictionary["userName"] as! String

        // dictionaryに対するエラーチェックは省略しています
        do {
            let urlString = dictionary["userIcon"] as! String
            let decodedData = Data(base64Encoded: urlString, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)
            let decodedIcon = UIImage(data: decodedData!)
            self.icon = decodedIcon!

        } catch {
       // 何かしらのエラー処理をするまたは
            // ※ダミーの画像などで初期化が必要
            self.icon = UIImage()
        }
    }
}

一意見ですので、ぜひ他の方のご意見も参考してみてください。

【追記】

Userインスタンスを作成するのは下記のような形でできませんでしょうか?

        ref.queryLimited(toLast: 1).observe(DataEventType.value, with: { (snapshot) in
       
            for item in snapshot.children {
                let snap = item as! DataSnapshot
                let dict = snap.value as! [String: Any]
           // データの中身を確認
                print(dict)

                let user = User(dictionary: dict)

                // メインスレッドで処理をする
                DispatchQueue.main.async { [weak self] in             
                    self?.nameTextField.text = user.name
                    self?.iconImageView.image = user.icon
                }
            }
        })

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/19 20:30

    ありがとうございます。
    修正して同じ操作を試して見たのですがやはりタイムラグの大きさは変わっていない様でした。。

    キャンセル

  • 2018/03/20 17:30

    そうですか。スレッドの影響かと考えましたが、違ったようです。失礼しました。
    son様のおっしゃるように画像はキャッシュや端末内に保存して再利用するとよいかと思います。

    キャンセル

  • 2018/03/20 21:13 編集

    とんでもないです!スレッドについては初めて触れるものだったのでこちらもよく調べて見ます。
    とりあえずはuserDefaultあたりにencodeした画像と名前を保存しておいて、それがなかった場合にのみfirebaseからプロフィールを取ってくる様にしたいと思います。キャッシュもじっくりと調べて見ることにします。
    ありがとうございます!!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る