Programming

ハンドメイドカレンダー(iOS用サンプル)を作りました (解説付)

 Author:fumiyasac

20141220

こちらは僕がiOSをはじめて触った際に作成したサンプルになります。正直まだまだコードに関しては荒い部分があるのですが、皆さんの開発にほんの少しでもご参考になればと思いまして、今回は公開&要点の解説をします。

1.概要

カレンダーの実装方法はさまざまな方法やアイデアがあるみたいです。
※CollectionViewやScrollViewを使った素敵なライブラリも多数存在します。

ですがなかなかカスタマイズをしようとすると辛い!という場面に出くわしてしまったこと、ある人にレビューしてもらったら「今のとこ使いたくない」や「もっとかわいいのがいい」という恐ろしいおねだりをされたこと、「できるかー!」と投げ出しそうになってしまった等のすったもんだありましたので、

  • もっと簡単に
  • かわいくって
  • カスタマイズの目処が立ちやすい

ものを作ろうと思い立って作りました。

というわけで今回はiOSアプリでもはじめに出てきて、なおかつよく使う

  • ボタン
  • ラベル

だけを使うシンプルなカレンダーを無理やりではありますが作ってみました。

[Github上のソース]
・Objective-C
Objective-C版を見る

・Swift
Swift版を見る

・プレゼン資料(12/21に追加しました ※リンク先がFacebookでしたのでSlideShareにアップしました。)
発表時に使用した資料

※こちらとダウンロードして頂いた上で、下記の 2.要点の解説(イントロ) 〜 5.要点の解説(その3)へ進んでいただけるとより理解が深まると思います。

2.要点の解説(イントロ)

このカレンダーを構成する上で重要な部分は下記の3つになります。
ここでは、

  1. カレンダーデータの取得方法
  2. ボタンを動的に配置する方法
  3. 動的に配置したボタンを押したときのふるまい

にフォーカスをして解説を行います。

3.要点の解説 (1.カレンダーデータの取得方法)

[解説]

※メンバ変数nowは現在の日付(NSDate型)になります。

日付の取得については、NSCalendarクラスを使用して取得することができます。
calendarIdentifier:の部分でどの種類のカレンダーから情報を取ってくるかを選びます。
変数rangeは、現在日付の日数(=結果的にはその月の最終日)を取得します。
rangeOfUnitメソッドで引数を(月, inUnit:日, forDate:現在の時刻)と指定することで取得します。

変数compsは、コンポーネントよりカレンダーのどの要素を持ってくるかを決定します。
今回の書き方では、

  • 年(year)
  • 月(month)
  • 日(day)
  • 曜日(dayOfWeek)

を取得します。

曜日を拾ってくる理由は、カレンダーで表示開始位置を決定するために必要なためです。

[参考資料]

カレンダークラスの使い方については下記のURL等も参考になります。

[Swiftでのコード]

  1. //inUnit:で指定した単位(月)の中で、rangeOfUnit:で指定した単位(日)が取り得る範囲
  2. var calendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
  3. var range: NSRange = calendar.rangeOfUnit(NSCalendarUnit.CalendarUnitDay, inUnit:NSCalendarUnit.CalendarUnitMonth, forDate:now)
  4. //最初にメンバ変数に格納するための現在日付の情報を取得する
  5. comps = calendar.components( NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitWeekday, fromDate:now )
  6. //年月日と最後の日付と曜日を取得(NSIntegerをintへのキャスト不要)
  7. var orgYear: NSInteger = comps.year
  8. var orgMonth: NSInteger = comps.month
  9. var orgDay: NSInteger = comps.day
  10. var orgDayOfWeek: NSInteger = comps.weekday
  11. var max: NSInteger = range.length
  12. year = orgYear
  13. month = orgMonth
  14. day = orgDay
  15. dayOfWeek = orgDayOfWeek
  16. maxDay = max

[Objective-Cでのコード]

  1. //inUnit:で指定した単位(月)の中で、rangeOfUnit:で指定した単位(日)が取り得る範囲
  2. NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
  3. NSRange range = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:now];
  4. //最初にメンバ変数に格納するための現在日付の情報を取得する
  5. flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday;
  6. comps = [calendar components:flags fromDate:now];
  7. //年月日と最後の日付を取得(NSIntegerをintへ変換)
  8. NSInteger orgYear = comps.year;
  9. NSInteger orgMonth = comps.month;
  10. NSInteger orgDay = comps.day;
  11. NSInteger orgDayOfWeek = comps.weekday;
  12. NSInteger max = range.length;
  13. year = (int)orgYear;
  14. month = (int)orgMonth;
  15. day = (int)orgDay;
  16. dayOfWeek = (int)orgDayOfWeek;
  17. //月末日(NSIntegerをintへ変換)
  18. maxDay = (int)max;

[注意点]

ソース的や処理に関しては、さほど差はないのですが、下記に挙げた部分が大きく異なる点です。

  1. Objective-C版とSwift版ではカレンダーユニットの書き方が異なります。
  2. Objective-C版はキャストをして型変換しています。。(強引ですみません)

4.要点の解説 (2.ボタンを動的に配置する方法)

[解説]

generateCalendar関数でボタンに関する設定(色をつけたり数字をつけたりなど)とボタンの配置を行っていますが、ここではボタンの配置に関する部分をメインに解説を行います。

この部分のキモとなる部分は、

  1. addSubView
  2. CGRectMake

の2つのメソッドになります。

簡単な解説ではありますが下記のような形で実装します。

1. addSubViewメソッド

サブビューを追加します。
今回は現在の画面にボタン・ラベル等を配置するために使います。

[Swift]

  1. self.view.addSubview( '追加したいもの' )

[Objective-C]

  1. [self.view addSubview: '追加したいもの' ];

※self.viewは今現在配置されている一番おおもとのviewの意味です。

2. CGRectMakeメソッド

対象オブジェクトを生成(矩形)を生成します。
今回は追加するボタンのサイズを決めるために使います。

[Swift]

  1. 例. CGRect rect = CGRectMake ([x座標], [y座標], [幅], [高さ] ※引数はCGFloat型)

[Objective-C]

  1. 例. CGRect rect = CGRectMake ([x座標], [y座標], [幅], [高さ] ※引数はfloat型)

※カレンダー表示の説明につきましては後ほどスライドをアップしますのでお待ち下さい。

[Swiftでのコード]

  1. //カレンダーを生成する関数
  2. func generateCalendar(){
  3.     
  4.     //タグナンバーとトータルカウントの定義
  5.     var tagNumber = 1
  6.     var total = 42
  7.     
  8.     //7×6=42個のボタン要素を作る
  9.     for i in 0...41{
  10.         
  11.         //配置場所の定義
  12.         var positionX = 15 + 50 * (i % 7);
  13.         var positionY = 80 + 50 * (i / 7);
  14.         var buttonSizeX = 45;
  15.         var buttonSizeY = 45;
  16.         
  17.         //ボタンをつくる
  18.         var button: UIButton = UIButton()
  19.         button.frame = CGRectMake(
  20.             CGFloat(positionX),
  21.             CGFloat(positionY),
  22.             CGFloat(buttonSizeX),
  23.             CGFloat(buttonSizeY)
  24.         );
  25.         
  26.         ----- (※詳しくは後でアップするスライドやgithubをご参考下さい) -----
  27.         
  28.         //配置したボタンに押した際のアクションを設定する
  29.         button.addTarget(self, action: "buttonTapped:", forControlEvents: .TouchUpInside)
  30.         
  31.         //ボタンを配置する
  32.         self.view.addSubview(button)
  33.         mArray.addObject(button)
  34.     }
  35. }

[Objective-Cでのコード]

  1. //カレンダーを生成する
  2. - (void)generateCalendar{
  3.     
  4.     //タグナンバーとトータルカウントを定義する
  5.     int tagNumber = 1;
  6.     int total = 42;
  7.     
  8.     //42個のボタンを配置する
  9.     for(int i=0; i<total; i++){
  10.         
  11.         //配置場所の定義
  12.         int positionX = 5 + 45 * (i % 7);
  13.         int positionY = 90 + 45 * (i / 7);
  14.         int buttonSize = 40;
  15.         
  16.         //42個分のボタンを配置
  17.         UIButton *button = [UIButton new];
  18.         button.frame = CGRectMake(positionX,positionY,buttonSize,buttonSize);
  19.         
  20.         ----- (※詳しくは後でアップするスライドやgithubをご参考下さい) -----
  21.         
  22.         //配置したボタンに押した際のアクションを設定する
  23.         [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
  24.         
  25.         //ボタンを配置する
  26.         [self.view addSubview:button];
  27.         [mArray addObject:button];
  28.     }
  29. }

[注意点]

ソース的や処理に関しては、さほど差はないのですが、下記に挙げた部分が大きく異なる点です。

  1. Objective-C版ではframeの大きさと位置を設定する際はint型の数値を入れていますが、Swift版ではCGFloat型にしないといけなくなりました。

5.要点の解説 (3.動的に配置したボタンを押したときのふるまい)

[解説]

動的にボタンの配置をするまでは良いのですが、実際にボタンを押した際に予定の追加画面を表示させたり等の処理が欲しいところです。
セグエでつないで遷移時に値を渡す方法もありますが、今回はセグエを使わずに実装してみます。

前の解説でも、ちらっと見えたこの部分がそれに該当します。

[Swift]

  1. button.addTarget(self, action: "buttonTapped:", forControlEvents: .TouchUpInside)

[Objective-C]

  1. [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];

上記2つの処理については、動的に配置したボタン(button)が押された際にbuttonTappedというメソッドを読んでねという意味です。
ということでbuttonTappedメソッドを作成します。

[Swiftでのコード]

  1. //カレンダーボタンをタップした時のアクション
  2. func buttonTapped(button: UIButton){
  3.     //@todo:画面遷移等の処理を書くことができます。
  4.     
  5.     //コンソール表示
  6.     println("\(year)年\(month)月\(button.tag)日が選択されました!")
  7. }

[Objective-Cでのコード]

  1. //カレンダーボタンタップ時の処理
  2. -(void)buttonTapped:(UIButton *)button
  3. {
  4.     NSLog(@"%d年%d月%d日が選択されました!", year, month, (int)button.tag);
  5. }

[処理の説明]

今回のハンドメイドカレンダーでは、カレンダーの値が入る場合、ボタンにタグを仕込むようにしてあります。
(タグの値=日の値)となるように処理をしてあるので、ボタンを押すと押した日の日付がコンソールに表示されます。

6.おまけ (Objective-CとSwiftを比べてみて)

Objective-CとSwiftで全く同じ挙動で全く同じ仕様のものを作ってみた際に気になったのは、とにかくこの2点です。

  1. “!”や”?”をつけるタイミングがよくわからん(これでエラーが出てしまった際、僕は「むかつくやつ」と呼んでいます)
  2. 引数とか本当に変わっているやつがないか(今回のカレンダーの部分はその点がハマったところでした)

1. 「むかつくやつ」について

幸いこの部分はXcodeがある程度補完をしてくれるわけなんですが、たまに自分が「ここだ!」と思ってつけたやつがエラーになったりすることがあります。
慣れないうちはXcodeの補完にある程度あやかってみて、それでもまた他の変数で「むかつくやつ」が出てきたら、順番につぶしていく形で良いかなと思います。

2. 「引数とか本当に変わっているやつがある」について

たまたまカレンダーの部分Objective-CとSwiftで差異があったわけですが、Objective-C → Swiftへ移植する時などには「使っているメソッド名 Swift」で検索してみるようにするといいかもしれません。Stack Overflowがヒットすることが多いのですが、だいたいコード例が載っている場合が多いので本当に振る舞いがことなっていないかとかを確認しながら進めてみても良いと思います。

7.最後に

このような長いやつを読んでいただいて、本当にありがとうございました。
サンプルを公開しているとはいえ自分もまだまだビギナーであることには変わりはないと思っています。
今後も「むかつくやつ」にイジワルされながら、楽しんでいければなんて思っています。