1. mono0926

    Posted

    mono0926
Changes in title
+ローディング時のズルい進捗表示
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,97 @@
+
+[今開発中のPlayer!](https://itunes.apple.com/jp/app/player!-re-kuang-xingsupotsuapuri/id897872395?mt=8)のログイン・登録画面で、こんな進捗表示をしていますが、これ実はフェイクだったりします( ´・‿・`)
+
+![2015-09-19 09_22_21.gif](https://qiita-image-store.s3.amazonaws.com/0/19398/83bbd40e-c3f9-dc8d-8419-128e5e76ba06.gif "2015-09-19 09_22_21.gif")
+
+## 経緯
+
+元々、この画面はこういう進捗表示では無く、単にインジケーターがクルクルするだけで、進捗状態が分からないものでした。
+特にネットワークが悪いところだと、バグって固まってしまったのでは?とユーザーを不安にさせるようで、たまにそういう声を聞くことがありました。
+登録フローは大事なところなので、そういうところでこれが原因で離脱してしまうと残念なので、改善が必要でした。
+
+そこで、ネットワーク処理にもたつきつつもちゃんと正常に処理をしているということを示すために、進捗を表示することにしました。
+
+ただ、例えば大きな画像などメディアファイルダウンロードなどならともかく、こういうJSONレスポンス受け取る処理って細かい進捗が取れず、それに頼ると0%から一気に100%に達するような感じになってしまい、今いちでした。
+
+本来の目的は、「バグって固まってしまったのでは?とユーザーを不安にさせる」ような状態の解消であって、フェイクでも良いから何となくそれっぽく出来ないかなとやってみました。
+
+## どういったフェイク進捗を行うか
+
+ネットワーク良い状態でも悪い状態でも、ジワジワ小気味よく進捗が進んで、安心出来る感じを目指しました。
+
+いくつか実験して見たところ、登録処理にかかる時間は概ね以下くらいでした。
+
+- ネットワーク良い状態: 3-5秒程度
+- ネットワーク悪い状態: 10秒以上かかることもあり
+
+これを元に、以下が良さそうと思い実装したら、Gif動画のようになかなか良い感じになりました。
+
+- ネットワーク状態が悪いときでも、90%程度まで10秒程度かけて進捗が進むようにする
+- 進捗の進む間隔・進む割合を、ランダムに1秒前後開けつつ、10%前後進めるようにする
+ - ランダムにすることでフェイクっぽさが消える
+- ネットワークが良い状態では、進捗が半分弱くらいで実際の処理が終わってしまうが、そのタイミングで一気に100%まで進めて小気味良くフィニッシュした感じにしてごまかす
+ - Gif動画はこのパスです
+
+この実装後は、特にこれに関してマイナス意見聞くことが無くなったので、なかなか良い塩梅で実装出来たのでは、と思っています。
+僕自身、開発中に「あれ、止まった?」と思うことが無くなりました。
+
+## 実装
+
+Swiftコードかつ[SVProgressHUDライブラリ](https://github.com/TransitApp/SVProgressHUD)を使っていますが、肝の部分は他のプラットフォームでも同様に応用出来ると思います。
+本当は、別ファイルに定義してある関数など引っ張ってきて、1つのコードとして載せています。
+
+```swift
+// ヘルパー的な関数群
+func random(num: Int) -> Int {
+ return Int(arc4random_uniform(UInt32(num)))
+}
+func random(min: Double, _ max: Double) -> Double {
+ let random = Int(arc4random_uniform(UInt32(max*100 - min*100)))
+ return min + Double(random) / 100.0
+}
+public func dispatchOnMainThread(delay delay: NSTimeInterval = 0, block: () -> ()) {
+ if delay == 0 {
+ dispatch_async(dispatch_get_main_queue()) {
+ block()
+ }
+ return
+ }
+ let d = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
+ dispatch_after(d, dispatch_get_main_queue()) {
+ block()
+ }
+}
+
+private var progressCompleted = false
+
+/** これがフェイク進捗の肝のメソッド */
+func increaseProgress(progress: Float, message: String?, until: Float = 1) {
+ if progressCompleted {
+ return
+ }
+ SVProgressHUD.showProgress(min(progress, until), status: message, maskType: .Gradient)
+ // 進捗がuntilで設定した最大値を超えたらそれ以上進捗を増やさずに待機
+ if progress >= until {
+ return
+ }
+ // ジワジワ動く間隔を0.5〜2秒でランダムに
+ let delay = NSTimeInterval(random(0.5, 2))
+ dispatchOnMainThread(delay: delay) {
+ // 1回の進捗更新でどれくらい進むかを0.05〜0.15でランダムに設定して再帰呼び出し
+ self.increaseProgress(progress + Float(random(0.05, 0.15)), message: message, until: until)
+ return
+ }
+}
+
+/** サインアップ処理 */
+private func signUpImpl() {
+ progressCompleted = false
+ increaseProgress(0.05, message: message, until: 0.9)
+ // 実際のログイン処理を呼ぶ
+ // ...
+
+ // 終わったら100%に進める
+ progressCompleted = true
+ SVProgressHUD.showProgress(1, status: message, maskType: .Gradient)
+}
+```