Raspberry Pi 2で外付けHDDを利用する

Raspberry Pi 2で外付けHDDを利用する。

パーティションの作成

USBポートにHDDを接続する。

pi@raspberry ~ $ sudo fdisk -l

を実行すると接続したHDDは/dev/sdaだと確認できたが、WARNING: GPT (GUID Partition Table) detected on '/dev/sda'! The util fdisk doesn't support GPT. Use GNU Parted.という警告が表示されたのでgdiskをインストールして初期化する。


pi@raspberry ~ $ sudo gdisk /dev/sda
GPT fdisk (gdisk) version 0.8.5

Partition table scan:
MBR: MBR only
BSD: not present
APM: not present
GPT: present

Found valid MBR and GPT. Which do you want to use?
1 - MBR
2 - GPT
3 - Create blank GPT

Your answer: 2
Using GPT and creating fresh protective MBR.

Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y

Command (? for help): n
Partition number (1-128, default 1):
First sector (34-976773134, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-976773134, default = 976773134) or {+-}size{KMGTP}:
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to 'Linux filesystem'

Command (? for help): p
Disk /dev/sda: 976773168 sectors, 465.8 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): D595477B-ABF6-4B84-BF36-2BD97357CE4D
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 976773134
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number Start (sector) End (sector) Size Code Name
1 2048 976773134 465.8 GiB 8300 Linux filesystem

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sda.
The operation has completed successfully.

パーティションのフォーマット

パーティションをext4でフォーマットする。

pi@raspberry ~ $ sudo mkfs.ext4 /dev/sda1

マウント

マウントする。

pi@raspberry ~ $ sudo mkdir /workspace
pi@raspberry ~ $ sudo mount /dev/sda1 /workspace

自動でマウントするように設定する。/etc/fstabに以下を追記する。
/dev/sda1 /workspace ext4 defaults 0 0

Mac版SkypeでCommand+Returnで投稿する

Mac版SkypeのチャットではReturnキーで投稿するようになってます。これが私には非常に不評でして、何度も間違えて投稿してしまっていました。

どうにかならないものかと思っていたのですが、ハッと気がつきました。我らがKarabiner(元KeyRemap4MacBook)を使えばいいということに。

早速private.xmlに記述。

<appdef>
  <appname>Skype</appname>
  <equal>com.skype.skype</equal>
</appdef>        
<item>
  <name>Command+Return to Return in Skype</name>
  <identifier>private.skype</identifier>
  <only>Skype</only>
  <autogen>__KeyToKey__ KeyCode::RETURN, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_COMMAND | ModifierFlag::NONE, KeyCode::RETURN,
  </autogen>
  <autogen>
    __KeyToKey__ KeyCode::RETURN, KeyCode::RETURN, MODIFIERFLAG_EITHER_LEFT_OR_RIGHT_OPTION
  </autogen>
</item>

これの設定をOnにするとSkypeの時にだけReturnキーで改行、Command+Returnキーで投稿できるようになります。

しかしこのままではIMEでもCommand+Returnでしか変換の確定できなくなります。これはIMEの設定でOption+Returnでも確定できるように設定することで回避できます。

iOSでフィードバックを送信するUIライブラリ「CTFeedback」を公開

iOSでフィードバックをメールで送信するライブラリ「CTFeedback」を作成・公開しました。iOS6.0以降で、MITライセンスです。

AAMFeedbackが更新されないので、似たようなのを自作しました。

1
2
3
CTFeedbackViewController *feedbackViewController = [CTFeedbackViewController controllerWithTopics:CTFeedbackViewController.defaultTopics localizedTopics:CTFeedbackViewController.defaultLocalizedTopics];
feedbackViewController.toRecipients = @[@"ctfeedback@example.com"];
[self.navigationController pushViewController:feedbackViewController animated:YES];

上記のコードで

CTFeedback

このような画面が表示されます。

独自のUINavigationControllerに載せてモーダルに表示しても利用することが可能です。

メールボタンをタップすると、入力した情報と基本情報が入力されているメール作成画面が表示されます。

トピックは独自に作成することが可能です。

CocoaPodsにもPull Requestを送っていますが、現状でもpodspecを提供しているのでPodfileで直接指定すればCocoaPodsでインストールすることができます。

iOS7でのUITableViewの背景色

iOS7ではUITableViewの背景色を変更してもCellの背景色は白のままになる。
Appleのドキュメントによると

UITableViewCell Class Reference: “Whether you use a predefined or custom cell, you can change the cell’s background using the backgroundView property or by changing the inherited backgroundColor property. In iOS 7, cells have a white background by default; in earlier versions of iOS, cells inherit the background color of the enclosing table view. If you want to change the background color of a cell, do so in the tableView:willDisplayCell:forRowAtIndexPath: method of your table view delegate.”

ということで、iOS7ではセルの背景色は白がデフォルトということなので、tableView:willDisplayCell:forRowAtIndexPath:メソッドでセルの背景色を変更してやることになる。

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
    cell.backgroundColor = [UIColor lightGrayColor];
}

Reactive-Evernote-SDK-iOSを作りました

Reactive-Evernote-SDK-iOSを作りました。Evernote SDKをReactiveCocoaで使えるライブラリーです。GitHubで公開しています。

下記のように使います。

[[[RACEvernoteNoteStore noteStore] listNotebooks] subscribeNext:^(NSArray *notebooks) {
    NSLog(@"%@", notebooks);
}                                                         error:^(NSError *error) {
    NSLog(@"error: %@", error);
}                                                     completed:^{
    NSLog(@"completed");
}];

さすがにThriftのコードジェネレータを作成するのは難しいので、[RACSignal createSignal:]で既存のEDAM系のクラスを呼んでいます。

まだlistNotebookとlistTagsしか試してないので、お暇な方は試してバグ報告よろしくです。podspecもあるのでCocoaPodsで簡単に導入できると思います。

Objective-CのFunctional Reactive ProgrammingフレームワークReactiveCocoaを試してみる

今、一部で話題のReactive Programmingですが、Objective-CにもReactiveCocoaというフレームワークが存在します。ということでReactiveCocoaを試しに使ってみます。

GitHubにソースコードを置きました。

iOSアプリのシングルビュー構成のプロジェクトを作成します。

今回はReactiveCocoaはCocoaPodsでインストールします。Podfileを作成してpod ‘ReactiveCocoa’と記述します。

まず、テキストフィールドへの入力によってボタンの表示・非表示を行ってみます。
StoryboardでビューにUITextFieldを二つとUIButtonを一つ配置し、コントローラのヘッダにプロパティを作成します。

@property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
@property (weak, nonatomic) IBOutlet UITextField *passwordTextField;
@property (weak, nonatomic) IBOutlet UIButton *logInButton;

コントローラのviewDidLoadで以下のように記述します。

    [self.usernameTextField.rac_textSignal subscribeNext:^(NSString *text) {
        self.logInButton.hidden = (text.length == 0);
    }];

UITextFieldのカテゴリメソッドrac_textSignalでtextの変更のシグナルのRACSignalが取得できます。RACSignalとはその名の通りイベントのシグナルです。
シグナルが正常に発生するとsubscribeNext:で登録したブロックが実行されます。ログインボタンのhiddenにテキスト長が0かの真偽値を設定しています。これでusernameのtextが入力されていない場合にlogInButtonが非表示になり、値を入力するとlogInButtonが表示されます。

今度はusernameとpasswordに入力するとログインボタンを表示するように変更してみます。先ほどのコードを削除して以下のコードを記述します。

    [[RACSignal combineLatest:@[self.usernameTextField.rac_textSignal, self.passwordTextField.rac_textSignal] reduce:^(NSString *username, NSString *password) {
        return @(username.length == 0 || password.length == 0);
    }] subscribeNext:^(NSNumber *hidden) {
        self.logInButton.hidden = hidden.boolValue;
    }];

二つのUITextFieldのシグナルを[RACSignal combineLatest:reduce:]でまとめて、一つのシグナルとしてあつかいます。combineLatestで二つのシグナルを登録して、reduceで二つのテキストの長さのいずれかが0の場合にYES(のNSNumber)を返しています。
これでusernameとpasswordのtextが入力されていない場合にlogInButtonが非表示になり、どちらともに値を入力するとlogInButtonが表示されます。

次に非同期処理としてTwitterからユーザ名を取得してみます。
Storyboardにボタンとラベルを追加して、コントローラのヘッダのプロパティにバインドします。

@property (weak, nonatomic) IBOutlet UIButton *twitterButton;
@property (weak, nonatomic) IBOutlet UILabel *twitterLabel;

viewDidLoadには以下のように記述します。

    @weakify(self);
    [[self.twitterButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *button) {
        [[[[TwitterManager sharedManager] requestAccessToAccounts] sequenceNext:^{
            return [[TwitterManager sharedManager] fetchName];
        }] subscribeNext:^(NSString *name) {
            @strongify(self);
            dispatch_async(dispatch_get_main_queue(), ^{
                self.twitterLabel.text = name;
            });
        }          error:^(NSError *error) {
            @strongify(self);
            dispatch_async(dispatch_get_main_queue(), ^{
                self.twitterLabel.text = error.localizedDescription;
            });
        }];
    }];

rac_signalForControlEvents:でボタンのイベントのシグナルを取得して、subscribeNext:でボタンのイベントで実行するブロックを登録しています。
TwitterManagerのrequestAccessToAccountsを実行すると、これもシグナルを返します。それが成功すると、fetchNameを実行して、さらにシグナルを返しています。そのシグナルが成功するとtwitterLabel.textに名前を設定し、エラーが発生するとエラー内容を設定しています。

TwitterManagerでの非同期処理の記述の仕方は以下のようになります。

+ (TwitterManager *)sharedManager {
    static TwitterManager *_instance = nil;
 
    @synchronized (self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
 
    return _instance;
}
 
- (id)init {
    self = [super init];
    if (self) {
        self.accountStore = [[ACAccountStore alloc] init];
        self.accountType = [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    }
    return self;
}
 
- (RACSignal *)requestAccessToAccounts {
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
        [self.accountStore requestAccessToAccountsWithType:self.accountType options:nil completion:^(BOOL granted, NSError *error) {
            if (granted) {
                NSArray *accounts = [self.accountStore accountsWithAccountType:self.accountType];
                if (accounts.count > 0) {
                    self.account = accounts[0];
                    [subscriber sendNext:nil];
                } else {
                    [subscriber sendError:[NSError errorWithDomain:@"ERRORDOMAIN" code:0 userInfo:nil]];
                }
            } else {
                [subscriber sendError:(error ? error : [NSError errorWithDomain:@"ERRORDOMAIN" code:0 userInfo:nil])];
            }
 
            [subscriber sendCompleted];
        }];
 
        return [RACDisposable disposableWithBlock:nil];
    }];
 
    return signal;
}
 
- (RACSignal *)fetchName {
    @weakify(self);
    RACSignal *signal = [RACSignal startWithScheduler:[RACScheduler scheduler] subjectBlock:^(RACSubject *subject) {
        @strongify(self);
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.twitter.com/1.1/users/show.json?screen_name=%@", self.account.username]];
        SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:nil];
        request.account = self.account;
        [request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
            if (error) {
                [subject sendError:error];
            } else {
                NSError *subError;
                NSDictionary *status = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&subError];
                if (subError) {
                    [subject sendError:subError];
                } else if (status[@"name"]) {
                    [subject sendNext:status[@"name"]];
                } else {
                    [subject sendError:[NSError errorWithDomain:@"ERRORDOMAIN" code:2 userInfo:nil]];
                }
            }
 
            [subject sendCompleted];
        }];
    }];
 
    return signal;
}

requestAccessToAccountsは[RACSignal createSignal:]でシグナルを作成して返しています。リクエストが成功すると[subscriber sendNext:status[@”name”]]でシグナルを送り、エラーの場合はsendError:でエラーを送ります。完了するとsendCompletedを送ります。RACSignalではsubscribeNext:やsubscribeError:以外にもsubscribeCompletedで完了通知を受けとることができます。return [RACDisposable disposableWithBlock:];でキャンセルする時の処理を記述して返します。今回はキャンセル処理は無いのでnilを渡しています。(createSignal:って非同期処理に利用していいのかな?このあたり良く理解していないです。)

fetchNameでは[RACSignal startWithScheduler:subjectBlock:]でシグナルを作成して返しています。RACDisposableを返さない事以外はcreateSignal:とほぼ同様のようです。

ということで、ReactiveCocoaを試してみましたが、非Reactive Programmingとはかなり記述が変わってきますね。ReativeCocoaを使いこなせばUIイベントの処理や非同期処理が記述しやすくなる感触が得られました。

iPhoneアプリ「Everword – Evernote暗記カード」を公開しました

Evernoteを利用して暗記学習を行うためのiPhoneアプリ「Everword – Evernote暗記カード」を公開しました。

Evernoteのノートブックを登録して、ノートブックにあるノートをカード形式で表示して暗記学習をするためのアプリです。

Evernoteのノートは複雑な書式が使えるので、単純なノートの英単語の暗記から複雑なノートが必要な専門分野の知識の記憶まで可能です。Evernoteに情報を集約することになるので、情報の活用の幅が広がります。

私は英語の書籍を読む時に、分からない単語をEvernoteに入れていって、辞書アプリから単語の意味をコピーしてノートを作り、それをこのEverwordで学習しています。

また、習熟度別で3日後7日後など出題間隔が開くので、効率的な学習が出来ます。

無料ですので、ぜひ試しにダウンロードしてみてください。

無料ではノートブックの登録が一つに制限され、広告が表示されるという制約があります。アプリ内課金で250円支払っていただくと、ノートブックの登録制限と広告の表示が解除されます。複数のノートブックを使った方が、学習内容をよりマネージメントしやすくなります。