Help us understand the problem. What is going on with this article?

ローディング時のズルい進捗表示

More than 3 years have passed since last update.

今開発中のPlayer!のログイン・登録画面で、こんな進捗表示をしていますが、これ実はフェイクだったりします( ´・‿・`)

2015-09-19 09_22_21.gif
(Qiitaの画像サイズ制限が厳しくて粗いです。キレイなものは実際にアプリダウンロードしてご覧下さい。)

経緯

元々、この画面はこういう進捗表示では無く、単にインジケーターがクルクルするだけで、進捗状態が分からないものでした。
特にネットワークが悪いところだと、バグって固まってしまったのでは?とユーザーを不安にさせるようで、たまにそういう声を聞くことがありました。
登録フローは大事なところなので、そういうところでこれが原因で離脱してしまうと残念なので、改善が必要でした。

そこで、ネットワーク処理にもたつきつつもちゃんと正常に処理をしているということを示すために、進捗を表示することにしました。

ただ、例えば大きな画像などメディアファイルダウンロードなどならともかく、こういうJSONレスポンス受け取る処理って細かい進捗が取れず、それに頼ると0%から一気に100%に達するような感じになってしまい、今いちでした。

本来の目的は、「バグって固まってしまったのでは?とユーザーを不安にさせる」ような状態の解消であって、フェイクでも良いから何となくそれっぽく出来ないかなとやってみました。

どういったフェイク進捗を行うか

ネットワーク良い状態でも悪い状態でも、ジワジワ小気味よく進捗が進んで、安心出来る感じを目指しました。

いくつか実験して見たところ、登録処理にかかる時間は概ね以下くらいでした。

  • ネットワーク良い状態: 2-3秒程度
  • ネットワーク悪い状態: 10秒以上かかることもあり

これを元に、以下が良さそうと思い実装したら、Gif動画のようになかなか良い感じになりました。

  • ネットワーク状態が悪いときでも、90%程度まで10秒程度かけて進捗が進むようにする
  • 進捗の進む間隔・進む割合を、ランダムに1秒前後開けつつ、10%前後進めるようにする
    • ランダムにすることでフェイクっぽさが消える
    • 処理中のまま10秒程度経つと、90%で止まってしまうのは許容
  • ネットワークが良い状態では、進捗が半分足らずで実際の処理が終わってしまうが、そのタイミングで一気に100%まで進めて小気味良くフィニッシュした感じにしてごまかす
    • Gif動画はこのパスに近いです

この実装後は、特にこれに関してマイナス意見聞くことが無くなったので、なかなか良い塩梅で実装出来たのでは、と思っています。
僕自身、開発中に「あれ、止まった?」と思うことが無くなりました。

ちなみに、シミュレーター上で低速回線のエミュレートをする場合は、Network Link Conditioner - NSHipsterを使うと便利です。

実装

SwiftコードかつSVProgressHUDライブラリを使っていますが、肝の部分は他のプラットフォームでも同様に応用出来ると思います。
本当は別ファイルに定義してある関数など引っ張ってきて、1つのコードとして載せています。

// ヘルパー的な関数群
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 = 0.9) {
    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)
    // 実際のログイン処理を呼ぶ
    // ...

    // 終わったら100%に進める
    progressCompleted = true
    SVProgressHUD.showProgress(1, status: message, maskType: .Gradient)
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away