jubilee

Programing, Books and more...

UITableViewのセルのハイライト制御

セルをタップして詳細画面に遷移した後に、戻ってきた時、タップしたセルのハイライトが残っている。
このハイライトを消す方法。
UITableViewControllerを使用しているぶんには、特に意識する必要はない。

UITableViewControllerを継承してUITableViewを使う場合

UITableViewControllerのviewWillAppear:にはハイライト解除の処理が定義してある。
なので、viewWillAppear:メソッドをオーバーライドするときは、必ず親クラスのviewWillAppear:メソッドを呼ぶこと。

1
2
3
4
5
- (void)viewWillAppear:(BOOL)animated
{
  // 必ずこれを呼ぶこと
  [super viewWillAppear:animated];
}

UITableViewControllerを継承せずにUITableViewを使う場合

自分で実装すること。

1
2
3
4
5
6
- (void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];
  // ハイライト解除
  [_tableView deselectRowAtIndexPath:[_tableView indexPathForSelectedRow] animated:YES];
}

参考

http://d.hatena.ne.jp/glass-_-onion/20090324/1237864499

UITableViewのセル編集をさせない方法

セルスワイプでの削除をやめる方法。
セルの編集を許可するかどうかを返すメソッドでNOを返す。

1
2
3
4
5
6
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return NO if you do not want the specified item to be editable.
    // TODO: セル削除を可能にする時はYESに変更すること
    return NO;
}

もしくは、以下のメソッドをコメントアウト。

1
2
3
4
5
6
7
8
9
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [_objects removeObjectAtIndex:indexPath.row];
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    } else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
    }
}

アプリのデータをiTunesで共有する方法

アプリ内のデータをiTunesから取得したり、追加したりする際の設定方法。
デバイスをiTunesに接続し、上のAppタブを選択、ファイル共有で対象のアプリを選択すると、共有できるファイルが表示される。

Supporting Files

  • {アプリ名}-Info.plistを開く
  • Information Property Listの隣の+をクリックし、行を追加
  • Application supports iTunes file sharingを選択
  • 値のYESを選択

iTunes共有1

参考: http://qiita.com/SolaRayLino/items/8a4eb1821559122b4cbf

アプリのバージョン取得

ソース内でアプリのバージョンを取得する方法。

1
2
3
4
5
// バージョン:TARGET- General - Identity - Version(Info.plist : Bundle version strings short)
NSString *version = [[NSBundle mainBundle]infoDictionary][@"CFBundleShortVersionString"];

// バンドル:TARGET- General - Identity - Bundle(Info.plist : Bundle version)
NSString *bundle = [[NSBundle mainBundle]infoDictionary][@"CFBundleVersion"];

VersionとBundleの違いについては、理解不足。

  • Version
    • 表記用のリリースバージョン
  • Bundle
    • 内部開発用のビルドバージョン

とのことみたいだが、リリース時は両方が一致している必要があるらしいとのこと。

カメラで撮影した動画をフレーム分割する方法

UIImagePickerで撮影した動画を1秒30フレーム程度で、分割し静止画にする方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/*!
 * 撮影ボタン(作成する)
 */
- (IBAction)shutterButtonTapped:(id)sender {
    // UIImagePickerで動画の撮影をする
    UIImagePickerController *picker = [[UIImagePickerController alloc]init];
    picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    picker.mediaTypes = @[(NSString *)kUTTypeMovie];
    picker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
    picker.videoQuality = UIImagePickerControllerQualityType640x480;
    picker.videoMaximumDuration = 10;
    picker.allowsEditing = NO;
    picker.delegate = self;
    [self presentViewController:picker animated:YES completion:nil];
}

/*!
 * 撮影終了のタイミング
 */
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    // UIImagePickerをしまう
    [picker dismissViewControllerAnimated:YES completion:nil];

    // ~/Documentsディレクトリの取得
    NSArray *directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = directories[0];

    // 撮影した動画をAssetに格納
    NSURL *assetURL = info[UIImagePickerControllerMediaURL];
    AVURLAsset *asset = [[AVURLAsset alloc]initWithURL:assetURL options:nil];

    // 動画を切り出す間隔を配列に格納する
    Float64 frameParSecond = 0.033f;
    Float64 durationSeconds = CMTimeGetSeconds([asset duration]);   // 動画の長さ
    Float64 frameTimeStamp = 0;
    NSMutableArray *times  = [NSMutableArray array];
    while (frameTimeStamp <= durationSeconds) {
        [times addObject:[NSValue valueWithCMTime:CMTimeMakeWithSeconds(frameTimeStamp, 600)]];
        CMTime nextTime = CMTimeMakeWithSeconds(frameTimeStamp, 600);
        frameTimeStamp = CMTimeGetSeconds(nextTime) + frameParSecond;
    }

    // 画像ジェネレーターを生成
    AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc]initWithAsset:asset];
    generator.appliesPreferredTrackTransform = YES;
    generator.requestedTimeToleranceBefore = kCMTimeZero;
    generator.requestedTimeToleranceAfter = kCMTimeZero;

    // 一連の画像を生成(timesに格納されている分だけ、第2引数がコールバックされる)
    [generator generateCGImagesAsynchronouslyForTimes:times
                                    completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
                                        NSString *requestedTimeString = (NSString*)CFBridgingRelease(CMTimeCopyDescription(NULL, requestedTime));
                                        NSString *acturalTimeString = (NSString*)CFBridgingRelease(CMTimeCopyDescription(NULL, actualTime));
                                        NSLog(@"Requested: %@; actual: %@", requestedTimeString, acturalTimeString);
                                        if (result == AVAssetImageGeneratorSucceeded) {
                                            // JPEG画像を生成し、保存する
                                            [self writeCGImageTo:documentDirectory image:image];
                                            // iCnt++;
                                            // 完了した判断はtimesの個数と保存した個数でする?
                                            // [times count] == iCntだったら、SVProgressHub停止みたいな
                                        }
                                        if (result == AVAssetImageGeneratorFailed) {
                                            NSLog(@"Failed with error: %@", [error localizedDescription]);
                                        }
                                        if (result == AVAssetImageGeneratorCancelled) {
                                            NSLog(@"Cancelled");
                                        }
                                    }];
}

/*!
 * 動画から切り出されたCGImageRef形式のデータをJPEG画像(ファイル名は連番:imageNameNumber)に生成し、指定ディレクトリに書き出す
 */
- (BOOL)writeCGImageTo:(NSString*)path image:(CGImageRef)cgImage
{
    NSLog(@"Called");

    NSString *str = [NSString stringWithFormat:@"img_%@.jpg", @(self.imageNameNumber++)];
    path = [path stringByAppendingPathComponent:str];

    UIImage *saveImage = [UIImage imageWithCGImage:cgImage];
    NSData *data = UIImageJPEGRepresentation(saveImage, 1.0);   // 0.0(低水準)〜1.0(高水準)
    BOOL result = [data writeToFile:path atomically:YES];
    return result;
}

参考:公式の「AVFoundationプログラミングガイド」P17 アセットの使用 ビデオから静止画像の取得 一連の画像の生成

シングルトン(singleton)

シングルトンとは?

  • デザインパターンの1つ
  • 「ただ1つの」インスタンスしか持たない→どこからアクセスしても同じインスタンス
  • 複数のクラス間での変数やオブジェクトの共有が可能
  • 複数のクラス間でメソッドの共有ができる
  • NSUserdefaultsと同様

SingletonManager.h

1
2
3
4
5
6
7
8
9
10
11
@interface SingletonSample : NSObject
// このシングルトンが管理するプロパティ
@property(nonatomic) NSInteger managedParam;
@property(strong, nonatomic) NSString *catName;
...
// インスタンス作成のためのクラスメソッド
+ (SingletonSample *)sharedManager;

// このシングルトンが保有するメソッド
- (void)sampleMethod:(NSString*)name;
@end

SingletonManager.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@implementation SingletonManager

static id sharedManager_ = nil;
+ (id)sharedManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedData_ = [SingletonManager new];
    });
    return sharedManager_;
}

- (void)sampleMethod:(NSString*)name
{
    ...
}
@end

ViewController.m

1
2
3
4
5
6
7
8
#import "SingletonManager.h"

- (void)viewDidLoad
{
    SingletonManager *single = [SingletonManager sharedManager];
    single.managedParam = 100; // 変数に値をセット
    [single sampleMethod:name]; // メソッドを使用
}

参考:
http://ylb.jp/iOSDev/SingletonSample.pdf http://qiita.com/yuky_az/items/27031ec5ca55a95d6209

ビューの重なり順を変更する

ビューの重なり順をコードで変更する方法。

自分に追加されているサブビューの配列を取得

1
self.subviews

自分と同じスーパービューをもつサブビューの配列を取得

1
superview.subviews

挿入位置をインデックス番号で指定する

1
[self.view insertSubview:_imageView atIndex:1];

指定ビューの前/後ろに挿入する

1
2
3
4
// _targetViewの下にする
[self.view insertSubview:_imageView belowSubview:_targetView];
// _targetViewの上にする
[self.view insertSubview:_imageView aboveSubview:_targetView];

ビューを最前面/再背面に移動させる

1
2
3
4
// 最前面
[self.view bringSubviewToFront:_imageView];
// 再背面
[self.view sendSubviewToBack:_imageView];

指定したインデックスのビューの重なりを入れ替える

1
2
// 1番目と3番目を入れ替える
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:3];

ファイルパス(NSString)をNSURLに変換する方法(日本語OK)

日本語のディレクトリ名が入るとおかしくなるが、この方法だとOK。

1
2
3
4
NSString* string = @"/Users/abt/Documents/日本語のフォルダ名/file.txt";
NSURL* url = [NSURL fileURLWithPath:string];
// NGケース(nilが返る)
NSURL* nilUrl = [NSURL URLWithString:string];

引っ張って更新を使う(UITableViewController / UITableView)

UITableViewControllerを使うか、UITableViewを直で使うかによって、コードの差異あり。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@property (weak, nonatomic) IBOutlet UITableView *postlistTableView;
@property (strong, nonatomic) UIRefreshControl *refreshControl;

- (void)viewDidLoad
{
    _refreshControl = [UIRefreshControl new];
    [_postlistTableView addSubview:_refreshControl];    // (差異)UITableViewを直で使う場合
    self.refreshControl = refreshControl;               // (差異)UITableViewControllerを使う場合
    [_refreshControl addTarget:self
                        action:@selector(refreshOccured:)    // 更新時の処理を行う自作メソッド名
              forControlEvents:UIControlEventValueChanged];
    _postlistTableView.alwaysBounceVertical = YES;          // セルが空でも可能とする
}

- (void)refreshOccured:(id)sender
{
    // 更新で行う処理を記述
    // 更新処理を終了させる
    [_refreshControl endRefreshing];
    [_postlistTableView reloadData];
}