tah nn

主にアプリ開発の技術メモを残していきます。

LogoMe-シルエット写真加工アプリ-リリースしました!

写真を簡単にオシャレなシルエットにするアプリ LogoMe をリリースしました。



こちらのアプリを使用すると下記イメージのような画像が簡単に作れます。

編集前

編集後


シルエットエフェクトだけでなく、ペイントや文字入れ機能を入れました。イメージの編集後にある「LogoMe」という文字はシルエットと同化させています。


今回は本アプリで行なっているシルエット加工の方法をご紹介します。


シルエット加工はGPUImageライブラリを使用して、下記例のようにGPUImageLuminanceThresholdFilter, GPUImageGaussianBlurFilter, GPUImageUnsharpMaskFilterの3つ組み合わせて画像処理を行ないました。

GPUImageLuminanceThresholdFilter *thresholdFilter = [[GPUImageLuminanceThresholdFilter alloc] init];
[(GPUImageLuminanceThresholdFilter *)thresholdFilter setThreshold:_thresholdLevel];
    
GPUImageGaussianBlurFilter* blurfilter = [[GPUImageGaussianBlurFilter alloc] init];
[(GPUImageGaussianBlurFilter *)blurfilter setBlurRadiusInPixels:_blurLevel];
    
GPUImageUnsharpMaskFilter* unsharpMaskfilter = [[GPUImageUnsharpMaskFilter alloc] init];
[(GPUImageUnsharpMaskFilter *)unsharpMaskfilter setIntensity:_unsharpMaskLevel];
    
filterGroup = [[GPUImageFilterGroup alloc] init];
[(GPUImageFilterGroup *)filterGroup addFilter:thresholdFilter];
        
[thresholdFilter addTarget:blurfilter];
[blurfilter addTarget:unsharpMaskfilter];
        
[(GPUImageFilterGroup *)filterGroup setInitialFilters:@[thresholdFilter]];
[(GPUImageFilterGroup *)filterGroup setTerminalFilter:unsharpMaskfilter];
        
[sourcePicture addTarget:filterGroup];
[filterGroup addTarget:self.gpuImageView];

[sourcePicture processImage];
// ... イメージを取得

ペイント機能も苦労したので、それは次回記事にしたいと思います。

以上です。
気になった方は是非ダウンロードしてください!

【Objective-C】UITableViewを Dynamic Type に対応する

UITableViewを文字サイズの変更 (Dynamic Type) に対応する方法です。
(環境: xcode 6.3, iOS8.3)

iOS7から設定アプリ > 一般 > アクセシビリティ から文字サイズを変更できます。
f:id:tsuyushiga:20150419182743p:plain
こちらの設定が変更された際に、設定変更の検知し、フォントサイズとセルの高さを動的に変更する方法を紹介します。

フォントサイズの指定

UITableViewのCellの表示内容を設定するメソッド、もしくはstoryboardなどでフォントサイズをDynamicTypeに対応しているスタイルに設定します。
下記はtableView:cellForRowAtIndexPath:メソッドで設定している例です。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
    
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil];
    }
    
    cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
    cell.textLabel.numberOfLines = 0;

    // フォントサイズのスタイルを設定
    cell.textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    cell.detailTextLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2];
    
    HogeItem* item = [self getItem:incexPath];
    
    cell.textLabel.text = item.text;
    cell.detailTextLabel.text = item.subText;
    
    [cell.textLabel sizeToFit];
    
    return cell;
}
セルの高さを設定

セルの高さを動的にします。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    HogeItem* item = [self getItem:indexPath];
    
    NSString *text = item.text;
    UIFont *labelFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    CGFloat padding = 40.0;
    CGRect totalRect = [text boundingRectWithSize:CGSizeMake(self.tableView.frame.size.width - 30, CGFLOAT_MAX)
                                          options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                       attributes:[NSDictionary dictionaryWithObject:labelFont forKey:NSFontAttributeName]
                                          context:nil];
    return MAX(totalRect.size.height + padding, 60);
}
フォントサイズ設定変更の検知

最後にDynamicTypeが変更されたことを検知し、設定を反映するようにします。

// viewDidLoadで通知センターにフォントサイズが変更された際に呼ばれるメソッドを登録
- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(preferredContentSizeChanged:)
                                                 name:UIContentSizeCategoryDidChangeNotification
                                               object:nil];
}
// フォントサイズが変更された際に呼ばれるメソッド
- (void)preferredContentSizeChanged:(NSNotification *)aNotification {
    [self.tableView reloadData];
}
ヘッダーのサイズ

また、テーブルにヘッダーがある場合は、下記のようにしてヘッダーのサイズも動的に変更できます。

- (void)sizeHeaderToFit {
    self.headerLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    
    UIView *header = self.tableView.tableHeaderView;
    
    [header setNeedsLayout];
    [header layoutIfNeeded];
    
    CGFloat height = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    CGRect frame = header.frame;
    
    frame.size.height = height;
    header.frame = frame;
    
    self.tableView.tableHeaderView = header;
}

// tableViewをリロードするタイミングで...
[self sizeHeaderToFit];


以上です。

参考:
iOSアプリで動的にフォントサイズを変更する「Dynamic Type」新機能の実装法 #ios7yahoo|CodeIQ MAGAZINE
ios - table header view height is wrong when using auto layout, IB, and font sizes - Stack Overflow

【Swift】Optional型 使用方法まとめ

Optional型の使用方法がいまいちわかっていなかったので整理しました。
(環境: xcode 6.2, Swift 1.1)

nil の代入可否
  • 非Optional型
var vN1 : UIView
vN1 = nil  //  エラー
  • Optional型
var vO1 : UIView?
vO1 = nil    //  OK
  • Implicitly Unwrapped Optional型
var vI1 : UIView!
vI1 = nil    //  OK
変数の参照
  • 非Optional型
var vN2 : UIView
vN2 = UIView()
vN2.frame // OK (非Optional型はnilじゃないことが保証されてる)
  • Optional型
var vO2 : UIView?
// nilじゃない場合
vO2 = UIView()
vO2?.frame // OK (Optional型を返す)
vO2!.frame // OK (非Optional型を返す(アンラップされて))
// nilの場合
vO2 = nil
vO2?.frame // OK
vO2!.frame // エラー
  • Implicitly Unwrapped Optional型
var vI2 : UIView!
// nilじゃない場合
vI2 = UIView()
vI2.frame // OK (非Optional型を返す)
vI2!.frame  // OK (非Optional型を返す。自動アンラップすると宣言してるが、手動でアンラップ...)
vI2?.frame // OK (Optional型を返す)
// nilの場合
vI2 = nil
vI2.frame // エラー
vI2!.frame // エラー
vI2?.frame // OK

以上です。

【Objective-C】OCMockでpropertyの返り値を固定にする

OCMockで単体テストする際に、propertyの返り値を固定にする方法です。
(環境: xcode 6.2)

// 例えば Hogeクラスに title というpropertyがあったら...
@property (nonatomic, readonly) NSString *title;

// 以下のように返り値を固定にできます
id mock = [OCMockObject partialMockForObject:[Hoge alloc]];
[[[mock stub] andReturn:@"hoge!"] title];

以上です。

参考:
mocking - IPhone unit testing OCMock, how to mock read only variables? - Stack Overflow

【Objective-C】iOS8 でプッシュ・ローカル通知の許可アラート表示

iOS8以降の端末でのプッシュ・ローカル通知に関するメモです。
・通知の許可アラートを表示するための方法
・通知を許可しているかどうか確認するための方法
の2点です。
(環境 xcode 6.1: iOS 8.1)

  • 通知の許可アラートを表示するための方法
// AppDelegate.m の - (BOOL)application:didFinishLaunchingWithOptions: メソッドで...
// iOS8以降はこちらでアラート表示
if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:
                                                UIUserNotificationTypeBadge|
                                                UIUserNotificationTypeAlert|
                                                UIUserNotificationTypeSound categories:nil];
    [application registerUserNotificationSettings:settings];
    [application registerForRemoteNotifications];

// iOS8以前はこちらでアラート表示
} else {
    [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge|
                                               UIRemoteNotificationTypeAlert|
                                               UIRemoteNotificationTypeSound];
}
  • 通知を許可しているかどうか確認するための方法
NSUInteger types;
if ([[[UIDevice currentDevice] systemVersion] >= 8.0) {
    types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types];
}else{
    types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
}

// 許可している場合...
if (types != UIRemoteNotificationTypeNone) {
    // 何らかの処理
}

以上です。

※参考
http://qiita.com/caesar_cat/items/08018ab22bea27b55443
http://stackoverflow.com/questions/26091875/how-to-update-code-using-enabledremotenotificationtypes-because-it-is-not-suppo

【Objective-C】背景が透明なUIViewControllerを呼び出す(iOS8)

背景が透明なUIViewControllerをiOS8で呼び出す方法です。今までと少しやり方が異なっています。
(確認: xcode6.1 iOS8.1)

    // 適当にViewControllerを作って...
    UIViewController* bViewController = [[UIViewController alloc] init];
    CGRect screen = [[UIScreen mainScreen] bounds];
    bViewController.view.frame = CGRectMake(0.0, 0.0, screen.size.width, screen.size.height);
    bViewController.view.backgroundColor = [UIColor colorWithRed:0.0 green:128.0 blue:128.0 alpha:0.3];
    
    // 透明になるよう設定...
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
        self.providesPresentationContextTransitionStyle = YES;
        self.definesPresentationContext = YES;
        [bViewController setModalPresentationStyle:UIModalPresentationOverCurrentContext]; // selfは呼び出し元のUIViewController
    } else {
        self.modalPresentationStyle = UIModalPresentationCurrentContext;
    }
    [self presentViewController:bViewController animated:YES completion:nil];

以上です。

【Objective-C】Webビュー(UIWebView, WKWebView)の動画再生を検知

WebビューでYoutubeなどの動画を再生したとき、再生されたイベントを検知する方法です。
(環境:xcode6, iOS6 ~ iOS8)

float osVersion = [[[UIDevice currentDevice] systemVersion] floatValue];

if (osVersion >= 6.0) {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movieStarted:) name:@"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
}

if (osVersion >= 8.0) {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movieStarted:) name:UIWindowDidBecomeVisibleNotification object:self.view.window];
}

上記の例では、Webビューの動画が再生されたとき、movieStarted:というメソッドが呼ばれるようになります。

以上です。

・参考
objective c - Rotate when enters a video in iOS 8 - Stack Overflow