読者です 読者をやめる 読者になる 読者になる

まいまいワークス

主にiPhoneアプリの開発で考えた事、調べた事、感じた事などを記していきます。

レビューと言えないような酷い書き込みは削除できるらしい

f:id:cccookie:20140502142847p:plain


App Storeに投稿された不快なレビューを削除する方法


一時期、日本のApp Storeの一部のアプリのレビュー欄で誹謗中傷とか、的外れな批判が酷いという話を聞いた事があるのですが、 レビューと言えないような不快な書き込みは消す事もできるようです。

MacからiTunesを開き、不快なレビューの所にある「問題を報告する」をクリック。 選択肢の中から「問題点」を選択し、コメントを書き込んで「送信」すれば完了のようです。

とはいえ、真っ当な意見の場合は、評価が低くても真摯に受け取りアプリの改善に努めたいですね。

ホームスクリーンに表示されるタイトルもASOの対象?

f:id:cccookie:20140501212505j:plain


BundleDisplayNameを使ったASO対策がローカライズ対応に凄く使える


ASOといえば、

  • アプリタイトル
  • 説明文
  • keyword
  • レビュー評価
  • レビュー文言

などが重要かなと思っていたのですが、タイトル欄に入りきらない文言も検索対象になっているとの事。
個人的にはタイトルが…で省略されるのはちょっといやなんですけど、keywordをガッツリ盛りたい場合は有効かも。

但し、やり過ぎると常にリジェクトのリスクがあります。 エンジニアが死ぬ気で詰めた日程を、このようなリジェクトでふいにしてしまうのは何とも悲しい事ですね。

プログラムの処理時間を計測する

処理時間計測の簡単な方法としてはNSLogを処理の始めと終わりに出力してその時間差を計算する方法がありますが、もう少し正確な計測をしてみたいと思います。

方法は以下の通り!

//これをインポートしておく
#import <sys/time.h>


//任意のメソッド内の記述
struct timeval start, stop;
gettimeofday(&start, NULL);

*** 実際の処理を行う記述 ***

gettimeofday(&stop, NULL);


double sec = stop.tv_sec - start.tv_sec;
double usec = stop.tv_usec-start.tv_usec;
double time = (stop.tv_sec*1000000+stop.tv_usec)-(start.tv_sec*1000000+start.tv_usec);
    
NSLog(@"sec=%f",sec);
NSLog(@"usec=%f",usec);
NSLog(@"time=%f",time);

tv_secで処理時間の秒の部分、tv_usecでマイクロ秒の部分を取得します。
トータルの処理時間は
double time = (stop.tv_sec*1000000+stop.tv_usec)-(start.tv_sec*1000000+start.tv_usec);
このような形で計算します。

[NSThread sleepForTimeInterval:0.5f];
このような形で処理の部分にwait timeを設定して挙動を確認してみてください。

意外と面倒!タイムラインで見られる1時間前、1日前といった文字列を生成する

関連記事  NSDateの罠 - まいまいワークス

サーバーから取得した時間情報を元に、現在の時刻から計算を行い、タイムラインで見られる1時間前、1日前といった文字列を生成します。
サーバーが日本時間を基準に値を返し、iPhoneで設定された時間帯がアメリカだったりすると時差の関係で表示がおかしくなったりしますが、ここも考慮していきます。
日付関連はハマりポイントが多いです!

ソースは以下の通り
サーバーが2014-04-25 12:34:56という値を返したと想定します。

    //サーバーがこの値を返したと仮定
    NSString* dateStr = @"2014-04-25 12:34:56";
    
    //時差補正用数値の指定
    float diff = [[NSTimeZone localTimeZone] secondsFromGMT];   //時差補正用
    float diffTokyoTimeZone = 9*60*60;  //サーバーがGMTの時間を送ってきていればここは0になる
    
    //フォーマット指定
    NSDateFormatter* format = [NSDateFormatter new];
    [format setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    //これで端末の設定に関わらずデータを取得する。
    [format setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
    
    //ロケーションに関わらずその瞬間のGMTを取得
    NSDate* now = [NSDate date];

    //sinceDate以降はロケーションによって値が時差分補正されたGMTになる (datestrは不変)
    //dateWithTimeInterval以降のオフセットはdiffとdiffTokyoTimeZoneで指定。
    //diff:GMTからの時差(秒)で補正。sinceDate以降の値の補正を相殺→ロケーションに関わらず一定のNSDate値を使用できる
    //diffTokyoTimeZone:stringで得られる時刻がJSTで得られる前提でロケーションに関わらず9時間戻す
    NSDate* date = [NSDate dateWithTimeInterval:diff-diffTokyoTimeZone sinceDate:[format dateFromString:datestr]];
    
    
    //経過時間の計算
    NSCalendar* cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents* comps = [NSDateComponents new];
    
    //秒を計算
    comps = [cal components:NSSecondCalendarUnit fromDate:date toDate:now options:0];
    NSUInteger sec = [comps second];
    
    //分を計算
    comps = [cal components:NSMinuteCalendarUnit fromDate:date toDate:now options:0];
    NSUInteger min = [comps minute];
    
    //時間を計算
    comps = [cal components:NSHourCalendarUnit fromDate:date toDate:now options:0];
    NSUInteger hour = [comps hour];
    
    //日数を計算
    comps = [cal components:NSDayCalendarUnit fromDate:date toDate:now options:0];
    NSUInteger day = [comps day];
    
    //週を計算
    comps = [cal components:NSWeekCalendarUnit fromDate:date toDate:now options:0];
    NSUInteger week = [comps week];
    
    //月を計算
    comps = [cal components:NSMonthCalendarUnit fromDate:date toDate:now options:0];
    NSUInteger month = [comps month];
    
    //年を計算
    comps = [cal components:NSYearCalendarUnit fromDate:date toDate:now options:0];
    NSUInteger year = [comps year];

sec,min,hour,day,week,month,yearにそれぞれの単位で計算された差分が入ります。
あとはこれを煮るなり焼くなりなのですが、今回は以下のようにしてみます。
差分が30秒未満ならたった今それ以外なら単純に〜時間前といった値を生成します。

    NSString* outStr;
    if (year != 0) {
        outStr = [NSString stringWithFormat:@"%ld年前",year];
    }else if (month != 0) {
        outStr = [NSString stringWithFormat:@"%ldヶ月前",month];
    }else if (week != 0){
        outStr = [NSString stringWithFormat:@"%ld週間前",week];
    }else if (day != 0){
        outStr = [NSString stringWithFormat:@"%ld日前",day];
    }else if (hour != 0){
        outStr = [NSString stringWithFormat:@"%ld時間前",hour];
    }else if (min != 0){
        outStr = [NSString stringWithFormat:@"%ld分前",min];
    }else if (sec != 0){
        if (sec < 30) {
            outStr = @"たった今";
        }else{
            outStr = [NSString stringWithFormat:@"%ld秒前",sec];
        }
    }else{
        outStr = @"error";
    }

Xcodeユニットテスト事始め

自作クラスに対して、修正が入るたびに手動でテストを行うのはあまりにも非効率なのでユニットテストの調査を行いました。
本エントリはその初歩の初歩。

テスト対象のクラスを作成

ここではテスト用の簡単なクラスを定義します。

#import <Foundation/Foundation.h>

@interface MyClass : NSObject


@property(nonatomic, assign)int a;
@property(nonatomic, assign)int b;
-(int)calcAdd;


@property(nonatomic, strong)NSString* aaa;
@property(nonatomic, strong)NSString* bbb;
-(NSString*)strfunc;

@end

ここではint型のaとbをcalcAddに食わせて結果を得るメソッド
NSString型のaaaとbbbをstrfuncに食わせて結果を得るメソッドの2つを定義します。

#import "MyClass.h"

@implementation MyClass

-(int)calcAdd{
    return _a+_b;
}

-(NSString*)strfunc{
    return [NSString stringWithFormat:@"%@%@",_aaa, _bbb];
}

@end

aとbを加算、aaaとbbbを接続するだけの簡単なクラスです。

テストの記述

xxxx.hファイルは不要、xxxx.mだけで良さそうです。
ここではnewTest.mファイルを作成します。
testではじまるメソッド名のメソッドがテストメソッドと見なされるようです。

#import <XCTest/XCTest.h>       //これはお約束
#import "MyClass.h"         //テスト対象のクラスをインポート

@interface newTest : XCTestCase

@end


@implementation newTest

-(void)testCalcAdd{
    MyClass* calc = [MyClass new];
    calc.a = 3;
    calc.b = 4;
    XCTAssertEqual([calc calcAdd], 7, @"エラー!");
}

-(void)testStr{
    MyClass* str = [MyClass new];
    str.aaa = @"abc";
    str.bbb = @"def";
    XCTAssertEqualObjects([str strfunc], @"abcdef", @"エラー!");
}

@end

各メソッドともにインスタンス化してプロパティ設定を行うところまでは通常のクラスの実行と同じです。
この後の行に書かれているのがテスト関数になります。
XCTAssertEqual(値1, 値2, @"メッセージ");
値1と値2を比較して、値1 == 値2となることが確認できればテスト成功。
失敗の場合はメッセージを出力します。

XCTAssertEqualObjects(object1, object2, @"メッセージ");
object1とobject2を比較して[object1 isEqual:object2]で等しい事を確認できればテスト成功。
失敗の場合はメッセージを出力します。

テスト実行

Product>Testもしくは⌘Uでテストを実行します。

詳細な情報

関数などの詳細な情報はXcodeユニットテストガイドをご一読ください。

アラートにテキストフィールドを追加する

ちょっとしたテキスト情報の入力とか、ID/Passの入力などわざわざviewをつくるよりアラートでできた方が便利ですね。
今回はそのやり方。

f:id:cccookie:20140415104555p:plain

    UIAlertView* alert = [UIAlertView new];
    alert.title = @"タイトル";
    alert.message = @"メッセージ";
    alert.delegate = self;  //<UIAlertViewDelegate>を設定する事
    [alert addButtonWithTitle:@"cancel"];
    [alert addButtonWithTitle:@"OK"];
    alert.alertViewStyle = UIAlertViewStyleDefault;
    
    [alert textFieldAtIndex:0].placeholder = @"プレースホルダー";
    [alert textFieldAtIndex:0].clearButtonMode = UITextFieldViewModeWhileEditing;
    [alert show];

alertViewStyleプロパティには以下の3つが設定できます。

  • UIAlertViewStylePlainTextInput
  • UIAlertViewStyleLoginAndPasswordInput
  • UIAlertViewStyleSecureTextInput

UIAlertViewStyleDefaultにすると普通のアラートが出てきます。(デフォルト)

それぞれのテキストフィールドの設定を行う場合は
[alert textFieldAtIndex:0]
[alert textFieldAtIndex:1]
といった感じで指定します。
上記の例だと、placeholderとclearButtonModeの設定を行っています。

入力した情報の取得は以下の通り

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    
    if (buttonIndex==1) {
        NSLog(@"text=%@",[[alertView textFieldAtIndex:0] text]);
        NSLog(@"text=%@",[[alertView textFieldAtIndex:1] text]);
    }
}

CocoaPodsでライブラリのライセンスの表記を自動生成してみた

オープンソースのライブラリの中には使用している旨を明記しなければいけないものがあったりするのですが、これがなかなかめんどくさいのです。
しかしながらCocoaPods自動生成のフローを一度作ってしまえば後はファイルのコピペと少しの書き換え程度で使い回しができそうです。

手順1 Settings.bundleの作成

New File…からSettings.bundleを作成。
同時に生成されるRoot.plistを編集し、Acknowledgements.plistを追加します。

f:id:cccookie:20140328182140p:plain

Root.plistの編集

Preference Itemsの配下にアイテムをひとつ配置して、 以下のように設定する。

type = Child Pane
Title = Acknowledgements
Filename = Acknowledgements

f:id:cccookie:20140328182221p:plain

Acknowledgements.plistを追加

普通にファイルを追加しようとしたのですが、うまくいかなかったので Root.plistあたりを右クリックでShow in Finder
ファインダでRoot.plistを複製してファイル名をAcknowledgements.plistに変更。
中の要素を削除。ってな感じで対処しました。

f:id:cccookie:20140328182300p:plain

Podfileの編集

Podfileに以下を追加します。

post_install do | installer |
require 'fileutils'
FileUtils.cp_r('Pods/Pods-acknowledgements.plist', 'abcde/Settings.bundle/Acknowledgements.plist', :remove_destination => true)

Setting.bundleのパスが下記のようになっている場合のソースです。 'abcde/Settings.bundle/Acknowledgements.plist'の部分はそれぞれの環境によって書き換えてください。

f:id:cccookie:20140328182346p:plain


Setting.bundlePodfileの定型文を一度書いてしまえばあとはコピペで行けそうですね。
pod installを実行して、ビルドするとライセンス表記が自動生成されます。
素晴らしい!

f:id:cccookie:20140328174740j:plain