文系プログラマの勉強ノート

スマホアプリ開発やデザインなどについて勉強したことをまとめています

【Xcode】アナログ時計を作る

画像を使ってアナログ時計を作ってみます。

プロジェクトの作成

Single View Applicationのプロジェクトを作成します。
名前は「AnalogClock」にしました。

また、必要な画像(文字盤、時針、分針、秒針)をプロジェクトに追加しておきます。
以下のような画像を用意しました。

f:id:an3714106:20140117233848p:plainf:id:an3714106:20140117233852p:plainf:id:an3714106:20140117233855p:plainf:id:an3714106:20140117233857p:plain

Storyboardで画像を配置

画面に画像を配置し、アウトレット変数を用意します。
今回は文字盤、時針、分針、秒針をすべて同じ画像サイズにしているので
ぴったり重なるように配置します。

f:id:an3714106:20140117231644p:plain

@property (weak, nonatomic) IBOutlet UIImageView *backImageView;
@property (weak, nonatomic) IBOutlet UIImageView *secImageView;
@property (weak, nonatomic) IBOutlet UIImageView *hourImageView;
@property (weak, nonatomic) IBOutlet UIImageView *minImageView;

画像を回転させる

今回は秒針があるので、1秒毎に時計を更新する必要があります。
なので viewWillAppear: で更新処理を呼ぶタイマーをセットします。
fireを呼ぶことで即座にタイマーが開始されます。

- (void)viewWillAppear:(BOOL)animated
{
// 1秒ごとに更新処理を呼ぶ
[[NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(reloadClock) userInfo:nil repeats:YES] fire];
}

続いて時計更新処理。

- (void)reloadClock
{
// 現在時刻を取得
NSDate *date = [NSDate date];
// 時、分、秒を取得
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateComps = [calendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:date];

// 回転角度を計算
NSInteger minute = 60 * dateComps.hour + dateComps.minute;
CGAffineTransform rotateHour = CGAffineTransformMakeRotation( 2 * M_PI * minute / (60 * 12));
CGAffineTransform rotateMin = CGAffineTransformMakeRotation( 2 * M_PI * dateComps.minute / 60);
CGAffineTransform rotateSec = CGAffineTransformMakeRotation( 2 * M_PI * dateComps.second / 60);

// アニメーションをつけて実行
[UIView animateWithDuration:0.1f
animations: ^{
self.hourImageView.transform = rotateHour;
self.minImageView.transform = rotateMin;
self.secImageView.transform = rotateSec;
} ];

}

NSDateから時、分、秒を取得する処理はこちらのサイトを参考にしました。ありがとうございます!
 [iOS開発 TIPS] NSDate を使って現在の日付を年・月・日・分・秒単位で取得してみる | ALOG | THE AGE

回転角度の計算ですが、時、分、秒が0の時、回転角度は0°です。
時、分、秒が最大値(時なら12、分、秒なら60)の時、
針は一周して0の位置に来るので回転角度は360°です。
そして、例えば12時30分だったとしたら分針の回転角度は真ん中で180°ですね。
360°×60分の30という計算で求められます。
それをCGAffineTransformMakeRotation用に書くと上記のようになります。
元画像からCGAffineTransformMakeRotationによって指定した角度だけ画像が回転します。

時針は1時間に1回一気に動くのではなく、徐々に動かすため、
分に直してから回転角度を計算しています。
12時間で360° イコール 720分で360°ですので、
例えば1時15分だとしたら360°×720分の75という計算です。

動かしてみた

これでOKと動かしてみたところ…

f:id:an3714106:20140118000558p:plain

針がずれた。

画像の中心を原点に回転するのかと思いきや、違うのでしょうか?
でも回転し続けていると時々元に戻るし、一定の座標が原点になっているとは思えない若干不規則なずれ方をします。

これはAutolayoutのしわざです。
今回不要なので、storyboardのFile InspectorからUse Autolayoutのチェックを外します。

f:id:an3714106:20140118002152p:plain

完成

f:id:an3714106:20140118002854p:plain

ちゃんと動くようになりました。

画像をきちんと作っておけば、プログラムの方はこんなに簡単にできるんですね。
時計の他に、コンパス、車のメーターなども同じ要領でできそうです。

2014.11.3 追記

コメントを頂き、2箇所ソースコードを修正しました。
 ・時、分、秒を取得する箇所の定数(NSYearCalendarUnitなど)を変更
  - iOS8から定数が変更になりました

 ・時針の回転角度計算を変更
  - 以前は1時間に1回針が動く形でしたが、徐々に動くようにしました
  - 秒針、分針はカチカチ動く方が好みなのでそのままです:-)