Evernote Safari5.1 Web クリッパーが公開された

Evernote Safari Web クリッパー公開 « Evernote日本語版ブログ: “Safari のエクステンション Evernote Web クリッパーが Apple Extension Gallery で公開されました。Apple からダウンロード または Evernote から直接ダウンロード。”

やっとEvernoteのWebクリッパーエクステンションが公開されました。Chromeの機能拡張に近い機能を持っているようです。

が、PDFで保存することはできなくなったようです。PDFで保存したい場合はSafari5.1からEvernoteにページをPDFで保存する方法を試してみてください。

Safari5.1からEvernoteにページをPDFで保存する方法

Safari5.1になってEvernoteプラグインが機能しなくなって久しいですが、いまだ新版はリリースされません(と、書いているとリリースされたりして)。

なので、プリントダイアログからEvernoteにPDFで保存を実行すればできますが、SafariからEvernoteにPDFで保存するのが面倒になりました。

それをAppleScriptで簡単にしてしまおうというのがこの投稿の趣旨です。

で、そのAppleScriptスクリプトですが、以前に色々なアプリケーションからPDFとしてEvernoteに保存するAppleScriptで投稿済みだったりします。SafariだけでなくGoogle Chromeやその他のアプリからも保存が可能です。このスクリプトをSparkやLaunchBarなどから実行すれば、自動的にPDFがEvernoteに保存されます。また、アプリケーションとして保存すれば、Dockに登録して使うこともできます。

ということで、再度掲載しておきます。

-- LaunchBarから実行する場合は以下を有効にする
-- tell application "LaunchBar" to hide
 
tell application "System Events"
	-- AppleScript Editorから実行する場合は以下の2行を有効にする
	-- set theName to name of the first process whose frontmost is true
	-- set visible of process theName to false
	set theName to name of the first process whose frontmost is true
end tell
 
tell application theName to activate
 
if theName is "Google Chrome" then
	printPDF2EvernoteChrome(theName)
else
	printPDF2Evernote(theName)
end if
 
on printPDF2EvernoteChrome(theName)
	tell application "System Events" to tell process theName
		try
			keystroke "p" using command down
			repeat until exists menu button named "PDF" of window 1
				delay 0.2
			end repeat
			tell window 1
				click menu button "PDF"
				repeat until exists menu item named "PDFをEvernoteに保存" of menu 1 of menu button "PDF"
					delay 0.2
				end repeat
				click menu item named "PDFをEvernoteに保存" of menu 1 of menu button "PDF"
			end tell
		end try
	end tell
end printPDF2EvernoteChrome
 
on printPDF2Evernote(theName)
	tell application "System Events" to tell process theName
		try
			keystroke "p" using command down
 
			repeat 20 times
				if exists menu button named "PDF" of sheet 1 of window 1 then exit repeat
				delay 0.2
			end repeat
 
			tell sheet 1 of window 1
				click menu button "PDF"
 
				repeat 20 times
					if exists menu item named "PDFをEvernoteに保存" of menu 1 of menu button "PDF" then exit repeat
					delay 0.2
				end repeat
				click menu item named "PDFをEvernoteに保存" of menu 1 of menu button "PDF"
			end tell
		end try
	end tell
end printPDF2Evernote

Mac App Storeでアプリのアップデートができなくなったら

SpparowのアップデートがあったのでMac App Storeでアップデートしようとしたら、購入した時のアカウントでサインインしてアップデートするようにいわれてアップデートできませんでした。アカウントは購入した時のアカウントだったので、何か障害が発生しているようです。

ので、Appleに問い合わせたところ、アプリをいったん削除して再インストールしてみてくださいということでした。

再インストールでSparrowは無事インストールすることができました。今後アップデートがあるときに同様のことが起こるかはわかりませんが、とりあえず、アカウントが違うといわれたらアプリを削除して再インストールで回避できるようです。

追記: アプリのみ削除して設定ファイル等は削除しないので、アカウントの再設定等は必要ありませんでした。

UINavigationControllerのアニメーションをカスタマイズする方法

UINavigationControllerでビューコントローラーをpush/popするときのアニメーションはスライドです。このアニメーションをカスタマイズしてみます。

ここではフェードを行ってみます。

まずはpushです。

- (IBAction)push {
    SubViewController *subViewController = [[[SubViewController alloc] initWithNibName:nil bundle:nil] autorelease];
    subViewController.view.frame = self.view.bounds;
    subViewController.view.alpha = 0.0;
    [self.view addSubview:subViewController.view];
 
    [UIView transitionWithView:subViewController.view duration:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void) {
        subViewController.view.alpha = 1.0;
    } completion:^(BOOL finished) {
        [self.navigationController pushViewController:subViewController animated:NO];
    }];
}

pushする対象はSubViewControllerです。現在のビューと同じサイズに設定して、透明にして、現在のビューにaddSubviewします。
アニメーションですが、alphaを1.0にして不透明にします。アニメーションが完了したらpushViewController:animated:でpushを行います。animatedにはNOを指定して、デフォルトのスライドアニメーションを行わないようにします。

次にpopです。

- (IBAction)pop {
    NSArray *viewControllers = [self.navigationController viewControllers];
    NSUInteger previousIndex = [viewControllers count] - 2;
    UIViewController *previousViewController = [viewControllers objectAtIndex:previousIndex];
    previousViewController.view.alpha = 0.0;
    [self.view addSubview:previousViewController.view];
 
    [UIView transitionWithView:previousViewController.view duration:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void) {
        previousViewController.view.alpha = 1.0;
    } completion:^(BOOL finished) {
        [self.navigationController popViewControllerAnimated:NO];
    }];
}

viewControllers から、pop後に表示される前のビューコントローラーを取得します。viewControllers配列の後ろから2番目に入っています。このコントローラーのビューのalphaを0.0に設定して透明にして、現在のコントローラーのビューに載せます。
アニメーションも同様にalphaを1.0に設定して、不透明にします。アニメーションが終了したらpopViewControllerAnimated:でpopします。これもNOを指定してデフォルトアニメーションを行わないようにします。

このように、UINavigationControllerでのフェードアニメーションは、push/popはどちらも、これから表示するビューを上に載せて、透明から不透明にアニメーションを行い、アニメーションが終了したタイミングでpush/popを行うことになります。

スレッド毎にNSManagedObjectContextを管理する

基本的にはNSManagedObjectContextはスレッド毎に作成して利用しなければいけません。以下は、その為にスレッド毎にContextを作成して管理するためのコードです。複雑なアプリでなければ、この管理法で十分でしょう。シングルトンなManagerクラスでコンテキストを管理する方法は簡単ですが、スレッドを多用する場合はスレッド毎に管理する方が良いでしょう。

managedObjectContextForCurrentThreadでコンテキストを取得し、保存は[NSManagedObjectContext save:&error]のように、クラスメソッドを呼びます。保存するとNSNotificationでコンテキスト自身のmanagedObjectContextDidSave:が実行され、メインスレッドのコンテキストに変更が反映されます。スレッド毎に自動的にthreadDictionaryにNSManagedObjectContextを登録し、スレッドが破棄されるとコンテキストも破棄されるようになっているので、明示的にコンテキストを破棄する必要はありません。

@interface NSManagedObjectContext (Extras)
+ (NSManagedObjectContext *)managedObjectContextForThread:(NSThread *)thread;
+ (NSManagedObjectContext *)managedObjectContextForCurrentThread;
+ (NSManagedObjectContext *)managedObjectContextForMainThread;
+ (BOOL)save:(NSError **)error;
@end
#import "NSManagedObjectContextExtras.h"
 
NSString * const NSManagedObjectContextThreadKey = @"NSManagedObjectContextThreadKey";
 
@interface NSManagedObjectContext ()
- (void)managedObjectContextDidSave:(NSNotification*)notification;
@end
 
@implementation NSManagedObjectContext (Extras)
 
+ (NSManagedObjectContext *)managedObjectContextForThread:(NSThread *)thread {
    NSMutableDictionary *threadDictionary = [thread threadDictionary];
    NSManagedObjectContext *context = [threadDictionary objectForKey:NSManagedObjectContextThreadKey];
 
    if (!context) {
#ifdef TARGET_OS_IPHONE
        id appDelegate = [[UIApplication sharedApplication] delegate];
#else
        id appDelegate = [NSApp delegate];
#endif
        NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
 
        if ([[NSThread currentThread] isMainThread]) {
            context = mainContext;
        } else {
            context = [[[NSManagedObjectContext alloc] init] autorelease];
            [context setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
        }
 
        [threadDictionary setObject:context forKey:NSManagedObjectContextThreadKey];
    }
 
    return context;
}
 
+ (NSManagedObjectContext *)managedObjectContextForCurrentThread {
    return [NSManagedObjectContext managedObjectContextForThread:[NSThread currentThread]];
}
 
+ (NSManagedObjectContext *)managedObjectContextForMainThread {
    return [NSManagedObjectContext managedObjectContextForThread:[NSThread mainThread]];
}
 
+ (BOOL)save:(NSError **)error {
    NSManagedObjectContext *context = [NSManagedObjectContext managedObjectContextForCurrentThread];
    BOOL isMainThread = [[NSThread currentThread] isMainThread];
 
    if (!isMainThread) {
        [[NSNotificationCenter defaultCenter] addObserver:context
                                                 selector:@selector(managedObjectContextDidSave:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:context];
    }
 
    BOOL result = [context save:error];
 
    if (!isMainThread) {
        [[NSNotificationCenter defaultCenter] removeObserver:context
                                                        name:NSManagedObjectContextDidSaveNotification 
                                                      object:context];
    }
 
    return result;
}
 
- (void)managedObjectContextDidSave:(NSNotification*)notification {
    NSManagedObjectContext *context = [NSManagedObjectContext managedObjectContextForMainThread];
 
    [context performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
                              withObject:notification
                           waitUntilDone:YES];
}
@end

iCloudは「クラウド」なのか

「クラウド」は正式(?)にはクラウドコンピューティングといい、どちらかというとネットワークとハードウェアのハンドリングについて意味する言葉です。ネットワーク・コンピューティングを言い換えただけで宣伝文句がかわっただけという指摘もあるくらいです。

iCloudもネットワークやハードウェアはたしかに「クラウド」になっています。が、それだけではないシステムのようです。データのハンドリングまで雲の中に隠れて行ってくれるようになっているようです。「クラウド」はネットワークやハードは雲の中に隠れていましたが、ユーザはデータの同期などまだまだ意識する点がありました。iCloudは目の前のデータすら雲に入れて、「体験する」クラウドを提供することになるのではないでしょうか。

つまりiCloudは、これまで言い換えただけといわれていたハード中心の「クラウド」から、体験としてのクラウドに「進化」したといえるのではないでしょうか。

(まぁ、使ってみないことには分からないですけどね。)

iOS5はサードパーティにとってどのような意味があるか

iOS5の標準アプリが充実して、サードパーティつぶしだ、いや開発者ががんばればいいんだ、というような記事が散見されますが、私はそのようなレベルの話ではないと考えています。

私の知人にiPhone4ユーザがいます。その人のPCはネットに繋がっていません。App Storeからアプリもほとんどダウンロードしていないようです。iTunesすら知らないのです。

今後はiPhoneとiPadだけしか使わないという人も現れるでしょう。(既に存在するかな)

そのような人がiPhoneを使うことを考えると、自然と標準アプリの充実やiCloudに行き着くと思うのです。

私は、AppleがiOS5はそのような「ライトユーザ」に向けで開発したものではないかと考えています。ある人の言ですが「新しい携帯としてスマホを選ぶ」という状況になりつつあり、iPhoneもAndroidも同じような状況ではないでしょうか。

AppleはiOS5でサードパーティに「ライトユーザ」という新しい顧客を連れてきてくれます。標準アプリの充実は決してApple vs サードパーティではないのです。そして、今後はこれまで以上に「ライトユーザ」を意識した開発やマーケティングが必要になるから、みんながんばってね♡ by Apple、というのがiOS5がサードパーティにとって意味するところではないでしょうか。