2013年10月25日金曜日

iOS7のナビゲーションバーを考慮した位置の模索

iOS7になって、画面領域がステータスバー(20px)も含むようになりました。
これにより、ナビゲーションバーの高さは、iOS6で44px, iOS7で64pxになります。

Storyboardで、Adjust Scroll View Insets にチェックがついている場合、
UITableViewやUICollectionViewのようなUIScrollViewを継承したものをViewの一番目に配置すると、コンテンツ領域が自動的にナビバーの下から表示されるようになります。
(Insetsが自動的に設定される)



Adjust Scroll View Insets のチェックを外すと、

こんな感じに。

UITableViewなどは、Storyboard上でも上手くレイアウトしてくれるのでデザインしやすいのですが、UIScrollViewを使った場合にはStoryboard上ではレイアウトしてくれない。

レイアウト時には、Storyboard上のView asの設定を 「iOS6.1 and Earlier」にすると NavigationBarがある状態でもY位置が0pxの位置からデザインができる
(他に良い方法があるのだろうか....?)

そもそも、縦スクロールはない とか、スクロールしてもナビゲーションバーの下に表示されなくてもいい のであれば、Storyboard上のViewControllerの設定で Under Top Bars のチェックを外せばOK。
0,0 の位置がナビゲーションバーの下に表示されるのでレイアウトしやすくなります。


多分、Under Bottom Bars はToolBarかな?

iOS6の時は44px...とコードに直接書きたくはないし、3.5inch/4inchで画面が違うときには...と考えたくもない。というわけで、Storyboard上でAutoLayoutを使いつつ頑張ってレイアウトする訳ですが、動的にUIScrollViewにViewを追加する場合もある。

AutoLayoutを使わず、Autosizingを使えば簡単にできそうと思う所も、AutoLayoutを使いたいのだ。という場面に まぁ なる。

AutoLayoutをプログラムで指定するには、NSLayoutConstraintをを作って追加します。

例:
        [contentView addConstraint:
         [NSLayoutConstraint constraintWithItem:customView
                                      attribute:NSLayoutAttributeWidth
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:contentView
                                      attribute:NSLayoutAttributeWidth
                                     multiplier:1
                                       constant:0]];
        
        
        [contentView addConstraint:
         [NSLayoutConstraint constraintWithItem:customView
                                      attribute:NSLayoutAttributeTop
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:contentView
                                      attribute:NSLayoutAttributeTop
                                     multiplier:1

                                       constant:0]];

        [contentView addConstraint:
         [NSLayoutConstraint constraintWithItem:customView
                                      attribute:NSLayoutAttributeHeight
                                      relatedBy:NSLayoutRelationEqual
                                         toItem:contentView
                                      attribute:NSLayoutAttributeHeight
                                     multiplier:1
                                       constant:0]];


UIScrollViewに動的にViewを追加した場合、ContentSizeを変更する必要があるが、このタイミングも間違えてはいけない。

UIViewController#loadView などでは、まだ正確なレイアウトはできていない。
loadViewで UIScrollView#setContentSize をしても、Adjust Scroll View Insetsの機能がその後に実装されて設定が有効にならない。

AutoLayout, Adjust Scroll View Insets が反映された後のタイミングは、
UIViewController#viewDidLayoutSubviews になりますので、ここで処理を入れます。

このタイミングで、UIScrollViewの情報を見ると、iOS7の場合だと
UIScrollView#contentInsets は {64, 0, 0, 0}
UIScrollView#contentOffset は {0, -64}
に自動的に設定されている。

ということで、UIScrollViewに設定するcontentSizeについてはInsetsの値を考慮した値にします。
たとえば、縦はScrollViewの高さと同じにしたい場合は、
scrollView.bounds.size.height - contentInsets.top
という形で。

contentOffsetを指定する場合も、 y = -64 というデフォルト値が入っていることを考慮するのを忘れずに。


iOS7から
UIViewControllerに、topLayoutGuide, bottomLayoutGuide のプロパティが追加されていて、topLayoutGuide の値に 64 が入っていました。

iOS6ではこのプロパティは使えないので、このプロパティがなかったら0(+NavigationBar)、あったらその値 という感じで、Contents領域の上の位置を取得する 感じで位置取得も可能そう..なのかな?



• • •

2013年10月9日水曜日

続・iOSの日付処理まとめ

Timeletを作成したときに、またもや日付周りでいろいろ悩みました。
そのネタをまとめてみます。
以前日付関連でまとめたブログ記事→ iOSの日付処理まとめ

日付時間フォーマットをiOS設定通りで表示する


NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle: NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
NSLog(@"%@", [dateFormatter stringFromDate:[NSDate date]]);

NSDateFormatterStyle はDate, Time毎に以下から選択します。
  •   NSDateFormatterNoStyle
  •   NSDateFormatterShortStyle
  •   NSDateFormatterMediumStyle
  •   NSDateFormatterLongStyle
  •   NSDateFormatterFullStyle
結果は以下の通り。

書式カレンダー24時間表示結果
Japan西暦2013/10/09 1:08
Japan西暦×2013/10/09 午前1:11
Japan和暦×H25/10/09 1:12
United States西暦Oct 9, 2013, 1:16
United States西暦×Oct 9, 2013, 1:13 AM
United States和暦×Oct 9, 25 Heisei, 1:14 AM

日本だと 0:00 AM である時刻表示は、他の国だと 12:00 AM なんですよね。
12時間表示でも、国によって "01:00 AM" の所もあれば、"1:00 AM" の所もある様子。

知らなかった! 忘れてた! 各国の書式の差をカバーしてくれます。
ただ、文字の長さがどれくらいになるかは設定依存になるので、文字がはみ出さないように画面上にレイアウトするように気をつける必要があります。


日付フォーマット書式


setDateStyle, setTimeStyle ですが、内部ではDateFormatの値に該当する書式を設定してくれています。その結果を見ることで書式の設定方法が分かります。

書式が日本、カレンダーが和暦、24時間表示OFF だと

NSDateFormatter *dateFormatter = [[NSDateFormatter allocinit];
[dateFormatter setDateStyle: NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
NSLog(@"%@", dateFormatter.dateFormat);

の結果は、
GGGGGyy/MM/dd aK:mm 
になります。

指定の文字は何を表しているのかは、LDML DateFormatPatterns で確認できます。

たとえば、時間は
h : 1 〜 12 の12時間表示。
H : 0 〜 23 の24時間表示。
K : 0 〜 11 の12時間表示。
k : 1〜 24 の24時間表示。
hh, HH など2つ重ねると 0Padding です。

書式を固定する


書式に固定したい場合は、NSLocaleを指定します。

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle: NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
NSLog(@"端末の書式:[%@]", dateFormatter.dateFormat);

NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
dateFormatter.locale = locale;
NSLog(@"en_US指定書式:[%@]", dateFormatter.dateFormat);
NSLog(@"結果 : %@", [dateFormatter stringFromDate:[NSDate date]]);

上記の結果は以下。(書式は日本、和暦の設定の場合)

端末の書式:[GGGGGyy/MM/dd H:mm]
en_US指定書式:[MMM d, y, h:mm a]
結果 : Oct 9, 2013, 1:48 PM

日付フォーマットとLocaleを一緒に指定することも可能です。

NSDateFormatter *dateFormatter = [[NSDateFormatter allocinit];
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
dateFormatter.locale = locale;
[dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"
yyyyMMdd hmma" options:0 locale:locale]];
NSLog(@"%@", [dateFormatter stringFromDate:[NSDate date]]);

結果↓
10/09/2013, 2:01 PM

書式を固定すると、各国の表示形式に合わせることはできないので注意。
たとえば、日本でも 0:00 AM でなく、12:00 AM の表示になります。
(hなのか、Kなのか という書式のところ)

常に12時間表示の時刻を表示する


12時間表示の日付フォーマットは @"hhmm a"になるので、これを指定すれば12時間表示を期待するのですが、上手くいかないパターンもあります。

NGパターン1
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"h:mm a"];
NSLog(@"%@", [dateFormatter stringFromDate:[NSDate date]]);

書式カレンダー24時間表示結果
Japan西暦12:36 午前
Japan西暦×12:36 午前
United States西暦0:36
United States西暦×12:36 AM

NGパターン2
NSDateFormatter *dateFormatter = [[NSDateFormatter allocinit];

NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
[dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"hmm a" options:0 locale:locale]];
NSLog(@"%@", [dateFormatter stringFromDate:[NSDate date]]);

書式カレンダー24時間表示結果
Japan西暦12:36 午前
Japan西暦×12:36 午前
United States西暦0:36
United States西暦×12:36 AM

NGパターン3
NSDateFormatter *dateFormatter = [[NSDateFormatter allocinit];

NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
dateFormatter.locale = locale;
[dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"hmm a" options:0 locale:
[NSLocale currentLocale]]];
NSLog(@"%@", [dateFormatter stringFromDate:[NSDate date]]);

書式
カレンダー
24時間表示
結果
Japan
西暦
AM0:36
Japan
西暦
×
AM0:36
United States
西暦
0:36
United States
西暦
×
12:36 AM

OKパターン
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
dateFormatter.locale = locale;
[dateFormatter setDateFormat:[NSDateFormatter dateFormatFromTemplate:@"hmm a" options:0 locale:locale]];
NSLog(@"%@", [dateFormatter stringFromDate:[NSDate date]]);

書式
カレンダー
24時間表示
結果
Japan
西暦
12:36 AM
Japan
西暦
×
12:36 AM
United States
西暦
12:36 AM
United States
西暦
×
12:36 AM


端末の設定で、書式を日本にしていると一見上手くいっているのですが、違う国に設定すると上手くいかないのです。
これにハマった!

OKパターンは、en_US書式固定したパターンです。
端末設定に依存せず12時間表示 かつ 表示形式は端末設定の書式を使う
ができると嬉しいのですが、その方法がみつかりませんでした。
NGパターン3が惜しかったんだけどなー。

12時間表示の設定かどうかを判断する


+ (BOOL)timeIs24HourFormat {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setLocale:[NSLocale currentLocale]];
    [formatter setDateStyle:NSDateFormatterNoStyle];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    NSString *dateString = [formatter stringFromDate:[NSDate date]];
    NSRange amRange = [dateString rangeOfString:[formatter AMSymbol]];
    NSRange pmRange = [dateString rangeOfString:[formatter PMSymbol]];
    BOOL is24Hour = (amRange.location == NSNotFound && pmRange.location == NSNotFound);
    return is24Hour;

}
• • •

時差時計 Timelet リリースしました!

8月に Flask LLPを +Takako Horiuchi さんと共に設立しました。
まだApple開発者登録が完了できていない状態なのですが、第一弾アプリをリリースしました!

時差時計 Timelet


各国で開催されるイベントもYouTubeなどでリアルタイムにOnAirされる時代。
日本にずっといてても、サンフランシスコでこの時間は日本だと何時なんだ?
と思う事が多くなりました。

リアルタイムにイベントと、関連ツイートをみつつ、みんなとお祭りのように盛り上がるってのはとっても楽しいですよねー。
翌日になれば、まとめ記事ができるわけなんですが、その頃には熱が冷めちゃってたりしてたりw

そんなお祭りに参加する時間を知るために、いままでは、

  1. iPhoneの設定で時間帯をサンフランシスコに変更し、日付時刻をイベント時刻に変更する。
  2. その後、時間帯を日本に戻して日付時刻をみる。

...で時間を確認していました。

時差は知っているものの、サマータイムっていつからいつまでだっけ?とか考えて計算するのが面倒なので、なにかに頼らないと無理...。

そんなあなたにこのアプリ。
各国の現在時刻が表示される他に、日/時間 を変更してその時の日本時間が確認できます。

これで、
時刻を早めたから、いろんな通知が一気にきたーー
iPhoneの時刻を元に戻すの忘れてたーー
なんてことは起こらないはず。(よくやってました)

機能はこれだけのプチアプリですが、スッキリデザインで見てて気持ちよいアプリになってると思います。
私も、行移動がD&Dでできるように実装を頑張りましたよっ
あと、日付周りに再びハマったりと....そのネタは次回のブログに。

良ければお使いくださいませー。



• • •