Nifty Cloud Mobile Backendのファイルストア・基本編
Nifty Cloud Mobile Backendでいろいろやってみようシリーズ、第5弾(?)
今回はファイルストアを利用して、画像データのアップロード/ダウンロードの処理を試してみました。
ソースコードを公開してあるので、よくわからない部分は直接見て下さい。
See_Ku / NcmbFilestoreDemo — Bitbucket
https://bitbucket.org/See_Ku/ncmbfilestoredemo
動作を確認した環境
環境 | 情報 |
---|---|
Xcode | 7.2.1 (7C1002) |
iOS | 9.2 |
Swift | 2.1.1 |
Date | 2016/3/9 |
NCMB SDK | 2.3.0 |
画像データの保存(アップロード)
UIImageJPEGRepresentation()でNSDataにして、あとはNCMBFile::saveInBackgroundWithBlock()で保存しているだけです。
画像データのアップロードする場合、Nifty Cloud Mobile Backend側のファイルサイズ制限に引っ掛からないようにする点が問題になりそうですが、ここでは『UIImagePickerControllerの編集結果なら大丈夫』という楽観論で通してあります。
本番用のアプリでは、このような手抜きはやめましょう。
@IBAction func onUpload(sender: AnyObject) { // 画像ファイルをJPEG形式でデータ化 guard let image = imageView.image else { return } guard let data = UIImageJPEGRepresentation(image, 0.95) else { return } // ※本来ならここでサイズのチェックが必要 // NCMBFileを生成 let fn = NSUUID().UUIDString + ".jpg" let file = NCMBFile.fileWithName(fn, data: data) // アップロード file.saveInBackgroundWithBlock() { error in if let error = error { print("File save error : ", error) } else { print("File save OK: \(fn)") } } }
画像データの取得(ダウンロード)
ファイルの情報を取得
ファイルの情報を取得する所まではデータストアとほぼ同じです。使用するクラスがNCMBFile()になったぐらいでしょうか?
この後、実際のデータを取得する処理が入ります。
/// データを読み込み ※情報取得までは同期処理 func readDataSync() { let query = NCMBFile.query() query.limit = 20 do { fileArray = try query.findObjects() as? [NCMBFile] fetchData() print("read ok: \(fileArray?.count)") } catch { fileArray = nil print("read error: \(error)") } }
実際のデータを取得
NCMBFileのgetDataInBackgroundWithBlock()でデータを取得しているだけです。簡単そうに見えますよね?
/// データの取得処理を実行 func fetchData() { guard let ar = fileArray else { return } for file in ar { // 取得済みのデータはスキップ if imageDic[file.name] != nil { print("imageDic: \(file.name)") continue } // データを取得 file.getDataInBackgroundWithBlock() { data, error in if let error = error { print("get error: \(error)") } else { let image = UIImage(data: data) self.imageDic[file.name] = image self.collectionView.reloadData() print("get file.name: \(file.name)") } } } }
実は、この部分の処理にはいろいろと課題や問題点があります。
ファイルをローカルにキャッシュする仕組みが無い
少なくとも、Nifty Cloud Mobile Backend iOS SDK v2.3.0には、そのような機能は無いようです。getDataInBackgroundWithBlock()を呼び出すと、その回数だけ、毎回毎回、サーバーに取りに行ってくれます。必要であれば自力でキャッシュする仕組みを作りましょう。getDataInBackgroundWithBlock()は内部でシリアライズされない
getDataInBackgroundWithBlock()を10回呼んだら10本、20回呼んだら20本、同時に処理が走ってしまいます。出来れば、ある程度呼び出しを制限する仕組みを自力で作った方が良いでしょう。同じファイルへの同時取得を考慮してくれない
同じファイルにアクセスに行った場合、素直に、同時に取得処理が走ります。この部分も、自力でどうにかして無駄にアクセスしないような仕組みを作るべきでしょう。
・・・SDKを使っても、意外と面倒な事が多いですね。
その他
SDKのソースを読んでいて、少し気になった点です。
NCMBFile::isDirtyはサーバーでの変更に関知してない?
単純に、サーバーに保存済みかどうかのフラグのようです。サーバー側でデータが更新されたかどうかのチェックには使えない模様です。NCMBFile::isDataAvailableが機能してない?
まったく、何の処理もしていないように思えるんですが・・・ 何なんでしょうね、これ。
最大容量/最大サイズを指定してUIImageをNSDataに変換する
せっかくUIImagePickerControllerで画像を選べるようになったので、ネット経由でいろいろしようかと思ったのですが、最近のiPhoneはカメラが良い事もあって、画像データがそこそこ大きかったりします。
そこで、データにした後の容量と長辺の最大サイズを制限した上で、NSDataにしてくれるような処理を書いてみました。
ソースコードはこちら。(前回のデモの流用です)
See_Ku / ImagePickerDemo — Bitbucket
https://bitbucket.org/See_Ku/imagepickerdemo
ちなみに、UIImagePickerControllerの話はこちら。
UIImagePickerControllerを使ってみた - 開発メモ
http://seeku.hateblo.jp/entry/2016/02/29/210359
動作を確認した環境
環境 | 情報 |
---|---|
Xcode | 7.2.1 (7C1002) |
iOS | 9.2 |
Swift | 2.1.1 |
Date | 2016/3/6 |
ポイント解説
やってる事は単純です。
- 指定された範囲に収まるように画像をリサイズ
- JPEGの圧縮率を下げながら何度もデータ化
基本はこれだけです。簡単ですね。
指定された範囲に収まるように画像をリサイズ
画像のリサイズはこんな感じ。
/// イメージのサイズを変更 func resizeImage(src: UIImage) -> UIImage { // リサイズが必要か? let ss = src.size if maxLongSide == 0 || ( ss.width <= maxLongSide && ss.height <= maxLongSide ) { resizedSize = ss return src } // TODO: リサイズ回りの処理を切りだし // リサイズ後のサイズを計算 let ax = ss.width / maxLongSide let ay = ss.height / maxLongSide let ar = ax > ay ? ax : ay let re = CGRect(x: 0, y: 0, width: ss.width / ar, height: ss.height / ar) // リサイズ UIGraphicsBeginImageContext(re.size) src.drawInRect(re) let dst = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() resizedSize = dst.size return dst }
アスペクト比の計算から実際のリサイズまで、ほとんど、お約束の流れのままです。
JPEGの圧縮率を下げながら何度もデータ化
単純に、forでループを回してるだけです。
// JPEG形式でデータ化 for qualityUse = qualityMax; qualityUse >= qualityMin; qualityUse -= qualityDif { let data = UIImageJPEGRepresentation(resize, qualityUse) if checkDataSize(data) { return data } }
こういう処理を書いてると、C言語風のforループもあって良い気がするんですけど・・・ 本当に無くすんでしょうかね?
SK4ShrinkRepresentationの使い方
実際にイメージをデータに変換する処理をまとめたのが、SK4ShrinkRepresentationクラスになります。最大容量/最大サイズを制限してデータにする場合、こんな感じになります。
// 単純に制限付きでデータにするだけならこんな感じ let data = SK4ShrinkRepresentation.makeData(image, maxDataByte: 1024 * 1024 * 4, maxLongSide: 1024 * 2)
もっと面倒な使い方は・・・ たぶん、必要ないと思うんですけど、気になる方はソースコードを読んで下さい。
XcodeからgitとGitHubを使う方法・応用編
Xcodeで開発を行うときにgitやGitHubとあわせて使う方法の応用編です。
XcodeからgitとGitHubを使う方法・基本編 - 開発メモ
http://seeku.hateblo.jp/entry/2016/03/01/232409
今回の内容はこんな感じ。応用編のつもりでいたのですが、よく考えると基本編で基本的な操作の説明が抜けていたので、その部分もまとめてあったりしますorz
- 最後にコミットした状態に戻す
- 古いソースコードを参照する
- 複数のバージョンを並行して開発する
- 後からgitを使えるようにする
- コミットと同時にGitHubにプッシュ
- GitHub上の変更を取りこむ
- GitHubでREADMEを追加
- GitHubでLICENSEを追加
動作を確認した環境
環境 | 情報 |
---|---|
Xcode | 7.2.1 (7C1002) |
iOS | 9.2 |
Swift | 2.1.1 |
Date | 2016/3/2 |
gitに関する操作編
最後にコミットした状態に戻す
ソースコードにいろいろ手を入れたけどどうしても気に入らないとき、gitを使っていれば、最後にコミットした状態まですぐに戻す事が出来ます。
メニューの [Source Control] - [Discard All Changes] を選択すると確認画面が出るので、よく確認して実行するだけです。簡単ですね。
古いソースコードを参照する
リポジトリに登録された古いソースコードと、現在のソースコードを見比べながら作業する事も出来ます。
Xcodeの右上にある矢印が左右を向いてるっぽいアイコンを押すと、画面が分割されて古いバージョンを表示できるようになります。ちなみに、メニューから操作する場合は [View] - [Version Editor] - [Show Version Editor] で表示できます。
Version Editorの下の部分をクリックする事で、過去の、どのソースコードを表示するか選ぶ事が出来ます。いざというときに助かります。
複数のバージョンを並行して開発する
例えば『Ver 1.0をリリースしたのでVer 2.0に取りかかりたいけど、Ver 1.0のバグ取りもしたい』みたいなときに、gitを使ってると簡単に平行して作業する事が出来ます。
ブランチとは?
ブランチというのは、平行して作業する時に作業をまとめて管理する単位です。ブランチを分ける事で、同じリポジトリでもそれぞれの作業を独立して行う事が出来ます。・・・言葉で説明するとわかりにくいですね。実際に使ってみれば、すぐに慣れると思います。
それぞれのバージョンに対応するブランチを作成
Xcodeでは、メニューの [Source Control] - [プロジェクト名 - 現在のブランチ名] - [New Branch] で新しいブランチを作る事が出来ます。
特に切り替え作業を行ってない場合、masterブランチで作業が管理されます。複数のバージョンを並行して開発する場合、masterで最新の開発を行い、それ以外のバージョンは対応するブランチで作業するやり方が多い・・・ のかな? この辺りは、個人の好みが大きいような気がします。
ブランチを切り替える
開発を進めるバージョンを切り替える場合、対応するブランチを切り替える事になります。
Xcodeでは、メニューの [Source Control] - [プロジェクト名 - 現在のブランチ名] - [Switch Branch] でブランチを切り替える事が出来ます。
ブランチを切り替える際に、こんなエラーが出る事があります。
ローカルのブランチから、リモートのブランチに切り替えようとするとこのエラーが出るようですが・・・ 詳細は不明です。とりあえず、ローカルのブランチだけ使っていれば問題ないと思われます。
後からgitを使えるようにする
Xcodeのプロジェクトを作成する時点で、gitのリポジトリも作っておくのがおすすめなのですが、もし、万が一、運悪くリポジトリの作成を忘れていた場合でも、後から簡単に対応する事が出来ます。
メニューの [Source Control] - [Create Working Copy] を押すと確認画面が出るので、リポジトリを作成しましょう。
GitHubに関する操作編
コミットと同時にGitHubにプッシュ
毎回毎回、コミットしてからプッシュする作業を繰り返してるような人は、コミット画面の左下にある『Push to remote』をチェックしておくと、幸せになれるかもしれません。
GitHub上の変更を取りこむ
複数人で開発していると、自分のXcodeで作業しているソースコードより、GitHubのリポジトリにあるソースコードの方が新しい状況になる事があります。
このような場合、メニューの [Source Control] - [Pull] で、GitHubのリポジトリを取りこむ事が出来ます。
GitHubでREADMEを追加
READMEが登録されていないリポジトリを開くと、画面の右下の方に『Add a README』ボタンがあります。
これを押すとREADMEの編集画面になるので、その場で編集&コミットする事が出来ます。便利ですね。
GitHubでLICENSEを追加
READMEの追加に比べると、LICENSEの追加はちょっと面倒です。
GitHubでリポジトリのページを開いて、ボタンの並んでるところにある『New file』をクリック。
あとは、画像の通りに操作して、ライセンスを選択&コミットするだけです。ファイル名に『LICENSE』を入力するのがポイント?
XcodeからgitとGitHubを使う方法・基本編
Xcodeで開発を行うときに、gitやGitHubとあわせて使う方法について調べてみたのですが、いまいち、しっくりくるサイトが見つからなかったので自分でまとめてみました。
ここでは、以下のような情報についてまとめてあります。
あくまで基本編と言う事で、あまり難しい話はしません。
(SSH鍵を登録する話やgitignoreの話は無し)
応用編はこちら。
XcodeからgitとGitHubを使う方法・応用編 - 開発メモ
http://seeku.hateblo.jp/entry/2016/03/02/232151
動作を確認した環境
環境 | 情報 |
---|---|
Xcode | 7.2.1 (7C1002) |
iOS | 9.2 |
Swift | 2.1.1 |
Date | 2016/2/25 |
基礎知識編
gitとは?
主に、プログラムのソースコードを管理するためのシステムです。gitをちゃんと使えば、古いソースコードを取り出したり、複数のバージョンを並行して開発する事が簡単になります。
ちなみに、『ギット』って呼ぶ人が多いですが『ジット』って呼ぶ人もいます。たまに。はい。
GitHubとは?
簡単に言うとgitをネット経由で使えるようにして、いろいろと便利な機能を付け加えたサービスです。ソースコードを公開するときに便利です。上手く使えば、複数人での開発が簡単になったりします。
リポジトリとは?
ソースコードを管理する単位です。プロジェクトごとに対応するリポジトリが作られると思っておけば、だいたい合ってます。gitでソースコードを管理する場合、同じようなリポジトリを複数の場所に分散しておく事が出来ます。
コミットとは?
ソースコードをリポジトリに登録する操作の事です。コミットしてないと、gitは何の役にも立ちません。キリが良いと思ったら、どんどんコミットしましょう。
※とにかく、コミット大事
プッシュとは?
Xcodeで作ったリポジトリをGitHubに送る事だと覚えておけば、だいたい合ってます。たぶん。
各種操作編
gitの初期設定
最低でも、名前とメールアドレスの設定はしておきましょう。こんな感じで設定する事が出来ます。
$ git config --global user.name "John Doe" $ git config --global user.email johndoe@example.com
ユーザー名やメールアドレスには、この後、GitHubで登録するのと同じものを指定しておくのが良いでしょう。後々の事を考えると、開発専用に新しいメールアドレスを用意するのがおすすめです。
gitの設定に関して、詳しい説明は以下のサイトを参考にしてください。
GitHubのユーザー登録
以下のサイトでユーザー登録を行います。ユーザー名は慎重に。
GitHub · Where software is built
https://github.com/
プロジェクトと同時にリポジトリを作成
Xcodeでプロジェクトを作成する時点で、gitのリポジトリも作るように指定する事が出来ます。gitのリポジトリを作成する事によるデメリットはほぼ無いので、必ず作るようにしましょう。
とにかくコミットが大事
開発を進めていくと、Project Navigatorのファイル名の横に、何かマークが現れる事があります。これは、リポジトリに登録されていたファイルに何らかの変更があった事を意味します。ファイルの編集だったり、追加だったり、削除だったりですね。
マークが出ると言う事は開発が進んだと言う事なので(?)、キリが良いところでソースコードをリポジトリに収めます。
メニューの[Source Control] - [Commit...]を選択すると、コミットに関する情報を入れる画面になります。
最初のうちは、プロジェクトで使ってるファイルは、全てリポジトリに登録する方針で問題ないでしょう。たぶん。
コミットメッセージは、自分なりに何をどうしたのかを書けば良いです。特に思いつかなかったら適当に埋めても問題ありません。個人的なプロジェクトであれば。
一番大事なのはコミットする事です。
キリが良いなと思ったら、どんどんコミットしましょう。大事な事なので。
GitHubにリポジトリを登録
ソースコードをGitHubで公開する場合、まず、GitHubでリポジトリを登録します。
GitHubの自分のページを開いて、右上の『+』をクリック。出てきたメニューから『New repository』を選択します。
次の画面で新しいリポジトリの情報を入力します。Xcodeで作成したプロジェクトをGitHubに登録する場合、『Initialize this repository with a README』のチェックは外しておくのが良さそうです。
(READMEやLICENSEは後から追加する事も出来ます)
GitHubにリポジトリが出来たらURLをコピーしておきます。
HTTPSの方です。
Xcodeでリモートリポジトリを登録
Xcodeで作成したリポジトリに、GitHubで作成したリポジトリを登録します。
まず、メニューの [Source Control] - [プロジェクト名 - ブランチ名] - [Configure - プロジェクト名] で設定画面を出します。
Nameはoriginのままで、AddressにGitHubのリポジトリを指定します。
GitHubにプッシュ
メニューの [Source Control] - [Push]でプッシュします。
GitHubのユーザー名とパスワードを聞かれるので、入力しましょう。
簡単ですね。
UIImagePickerControllerを使ってみた
iPhone / iPadで、単純に画像を一枚取得するだけであればUIImagePickerControllerを使うのが便利です。
そこでいろいろ調べたのですが、最近のSwiftで書かれた良い感じの例が見つからなかったので、自分で作ってみました。
ソースコードはこちら。
(このページで紹介しているコードのブランチはv1.0です)
See_Ku / ImagePickerDemo — Bitbucket
https://bitbucket.org/See_Ku/imagepickerdemo
動作を確認した環境
環境 | 情報 |
---|---|
Xcode | 7.2.1 (7C1002) |
iOS | 9.2 |
Swift | 2.1.1 |
Date | 2016/3/5 |
ポイント解説
1. 使える機能を確認
画像を選ぶ前に、そもそも、その機能が使えるのかを確認する必要があります。具体的にはこんな感じになります。
// カメラは使えるか? if UIImagePickerController.isSourceTypeAvailable(.Camera) == false { cameraButton.enabled = false }
この場合、カメラ機能が使えるかを調べて、使えないときは対応するボタンを無効にしています。今のところ、カメラ以外はどんな環境でも使えるような気がしますが、念のために他の機能を使うときもチェックするのが良いでしょう。たぶん。
2. メディアタイプを確認
本来ならそれぞれの機能でどんなメディアタイプが使えるかを調べないといけません。 (availableMediaTypesForSourceType(_:) を使います)
が、画像はどの機能でも確実に取れるようなので、ここではチェックを省いています。いけませんね。
3. UIImagePickerControllerを作成&画面遷移
UIImagePickerControllerを作成し、必要な情報を設定して画面を遷移させます。具体的にはこんな感じ。
/// Cameraから画像を取得 @IBAction func onCamera(sender: AnyObject) { // ImagePickerを作成 let ipc = UIImagePickerController() ipc.delegate = self ipc.sourceType = .Camera ipc.allowsEditing = true // Cameraは全画面で選択 presentViewController(ipc, animated: true, completion: nil) }
ポイントとしては、 カメラの場合は全画面で、それ以外の時はモーダルフォーム で使用する必要があります。厳密に言うとカメラをモーダルフォームで使う事も出来るようですが、アップル推奨は全画面、と。そういう事のようです。
4. 画像の取得/キャンセルに対応
あとは、画像が正常に取得できたときとキャンセルされたときの処理を書くだけです。
/// 画像を取得できた func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) { // 編集後のイメージを取り出し if let image = info[UIImagePickerControllerEditedImage] as? UIImage { imageView.image = image } // ImagePickerを終了 dismissViewControllerAnimated(true, completion: nil) } /// キャンセルされた func imagePickerControllerDidCancel(picker: UIImagePickerController) { // ImagePickerを終了 dismissViewControllerAnimated(true, completion: nil) }
わざわざ、辞書からimageを取り出さないといけないのが、ちょっと面倒かもしれません。
情報元
UIImagePickerController Class Reference
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImagePickerController_Class/
履歴
- 2016/03/05 Deprecatedなメソッドを使っていたのを修正
枠線付きのボタン
UIButtonに枠線をつける方法はいろんなところで紹介されていますが、どれもいまいち満足できなかったので、自分で作ってみました。
動作を確認した環境
環境 | 情報 |
---|---|
Xcode | 7.2.1 (7C1002) |
iOS | 9.2 |
Swift | 2.1.1 |
Date | 2016/2/25 |
できる事
以下のような機能が実装されています。
- 押したときに枠線の色がちゃんと変わる
- currentTitleColorから自動で色を設定
- disableにした時の色も自動で設定
- アラートが表示されたときの色変更にも対応
- Storyboardから枠線の太さ/コーナー半径の変更に対応
特にすごい事はやってません。
ソースコード
こんな感じになりました。
import UIKit /// 枠線付きのボタン @IBDesignable public class SK4BorderButton: UIButton { @IBInspectable var borderWidth: CGFloat = 1.0 { didSet { layer.borderWidth = borderWidth setNeedsDisplay() } } @IBInspectable var cornerRadius: CGFloat = 4.0 { didSet { layer.cornerRadius = cornerRadius setNeedsDisplay() } } override public var enabled: Bool { didSet { layer.borderColor = currentTitleColor.CGColor } } override public var highlighted: Bool { didSet { let col = currentTitleColor let key = "borderColor" if highlighted { layer.borderColor = col.colorWithAlphaComponent(0.2).CGColor layer.removeAnimationForKey(key) } else { layer.borderColor = col.CGColor let anim = CABasicAnimation(keyPath: key) anim.duration = 0.2 anim.fromValue = col.colorWithAlphaComponent(0.2).CGColor anim.toValue = col.CGColor layer.addAnimation(anim, forKey: key) } } } override public init(frame: CGRect) { super.init(frame: frame) setupBorder() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupBorder() } override public func tintColorDidChange() { super.tintColorDidChange() layer.borderColor = currentTitleColor.CGColor } public func setupBorder() { layer.borderWidth = borderWidth layer.cornerRadius = cornerRadius layer.borderColor = currentTitleColor.CGColor clipsToBounds = true } }
UITableViewCellに置いたUIButtonがすぐに反応するようにする方法
かなりマニアックなtipsシリーズです。
UITableViewCellにUIButtonを置いた場合、タッチしてすぐに指を離すとボタンがハイライト状態になりません。プログラム的に見ると、ちゃんと押されているのですが・・・ 使ってる側から見ると、まったく反応していないように見えて不親切です。
しばらく押してから離すとハイライト状態になりますが・・・ わかりにくいですよね? これをどうにかする方法のメモです。
動作を確認した環境
環境 | 情報 |
---|---|
Xcode | 7.2.1 (7C1002) |
iOS | 9.2 |
Swift | 2.1.1 |
Date | 2016/2/23 |
すぐに反応するようにする方法
すぐに反応してくれない原因はUIScrollViewのdelaysContentTouchesがtrueになっていることです。たぶん。
ですから、これをfalseにしてやればいいんですが、UITableViewは内部でUIScrollViewを使用しているため、一工夫必要になります。
具体的にはこんな感じ。
class ViewController: UIViewController { @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() // UITableViewのdelaysContentTouchesをfalseに tableView.delaysContentTouches = false // 中のUIScrollViewのdelaysContentTouchesをfalseに for vi in tableView.subviews { if let vi = vi as? UIScrollView { vi.delaysContentTouches = false } } // 実際にUITableViewを使う処理 } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
簡単ですね。