「UIView」カテゴリーアーカイブ

recursiveDescription


recursiveDescriptionメソッドを使うと、UIViewの中身を階層的に表示できます。そんなメソッドみたことない?そりゃそうです。ドキュメントに記載されてませんから。普通にメソッド呼び出しすると怒られちゃうので、performSelectorを使って呼び出しちゃいましょう。

使用例

NSLog(@"%@", [viewController1.view performSelector:@selector(recursiveDescription)]);

実行結果

<UIView: 0x7665600; frame = (0 0; 320 411); autoresize = W+H; layer = <CALayer: 0x76656b0>>
   | <UILabel: 0x7665770; frame = (20 79; 280 43); text = 'First View'; clipsToBounds = YES; opaque = NO; autoresize = W+BM; userInteractionEnabled = NO; layer = <CALayer: 0x7665830>>
   | <UITextView: 0x90f8000; frame = (20 162; 280 88); text = 'Loaded by the first view
...'; clipsToBounds = YES; opaque = NO; autoresize = W+BM; userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x7667070>; layer = <CALayer: 0x7666580>; contentOffset: {0, 0}>
   |    | <UITextSelectionView: 0x7666850; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x7666920>>
   |    | <UIImageView: 0x7667840; frame = (273 0; 7 88); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x76679f0>>
   |    | <UIWebDocumentView: 0x9100400; frame = (0 0; 280 88); text = 'Loaded by the first view
...'; opaque = NO; userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x715e4d0>; layer = <UIWebLayer: 0x715cf20>>
   |    |    | <TileHostLayer: 0x715d5b0> (layer)
   |    |    |    | <TileLayer: 0x8a99640> (layer)
   |    | <UITextSelectionView: 0x8a8abc0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x8a8ac40>>

便利なメソッドだけど、ドキュメントに記載されていない脱法メソッドなので、公開申請する前にはきっちりコメントアウトするように!

アプリ内のView階層を出力


自分で作ってるアプリのViewの構造がよくわからなくなってしまった。標準UIの中身がどういう構造になっているのか調べてみたい。そんなときは、dumpAllViewsを使ってみよう。画面に表示されているUIViewを渡すと、すべてのUIViewが芋づる式に表示されます。

-(void)dumpAllViews:(UIView *) view{
    UIView * rootView = [self getRootView:view];
    [self showSubViews:rootView depth:0];
}
-(UIView *)getRootView: (UIView *) view{
    while([view superview]){
        view = [view superview];
    }
    return view;
}
-(void)showSubViews:(UIView *) view depth:(int)depth{
    NSLog(@"%@%@ (%lf,%lf) (%lf,%lf)",
          [@"----------" substringToIndex:depth],
          [view class],
          view.frame.origin.x,
          view.frame.origin.y,
          view.frame.size.width,
          view.frame.size.height);
    for(UIView * subview in [view subviews]){
        [self showSubViews:subview depth:depth + 1];
    }
}

実行例
Tabbed Applicationで実行してみるとこんな感じ。ドキュメントに記載されていないクラスがちらほら出ていますね。

UIViewDumper[14013:c07] UIWindow (0,0) (320,480)
UIViewDumper[14013:c07] -UILayoutContainerView (0,0) (320,480)
UIViewDumper[14013:c07] --UITransitionView (0,0) (320,431)
UIViewDumper[14013:c07] ---UIViewControllerWrapperView (0,20) (320,411)
UIViewDumper[14013:c07] ----UIView (0,0) (320,411)
UIViewDumper[14013:c07] -----UILabel (20,79) (280,43)
UIViewDumper[14013:c07] -----UITextView (20,162) (280,88)
UIViewDumper[14013:c07] ------UITextSelectionView (0,0) (0,0)
UIViewDumper[14013:c07] ------UIImageView (273,0) (7,88)
UIViewDumper[14013:c07] ------UIWebDocumentView (0,0) (280,88)
UIViewDumper[14013:c07] ------UITextSelectionView (0,0) (0,0)
UIViewDumper[14013:c07] --UITabBar (0,431) (320,49)
UIViewDumper[14013:c07] ---_UITabBarBackgroundView (0,0) (320,49)
UIViewDumper[14013:c07] ---UIImageView (0,-3) (320,3)
UIViewDumper[14013:c07] ---UITabBarButton (2,1) (156,48)
UIViewDumper[14013:c07] ----UITabBarSelectionIndicatorView (0,2) (156,44)
UIViewDumper[14013:c07] ----UITabBarSwappableImageView (54,2) (48,32)
UIViewDumper[14013:c07] ----UITabBarButtonLabel (67,34) (22,13)
UIViewDumper[14013:c07] ---UITabBarButton (162,1) (156,48)
UIViewDumper[14013:c07] ----UITabBarSwappableImageView (54,2) (48,32)
UIViewDumper[14013:c07] ----UITabBarButtonLabel (60,34) (37,13)

UITabBarのタブを画面の上部に配置する方法


UITabBarではTabは画面下部に表示される。Tabを上側に表示するためには以下のようにすればよい。
・TabBarを上に移動する
tabBarController.tabBar の位置を調整すればよい
・コンテンツ表示部を下に移動する
コンテンツを表示している部分のViewは公式ドキュメントに記載されていないので、どうにかこうにか頑張って見つけて、移動させる。非公開なオブジェクトを操作するのはルール違反なので、審査に落ちるかもしれません。自己責任でお願いします。

2013-02-16_1653

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
UIViewController *viewController1, *viewController2;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
viewController1 = [[MasterDetailFirstViewController alloc] initWithNibName:@"MasterDetailFirstViewController_iPhone" bundle:nil];
viewController2 = [[MasterDetailSecondViewController alloc] initWithNibName:@"MasterDetailSecondViewController_iPhone" bundle:nil];
} else {
viewController1 = [[MasterDetailFirstViewController alloc] initWithNibName:@"MasterDetailFirstViewController_iPad" bundle:nil];
viewController2 = [[MasterDetailSecondViewController alloc] initWithNibName:@"MasterDetailSecondViewController_iPad" bundle:nil];
}
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = @[viewController1, viewController2];
self.window.rootViewController = self.tabBarController;

//ここから追記
//TabBarのサイズと、全体のサイズを取得
float t_hight = self.tabBarController.tabBar.frame.size.height;
float w_width = self.window.frame.size.width;
float w_height = self.window.frame.size.height;

//TabBarを移動
self.tabBarController.tabBar.frame = CGRectMake(0.0f, 20.0f, w_width, t_hight);

//コンテンツ表示部を移動
UIView * contentView = [[[self.tabBarController.tabBar superview] subviews] objectAtIndex:0];
contentView.frame = CGRectMake(0, t_hight, w_width, w_height - t_hight);
//ここまで追記

[self.window makeKeyAndVisible];
return YES;
}

UITextViewの文章を選択禁止


UITextViewを継承したクラスを作成し、canBecomeFirstResponderをオーバーライドすればよい。

UnselectableTextView.h

#import <UIKit/UIKit.h>
@interface UnselectableTextView : UITextView
@end

UnselectableTextView.m

#import "UnselectableTextView.h"
@implementation UnselectableTextView
-(BOOL)canBecomeFirstResponder{
    return NO;
}
@end

参考
UITextViewの虫めがねをとってしまおうiPhone開発

画面を回転させる方法


iPhoneやiPadで、端末を物理的に回転させずに、画面だけ回転させたいということありませんか?例えば、ターン制のゲームで、二人で向かい合ってプレイする場合などです。

デバイスの向きは読み取り専用のプロパティーで変更できません。そこで、画面全体を包含しているUIViewのtransformを設定することで画面を回転させます。後半部分はステータスバーの場所を切り替えています。ランドスケープ(横置き)の場合はこのままコピペで大丈夫です。ポートレート(縦置き)の場合は、適宜向きを書き換えてご利用ください。

-(void)flip{
    self.view.transform = CGAffineTransformRotate(self.view.transform, 3.1415926535);
   
    if([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight){
        [UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeLeft;
    }else{
        [UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeRight;
    }
}

参考
How to rotate iPad screen upside down

modalViewを表示する/取り除く


modalViewControllerの使い方メモ。

新しいViewをmodalViewとして表示する

UIViewControllerのpresentModalViewController:animated:を使うと、新しいViewを現在のViewの上に表示できる。

[self presentModalViewController: myViewController animated:YES];

modalViewを取り除く

親のViewからmodalViewを取り除けばよい。

[self.parentViewController dismissModalViewControllerAnimated:YES];

おまけ

トランジションのアニメーションを変更できる。UIModalTransitionStyleFlipHorizontalは水平方向に回転。

myViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController: myViewController animated:YES];

クラスごとのtintColor実装バージョン


iOS5.0以降では、tintColorを利用できるクラスが増えました。iOS4系以前との互換性を保つためには、新規導入されたtintColorを使わないか、tintColorが実装されていない場合の代替処理を記述しておく必要があるので注意が必要です。

iOS 5.0以降

以下の3つのクラスではiOS5.0以降でtintColorを設定できます。

  • UIBarButtonItem
  • UIButton
  • UITabBar

    iOS2.0以降

    以下の4つのクラスではiOS2.0以降でtintColorを設定できます。

  • UINavigationBar
  • UISearchBar
  • UISegmentedControl
  • UIToolbar
  • UIViewの階層構造を解析


    これはちょっとグレーな話題です。UIViewのsubViewメソッドを実行すると、そのView内に含まれているViewの一覧を取得できます。再帰的に実行すれば、全てのViewの構造がわかります。これを使うとフレームワークに含まれている特殊なViewの中もみごとに解析できちゃいます。

    SubViewをすべて表示するためのメソッド

    2番目のメソッドを呼び出すと、1番目のメソッドが実行され、階層構造がコンソールに表示されます。なお、20階層以上を超える場合はエラーが発生するのでご注意下さい。

    -(void)dumpSubViews: (UIView*) view depth: (int) depth{
        NSString * indent = [@"--------------------" substringToIndex:depth];
        NSLog(@"%@%@", indent, view);
        for(UIView * subView in [view subviews]){
            [self dumpSubViews:subView depth:depth + 1];
        }
    }
    -(void)dumpSubViews: (UIView*) view{
        [self dumpSubViews:view depth:0];
    }

    テスト用のコード

    例によってEmpty Applicationをベースに作成しました。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        self.window.backgroundColor = [UIColor whiteColor];

        UIViewController * viewCtr = [[UIViewController alloc] init];
        UINavigationController * navCtr = [[UINavigationController alloc] initWithRootViewController:viewCtr];
        self.window.rootViewController = navCtr;
        viewCtr.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"info" style:UIBarButtonItemStylePlain target:nil action:nil];
       
        navCtr.navigationBar.tintColor = [UIColor redColor];   
       
        [self dumpSubViews:navCtr.navigationBar];
       
        [self.window makeKeyAndVisible];
        return YES;
    }

    実行結果

    View内部の構造が丸裸になりました。この情報はドキュメントに記載されていないものなので、将来的に、警告無く変更される可能性があるのでご注意下さい。この手法では、ドキュメントに記載されているAPIしか使っていませんが、やってることはリバースエンジニアリングの類なので、アプリの承認に影響を及ぼす可能性があります。

    2011-11-18 00:04:47.531 UIViewHack[9920:f803] <UINavigationBar: 0x6dac460; frame = (0 20; 320 44); autoresize = W; layer = <CALayer: 0x6dcddc0>>
    2011-11-18 00:04:47.532 UIViewHack[9920:f803] -<UINavigationBarBackground: 0x6da4970; frame = (0 0; 320 44); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6da51d0>> - (null)
    2011-11-18 00:04:47.533 UIViewHack[9920:f803] -<UINavigationItemView: 0x6dafcb0; frame = (160 21; 0 0); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6dad340>>
    2011-11-18 00:04:47.533 UIViewHack[9920:f803] -<UINavigationButton: 0x6dbe410; frame = (267 7; 48 30); opaque = NO; layer = <CALayer: 0x6dbbb30>>
    2011-11-18 00:04:47.534 UIViewHack[9920:f803] --<UIButtonLabel: 0x6dbf730; frame = (0 0; 0 0); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6dc0990>>