【サンプルコード有】スライドするサイドメニューを表示させる

iPhone

アプリでよくあるUIで、ボタンをタップすると、画面外から横スライドして表示されるメニューを実装したいと思います。
今回はスライドしたメニューが画面に被さるように表示させてみます。

まず、スライドさせるビューをつくります。
今回はただのViewを使いますが、UITableViewの方がいいかと思います。

RightSlideMenuView.h

 @interface RightSlideMenuView : UIView
 @end

RightSlideMenuView.m

 @implementation RightSlideMenuView
 
 - (id)initWithFrame:(CGRect)frame
 {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.frame = CGRectMake( [UIScreen mainScreen].bounds.size.width, 0, 160, 568);
        self.backgroundColor = [UIColor yellowCollor];
        UILabel* title = [[UILabel alloc] initWithFrame:CGRectMake(30, 100, 100, 100)];
        title.text = @"スライドメニューが表示されました";
        title.backgroundColor = [UIColor clearColor];
    }
    return self;
 }

次に、メニューを表示させたいViewControllerをつくります。

 #import "RightSlideMenuView.h"
 
 @interface MainViewController ()
 {
    RightSlideMenuView* sideMenuView_;
    UIView* viewForClosingSideMenu_;
 } 
 
 - (void)viewDidLoad
 {
    [super viewDidLoad];
    sideMenuView_ = [[RightSlideMenuView alloc] initWithFrame: self.view.frame];
    [self.view addSubview: sideMenuView_];
 }
 
 //スライドさせてメニューを表示させる
 - (IBAction)showSideMenu:(id)sender
 {
    // show side menu with animation
    CGRect sideMenuFrame = sideMenuView_.frame;
    [UIView animateWithDuration:0.3f
                          delay:0.0f
                        options:UIViewAnimationOptionCurveEaseIn
                     animations:^{
                         // アニメーションをする処理
                         sideMenuView_.frame = CGRectMake( insideSideMenuX,
                                                          sideMenuFrame.origin.y,
                                                          sideMenuFrame.size.width,
                                                          sideMenuFrame.size.height);
                     } completion:^(BOOL finished) {
                         // アニメーションが終わった後実行する処理
                     }];
    
    // メニュー外をタップしたら、メニューを閉じるようにする
    // そのためのUIViewをメニュー外に設置し、これをタップしたらメニューを閉じるようにする
    if (!viewForClosingSideMenu_)
    {
        viewForClosingSideMenu_ = [[UIView alloc] initWithFrame:
                                   CGRectMake(0,
                                              0,
                                              self.view.frame.size.width
                                              - sideMenuFrame.size.width,
                                              self.view.frame.size.height)];
        viewForClosingSideMenu_.backgroundColor = [UIColor clearColor];
        UITapGestureRecognizer *closeSideMenuTap =
        [[UITapGestureRecognizer alloc] initWithTarget:self
                                                action:@selector(closeSideMenu:)];
        [viewForClosingSideMenu_ addGestureRecognizer:closeSideMenuTap];
        [self.view addSubview: viewForClosingSideMenu_];
    }
 }
 //メニュー外をタップしたときに呼び出されるメソッド
 -(void)closeSideMenu:(int)destination
 {
    CGRect sideMenuFrame = sideMenuView_.frame;
    [UIView animateWithDuration:0.3f
                          delay:0.0f
                        options:UIViewAnimationOptionCurveEaseIn
                     animations:^{
                         // アニメーションをする処理
                         sideMenuView_.frame = CGRectMake( outsideSideMenuX,
                                                          sideMenuFrame.origin.y,
                                                          sideMenuFrame.size.width,
                                                          sideMenuFrame.size.height);
                     } completion:^(BOOL finished) {
                         // アニメーションが終わった後実行する処理
                     }];
    
    // メニューを閉じるためのUIViewを削除
    if (viewForClosingSideMenu_)
    {
        [viewForClosingSideMenu_ removeFromSuperview];
        viewForClosingSideMenu_ = nil;
    }
 }

【サンプルコードあり】URLから画像を非同期に取得する2

iPhone

【サンプルコードあり】URLから画像を非同期に取得するの続きです。

前回は、URLから画像をとってきてローディングさせるためのUIImageViewの専用サブクラス、LoadingImageViewを作りました。

次に他クラスで、LoadingImageViewを使って、非同期にURLから画像を取得、表示させる方法です。
また、ローディング中は画像が表示される場所にインジケータアニメーション画像が表示されます。

ストーリーボード編

まず、StoryBoardで、UIImageViewを置き、そのオブジェクトのクラスをLoadingImageViewクラスに変更します。

プログラム編

Sample.h

 #import "LoadingImageView.h"
 @interface Sample : UIViewController
 @property (retain, nonatomic) LoadingImageView *loadingImageView;
 

Sample.m

 _loadingImageView.imageUrl = [NSURL URLWithString: imageURLStr];
 [_loadingImageView startLoadImage];

プロパティのimageUrlに、画像のURLをセットします。
次に、startLoadImageメソッドで、画像を非同期で取得してきます。
その際、画像が取得、表示されるまでシンジケータが表示されます。

今回はmmasashi にお世話になりました。
参考にしてください。
http://d.hatena.ne.jp/mmasashi/20110924/1316940621

【サンプルコードあり】URLから画像を非同期に取得する

iPhone

画像をURLから取得するとき、非同期で行うようにしたいとおもいます。
まず、専用のImageViewのサブクラスをつくります。

LoadingImageView.h

 @interface LoadingImageView : UIImageView
 
 - (id)initWithFrame:(CGRect)frame withUrl:(NSURL *)url;
 
 // Start load the image of the specified url.
 // Doesn't load the image if an image has been already set. 
 - (void)startLoadImage;
 
 // Reload load the image of the specified url.
 // Load the image even if a connection was already created.
 - (void)reloadImage;
 
 // Cancel loading if requesting.
 - (void)cancelLoading;
 
 // Set the url of the image to reqeust.
 @property (nonatomic, retain) NSURL *imageUrl;
 
 @end

LoadingImageView.m

 #import "LoadingImageView.h"
 
 typedef enum LazyImageViewTag_ {
    LazyImageViewTagIndicatorView = 1,
 } LazyImageViewTag;
 
 @interface LoadingImageView ()
 
 - (void)setLoadingImage;
 - (void)setLoadErrorImage;
 
 @property (nonatomic, retain) NSURLConnection *connection;
 @property (nonatomic, retain) NSMutableData *imgData;
 
 @end
 
 @implementation LoadingImageView
 
 - (id)initWithFrame:(CGRect)frame withUrl:(NSURL *)url
 {
    self = [super initWithFrame:frame];
    
    if (self) {
        [self setImageUrl:url];
    }
    
    return self;
 }
 
 - (void)startLoadImage
  {
    
    if (self.image) return;
    
    if (self.connection)
    {
        [self.connection cancel];
    }
    
    [self setImgData:[NSMutableData data]];
    
    NSURLRequest *req = [NSURLRequest requestWithURL:self.imageUrl];
    NSURLConnection *con = [NSURLConnection connectionWithRequest:req delegate:self];
    [self setConnection:con];
    
    [self setLoadingImage];
    
 }
 
 
 - (void)reloadImage
 {
     [self setImage:nil];
    [self startLoadImage];
 }
 
 - (void)cancelLoading {
    [self.connection cancel];
    [self setConnection:nil];
     
    if (self.image == nil) {
        [self setLoadErrorImage];
    }
 }
   
 #pragma mark - NSURLConnectionDelegate
 
 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
 {
     [self.imgData appendData:data];
 }
 
 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
 {
    [self setLoadErrorImage];
 }
 
 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
 {
    UIActivityIndicatorView *iv = (UIActivityIndicatorView *)
    [self viewWithTag:LazyImageViewTagIndicatorView];
    if (iv) [iv removeFromSuperview];
    
    [self setImage:[UIImage imageWithData:self.imgData]];
 }
 
 #pragma mark -
 
 - (void)setLoadingImage
 {
    UIActivityIndicatorView *iv = (UIActivityIndicatorView *)
    [self viewWithTag:LazyImageViewTagIndicatorView];
    if (iv == nil)
    {
        iv = [[UIActivityIndicatorView alloc]
 initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
 //        [iv setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
        [iv setTag:LazyImageViewTagIndicatorView];
        [self addSubview:iv];
    }
    iv.frame = self.frame;
    iv.center = self.center;
    [iv startAnimating];
    [iv setHidden:NO];
    
    //[self setBackgroundColor:[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.3]];
    [self setBackgroundColor:[UIColor clearColor]];
 }
 
 - (void)setLoadErrorImage
 {
    UIActivityIndicatorView *iv = (UIActivityIndicatorView *)
    [self viewWithTag:LazyImageViewTagIndicatorView];
    [iv removeFromSuperview];
    
    [self setImage:nil];
    //[self setBackgroundColor:[UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.4]];
    [self setBackgroundColor:[UIColor blackColor]];
 }
 
 #pragma mark -
 
 - (void)dealloc
 {
    [self setImageUrl:nil];
    [self.connection cancel];
    [self setConnection:nil];
    [self setImgData:nil];
 }
 
 @synthesize imageUrl;
 @synthesize connection;
 @synthesize imgData;
 
 @end

今回はmmasashi にお世話になりました。
参考にしてください。
http://d.hatena.ne.jp/mmasashi/20110924/1316940621

続きは、【サンプルコードあり】URLから画像を非同期に取得する2です。
他クラスからLoadingImageViewを使う方法です。

【サンプルコードあり】NSArrayをNSDictionaryの特定キーの値でソート

iPhone

NSDictionaryを含むNSArrayを、NSDictionaryの特定のキーでソートしたい時があるかと思います。

例えば、NSDctionaryの数値が値になる「English」というキーで配列をソートしてみます。
誰が英語の点数が良かったかを出力しようかと思います。

まず、教科ごとの点数と名前を持つNSDictionaryの配列を用意

    NSDictionary* yamachan = [[NSDictionary alloc] initWithObjects: @[@"山ちゃん", @"80", @"30"] 
                                                                                          forKeys: @[@"Name", @"English", @"Mathmatics"]];
    NSDictionary* okamato = [[NSDictionary alloc] initWithObjects: @[@"岡本", @"67", @"60"] 
                                                                                        forKeys: @[@"Name", @"English", @"Mathmatics"]];
    NSDictionary* yamane = [[NSDictionary alloc] initWithObjects: @[@"山根", @"40", @"20"] 
                                                                                      forKeys: @[@"Name", @"English", @"Mathmatics"]];
    NSArray* students = @[yamachan, okamato, yamane];

次にこの配列を英語の点数順にソートします。

    NSArray* sorted_students = [students sortedArrayUsingFunction:compareFloat context:NULL];

ソートするには、「sortedArrayUsingFunction: context:」がキモ。
第1引数は、比較を行うためのC言語の関数を定義
第2引数は、第1引数で指定した関数の引数を定義

比較を行う関数は以下です。

 NSComparisonResult compareFloat(id value1, id value2, void *context)
 {
    float floatValue1 = [(NSNumber*)[value1 valueForKey: @"English"] floatValue];
    float floatValue2 = [(NSNumber*)[value2 valueForKey: @"English"] floatValue];
    
    if(floatValue1 > floatValue2){
        return NSOrderedDescending;
    }else if(floatValue1 < floatValue2){
        return NSOrderedAscending;
    }else{
        return NSOrderedSame;
    }
 }

あとは配列を出力するだけ。

    NSDictionary* top = [sorted_students objectAtIndex: 0];
    NSLog(@"一番英語の点数が良かったのは、%@です。", [top valueForKey:@"Name"]);

【iPhoneアプリ開発】ビューのサイズが変わるタイミング viewDidLayoutSubviews!

【iPhoneアプリ開発】ビューのサイズが変わるタイミング。viewDidLoad? viewWillAppear?それとも…な時

iOSアプリでの画面表示の以下の流れはご存じの通りですね。

viewDidLoad

viewWillAppear

viewDidAppear

AutoLayoutやAutoResizeをボタン等のビューに設定している場合、
viewDidLoadでビューのサイズを取得しようとすると、
AutoLayout等が適用される前のビューのサイズになってしまいます。

そのため、
適用後のサイズはviewWillAppearで取得すれば大丈夫、と先週くらいまで思っていました。

が!
特定の条件で、viewWillAppearの中なのに適用前のサイズしか取得できないということが最近起こりました。

なんだこれは!!

と思いました。
試しにviewDidAppearでサイズを取得したら、適用後のサイズになったのですが、
画面が表示された後にサイズを調整するとユーザーにその動きが丸見えだったのでボツ:(

viewWillAppearとviewDidAppearの間に何か無いんすか!Googleさん!

と探していたら求めていた答えが見つかりました。

viewDidLayoutSubviews

です。

 -(void)viewDidLayoutSubviews
 {
     //この中に色々書く
 }

この中でもう一度サイズを取得して、ビューのフレームを変えたら完全に解決しました。

viewDidLoadとviewWillAppearに加えてviewDidLayoutSubviews、このトリオがいればiOSアプリ開発、怖くないですね。

では。

by 芦野輝明
twitter→https://twitter.com/teriyakiegg

Xcode