「標準コントロールを使用する」またはAppleからカレンダーを盗んだ方法

この記事では、私たちのチームが1つのプロジェクトの一部として設定したカレンダー作成のかなり一般的なタスクを読者に紹介したいと思います。



そのようなアプリケーションを実装する際に直面しなければならない困難を克服する経験を共有したいという願望は、主に生産性の点で私たちに合った既製のソリューションを見つけられなかったために生じました。



このため、テクノロジーの研究と比較に時間を割く必要があり、経験を共有する準備ができています。 特に、この記事では、セルの迅速なレンダリングとスムーズなアニメーション、データベースからのカレンダーのイベントの非同期ロードに関連する一連のタスクのソリューションを共有したいと思います。



私は猫の下で興味があるすべての人に尋ねます。



最初に決めたのは、カレンダーの外観です。 カレンダーをAppleのソリューションに可能な限り似たものにすることは非常にクールだと思われました。 その結果、標準のカレンダーアプリケーションがプロトタイプとして満場一致で選ばれ、iOS 7.0のリリースで利用可能になりました。 iOS 7.0の標準カレンダーは、年、月、週の3つのビューで構成されています。



標準カレンダーでの年の表示は以下に見ることができます。







週次ビューでは、選択された日付に応じてロードされるイベントを含むテーブルが表示されるため、パフォーマンスの点で問題は見られませんでした。







スクロールリストである月次および年次の提出物については、物事はそれほど楽観的に見えませんでした。 主な問題は、アプリケーションが終了日なしで定期的なイベントの処理をサポートする必要があることでした。 さらに、最小繰り返し間隔は1日でしたが、もっと簡単に言えば、イベントを毎日スケジュールすることもできました。 タスクの分析の段階で既にこの事実は、データベースからプルされるべきであったデータの長い処理を意味しました。 これに加えて、ロード中のインジケーターを表示せずに無限のリストを実装したかったため、スクロール中の動的なデータのロードを意味していました。



さらに、この記事では、最も要求の厳しいシステムリソースとして年間プレゼンテーションを正確に作成するプロセスについて説明します。



カレンダーの最適化に関するすべての作業は、テーブルセルをすばやく描画する方法の検索から始まりました。 月と年の両方を表すために、テーブルまたはUITableViewを使用することにしました。 年を表すために、このソリューションは、UICollectionViewの場合のように個々の月ではなく、1年全体のデータをすぐにプルアップできるように選択されました。



最初に、UIKitライブラリのコンポーネントなど、シンプルで一般的なツールを使用してインターフェイスを作成しようとすることにしました。 カレンダーの日付のすべての位置を満たすUILabelから配列を作成することが決定されました。1か月の間に7 x 6 = 42です。







次に、すでに明らかなように、ラベル自体の位置を変更することなく、日番号のテキストが目的のUILabelに置き換えられました。 その結果、カレンダーがひどく遅くなり、スタックすることが判明しました。 なぜ面白くなったのか、そしてタイムプロファイラーの助けを借りて理由を明らかにすることができました。 問題はsetText関数にあり、さらに詳しくは、特定の日付のchar-s配列をグリフに変換し、さらにレンダリングするメカニズムにありました。



したがって、次のステップは、事前定義された数値テキストを使用して、1か月の最大日数に対して31個のUILabel配列を作成することでした。 このメソッドでは、特定のラベルの座標を操作します。







スクロールはすでに可能でしたが、レイヤーラスタライズの形式でいくつかのトリックを使用しても、最大で60の平均41 FPSしか達成できませんでした。 ただし、スクロールの開始時の痙攣とその減速は依然として顕著でした。



私たちのさらなる研究は、Quartzライブラリに向けられました。 ラベルを使用したメソッドの場合と同様に、31日目に定義済みの番号を使用した同じシステムを使用しました。 また、月に応じて、文字列の内容を変更せずにレイヤーの座標が設定されました。 その結果、平均FPSは44に増加することができました。



最後のオプションは、CoreTextを使用することでした。 それにより、年次カレンダー提出と53 FPSの必要な速度を達成することができましたが、次の課題が予想されていました。



標準のiOS 7.0カレンダーは非常にスムーズに機能しますが、特定の日のイベントを表示する機能はありません。これは、日付の下のドットとして月を表すことがわかります。







この機能を年次カレンダーで利用できるようにしたかったのです。 ここでも、パフォーマンスの問題が発生し始めました。毎日繰り返されるイベントを設定すると、FPSが低下し始めたためです。 この問題は、テキストのレンダリングにはすでに関係していませんでしたが、日付の背後に背景を描くことに関係していました。 したがって、すべてのイベントを個別のストリームに描画し、グラフィックコンテキストをUIImageオブジェクトに変換することにしました。 UIImageはスレッドセーフであるため、レンダリングイベントは特定の月の新しい画像を置き換えるように見えます。



iOSでマルチスレッドを操作するために利用できる多くのソリューションがあります。 この場合、最適な解決策は、相続人NSOperationとNSOperationQueueのクラスを使用することでした。 そうしないと、たとえばGCD(Grand Central Dispatch)を使用するときに、カレンダーへのデータの読み込みをキャンセルする際に問題が発生するため、追加のラッパーを記述する必要があります。



この段階で、データベースからデータをロードする問題をすぐに検討することにしました。 NSManagedObjectおよびNSManagedObjectContextのインスタンスはスレッドセーフではないため、プロジェクトではCoreDataを使用して非同期抽出に関連するすべての問題を解決しました。 フレームワークのこの機能を克服するために、NSOperationインスタンスのメイン関数が実行されると、プライベートNSManagedObjectContextが作成されます。これは、別のスレッドで実行されます。 これにより、2つのアクションを個別のスレッドで組み合わせることができました。

  1. データベースからカレンダーイベントを取得する、
  2. 別のストリームで絵を描いて作成します。


1年を表示するカレンダーの作業は、次の図でも説明できます。







その結果、Appleの標準アプリケーションとほぼ同じくらいスムーズに機能するカレンダーを作成することができました。 イベントのマッピングが導入された年を表すように機能を拡張しました。



このソリューションが、開発者が複雑でリソースを大量に消費するインターフェイスを備えたカレンダーを作成するだけでなく、表のコンテンツをレンダリングする時間が迅速な作業に不可欠なリストの実装にも役立つことを願っています。



カレンダーが表示されたアプリケーションの画面は、最終的には次のように表示されます。







PSサンプルプロジェクトのソースコードはこちらです。



PPS私も、同様の問題に対する他の可能な解決策に関するフィードバックを本当に楽しみにしています。



All Articles