Hatena::ブログ(Diary)

24/7 twenty-four seven Twitter

Bloglinesで閲読登録 ADD TO Hatena::RSS Subscribe with livedoor Reader Add to Google

mail address

Follow me on twitter.

iPhone アプリケーション

Hatena touch LDR touch TV Listings LCD Clock MyWebClip MyWebClip LITE  Japan Subway Route Map こころくろっく 英辞郎 on the WEB for iPhone(アルク)
i-Radio くるりんぱ性格診断 英辞郎検索ランキング(アルク) kotobank - コトバンク miil

iPad アプリケーション

LCD Clock HD 「超」整理手帳 for the iPad

Windows 8 Store アプリケーション

クックパッド

共著

2013-04-22

iOS/Macの両方で使えて、文字の選択やリンクのクリックに対応したテキストビューをテスト公開しました。

kishikawakatsumi/SECoreTextView ? GitHub

iOS ScreenShot 1


OS X ScreenShot 1


SECoreTextView はリッチテキストの表示と文字の選択(現在はOS Xのみ)やリンクがクリック可能だったりするテキストビューです。

別のアプリでテーブルビューのセルにリンクを含むテキストを表示するのに、既存のものでMacで使えるいい感じのものが今ひとつ見つからなかったので書きました。

OS X で使うだけだとなんなので、せっかくだから iOS にも対応してみました。

UITableVIewやNSTableVIewのセルで使うと便利だと思います。

iOS のほうは半日くらいでちょちょっと書いただけなのでおかしなところが結構あると思うので見つけたら教えてください。

2012-12-29

iPhone の画面操作を録画するライブラリを公開しました。

kishikawakatsumi/ScreenRecorder ? GitHub

ScreenRecorder は iOS デバイスの画面を連続的にキャプチャして、動画に変換することで画面の操作を録画することができる機能をアプリケーションに追加します。

開発中のソフトウェアのユーザーテストなどに利用すると効果的です。


使い方

1. 以下のファイルをプロジェクトに追加します

  • Lib/SRScreenRecorder.h
  • Lib/SRScreenRecorder.m
  • Vendor/KTouchPointerWindow.h
  • Vendor/KTouchPointerWindow.m

2. 以下のフレームワークをリンクします

  • QuartzCore.framework
  • CoreVideo.framework
  • CoreMedia.framework
  • AVFoundation.framework

startRecording で録画を開始します。

デフォルトの設定は

  • バックグラウンドに入ったときに自動保存
  • 10 分ごとに自動保存、ファイルのローテート
  • 30FPSで録画
  • タッチ箇所の表示

となっています。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[SRScreenRecorder sharedInstance] startRecording];
    return YES;
}

いくつかの挙動は、設定で変更することができます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    SRScreenRecorder *recorder = [SRScreenRecorder sharedInstance];
    recorder.frameInterval = 1; // 60 FPS
    recorder.autosaveDuration = 1800; // 30 minutes
    recorder.showsTouchPointer = NO; // hidden touch pointer
    recorder.filenameBlock = ^(void) {
        return @"screencast.mov";
    }; // change filename
    
    [recorder startRecording];
    
    return YES;
}

タッチ・ポインターの表示には @ さんの KTouchPointerWindow を利用しています。

iPhone/iPadの画面にタッチ位置を表示するためのソースコード KTouchPointerWindow を公開しました

itok/KTouchPointerWindow ? GitHub

2012-11-08

Windows 8 StoreアプリケーションでGoogleAnalyticsを使う

kishikawakatsumi/GoogleAnalytics-for-WinJS ? GitHub

Windows 8の Store アプリケーションで GoogleAnalytics を使う場合、まだ公式の SDK はありませんが、サードパーティからすでにいくつかライブラリがリリースされているのでそれを使うのがいいと思います。

ただ JavaScript(WinJS) で書いた Metro Style アプリケーション用のものはまだ無いみたいなので Windows Runtime Component を使って WinJS から C# のライブラリを扱う方法でやってみました。


Google Analytics for WinRT - Home

NuGet Gallery | Google Analytics for WinRT (Windows 8) apps 2.4

↑ GoogleAnalytics にリクエストを送る Windows 8 用のライブラリはいくつかありますが、今回はソースコードも公開されていて実装もシンプルな Google Analytics for WinRT - Home を使いました。


Windows Runtime Component のプロジェクトをソリューションに追加する

↓ 詳しい方法は下記のドキュメントを見てください。

Walkthrough: Creating a simple component in C# or Visual Basic and calling it from JavaScript

チュートリアル : C# または Visual Basic での単純なコンポーネントの作成および JavaScript による呼び出し

  1. Solution Explorer でソリューションを右クリックして、Add > New Project... と選択します。
  2. Visual C# > Windows Runtime Component と選択して、プロジェクト名を適当につけて [OK] を押します。
  3. Solution Explorer でWindows Store アプリケーションのプロジェクトを右クリックして、Add Reference... を選択します。
  4. Solution > Projects と選択して、先ほどの Windows Runtime Component として作ったプロジェクトにチェックをつけて [OK] を押します。

f:id:KishikawaKatsumi:20121108142646p:image

↑ ここまででこんな感じになります。


GoogleAnalytics のライブラリをプロジェクトに追加する

先ほどの Windows Runtime Component のプロジェクトから利用できるように GoogleAnalytics のライブラリを追加します。

  1. Solution Explorer でプロジェクトを右クリックして、Add > New Folder と選択します。
  2. そのフォルダに 'Nascent.GoogleAnalytics.dll' をコピーします。
  3. Solution Explorer で Windows Runtime Component のプロジェクトを右クリックして、Add Reference... を選択します。
  4. [Browse...] ボタンを押して 'Nascent.GoogleAnalytics.dll' を選択します。
  5. 'Nascent.GoogleAnalytics.dll' にチェックをつけて [OK] を押します。

ライブラリの呼び出しをブリッジするクラスを書く

↓ こんな感じで。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Nascent.GoogleAnalytics;

namespace WindowsRuntimeGoogleAnalytics
{
    public sealed class Tracker
    {
        public string webPropertyId { get; set; }

        public Tracker(string id)
        {
            webPropertyId = id;
        }

        public void SetCustomVariable(int index, string name, string value)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.SetCustomVariable(index, name, value);
        }

        public void TrackPageView(string page)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.TrackPageView(page);
        }

        public void TrackEvent(string category, string action)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.TrackEvent(category, action, null, null);
        }

        public void TrackEvent(string category, string action, string label)
        {
            AnalyticsTracker tracker = AnalyticsTracker.GetInstance(webPropertyId);
            tracker.TrackEvent(category, action, label, null);
        }
    }
}

JSのインターフェースを書く

↓ 直接呼んでもいいけどまとまってると何かと使いやすいのでこんな感じで。

(function () {
    "use strict";

    var Tracker = WinJS.Class.define(
        function (webPropertyId) {
            this.webPropertyId = webPropertyId;
            this.tracker = new WindowsRuntimeGoogleAnalytics.Tracker(webPropertyId);
        },

        {
            setCustomVariable: function (index, name, value) {
                var that = this;
                that.tracker.setCustomVariable(index, name, value);
            },

            trackPageView: function (page) {
                var that = this;
                that.tracker.trackPageView(page);
            },

            trackCurrentPageView: function () {
                var that = this;
                var page = Application.navigator.pageControl.uri.replace("ms-appx://" + Windows.ApplicationModel.Package.current.id.name.toLowerCase(), "");
                that.trackPageView(page);
            },

            trackEvent: function (category, action, label) {
                var that = this;
                that.tracker.trackEvent(category, action, label);
            }
        });

    WinJS.Namespace.define("GoogleAnalytics", {
        Tracker: new Tracker("UA-4291014-7")
    });
})();

WinJS から呼び出してみる

↓ たとえば、画面が表示されるたびにアクセスを記録するならこんな感じで。

ready: function (element, options) {
    GoogleAnalytics.Tracker.trackCurrentPageView();

    var listView = element.querySelector(".groupeditemslist").winControl;
    listView.groupHeaderTemplate = element.querySelector(".headertemplate");
〜(略)〜

↓ 今回のサンプルコードはこちらです。

kishikawakatsumi/GoogleAnalytics-for-WinJS ? GitHub

2012-10-24

リンクするだけで iOS 6 で Google Map が使えるようになる YAMapKit を公開しました。

kishikawakatsumi/YAMapKit ? GitHub

YAMapKit は MapKit.framework と(ほぼ)互換性のある代替ライブラリです。

Google Maps Javascript API と UIWebView を利用して iOS 6 で Apple の標準地図の代わりに Google Map を使った表示ができます。

MapKit.framework と(ほぼ)互換性があるのでリンク先を差し替えるだけで動作します(たいていの場合は)。

あまりヘビーな利用には向きませんが、アプリケーションの中でちょっと MapKit を使って地図を表示したりピンを挿したりしているという場合に効果的です。


まだ未サポートの機能がたくさんあるので、手伝ってくれる方や、バグレポート、要望を歓迎します。

使い方

  1. MapKit.framework のリンクを外します。
  2. libMapKit.a をリンクします。
  3. CoreLocation.framework をリンクします。

できないこと

  • ジオコーディング(代わりに 'CLGeocoder' を使ってください)
  • カスタムビューのオーバーレイ表示(組み込みのオーバーレイ (MKPolylineView, MKCircleView など) しか使えません)
  • アノテーションのドラッグ&ドロップ
  • アノテーションのコールアウトを表示したあとで更新する

(たぶん他にもいっぱいあります)


利用例

f:id:KishikawaKatsumi:20121024221827p:image:w200 f:id:KishikawaKatsumi:20121024221820p:image:w200 f:id:KishikawaKatsumi:20121024221831p:image:w200

f:id:KishikawaKatsumi:20121024221835p:image:w200 f:id:KishikawaKatsumi:20121024221839p:image:w200 f:id:KishikawaKatsumi:20121024221843p:image:w200

2012-10-23

Objective-C でサブクラスのインスタンスから任意のスーパークラスのメソッドを呼ぶ

サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドを呼びます。


↓ 下のように Shape クラスと Shape クラスを継承した Path クラス、および Path クラス を継承した Circle があります。

それぞれのクラスで draw メソッドをオーバーライドしています。

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end

↓ Circle クラスのインスタンスから draw メソッドを呼び出すと Circle クラスの draw メソッドが実行されて "Circle." と出力されます。

たまに多態性を無視してスーパークラスや、スーパークラスのさらにスーパークラスのメソッドを実行したいということってありますよね。

そういうときは 対象メソッドの IMP (メソッドを参照する関数へのポインタ) を使います。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

↑ IMP の定義のままだと ARC が戻り値を retain しようとするので、戻り値が void の関数ポインタにキャストしています。


↓ 上記のコードの出力は下記になります。

2012-10-23 13:15:20.632 Monomorphism[50033:c07] Circle.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Path.
2012-10-23 13:15:20.633 Monomorphism[50033:c07] Shape.

サブクラスのインスタンスからポリモーフィズムを無視して任意のスーパークラスのメソッドが呼べました。

↓ 試したコードの全体を載せておきます。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Shape
////////////////////////////////////////////////////////////////////////
@interface Shape : NSObject
@end

@implementation Shape

- (void)draw {
    NSLog(@"%@", @"Shape.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Path
////////////////////////////////////////////////////////////////////////
@interface Path : Shape
@end

@implementation Path

- (void)draw {
    NSLog(@"%@", @"Path.");
}

@end

////////////////////////////////////////////////////////////////////////
#pragma mark - Circle
////////////////////////////////////////////////////////////////////////
@interface Circle : Path
@end

@implementation Circle

- (void)draw {
    NSLog(@"%@", @"Circle.");
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Shape *shape = [[Circle alloc] init];
    [shape draw]; // => Circle.
    
    SEL selector = @selector(draw);
    
    void(*pathFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Path instanceMethodForSelector:selector];
    pathFunction(shape, selector); // => Path.
    
    void(*shapeFunction)(id, SEL, ...) = (void(*)(id, SEL, ...))[Shape instanceMethodForSelector:selector];
    shapeFunction(shape, selector); // => Shape.
}

@end

2012-10-20

iOS 6.0 と iOS 5.x の両方で動作するアプリケーションをビルドする設定

iOS 4.0 と iPhone OS 3.x の両方で動作するアプリケーションをビルドする設定 - 24/7 twenty-four seven

↑ こちらも参考に

iOS 4.0 が登場したくらいのときに上の記事を書いて、仕組みは変わってないのですけど Xcode 4.x 系だと UI が変わってるので現在のやり方をまとめます。


ベース SDK と Deployment Target を設定する

プロジェクトの "Build Settings" で "Base SDK" を "Latest iOS" にします。

前にも書きましたが、ベース SDK は最新を指定したほうがいいです。

f:id:KishikawaKatsumi:20121020113008p:image:w600


プロジェクトの "Info" で "Deployment Target" をサポートする OS の最も低いバージョンにします。

(下の場合は iOS 5.0 以降で動作する。)

f:id:KishikawaKatsumi:20121020113320p:image:w600


今なら、Base SDK 6.0 でビルドして、Deployment Target を 5.0 または 5.1 にするのが効果的でしょうか。2世代サポートということで。


新しい Framework を Weak Link (Optional) に設定する

古い環境には含まれていない Framework をリンクしていると、Dynamic Linker がシンボルのロードに失敗してアプリケーションが起動しません。

その場合は、新しい OS にのみ存在する Framework を Weak Link に設定します(今回は Social.framework)。

f:id:KishikawaKatsumi:20121020113833p:image:w600

↑ リンクの設定はプロジェクトではなくターゲットに対してしか行えないので、ターゲットを選択して "Build Phases" > "Link Binary With Libraries" から設定します。

リンクするフレームワークを設定して、 Required > Optional に変更します。


OS のバージョンごとに処理を分岐する

古い API には存在しないセレクタを呼び出したり、クラスを参照したりするとクラッシュしますので必要に応じて処理を分岐します。

- (void)tweet:(id)sender
{
    Class clazz = NSClassFromString(@"SLComposeViewController");
    if (clazz) {
        SLComposeViewController *controller = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
        [self presentViewController:controller animated:YES completion:nil];
    } else {
        TWTweetComposeViewController *controller = [[TWLandscapeTweetComposeViewController alloc] init];
        [self presentViewController:controller animated:YES completion:nil];
    }
}

Auto Layout に注意

Auto Layout は iOS 6 以降しか使えません。Auto Layout を使用していると iOS 5.x などで起動時に関連のクラスが無くてクラッシュします。StoryBoard を新しく追加したときなどにうっかり Auto Layout のチェックをつけたままにしてしまったりするので注意しましょう。

f:id:KishikawaKatsumi:20121020114404p:image:w300

2012-10-05

iOS 6 では Supported interface orientations の順番に注意!

f:id:KishikawaKatsumi:20121005033214p:image:w480

最近の Xcode ではアプリケーションが対応しているデバイスの向きをターゲットの Summary 画面から GUI を用いて設定できるようになりましたが、ここから設定する場合はボタンを押す順番に注意する必要があります。

というのも、この画面で設定した内容は、Info.plist の Supported interface orientations (UISupportedInterfaceOrientations) に反映されるのですが、この項目は Array の値で順番が起動時の状態に影響するからなのです。


上記の画面の状態になるように、ボタンを左から順に押していった場合、Info.plist の UISupportedInterfaceOrientations は下記のようになります。これは新規プロジェクトを作成した場合のデフォルト値です。

f:id:KishikawaKatsumi:20121005033335p:image:w480


今度は同じ状態になるように、ボタンを「右から」順に押していきます。すると Info.plist は下記のようになります。

値の順番が変わっています。

f:id:KishikawaKatsumi:20121005034024p:image:w480


実はこの順番は起動時の画面の向きに関係していて、iOS 6 では一番先頭に指定されている画面の向きで起動することになります。

つまり、前の例では縦画面で起動するのですが、後の例では横向きで起動することになります。


別のキーに Initial interface orientation (UIInterfaceOrientation) というものがあって、こちらを指定すると初期状態を指定できそうですが、試したところ iOS 6 ではどうもこの値は無視されるようです。

2012-10-04

iOS 6 ではグループスタイルのテーブルビューの背景色がこっそり非推奨になっている。

f:id:KishikawaKatsumi:20121004223424p:image:w320


iOS 6 では上記のカラーを生成するメソッドがヘッダのコメントでひそかに deprecated になっています。

UIInterface.h

// Group style table view backgrounds can no longer be represented by a simple color.
// If you want to have a background in your own view that looks like the table view background,
// then you should create an empty table view and place it behind your content.
+ (UIColor *)groupTableViewBackgroundColor; // This method will be deprecated during the 6.0 seed program

ドキュメントの記載は変わってないのですが、実際に使ってみると今までは下記のコードでピンストライプのカラーが設定されていましたが、iOS 6 だと真っ黒になってしまいます。

self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];

この背景色を使いたい場合は空のテーブルビューを設定しろということなので、iOS 6 から代替のコードは下記のようになります。

UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
[self.view addSubview:tableView];

2012-10-01

はやりのシンボルフォントを iOS で画像として扱える SymbolFontKit を公開しました。

kishikawakatsumi/SymbolFontKit ? GitHub

ScreenShot1

↑ シミュレータに表示されている画像やツールバーのボタン、タブバーのアイコンは全てフォントです。

シンボルフォントとは要するにアイコン画像などをフォント形式にしたものです。

Webだと最近のブラウザだとWebフォントが使えるので、利用者の環境にフォントがインストールされていなくても使えるので、最近は解像度非依存ということもあっていろいろな Github や Twitter などいろいろなサイトで利用されています。

シンボルフォントについて詳しくは下記のリンク先などを見てください。

Ligature Symbols 〜ほんとにべんりなフォントのはなし〜

【完全版】Ligature Symbols フォントセットの自作方法 - くらげだらけ

Ligature Symbols

で、フォントなのでベクターデータのためどんな解像度でもキレイに表示されることや、1つのアイコンで色違いや別のサイズを表示することが簡単だったり、はやっているので様々なデザインのシンボルフォントが使いやすいライセンスで入手できるなど、iOS でも利用できたら便利だと思って作りました。

使い方

  1. SFKImage.h/m をプロジェクトにコピーします。
  2. 利用したいフォントファイルをプロジェクトにコピーします。
  3. 上記でコピーしたフォントファイルのファイル名を UIAppFonts をキーにして Info.plist に追加します。

API は UIImage 互換なので UIImage と同様の使い方ができます。(実は現在は imageNamed: 以外のインスタンス化はできません)

SFKImage *image = [SFKImage imageNamed:@"print"];

インスタンス化した SFKImage オブジェクトは UIImage のインスタンスと同様に UIButton や UIImageView、UITabBarItem などに直接設定することができます。

self.imageView1.image = [SFKImage imageNamed:@"share"];

UITabBarItem *calendarTabBarItem = [[UITabBarItem alloc] initWithTitle:@"calendar" image:[SFKImage imageNamed:@"calender"] tag:1];
UITabBarItem *globeTabBarItem = [[UITabBarItem alloc] initWithTitle:@"globe" image:[SFKImage imageNamed:@"globe"] tag:2];
_tabBar.items = @[calendarTabBarItem, globeTabBarItem];

フォントのレンダリングは実際に画面に描画されるときまで遅延されるので、UIImage と違って、1つのインスタンスを途中で色や大きさを変えたりできます。

SFKImage *image = [SFKImage imageNamed:@"compass"];
image.size = CGSizeMake(20, 20);
image.color = [UIColor redColor];
self.imageView6.image = image;
    
image.size = CGSizeMake(40, 40);
image.color = [UIColor yellowColor];
self.imageView7.image = image;
    
image.size = CGSizeMake(80, 80);
image.color = [UIColor blueColor];
self.imageView8.image = image;

UIImage として振る舞うためにちょっと無茶をしているのでそのまま AppStore の審査に通るかどうかは「?」ですが近いうちに適当なアプリを提出して調査したいと思います。


SymbolFontKit は第1回iphone_dev_jp東京 iPhone/Mac Hackathon 〜みんなが幸せになるハッカソン〜の成果物です。

2012-09-25

はてな touch 1.2.7 をリリースしました。

Hatena touch

はてな touch/Hatena touch はてな touch / Hatena touch

はてな touch 1.2.7 がアップルの審査を通過しました。

iOS 6, iPhone 5をサポートしました。


変更点

  • iOS 6, iPhone 5をサポートしました。