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

【Objective-C】UICollectionViewCellの選択表示

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

UICollectionViewCellで選択表示をさせようと思ってハマったのでメモ。

参考:Collection View プログラミングガイド(iOS用)【PDF】
※P25〜P28あたり。

選択状態をコードで制御するには・・・

たとえば、セルの選択状態をアプリケーション側で描画したい場合は、selectedBackgroundViewプロパティをnilにし、デリゲートオブジェクトに、外観を変更する処理を組み込んでください。
collectionView:didSelectItemAtIndexPath:メソッドに外観を変更する処理、
collectionView:didDeselectItemAtIndexPath:メソッドに元の状態に戻す処理を実装します。

ということらしいので、コードを書いてみた。

//UICollectionViewCell:編集
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell1" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    cell.selectedBackgroundView = nil;
    
    //(中略)

    return cell;
}

//
-(void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"選択した %@",indexPath);
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor redColor];
}

-(void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"選択解除した %@",indexPath);
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
}

これだとタップしている間は、背景色が赤に変わるけれど、指を離すと白に戻ってしまう。

で、更に資料を読んでみると・・・

図 2-3に、未選択状態のセルにユーザがタッチしたときに発生する、一連の動作を示します。最初の「タッチダウン」イベントを受けて、コレクションビューはセルの「強調表示」状態をYESにします。もっとも、自動的にセルの外観に反映されるわけではありません。その後、「タッチアップ」イベントが発生すると、「強調表示」状態はNOに戻り、「選択」状態はYESに変わります。ユーザが選択状態を変更すると、コレクションビューはセルのselectedBackgroundViewプロパティに設定されているビューを表示します。コレクションビューが関与してセルの外観を変えるのは、この場合に限ります。ほかの視覚効果はデリゲートオブジェクトが行わなければなりません。

つまり、指を離すと選択状態にはなるものの didUnhighlightItemAtIndexPathが呼ばれて、表示が戻ってしまう・・・ということらしい。

結局、didHighlightItemAtIndexPathとdidUnhighlightItemAtIndexPathは使わず、以下の様に書いて解決。

//UICollectionViewCell:編集
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell1" forIndexPath:indexPath];
    
    //通常の背景
    UIView* backgroundView = [[UIView alloc] initWithFrame:cell.bounds];
    cell.backgroundColor = [UIColor whiteColor];
    cell.backgroundView = backgroundView;
    
    //選択時の背景
    UIView* selectedBGView = [[UIView alloc] initWithFrame:cell.bounds];
    selectedBGView.backgroundColor = [UIColor redColor];
    cell.selectedBackgroundView = selectedBGView;
    
    //(中略)
    
    return cell;
}

最初のコードで、選択時のイベントハンドラ(didSelectItemAtIndexPath)で選択済表示に変えることもできるけど、それだと選択済項目の表示を戻してやる処理が必要になるので、単一選択ではこれが一番簡単かなと。

【Objective-C】UIBarButtonItemの右ボタンを動的に差し替える

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

カメラロールでサムネイル表示したときに、右上に出る「選択」>「キャンセル」の切替と同じやつです。これってViewは切り換えてないよなあと思ったので、ボタンの差し替えをテストしてみました。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    //削除ボタンを追加
    UIBarButtonItem *delButton = [[UIBarButtonItem alloc]
                                  initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
                                                                               target:self
                                                                               action:@selector(delButtonDidPush:)];
    

    self.navigationItem.rightBarButtonItem = delButton;
}

-(void)delButtonDidPush:(UIBarButtonItem *)button
{
    NSLog(@" -- trash!!");
    
    //
    UIBarButtonItem *canelButton = [[UIBarButtonItem alloc]
                                    initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                                    target:self
                                    action:@selector(cancelButtonDidPush:)];
    self.navigationItem.rightBarButtonItem = canelButton;
}

-(void)cancelButtonDidPush:(UIBarButtonItem *)button
{
    NSLog(@" -- cancel!!");
    
    //削除ボタンを追加
    UIBarButtonItem *delButton = [[UIBarButtonItem alloc]
                                  initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
                                  target:self
                                  action:@selector(delButtonDidPush:)];
    
    
    self.navigationItem.rightBarButtonItem = delButton;
    
}

これでちゃんと動いてるし、タップのイベントも片方しか出てこないので、たぶんいいはず。
UIToolBarについては、こんな記事が。

なんとなく|UINavigationController の UIToolBar で Button 表示を切り替えたいメモ

たぶんnavigationItem.rightBarButtonItemは1つしか置けないので、重なったりすることはないのでは?と推測。

【Xcode】ARC配下でのメモリ管理関連リンク

ARC配下でのメモリ管理関連リンク。あとで読む用。

Second Flush ARCへの移行のリリースノート
infinite loop 技術ブログ Objective-Cのメモリ管理おさらいと解放tips
私的メモ [Objective-C] メモリリークをチェックする
ARCを使用したメモリ管理の基礎
ARC環境でメモリが枯渇するケース
sirochro Objective-C: これで解決!ARCを使用しReceived memory warningが出た時の対処方法
Developers.IO [Xcode 5] Xcode 5のデバッグ機能 “Debug gauges”
Instruments ユーザガイド[PDF]
【iOS/Mac開発】超サクサクアプリへの必須ツール Instruments を使いこなそう

【Xcode】データをアプリ内に保存するためのメモ2(画像編)

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

前回データ(変数)の保存について書きましたが、今回は画像です。アイコン程度の小さな画像をアプリ内に保存できるようにしたい。

・UIImageJPEGRepresentationを使って、画像をJPEG化。
・[NSHomeDirectory() stringByAppendingPathComponent:@”Documents”]で、データ保存用を一般的に使われるDocumentsディレクトリを指定する。

というところがミソのようです。

で、書いてみたコードがコレ。ここではカメラ/カメラロールを編集モードで呼び出した後に、250*250のjpegに保存する処理を書いています。カメラの呼び出しなどは割愛してます。関数「resize」は、自前の関数であることががわかるように書いておきました。

YoheiM.NET [XCODE] iPhoneで画像をアプリケーション内に保存するには

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    //編集された画像を取得
    UIImage *editedImage = (UIImage *)[info objectForKey:UIImagePickerControllerEditedImage];
    
    // 画像をリサイズ
    UIImage *resize = [self resize:editedImage rect:CGRectMake(0, 0, 250, 250)];
    
    //リサイズした画像をDocumentsフォルダに保存
    NSData *data = UIImageJPEGRepresentation(resize, 1.0f);
    
    icon_count++;
    
    //画像をアプリ内部に保存
    NSString *path = [NSString stringWithFormat:@"%@/newicon%@.jpg",
                      [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"],[NSString stringWithFormat:@"%d",icon_count]];
    
    //保存に成功・失敗したログをコンソールに出力(デバッグ用)
    if ([data writeToFile:path atomically:YES]) {
        NSLog(@"Save completed. %@",path);
        
        //保存した画像のパスを配列に追加
        [_array_icon addObject:[NSString stringWithFormat:@"../Documents/newicon%@.jpg",[NSString stringWithFormat:@"%d",icon_count]]];
        
        //アイコン画像配列:保存
        if ([_delegate respondsToSelector:@selector(saveData)]){
            [_delegate saveData];
        }
        
        //画像一覧を再描画
        [self.myCollectionView reloadData];
    } else {
        NSLog(@"could not save");
    }
    
    //---ここに保存後の処理を書く。
}

//画像をリサイズする
- (UIImage *)resize:(UIImage *)image rect:(CGRect)rect
{
    UIGraphicsBeginImageContext(rect.size);
    [image drawInRect:rect];
    UIImage* resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
    UIGraphicsEndImageContext();
    return resizedImage;
}

これで前回分と合わせると、保存した画像をアプリ起動時に呼び出せます。

【Xcode】データをアプリ内に保存するためのメモ1(変数編)

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.4】

設定などのデータ(変数など)をアプリ内に保存するには、NSUserDefaultsを使うといいらしい。

iPhoneアプリ開発の虎の巻 NSUserDefaults
A Day In The Life NSUserDefaults を使ったデータの保存方法

で、load,saveの際に呼び出す関数を書いてみた。

//アイコン関連情報:読み込み
-(void)loadData
{
    defaults = [NSUserDefaults standardUserDefaults];

    //アイコン画像配列
    NSArray *array = [defaults arrayForKey:@"icon"];
    if (array) {
        //既存値を使用
        array_icon = [array mutableCopy];

    } else {
        //配列を新規に作成
        array_icon = [NSMutableArray arrayWithObjects:@"icon_250.jpg", @"miku_250.jpg", @"logo_2tones_250.jpg",@"shugo_250.jpg", nil];

    }

    //アイコン選択番号
    int num = (int)[defaults integerForKey:@"selectNum"];
    if (num) {
        //既存値を使用
        select_num = (long)num;
    } else {
        //初期値を設定
        select_num = 0;
    }

}

//アイコン関連情報:保存
-(void)saveData
{
    //配列:設定
    NSArray *array = [array_icon copy];
    [defaults setObject:array forKey:@"icon"];

    //番号:設定
    int num = (int)select_num;
    [defaults setInteger:num forKey:@"selectNum"];

    //更新
    BOOL successful = [defaults synchronize];
    if (successful) {
        NSLog(@"%@", @"データの保存に成功しました。");
    }
}

loadDataは、viewDidLoadで、saveDataはデータに変更があったタイミングで呼び出しています。

上のコードでの注意点

1.アプリではNSMutableArray(可変配列)を使っているのだけれど、NSUserDefaultsにはNSArray(固定配列)しか保存できないので、ロード/セーブ時に配列をキャストしてやる必要がある、ということです。

iPhoneアプリ開発メモ NSArrayはNSMutableArrayにキャスト可能か?

2.データ保存の際は、明示的に synchronize で更新しないと保存タイミングがOS任せになるらしいです。

iPhoneアプリ開発者の憂鬱 NSUserDefaultsの保存処理でハマった

3.実際にNSUserDefaultsはどこに保存されるのか?テスト等での確認に。

iPhoneアプリ開発者の為のAndroidアプリ開発のポイント(現忘備録) [iOS]NSUserDefaultsの保存先・実機のデータを見る方法

【Xcode】UICollectionViewを使う

UICollectionViewで、グリッド的なものが出来るらしいです。まだテスト中なんだけど、参照リンクが多すぎるのでメモ。
Collection Viewの使い方(iOS/XCode/Storyboard/UICollectionView)
[iPhone] UICollectionView で Grid 表示
iOS6から追加されたCollectionViewのサンプル
UICollectionView その1 セルの表示
Collection View プログラミングガイド(iOS用)【PDF】

【2014.7.18追記】画面遷移がうまくいかず、これを見てsegue.identifierが設定されていないことに気づいた。

Storyboard での画面遷移前はprepareForSegue メソッドが呼ばれる

【Xcode】StoryBoardの使い方

以前はInteface Builderとか使ってなかったので、イマイチわかってない気が。勿論、簡単なサンプルとかは問題ないけど、自分でUI考えたりすると混乱するので、関連リンクを集めておきます。随時追加する予定。

 
[iOS]Storyboardで始めるiPhoneアプリ開発 #1 – pushセグエを使う
Xcodeのストーリーボードの使い方
iOS Storyboardによる画面遷移の実装(Navigation Controller)

UIImagwViewにAspect Fitで配置されたUIImageの位置・サイズを求める

UIImageViewでViewをAspect Fitに指定すると、比率を保ったままView内に画像を収めてくれます。こんな感じに。カメラロールで見るのと同じですね。

 

Aspect Fitでの画像表示

ただUIImageViewをクリックした時に、UIImageの座標を取得しようと思うと、基本的に縮小されているために計算が必要になります。その前段として、UIImageがUIImageView内でどのくらいのサイズで表示されているのかを取得する必要があります。まあ自前で計算できなくないですが、ちょっと面倒だし、そんなのメソッドがあるんじゃないかと探したら、見つかりました。

参考:UIImageView で ‘Aspect Fit (UIViewContentModeScaleAspectFit)’ を指定したときの画像サイズを取得する

    CGRect innerframe = AVMakeRectWithAspectRatioInsideRect(_originalImage.size, _myImageView.bounds);
 
    NSLog(@"--_originalImage.width %f",_originalImage.size.width);
    NSLog(@"--_originalImage.height %f",_originalImage.size.height);

    NSLog(@"--width %f",innerframe.size.width);
    NSLog(@"--height %f",innerframe.size.height);
    NSLog(@"--x %f",innerframe.origin.x);
    NSLog(@"--y %f",innerframe.origin.y);

※_originalImage:元画像(UIImage)
_myImageView:表示用View(UIImageView)

で、結果はこちら。

2014-07-06 02:15:32.946 faceCamera2[14996:60b] --_originalImage.width 2048.000000
2014-07-06 02:15:32.947 faceCamera2[14996:60b] --_originalImage.height 1536.000000
2014-07-06 02:15:32.948 faceCamera2[14996:60b] --width 320.000000
2014-07-06 02:15:32.950 faceCamera2[14996:60b] --height 240.000000
2014-07-06 02:15:32.951 faceCamera2[14996:60b] --x 0.000000
2014-07-06 02:15:32.953 faceCamera2[14996:60b] --y 142.000000

このメソッドは、AVFoundation.frameworkを使っているのでimportが必要です。しかし、UIImageViewにあってもよさそうなのに、何故、AVFoundationにあるのかは謎。おかげで見つけるのに時間かかりました。。。

iOSの顔検出を画像で表示する

要は、顔検出部分に画像を配置するだけなんですが、ちょっと引っかかったのでメモ。

参考:顔認識とCore Imageフィルタのコラボ

この手のサンプルはたくさんあるけど、大体がCIFilter使ってるパターンが多いので、その部分をUIImageで描画すればいいんじゃないかってメドをつけたんだけど、なかなかうまく行かなかった。大まかにいうと下記の点。

1.UIImageで描画する際に、y方向の座標が逆になる。

通常、左上が(0,0)になると思ってたんだけど、y方向は画像の左下が(0,0)になっているようで、その点を描画時に補正。

2.カメラ/カメラロールの画像の向き

iPhoneで撮影されたデータは、UIImage.imageOrientationに向きを持っている。

参考:まみったー iPhone の向きと UIImageOrientation メモ

で、この向きが縦位置の時にうまくいかなかったので、色々調べたら、一度、UIImageで書き直してしまえば問題ないらしい。

これはちょっと勘違いがありましたので、詳しくはこちらを。

参考:iPhoneカメラで撮影した画像のUIImageからCGImageを取り出すと90度傾く問題の解決法

これで基本的には問題ないようです。

一応、該当部分のソースはこんな感じです。

- (IBAction)checkFace2:(UIBarButtonItem *)sender {
    //UIImage.imageOrientationの値によって動作が異なるので、元画像を再描画しておく。
    UIImage *orgImage =[UIImage imageWithCGImage:_originalImage.CGImage];
    
    UIGraphicsBeginImageContext(orgImage.size);
    [orgImage drawInRect:CGRectMake(0, 0, orgImage.size.width, orgImage.size.height)];
    orgImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    
    CIImage *coreImage = [CIImage imageWithCGImage:orgImage.CGImage];
    
    //オプション辞書作成(顔検出の精度/高精度)
    NSDictionary *options = [NSDictionary
                             dictionaryWithObject:CIDetectorAccuracyHigh
                             forKey:CIDetectorAccuracy];
    
    //CIDetectorを生成(画像解析/現在は顔検出のみ指定可能)
    CIDetector *faceDetector = [CIDetector
                                detectorOfType:CIDetectorTypeFace
                                context:_imageContext
                                options:options];
    
    //顔検出(位置情報を配列で返す)
    NSArray *faces = [faceDetector featuresInImage:coreImage
                                           options:nil];
    //顔部分に画像を載せる
    for(CIFaceFeature *face in faces){
        NSLog(@"---check");
        coreImage = [CIFilter filterWithName:@"CISourceOverCompositing"
                               keysAndValues:kCIInputImageKey, [self makeIconForFace:face],
                     kCIInputBackgroundImageKey, coreImage, nil].outputImage;
    }
       
    CGImageRef cgImage = [_imageContext createCGImage:coreImage fromRect:[coreImage extent]];
    
    //image化するときの向きは、オリジナルに合わせないと横になる。何で?
    _myImageView.image = [UIImage imageWithCGImage:cgImage scale:1.0f orientation:_myImageView.image.imageOrientation];
    CGImageRelease(cgImage);

}
//顔にアイコンを描画
- (CIImage*)makeIconForFace:(CIFaceFeature*)face{
    
    // アイコンのUIImageを作成
    UIImage *iconImage = [UIImage imageNamed:@"icon_250_reasonably_small.jpg"];
    
    
    // 顔のサイズ情報を取得
    CGRect faceRect = [face bounds];
    
    int w = _originalImage.size.width;
    int h = _originalImage.size.height;
    int cx = faceRect.origin.x;
    int cy = faceRect.origin.y;
    int icon_w = faceRect.size.width;
    int icon_h = faceRect.size.height;
    
    //アイコンを顔サイズで描画(yは左下原点になるようなので補正)
    UIGraphicsBeginImageContext(CGSizeMake(w, h));
    [iconImage drawInRect:CGRectMake(cx, h - (cy + icon_h), icon_w, icon_h)];
    UIImage *iconImage_after = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    CIImage *image = [CIImage imageWithCGImage:iconImage_after.CGImage];
    
    return image;
}

テスト結果はこんな感じ。

iPhone実機でのテスト結果。
iPhone実機でのテスト結果。

テスト環境:iPhone5+Xcode 5.1.1

 

【Objective-C】CoreImageでの画像フィルタ

Swiftの話題が多い最近ですが、地味にObjective-Cのテストです。

カメラ又はカメラロールから読み込んだ画像にCoreImageでフィルタをかけてみました。こんな感じ。

IMG_5766

実際のコードなどはこの辺を参考に。

【iOS】たった数行で画像のフィルタ/エフェクトが実現できる超便利フレームワークCoreImage。とりあえず7つ紹介!

で、気づいた点は、iPhoneで撮影された画像は、UIImage.imageOrientation プロパティを持っているので、UIViewに戻すときに、指定してやらないと思わぬ向きになるのね。

  

_myImageView.image = [UIImageimageWithCGImage:cgimg scale:1.0forientation:_originalImage.imageOrientation];

結局こんな感じに元画像の値(_originalImage.imagePrientation)を使って設定するように書いたけど。あとはCGImageの解放を忘れずにってことでしょうか。

参考:
iPhone の向きと UIImageOrientation メモ
画像処理 CGImageRelease

やっぱり手を動かして書かないと気づかないことがあるなってことで、しばらく続ける予定、