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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1307閲覧

ユーザーデータの保存法

sam3457

総合スコア52

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2018/03/17 12:34

編集2018/03/18 10:39

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

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

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

swift

1import UIKit 2import Firebase 3 4class EditProfile: UIViewController, UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 5 6 @IBOutlet var iconImageView: UIImageView! 7 @IBOutlet var nameTextField: UITextField! 8 9 var ref: DatabaseReference! 10 11 12 13 override func viewDidLoad() { 14 super.viewDidLoad() 15 16 nameTextField.delegate = self 17 18 19 } 20 21 @IBAction func changeIcon(_ sender: UITapGestureRecognizer) { 22 23 if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) { 24 25 let pickerView = UIImagePickerController() 26 pickerView.sourceType = .photoLibrary 27 pickerView.delegate = self 28 self.present(pickerView, animated: true) 29 30 } 31 } 32 33 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 34 35 let image = info[UIImagePickerControllerOriginalImage] as! UIImage 36 iconImageView.image = image 37 dismiss(animated: true) 38 39 } 40 41 @IBAction func changeProfile(_ sender: Any) { 42 43 let user = User() 44 user.name = nameTextField.text! 45 user.icon = iconImageView.image! 46 47 var data: Data = Data() 48 if let image = iconImageView.image { 49 data = UIImageJPEGRepresentation(image,0.5)! as Data 50 } 51 let encodedImageData = data.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters) as String 52 53 let myProfile = ["myName": user.name, "myIcon": encodedImageData] 54 ref.childByAutoId().setValue(myProfile) 55 56 } 57 58 func textFieldShouldReturn(_ textField: UITextField) -> Bool { 59 textField.resignFirstResponder() 60 return true 61 } 62 63 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { 64 nameTextField.resignFirstResponder() 65 } 66 67 override func didReceiveMemoryWarning() { 68 super.didReceiveMemoryWarning() 69 // Dispose of any resources that can be recreated. 70 } 71 72}

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

swift

1import UIKit 2 3class User: NSObject { 4 5 var name: String = "匿名" 6 var icon: UIImage = UIImage(named: "user.png")! 7 8}

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

こんな感じです

swift

1let databaseRef = Database.database().reference() 2 3 //ユーザー名 4 let username = myProfileLabel.text 5 6 //コメント 7 let message = commentTexfView.text 8 9 //投稿画像 10 var data: NSData = NSData() 11 if let image = imageView.image { 12 data = UIImageJPEGRepresentation(image,1.0)! as NSData 13 } 14 let base64String = data.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) as String 15 16 //プロフィール画像 17 var data2: NSData = NSData() 18 if let image2 = myProfileImageView.image { 19 data2 = UIImageJPEGRepresentation(image2,1.0)! as NSData 20 } 21 let base64String2 = data2.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) as String 22 23 //サーバーに飛ばす箱 24 let user: NSDictionary = ["username": username!, "comment": message!, "postImage": base64String, "profileImage": base64String2] 25 databaseRef.child("posts").childByAutoId().setValue(user)

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

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

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

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

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

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

<追記>
変更後

swift

1import UIKit 2 3struct User { 4 let name: String 5 let icon: UIImage 6} 7 8extension User { 9 // このdictionaryはfirebaseから取得してきたデータ 10 init(dictionary: [String: Any]) { 11 self.name = dictionary["myName"] as! String 12 do { 13 let url = dictionary["iconImage"] as! String 14 let data = try Data(contentsOf: url) 15 let image = UIImage(data: data)! 16 self.icon = image 17 } catch { 18 // 何かしらのエラー処理 19 } 20 } 21} 22

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

swift

1override func viewDidLoad() { 2 super.viewDidLoad() 3 4 nameTextField.delegate = self 5 6 ref.queryLimited(toLast: 1).observe(DataEventType.childAdded, with: { (snapshot) in 7 let user = User(name: "userName", icon: "userIcon") 8 nameTextField.text = user.name 9 iconImageView.image = user.icon 10 11 }) 12 13 14 } 15 16

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

swift

1@IBAction func changeProfile(_ sender: Any) { 2 3 let userName = nameTextField.text 4 5 var data: Data = Data() 6 if let image = iconImageView.image { 7 data = UIImageJPEGRepresentation(image,0.5)! as Data 8 } 9 let encodedImageData = data.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters) as String 10 11 let user = ["userName": userName, "userIcon": encodedImageData] 12 ref.childByAutoId().setValue(user) 13 14 }

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

swift

1extension User { 2 // このdictionaryはfirebaseから取得してきたデータ 3 init(dictionary: [String: Any]) { 4 self.name = dictionary["myName"] as! String 5 do { 6 /* 7 let url = dictionary["iconImage"] as! String 8 let data = Data(contentsOf: url) 9 let image = UIImage(data: data)! 10 self.icon = image 11 */ 12 13 let decodeData = (base64Encoded: dictionary["iconImage"]) 14 let decodedData = Data(base64Encoded: decodeData as! String, options: Data.Base64DecodingOptions.ignoreUnknownCharacters) 15 let decodedIcon = UIImage(data: decodedData! as Data) 16 self.icon = decodedIcon! 17 18 } catch { 19 // 何かしらのエラー処理 20 } 21 } 22} 23

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

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

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/17 14:14

編集2018/03/19 10:34
newmt

総合スコア1277

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sam3457

2018/03/18 05:39 編集

回答ありがとうございます!いつも本当にお世話になっております。 お教え頂いた方法で実際に書いて見たのですが、イニシャライザを使ってインスタンスを作る時の引数の記述のしかたがどうしてもわかりませんでした、、どの様に記述すれば良いのでしょうか、、? 変更した部分を質問に追記しておきますので、見ていただけると幸いです。
sam3457

2018/03/18 10:41

追記ありがとうございます。 質問を追記致しましたのでよろしければご覧下さい。
newmt

2018/03/18 22:13

一部修正しました。 エラー個所の前の部分でprint(ref)、{}の中でprint(snapshot)を出力するとどうなりますでしょうか?どこかでnilになっている可能性があります。 また、エラーとは関係ないかもしれませんが、一点確認したい点として、firebaseに保存しているキー名は「userName」「userIcon」ではないのでしょうか?dictionary["myName"] 、dictionary["iconImage"]となっていましたので、そこで値が取れない可能性があります。
sam3457

2018/03/19 04:53

すみません、その部分の修正をし忘れていました、、 その修正後ビルドしてタブを押した結果、エラー個所の前に置いたprint(ref)の値はnilになっていました。また、{}内でのprint(snapshot)はエラーで止まってしまって到達しませんでした。 コレはfirebaseの導入時のミスですよね、、? AppDelegateのFirebaseApp.configure()の記述、GoogleService-Info.plist、コンソールのルールをtrue,trueにする、あたりを確認して見たのですが、問題はない様でした。
newmt

2018/03/19 06:03

変数refはどのように初期化していますでしょうか? let ref = Database.database().reference() のような形でしているとは思いますが、それでもnilになっていますでしょうか?
sam3457

2018/03/19 09:04 編集

ご指摘されたところを見ると、原因はまさにそれだったみたいです。 宣言だけして初期化をができていませんでした、、 そこを修正したところ、きちんと動作し、アプリ再起動しても名前と画像が入りました! 最後まで親身になって教えてくださり、本当にありがとうございます! firebase、data型もっと詳しく勉強して見ます。 最後に一つお聞きしたいのですが、再起動してプロフィール画面を開くと3秒くらいの時差で画像と名前が表示されたのですが、コレは一般的に、キャッシュに保存することで解決しているのでしょうか?
newmt

2018/03/19 10:35

もしかしたらスレッドの問題かもしれません。回答を一部修正したので一度試してみて頂けませんでしょうか?
sam3457

2018/03/19 11:30

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

2018/03/20 08:30

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

2018/03/20 12:13 編集

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問