2015/07/14

ナビゲーションバーの高さを変更する


Storyboardで作成した画面のナビゲーションバーの高さを変更したくて色々調査したので、結果をまとめておきます。

  • まず、storyboardで画面遷移を定義します。
    当然 Navigation Controllerを配置します。

  • NavigationBarのカスタムクラスを作成します。

-sizeThatFits: でNavigationBarのサイズを返します。
defaultのheightは 44.0 です。

また、高さを拡大すると、すべてのsubviewの上部分がその分だけ空いてしまうので、NavigationBarの中央に来るよう位置を変更します。

@implementation XXCustomNavigationBar

- (CGSize)sizeThatFits:(CGSize)size
{
  CGSize barSize = [super sizeThatFits:size];
  barSize.height += 20.0f; // 高さを変更
  return barSize;
}

- (void)layoutSubviews
{
  [super layoutSubviews];

  // vertically center
  float centerY = self.bounds.size.height / 2.0f;
  for (UIView *view in self.subviews) {
    // 位置変更
    CGPoint center = view.center;
    center.y = centerY;
    view.center = center;
  }
}
@end
  • 作成したカスタムクラスをNavigation Controllerに紐付けます

Storyboard にて Navigation Controller Scene -> Navigation Controller -> Navigation Bar を選択します。

Custom Class で 作成したカスタムクラス (この例では XXCustomNavigationBar) を指定します。


これで実行すると、Navigation Barが指定した高さで表示されるはずです。


2015/07/13

UIScrollViewのスクロール関連のプロパティについて

UIScrollView のスクロール関連のプロパティについて。


スクロール方向を固定する

斜め方向のスクロールを制限する場合、UIScrollViewdirectionalLockEnabledYES をセットする。

ただ、まれに斜め方向にスクロールしてしまう (正確に斜め方向にドラッグされた場合?)。
ドラッグ開始前後の位置を取得し、移動後にスクロール位置を矯正するよう実装する。

以下の UIScrollViewDelegate を使用する。

  • - (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView
  • - (void)scrollViewDidScroll:(UIScrollView*)scrollView

参考: http://iphone-dev.g.hatena.ne.jp/tokorom/20101002/1285998723



追記

- (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView だけでスクロール方向を取得する方法があったので、参考リンクを追加

UITableView/UIScrollViewでスクロール直後のスクロール方向を取得する



スクロール速度を変更する

正確にはスクロールが減速するスピードの比率。
UIScrollViewdecelerationRateUIScrollViewDecelerationRateFast を設定すると減速するスピードが早くなる (スクロール停止が早くなる→スクロール速度が遅くなったように見える)

scrollView.decelerationRate = UIScrollViewDecelerationRateFast;


参考: UIScrollView Class Reference



2015/07/06

UIImageViewにimageを設定する際に例外が発生する?

UIImageViewにimageを設定する際に例外!?

他人が作ったアプリのバグ修正依頼を受けた。

デバッグ実行してみると、
UIImageViewimageUIImage を設定しようとして、例外が発生していた。

[XxxxxView setImage:]: unrecognized selector sent to instance xxxxxxxxxx

unrecognized selector なので、メソッド名の指定に間違いがあるようだが…
ん、UIImageView でなく XxxxxView (該当画面のViewController) になってる?

ロジックを見てると、予め作成された UIImageView[self viewWithTag: tagNo] で取得していたんだが、条件によって tagNo0 になるパターンがあった。

for (int i=0; i<kX; i++) {
  for (int j=0; j<kY; j++) {
    NSInteger tagNo = [self getTagNoWithX:i Y:j]; // <- ここがおかしい
    UIImageView *img = (UIImageView *)[self viewWithTag: tagNo];
    img.image = [UIImage imageNamed:@"icon-name"];
  }
}

[self viewWithTag: 0] の時に 自分自身 (self) が返ってきてしまう模様。
適切なTagNoを返すように修正した。

2015/07/02

UIPopoverPresentationControllerが閉じる際にタップを連打するとアプリが落ちることへの対策

不具合内容

iPadアプリで、あるUITextFieldが選択されると、UIPopoverPresentationControllerでTableViewを表示して選択肢の中からユーザーに項目を選ばせる、という画面を実装していたところ、以下の様な不具合が発生した。

  • タップを連打すると、popoverがcloseするタイミングでエラーも出ずにアプリが落ちる
  • iOS 8.3, 8.4 では発生しない

最新のOSバージョンでは発生しないのでiOSの不具合と思われるが、どうしても対応して欲しいとのリクエストをお客さんからいただいたので、検討してみた。


対応方法

あまり時間を掛けたくなかったので、かなり場当たり的な対応だが、以下の方法で不具合を回避できた。

  • (BOOL)popoverPresentationControllerDidDismissPopover: にて、beginIgnoringInteractionEvents でタッチイベントを無効化する
  • 500ms後に endIgnoringInteractionEvents でタッチイベントを有効に戻す

コード例

#pragma mark - タップ連打防止
/**
 * iOS8.3未満の場合、タップを無効化する
 */
- (void)preventTapBarrage:(float)waitTime
{
  // iOS8.3未満の場合
  if ([[UIDevice currentDevice].systemVersion floatValue] < 8.3){
    // タッチイベントを無効にする
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];

    // 指定ms後、タッチイベントを有効に戻す
    [NSTimer scheduledTimerWithTimeInterval:waitTime
                                     target:self
                                   selector:@selector(enableTapEvents:)
                                   userInfo:nil
                                    repeats:NO];
  }
}

/**
 * タップを有効に戻す
 */
- (void)enableTapEvents:(NSTimer *)timer
{
  // タッチイベントが無効になっている場合
  if ([[UIApplication sharedApplication] isIgnoringInteractionEvents]){
    // タッチイベントを有効にする
    [[UIApplication sharedApplication] endIgnoringInteractionEvents];
  }
}

#pragma mark - UIPopoverPresentationControllerDelegate Methods
/**
 * iOS8.3未満において、popoverのclose時にタップ連打するとアプリが落ちるため
 * 一時的 (500ms) にタッチを無効化する
 */
- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController
{
  // 500msタッチ無効 (iOS version < 8.3 の場合のみ)
  [self preventTapBarrage:0.5f];
  return YES;
}