CSSとjQueryで画像をトリミング(画像の一部分の切り抜き)して表示する方法をご紹介します。
実際にトリミングされた画像をファイルとして保存する場合は、サーバ側で処理をする必要があります。
この記事では、ブラウザ上で画像の一部を表示したり、表示する部分をマウスで動かしたりする方法を紹介します。
まず、画像の切り抜き表示をやってみます。画像の切り抜き表示はCSSだけで出来ます。
以下のようにimgタグをdivタグで囲みます。
そして、CSSでそれぞれのタグのスタイルを以下のように設定します。
まず、divタグに切り抜く領域のサイズを指定します。今回、使用する画像のサイズは900×600なので、それより
小さなサイズになるように、widthとheightを指定しています。
次にdivタグのoverflow属性にhiddenを指定します。表示する画像「900×600」に対して、
250×200を指定しているので、外側のdivタグのボックスを画像がはみ出してしまいます。
そのはみ出した部分を表示しないように設定しているのが、overflow:hiddenです。
そして、外側のdivタグのposition属性をrelativeに、imgタグのposition属性をabsoluteに指定します。
absoluteは外側のタグの左上を基準位置として設定し、内側のタグのコンテンツの表示位置をtopやleft属性を用いて
そこからの距離で指定できるようにするためのおまじないです。
absoluteを指定した外側のタグにはralatitveを設定する必要があります。
topとleftには、それぞれ、-100pxを指定しています。これにより、枠内には、画像の(100,100)の位置から
250×200の分のサイズの画像が表示されます。これで、画像のトリミングは完成です。
さて次に、切り取り領域をマウスで移動させることに挑戦します。
考え方は簡単です。上記のCSSで指定したleftとtopの値をマウスの移動に応じて、変更するだけです。
マウスのボタンが押されている時だけ、画像を移動させたいので、それを管理するための変数、isMouseDownを
定義し、mousedownイベントでそれをtrueにします。そして、ボタンが離された時にそのフラグをfalseにします。
マウスが画像の領域外にでた際にもfalseにします。
メインはmousemoveイベントの処理です。マウスが移動した変化量をleftとtopに反映させています。
起点となる位置を最初にマウスボタンが押された位置に設定し、その位置とマウスが移動した後のマウスの位置の
変化量を計算し、それを現在のtopとleftの値に加算します。
このままだと、画像が移動しすぎて、枠内に画像が残らないようになってしまいますので、
枠内にスペースが出来ない範囲で画像が移動するように、座標を補正します。
mousemoveイベントはマウスが動いている間、継続して発生しますので、
一度処理が終わったら、次の起点の位置として、現在のマウスの位置を設定します。
これでマウスで枠内に表示する画像の領域を移動させることが出来るようになりました。
実際にトリミングされた画像をファイルとして保存する場合は、サーバ側で処理をする必要があります。
この記事では、ブラウザ上で画像の一部を表示したり、表示する部分をマウスで動かしたりする方法を紹介します。
縮小表示
トリミング表示

CSSで画像を切り抜く
まず、画像の切り抜き表示をやってみます。画像の切り抜き表示はCSSだけで出来ます。
以下のようにimgタグをdivタグで囲みます。
<div id="outer"> <img id="image" src="image.jpg"/> </div>
そして、CSSでそれぞれのタグのスタイルを以下のように設定します。
#outer { position:relative; width : 250px; height : 200px; overflow:hidden; border: 1px solid lightgray; } #image { position:absolute; left:-100px; top:-100px; }
まず、divタグに切り抜く領域のサイズを指定します。今回、使用する画像のサイズは900×600なので、それより
小さなサイズになるように、widthとheightを指定しています。
次にdivタグのoverflow属性にhiddenを指定します。表示する画像「900×600」に対して、
250×200を指定しているので、外側のdivタグのボックスを画像がはみ出してしまいます。
そのはみ出した部分を表示しないように設定しているのが、overflow:hiddenです。
そして、外側のdivタグのposition属性をrelativeに、imgタグのposition属性をabsoluteに指定します。
absoluteは外側のタグの左上を基準位置として設定し、内側のタグのコンテンツの表示位置をtopやleft属性を用いて
そこからの距離で指定できるようにするためのおまじないです。
absoluteを指定した外側のタグにはralatitveを設定する必要があります。
topとleftには、それぞれ、-100pxを指定しています。これにより、枠内には、画像の(100,100)の位置から
250×200の分のサイズの画像が表示されます。これで、画像のトリミングは完成です。
マウスでトリミング領域を移動させる
さて次に、切り取り領域をマウスで移動させることに挑戦します。
考え方は簡単です。上記のCSSで指定したleftとtopの値をマウスの移動に応じて、変更するだけです。
$(function () { var isMouseDown = false; var startX; var startY; $('#image').mousedown(function(e){ isMouseDown = true; startX = e.pageX; startY = e.pageY; return false; }); $('#image').mouseup(function(){ isMouseDown = false; return false; }); $('#image').mouseout(function(){ isMouseDown = false; return false; }); $('#image').mousemove(function(e){ if(isMouseDown) { //移動量を計算する var diffX = e.pageX - startX; var diffY = e.pageY - startY; //移動後の座標 var left = parseInt($('#image').css('left').replace('px','')) + diffX; var top = parseInt($('#image').css('top').replace('px','')) + diffY; /* 移動領域を枠内に制限する */ if($('#image').width() + left < $('#outer').width()) { left = $('#outer').width() - $('#image').width(); } else if(left > 0) { left = 0; } if($('#image').height() + top < $('#outer').height()) { top = $('#outer').height() - $('#image').height(); } else if(top > 0) { top = 0; } $('#image').css('left',left + 'px'); $('#image').css('top',top + 'px'); } //起点を現在のマウスの位置で更新する startX = e.pageX; startY = e.pageY; return false; }); });
マウスのボタンが押されている時だけ、画像を移動させたいので、それを管理するための変数、isMouseDownを
定義し、mousedownイベントでそれをtrueにします。そして、ボタンが離された時にそのフラグをfalseにします。
マウスが画像の領域外にでた際にもfalseにします。
メインはmousemoveイベントの処理です。マウスが移動した変化量をleftとtopに反映させています。
起点となる位置を最初にマウスボタンが押された位置に設定し、その位置とマウスが移動した後のマウスの位置の
変化量を計算し、それを現在のtopとleftの値に加算します。
このままだと、画像が移動しすぎて、枠内に画像が残らないようになってしまいますので、
枠内にスペースが出来ない範囲で画像が移動するように、座標を補正します。
mousemoveイベントはマウスが動いている間、継続して発生しますので、
一度処理が終わったら、次の起点の位置として、現在のマウスの位置を設定します。
これでマウスで枠内に表示する画像の領域を移動させることが出来るようになりました。
スポンサーサイト
iOSで文字列の数値チェックを行う方法はいろいろ有りますが、一番手っ取り早いのが
NSStringの「rangeOfString:options:」を使う方法ではないかと思います。
rangeOfStringの引数には正規表現が使用できます。
正規表現を変えることで半角英数などいろいろなチェックに応用できます。
NSStringの「rangeOfString:options:」を使う方法ではないかと思います。
NSString *target = _textView.text; //対象の文字列 NSRange match = [target rangeOfString:@"^[0-9]+$" options:NSRegularExpressionSearch]; //数値の場合 if(match.location != NSNotFound) { ・・・・・・・・ } //数値でない場合 else { ・・・・・・・・ }
rangeOfStringの引数には正規表現が使用できます。
正規表現を変えることで半角英数などいろいろなチェックに応用できます。
XMLデシリアライズを行うiOS用ライブラリ「ChimeraXML」をgithubに公開しました。
「ChimeraXML」はJavaの「Simple」ライブラリのようにXMLをオブジェクトに変換するためのObjective-Cのライブラリです。
解析対象のXMLに対応するクラスを定義し、そのプロパティのメタ情報を解析エンジンに提供するためのメソッドを実装するだけで、
XMLの内容が、定義したクラスのオブジェクトの各プロパティに設定されます。
「ChimeraXML」は「NSXMLParser」を使用し、要素を上から順にオブジェクトのプロパティにマッピングしながら、 プロパティに値を設定していきます。
「ChimeraXML」を使用すると、XMLを解析するためのコーディング量を大幅に削減することが可能になります。
開発者が行う必要がある作業は、ほぼ、XMLに対応するクラスを定義し、そのクラスで「propertyInfoForElement:」メソッドを実装するだけになります。
以下のURLから「ChimeraXML」のプロジェクト一式を取得してください。
https://github.com/mykonos2008/ChimeraXML
Workspaceを作って、「ChimeraXML」をプロジェクトとして追加する方法と、
ソースファイル一式(ChimeraXML-Prefix.pchをのぞく)をそのまま、アプリのプロジェクトに追加する方法があります。
前者は少し面倒なので、後者をおすすめします。
以下のような商品の一覧XMLをデシリアライズするとします。
まず、XMLのデシリアライズ先となるオブジェクトのクラスを定義します。
サンプルでは、大元のクラスであるResultEntityと各商品の情報を管理するItemEntity、
商品のサイズを管理するItemSizeEntity、取り扱い店舗の情報を管理するShopEntityの
4つのクラスを定義します。
デシリアライズ先となるオブジェクトは、CPXmlEntityクラスを継承している必要があります。
「ResultEntity.h」
そして、XMLの要素に対応するプロパティを定義します。上記のサンプルでは、
item-count要素に対応するitemCountプロパティ、items要素に対応するitemプロパティ、
categories要素に対応するcategoriesプロパティを定義しています。
プロパティの型として使用できるのは、「NSArray」、「NSString」、「NSNumber」、「CPXmlEntity」を 継承するクラスの4つです。
クラス名やプロパティ名は要素名と一致させる必要はありません。
「ResultEntity.m」
次に実装ファイルです。実装ファイルでは、CPXmlEntityで定義されているクラスメソッド「propertyInfoForElement:」を
オーバーライドして実装する必要があります。
Objective-cではJavaのようなアノテーションの仕組みが無いため、その代わりとして、このメソッドで
プロパティのメタ情報を提供するようにします。引数で指定されたXMLの要素名に対応するプロパティの情報を
NSString、またはCCPropertyInfoクラスのオブジェクトとして返却します。
NSArray型のプロパティ以外は対応するプロパティ名をNSString型で返却するだけです。
NSArray型の場合、格納されるオブジェクトの型が提供される必要があるため、CCPropertyInfo型を返すようにします。
CCPropertyInfoは、name、type、childType、implicitArrayの4つのプロパティを持っています。
nameにはプロパティ名を設定します。typeには値を設定する必要はありません。implicitArrayは後述します。
childTypeは、NSArrayの中に格納するオブジェクトの型を設定します。
このメソッドをどのように実装するかについてはルールは有りませんが、上記のサンプルコードのようにstatic指定子を使用した
NSDictionaryを使用すると効率が良いと思います。NSDictionaryのキーに設定しているのは、XMLの要素名です。
解析対象のXMLが持っている全ての要素について、対応するプロパティを定義しておく必要は有りません。
アプリケーションが必要とする項目についてのみ、プロパティを定義し、propertyInfoForElement:で値を
返却するようにしておけば問題ないです。
「ItemEntity.m」
「ItemEntity.m」
CPPropertyInfoの[implicitArray]プロパティは、サンプルのshop要素のように同じ名前を持つ要素がそれら一括りにする親要素を持たずに
複数回出現する場合、[YES]を設定する必要があります。category要素のようにそれらを一括りにする親要素(categories)を持つ場合は、
デフォルト値である、Noを設定しておいてください。
「ItemSizeEntity.h」
「ItemSizeEntity.m」
「ShopEntity.h」
「ShopEntity.m」
parseをする部分の処理はとてもシンプルです。_targetXMLは解析対象のXMLを保持するNSString型の変数です。
デシリアライズ処理を行うクラスは「ChimeraParser」というクラスで、ルート要素に対応するエンティティクラスの型で初期化します。
デシリアライズは、parseメソッドで行います。プロパティのメタ情報(CCPropertyInfo)に不備が有る場合など、
例外をスローするようになっていますので、開発段階では、try、catchでparseメソッドを囲っておいた方が良いと思います。
「ChimeraXML」はJavaの「Simple」ライブラリのようにXMLをオブジェクトに変換するためのObjective-Cのライブラリです。
解析対象のXMLに対応するクラスを定義し、そのプロパティのメタ情報を解析エンジンに提供するためのメソッドを実装するだけで、
XMLの内容が、定義したクラスのオブジェクトの各プロパティに設定されます。
「ChimeraXML」は「NSXMLParser」を使用し、要素を上から順にオブジェクトのプロパティにマッピングしながら、 プロパティに値を設定していきます。
「ChimeraXML」を使用すると、XMLを解析するためのコーディング量を大幅に削減することが可能になります。
開発者が行う必要がある作業は、ほぼ、XMLに対応するクラスを定義し、そのクラスで「propertyInfoForElement:」メソッドを実装するだけになります。
「ChimeraXML」の取得
以下のURLから「ChimeraXML」のプロジェクト一式を取得してください。
https://github.com/mykonos2008/ChimeraXML
「ChimeraXML」の利用方法
Workspaceを作って、「ChimeraXML」をプロジェクトとして追加する方法と、
ソースファイル一式(ChimeraXML-Prefix.pchをのぞく)をそのまま、アプリのプロジェクトに追加する方法があります。
前者は少し面倒なので、後者をおすすめします。
コードの書き方
以下のような商品の一覧XMLをデシリアライズするとします。
<?xml version="1.0" encoding="UTF-8" ?> <result> <item-count>2</item-count> <ignore>test</ignore> <items> <item> <item-name>商品1</item-name> <item-desc>商品1の説明</item-desc> <item-price>2500</item-price> <item-size> <width>10.5</width> <height>15.0</height> </item-size> <tel>03-1111-2222</tel> <tel>03-3333-4444</tel> </item> <item> <item-name>商品2</item-name> <item-desc>商品2の説明</item-desc> <item-price>3200</item-price> <item-size> <width>13.5</width> <height>18.2</height> </item-size> <shop> <shop-code>S01</shop-code> <shop-name>新宿南口店</shop-name> </shop> <shop> <shop-code>S02</shop-code> <shop-name>渋谷店</shop-name> </shop> </item> </items> <categories> <category>1</category> <category>2</category> </categories> </result>
まず、XMLのデシリアライズ先となるオブジェクトのクラスを定義します。
サンプルでは、大元のクラスであるResultEntityと各商品の情報を管理するItemEntity、
商品のサイズを管理するItemSizeEntity、取り扱い店舗の情報を管理するShopEntityの
4つのクラスを定義します。
デシリアライズ先となるオブジェクトは、CPXmlEntityクラスを継承している必要があります。
「ResultEntity.h」
#import "CPXmlEntity.h" @interface ResultEntity : CPXmlEntity @property(strong,nonatomic) NSNumber *itemCount; @property(strong,nonatomic) NSArray *items; @property(strong,nonatomic) NSArray *categories; @end
そして、XMLの要素に対応するプロパティを定義します。上記のサンプルでは、
item-count要素に対応するitemCountプロパティ、items要素に対応するitemプロパティ、
categories要素に対応するcategoriesプロパティを定義しています。
プロパティの型として使用できるのは、「NSArray」、「NSString」、「NSNumber」、「CPXmlEntity」を 継承するクラスの4つです。
クラス名やプロパティ名は要素名と一致させる必要はありません。
「ResultEntity.m」
#import "ResultEntity.h" #import "ItemEntity.h" @implementation ResultEntity + (id)propertyInfoForElement:(NSString *)element { static NSDictionary *propDic; if(!propDic){ CPPropertyInfo *items = [[CPPropertyInfo alloc] init]; items.name = @"items"; items.childType = [ItemEntity class]; CPPropertyInfo *categories = [[CPPropertyInfo alloc] init]; categories.name = @"categories"; categories.childType = [NSNumber class]; propDic = @{ @"item-count": @"itemCount", @"items":items, @"categories": categories }; } return propDic[element]; } @end
次に実装ファイルです。実装ファイルでは、CPXmlEntityで定義されているクラスメソッド「propertyInfoForElement:」を
オーバーライドして実装する必要があります。
Objective-cではJavaのようなアノテーションの仕組みが無いため、その代わりとして、このメソッドで
プロパティのメタ情報を提供するようにします。引数で指定されたXMLの要素名に対応するプロパティの情報を
NSString、またはCCPropertyInfoクラスのオブジェクトとして返却します。
NSArray型のプロパティ以外は対応するプロパティ名をNSString型で返却するだけです。
NSArray型の場合、格納されるオブジェクトの型が提供される必要があるため、CCPropertyInfo型を返すようにします。
CCPropertyInfoは、name、type、childType、implicitArrayの4つのプロパティを持っています。
nameにはプロパティ名を設定します。typeには値を設定する必要はありません。implicitArrayは後述します。
childTypeは、NSArrayの中に格納するオブジェクトの型を設定します。
このメソッドをどのように実装するかについてはルールは有りませんが、上記のサンプルコードのようにstatic指定子を使用した
NSDictionaryを使用すると効率が良いと思います。NSDictionaryのキーに設定しているのは、XMLの要素名です。
解析対象のXMLが持っている全ての要素について、対応するプロパティを定義しておく必要は有りません。
アプリケーションが必要とする項目についてのみ、プロパティを定義し、propertyInfoForElement:で値を
返却するようにしておけば問題ないです。
「ItemEntity.m」
#import "CPXmlEntity.h" @interface ItemEntity : CPXmlEntity @property(strong,nonatomic) NSString *itemName; @property(strong,nonatomic) NSString *itemDesc; @property(strong,nonatomic) NSNumber *itemPrice; @property(strong,nonatomic) ItemSizeEntity *itemSize; @property(strong,nonatomic) NSArray *tels; @property(strong,nonatomic) NSArray *shops; @end
「ItemEntity.m」
#import "ItemEntity.h" @implementation ItemEntity + (id)propertyInfoForElement:(NSString *)element { static NSDictionary *propDic; if(!propDic){ CPPropertyInfo *tels = [[CPPropertyInfo alloc] init]; tels.name = @"tels"; tels.childType = [NSString class]; tels.implicitArray = YES; CPPropertyInfo *shops = [[CPPropertyInfo alloc] init]; shops.name = @"shops"; shops.childType = [ShopEntity class]; shops.implicitArray = YES; propDic = @{ @"item-name": @"itemName", @"item-desc": @"itemDesc", @"item-price": @"itemPrice", @"item-size": @"itemSize", @"tel" : tels, @"shop" : shops, }; } return propDic[element]; } @end
CPPropertyInfoの[implicitArray]プロパティは、サンプルのshop要素のように同じ名前を持つ要素がそれら一括りにする親要素を持たずに
複数回出現する場合、[YES]を設定する必要があります。category要素のようにそれらを一括りにする親要素(categories)を持つ場合は、
デフォルト値である、Noを設定しておいてください。
「ItemSizeEntity.h」
@interface ItemSizeEntity : CPXmlEntity @property(strong,nonatomic) NSNumber *width; @property(strong,nonatomic) NSNumber *height; @end
「ItemSizeEntity.m」
#import "ItemSizeEntity.h" @implementation ItemSizeEntity + (id)propertyInfoForElement:(NSString *)element { static NSDictionary *propDic; if(!propDic){ propDic = @{ @"width": @"width", @"height": @"height" }; } return propDic[element]; } @end
「ShopEntity.h」
#import "CPXmlEntity.h" @interface ShopEntity : CPXmlEntity @property(nonatomic,strong) NSString *shopCode; @property(nonatomic,strong) NSString *shopName; @end
「ShopEntity.m」
#import "ShopEntity.h" @implementation ShopEntity + (id)propertyInfoForElement:(NSString *)element { static NSDictionary *propDic; if(!propDic){ propDic = @{ @"shop-code": @"shopCode", @"shop-name": @"shopName" }; } return propDic[element]; } @end
parseをする部分の処理はとてもシンプルです。_targetXMLは解析対象のXMLを保持するNSString型の変数です。
デシリアライズ処理を行うクラスは「ChimeraParser」というクラスで、ルート要素に対応するエンティティクラスの型で初期化します。
デシリアライズは、parseメソッドで行います。プロパティのメタ情報(CCPropertyInfo)に不備が有る場合など、
例外をスローするようになっていますので、開発段階では、try、catchでparseメソッドを囲っておいた方が良いと思います。
NSData *data = [_targetXML dataUsingEncoding:NSUTF8StringEncoding]; ChimeraParser *chimeraParser = [[ChimeraParser alloc] initWithTargetClass:[ResultEntity class]]; ResultEntity *result = [chimeraParser parse:data];
前回の記事でCore BlueToothを使用して、iOSデバイスをBLE機器として動作させるサンプルコードを紹介しました。
そこで今回は、そのサンプル機器にiPhoneから接続するセントラル(Central)を作成するサンプルコードを紹介します。

前回の記事では接続されるとHello!と返すだけの単純なペリファレル(BLE機器)を作成しました。
ペリファレルに1つのサービスを追加し、さらにそのサービスにキャラクタリスティックを1つ持たせる形になっていました。
ペリファレルを作成する際は、CBPeripheralManagerが中心的な役割を担っていましたが、セントラルを作成する場合は
CBCentralManagerがその役割を担います。
処理を行うViewControllerにはCBCentralManagerDelegateとCBPeripheralDelegateの2つのプロトコルを採用します。
CBPeripheralManagerと同じく、CBCentralManagerの場合も、Bluetoothが使用可能な状態になったことを
確認してから、後続の処理を行う必要があります。CBCentralManagerのインスタンスを生成すると、
centralManagerDidUpdateState:がコールされるので、そこで状態を確認するようにしています。
接続開始ボタンがおされると、scanForPeripheralsWithServices:options:で、アドバタイズをしているペリファレルを
探し始めます。第一引数に特定のサービスのUUIDを指定することで、そのUUIDに該当するサービスを提供するペリファレルのみを
取得出来ます。
ペリファレルが発見されると、centralManager:didDiscoverPeripheral:advertisementData:RSSI:がコールされます。
目的のペリファレルが発見されたので、これ以上のスキャンは必要無いので、まずスキャンを停止します。
そして、発見されたペリファレルにconnectPeripheral:options:で接続を試みます。
ペリファレルへ接続すると、centralManager:didConnectPeripheral:がコールされます。
CBPeripheralのdelegateに自身を設定し、次にCBPeripheralのdiscoverServices:を使用して
ペリファレルが持つ、サービスをもう一度検索します。
scanする際にサービスを指定してscanしているので、冗長な処理に思えますが、CBService
のインスタンスを取得するためには、この作業が必要になります。
サービスが発見されるとperipheral:didDiscoverServices:がコールされますので、今度は
CBPeripheralのdiscoverCharacteristics:forService:でキャラクタリスティックの検索を開始します。
キャラクタリスティックが発見されると、peripheral:didDiscoverCharacteristicsForService:error:が
コールされますので、そこでキャラクタリスティックから値の取得を行います。
キャラクタリスティックの値が取得されると、peripheral:didUpdateValueForCharacteristic:error:がコールされますので、
そこで画面のラベルに結果の値を設定しています。
そこで今回は、そのサンプル機器にiPhoneから接続するセントラル(Central)を作成するサンプルコードを紹介します。

前回の記事では接続されるとHello!と返すだけの単純なペリファレル(BLE機器)を作成しました。
ペリファレルに1つのサービスを追加し、さらにそのサービスにキャラクタリスティックを1つ持たせる形になっていました。
ペリファレルを作成する際は、CBPeripheralManagerが中心的な役割を担っていましたが、セントラルを作成する場合は
CBCentralManagerがその役割を担います。
処理を行うViewControllerにはCBCentralManagerDelegateとCBPeripheralDelegateの2つのプロトコルを採用します。
#import "ViewController.h" #define SERVICE_UUID @"9EA08FC0-FFCF-46D1-ABE3-A1A726699681" #define CHARACTERISTIC_UUID @"1E222259-9612-4139-B3CB-19BA1DFB9C0E" @interface ViewController () @end @implementation ViewController{ CBCentralManager *_centralManager; CBPeripheral *_peripheral; CBService *_service; IBOutlet UIButton *_startConnect; IBOutlet UILabel *_resultLabel; } -(id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self) { _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; } return self; } //CBCentralManagerのステータスが更新されるとコールされる -(void)centralManagerDidUpdateState:(CBCentralManager *)central { NSLog(@"State Updated"); if(central.state == CBCentralManagerStatePoweredOn) { NSLog(@"ready"); //接続開始ボタンを有効にする _startConnect.enabled = YES; } }
CBPeripheralManagerと同じく、CBCentralManagerの場合も、Bluetoothが使用可能な状態になったことを
確認してから、後続の処理を行う必要があります。CBCentralManagerのインスタンスを生成すると、
centralManagerDidUpdateState:がコールされるので、そこで状態を確認するようにしています。
//接続開始ボタンのハンドラ -(IBAction)startConnectTouched:(id)sender { CBUUID *serviceUUID = [CBUUID UUIDWithString:SERVICE_UUID]; [_centralManager scanForPeripheralsWithServices:@[serviceUUID] options:nil]; }
接続開始ボタンがおされると、scanForPeripheralsWithServices:options:で、アドバタイズをしているペリファレルを
探し始めます。第一引数に特定のサービスのUUIDを指定することで、そのUUIDに該当するサービスを提供するペリファレルのみを
取得出来ます。
//ペリファレルを発見するとコールされる -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { NSLog(@"Peripheral discoverd :%@",advertisementData); //スキャンを停止し、ペリファレルに接続する _peripheral = peripheral; [_centralManager stopScan]; [_centralManager connectPeripheral:peripheral options:nil]; }
ペリファレルが発見されると、centralManager:didDiscoverPeripheral:advertisementData:RSSI:がコールされます。
目的のペリファレルが発見されたので、これ以上のスキャンは必要無いので、まずスキャンを停止します。
そして、発見されたペリファレルにconnectPeripheral:options:で接続を試みます。
//ペリファレルに接続すると、コールされる -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@"Connected to peripherel"); //サービスを検索する _peripheral.delegate = self; [_peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]]; }
ペリファレルへ接続すると、centralManager:didConnectPeripheral:がコールされます。
CBPeripheralのdelegateに自身を設定し、次にCBPeripheralのdiscoverServices:を使用して
ペリファレルが持つ、サービスをもう一度検索します。
scanする際にサービスを指定してscanしているので、冗長な処理に思えますが、CBService
のインスタンスを取得するためには、この作業が必要になります。
//ペリファレルのサービスを発見するとコールされる -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { NSLog(@"Services discovered"); for (CBService *service in peripheral.services) { NSLog(@"Service :%@",service); [_peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service]; } } //サービスのキャラクタリスティックを発見するとコールされる -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"Discovered characteristic:%@",characteristic.UUID); [_peripheral readValueForCharacteristic:characteristic]; } }
サービスが発見されるとperipheral:didDiscoverServices:がコールされますので、今度は
CBPeripheralのdiscoverCharacteristics:forService:でキャラクタリスティックの検索を開始します。
キャラクタリスティックが発見されると、peripheral:didDiscoverCharacteristicsForService:error:が
コールされますので、そこでキャラクタリスティックから値の取得を行います。
//キャラクタリスティックの値を取得するとコールされる -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { NSData *data = characteristic.value; NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"data = %@",result); _resultLabel.text = result; [_centralManager cancelPeripheralConnection:_peripheral]; }
キャラクタリスティックの値が取得されると、peripheral:didUpdateValueForCharacteristic:error:がコールされますので、
そこで画面のラベルに結果の値を設定しています。
iOS7で「iBeacon」が発表されたことで、ますます注目が集まっているBluetooth Low Energyですが、
iOSでは、iOS5から「Core Blutooth Framework」が提供され、BLE機器と接続するアプリを開発することが
出来るようになっています。
また、iOS6からはiOSのデバイスをBluetooth対応機器として、動作させるためのAPIも提供されています。
そこで本日は、iOSのデバイスをBluetooth対応機器として、動作させるサンプルコードを紹介します。
コードの紹介に入る前にBLEの基本について少しお話します。BLEは、クライアント-サーバ型の形式になっていて、
主にデータを発信するサーバの役割を担う端末をペリファレル(Peripherel)、ペリファレルからデータを提供される側を
セントラル(Central)と言います。
iPhoneアプリはは体温計などのペリファレルからデータを受信するセントラルとして機能するケースが多いと
思いますが、今回はデータを提供する側のペリファレルとして動作するアプリを開発してみます。
ペリファレルは、セントラルに対して、サービスと呼ばれる一連のデータと機能をまとめたものを提供します。
例えば、心拍系であれば心拍を計測し、そのデータを提供するサービスを提供します。一つのペリファレルが、複数のサービスを提供することも可能です。
サービスはキャラクタリスティックと呼ばれるサービスの詳細情報を持つ属性を持っています。例えば心拍系の場合、心拍や、心拍系の場所などを
キャラクタリスティックとして、持つことになると思います。
つまり、ペリファレルは、ペリファレル→サービス→キャラクタリスティックという階層構造を持っていることになります。
最初に、「Link Binary with Libraries」で「CoreBluetooth.framework」を追加します。
そして、ペリファレルの機能を提供するクラスのヘッダで、CoreBluetooth/CoreBluetooth.hをインポートします。
今回は、ViewControllerに必要な機能を実装してみます。
ペリファレルを実装するにあたり、中心的な役割を担うのが、CBPeripheralManagerです。initでこのクラスのインスタンスを作成しています。
ペリファレルに対して発生する様々なイベントを処理するCBPeripheralManagerDelegateプロトコルをViewControllerに採用して、
initのdelegateに指定しています。
続いて、サービスとキャラクタリスティックを構築します。サービスとキャラクタリスティックにはそれぞれUUIDを持たせる必要があります。
MACのターミナルでuuidgenを実行し、それを割り当てています。キャラクタリスティックのvalueにはnilを指定していますが、
もし、値を指定した場合、それがキャッシュされ、自動的にその値をセントラルに返すような挙動をします。
状況に応じて動的に値を返したい場合、必ずnilを指定しておく必要があります。
CBPeripheralManagerのインスタンスを生成すると、Bluetoothが利用可能な状態になり、 peripheralManagerDidUpdateState:が
コールされます。もし、端末がBLEをサポートしていないケースなどを考慮し、ここで、状態をチェックしてから、後続の処理を
継続する必要があります。
ここでは、Bluetoothが利用可能な状態になっていることを確認し、サービスをCBPeripheralManagerに追加しています。
サービスとキャラクタリスティックが準備できたところで、次に「アドバタイズ」を開始します。
アドバタイズとは、セントラルに対して、ペリファレルの存在と公開しているサービスの情報を送信する行為のことを言います。
このサンプルアプリでは、2つのボタンを設置して、アドバタイズの開始と終了をボタンが押されたタイミングで
行うようにしました。

そして、最後にセントラルからの接続と要求を受けて、結果をセントラルに返す処理です。
セントラルから要求を受けると、peripheralManager:didReceiveReadRequest:がコールされます。
今回は単純に「Hello!」という文字列をセントラルに返すだけの処理になっています。
要求されたキャラクタリスティックが自身が提供するものと一致していることを確認してから処理を行っています。
前述しましたがCBCharacteristicのインスタンスを生成する際、valueを指定しているとこのメソッドは
コールされず、自動的にそこで指定した値が返されることになります。
peripheralManager:didReceiveReadRequest:がコールされると必ずCBPeripheralManagerの
respondToRequest:withResult:をコールして、応答をセントラルに返す必要があります。
今回はセントラルに対してキャラクタリスティックの値を返却するサンプルでしたが、逆にセントラルからキャラクタリスティックの値を
更新する要求を処理することも可能です。また、セントラルの要求に応じて、継続的にキャラクタリスティックの値の変化をセントラルに
通知する処理を書くことも可能です。
iOSでは、iOS5から「Core Blutooth Framework」が提供され、BLE機器と接続するアプリを開発することが
出来るようになっています。
また、iOS6からはiOSのデバイスをBluetooth対応機器として、動作させるためのAPIも提供されています。
そこで本日は、iOSのデバイスをBluetooth対応機器として、動作させるサンプルコードを紹介します。
ペリファレルとセントラルについて
コードの紹介に入る前にBLEの基本について少しお話します。BLEは、クライアント-サーバ型の形式になっていて、
主にデータを発信するサーバの役割を担う端末をペリファレル(Peripherel)、ペリファレルからデータを提供される側を
セントラル(Central)と言います。
iPhoneアプリはは体温計などのペリファレルからデータを受信するセントラルとして機能するケースが多いと
思いますが、今回はデータを提供する側のペリファレルとして動作するアプリを開発してみます。
サービスとキャラクタリスティック
ペリファレルは、セントラルに対して、サービスと呼ばれる一連のデータと機能をまとめたものを提供します。
例えば、心拍系であれば心拍を計測し、そのデータを提供するサービスを提供します。一つのペリファレルが、複数のサービスを提供することも可能です。
サービスはキャラクタリスティックと呼ばれるサービスの詳細情報を持つ属性を持っています。例えば心拍系の場合、心拍や、心拍系の場所などを
キャラクタリスティックとして、持つことになると思います。
つまり、ペリファレルは、ペリファレル→サービス→キャラクタリスティックという階層構造を持っていることになります。
Core Bluetoothを利用したペリファレルの実装
最初に、「Link Binary with Libraries」で「CoreBluetooth.framework」を追加します。
そして、ペリファレルの機能を提供するクラスのヘッダで、CoreBluetooth/CoreBluetooth.hをインポートします。
今回は、ViewControllerに必要な機能を実装してみます。
#define SERVICE_UUID @"9EA08FC0-FFCF-46D1-ABE3-A1A726699681" #define CHARACTERISTIC_UUID @"1E222259-9612-4139-B3CB-19BA1DFB9C0E" @implementation ViewController { CBPeripheralManager *_peripheralManager; CBMutableService *_service; CBCharacteristic *_characteristic; } -(id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self){ _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil]; //サービスとキャラクタリスティックを構築する CBUUID *characteristicUUID = [CBUUID UUIDWithString:CHARACTERISTIC_UUID]; _characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable]; CBUUID *serviceUUID = [CBUUID UUIDWithString:SERVICE_UUID]; _service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES]; _service.characteristics = @[_characteristic]; } return self; } //Bluetoothの状態が変更されるとコールされる -(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral { NSLog(@"State Updated"); if(peripheral.state == CBPeripheralManagerStatePoweredOn){ NSLog(@"ready"); [_peripheralManager addService:_service]; } } //サービスがペリファレルに追加されるとコールされる。 -(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error { if(error) { NSLog(@"Error adding Service:%@",[error localizedDescription]); } else{ NSLog(@"Adding Service Successful"); } }
ペリファレルを実装するにあたり、中心的な役割を担うのが、CBPeripheralManagerです。initでこのクラスのインスタンスを作成しています。
ペリファレルに対して発生する様々なイベントを処理するCBPeripheralManagerDelegateプロトコルをViewControllerに採用して、
initのdelegateに指定しています。
続いて、サービスとキャラクタリスティックを構築します。サービスとキャラクタリスティックにはそれぞれUUIDを持たせる必要があります。
MACのターミナルでuuidgenを実行し、それを割り当てています。キャラクタリスティックのvalueにはnilを指定していますが、
もし、値を指定した場合、それがキャッシュされ、自動的にその値をセントラルに返すような挙動をします。
状況に応じて動的に値を返したい場合、必ずnilを指定しておく必要があります。
CBPeripheralManagerのインスタンスを生成すると、Bluetoothが利用可能な状態になり、 peripheralManagerDidUpdateState:が
コールされます。もし、端末がBLEをサポートしていないケースなどを考慮し、ここで、状態をチェックしてから、後続の処理を
継続する必要があります。
ここでは、Bluetoothが利用可能な状態になっていることを確認し、サービスをCBPeripheralManagerに追加しています。
サービスとキャラクタリスティックが準備できたところで、次に「アドバタイズ」を開始します。
アドバタイズとは、セントラルに対して、ペリファレルの存在と公開しているサービスの情報を送信する行為のことを言います。
このサンプルアプリでは、2つのボタンを設置して、アドバタイズの開始と終了をボタンが押されたタイミングで
行うようにしました。

-(IBAction)startAdvertisingTouched:(id)sender { //アドバタイズを開始する [_peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[_service.UUID], CBAdvertisementDataLocalNameKey:@"HelloPeripheral"}]; } -(IBAction)stopAdvertisingTouched:(id)sender { //アドバタイズを停止する [_peripheralManager stopAdvertising]; } //アドバタイズが開始されるとコールされる -(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error { if(error){ NSLog(@"Error Advertising:%@",[error localizedDescription]); } else{ NSLog(@"Starting Advertising Successful"); } }
そして、最後にセントラルからの接続と要求を受けて、結果をセントラルに返す処理です。
セントラルから要求を受けると、peripheralManager:didReceiveReadRequest:がコールされます。
-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request { NSLog(@"Request received: %@ request = %@",request.characteristic.UUID.description,request.central.UUID); //キャラクタリスティックのUUIDが一致する場合 if([request.characteristic.UUID isEqual:_characteristic.UUID]) { request.value = [@"Hello!" dataUsingEncoding:NSUTF8StringEncoding]; [_peripheralManager respondToRequest:request withResult:CBATTErrorSuccess]; return; } [_peripheralManager respondToRequest:request withResult:CBATTErrorAttributeNotFound]; }
今回は単純に「Hello!」という文字列をセントラルに返すだけの処理になっています。
要求されたキャラクタリスティックが自身が提供するものと一致していることを確認してから処理を行っています。
前述しましたがCBCharacteristicのインスタンスを生成する際、valueを指定しているとこのメソッドは
コールされず、自動的にそこで指定した値が返されることになります。
peripheralManager:didReceiveReadRequest:がコールされると必ずCBPeripheralManagerの
respondToRequest:withResult:をコールして、応答をセントラルに返す必要があります。
今回はセントラルに対してキャラクタリスティックの値を返却するサンプルでしたが、逆にセントラルからキャラクタリスティックの値を
更新する要求を処理することも可能です。また、セントラルの要求に応じて、継続的にキャラクタリスティックの値の変化をセントラルに
通知する処理を書くことも可能です。
UITableViewはUIScrollViewを継承しているので、UITableViewのdelegateにUIScrollViewDelegateプロトコルを
採用したクラスを設定しておけば、そのクラスで、スクロールの開始、終了イベントの通知を受けることが出来ます。
(おそらく、UITableViewがUIScrollViewのdelegateをオーバーライドしているからだと思います。)
スクロールの開始時は、(厳密にはドラッグの開始)「scrollViewWillBeginDragging:」がコールされます。
その後、「scrollViewDidScroll:」がスクロールしている間、何回もコールされます。
ですので、スクロール開始時に何か処理をするとしたら、「scrollViewWillBeginDragging:」が適しています。
その後、ドラッグが終わると、「scrollViewDidEndDragging:willDecelerate:」がコールされ、その後、
しばらくスクロールが続いた後(スクロールは急停止せず、徐々にとまります)、完全停止した後に「scrollViewDidEndDecelerating:」がコールされます。
スクロール終了時の処理は、場合に応じてこの2つから選択すればよいと思います。
今回、これを利用した以下のようなサンプルアプリを作ってみました。

ViewControllerのViewにUITableViewとキャラクター画像のUIImageViewを貼付け、スクロールが開始すると、キャラクター画像を
アニメーションで徐々に透過し、スクロールが終了すると、今度はアニメーションで徐々に表示されるようにしました。
UITableViewのdelegateの設定は、storyboardで行っています。
採用したクラスを設定しておけば、そのクラスで、スクロールの開始、終了イベントの通知を受けることが出来ます。
(おそらく、UITableViewがUIScrollViewのdelegateをオーバーライドしているからだと思います。)
スクロールの開始時は、(厳密にはドラッグの開始)「scrollViewWillBeginDragging:」がコールされます。
その後、「scrollViewDidScroll:」がスクロールしている間、何回もコールされます。
ですので、スクロール開始時に何か処理をするとしたら、「scrollViewWillBeginDragging:」が適しています。
その後、ドラッグが終わると、「scrollViewDidEndDragging:willDecelerate:」がコールされ、その後、
しばらくスクロールが続いた後(スクロールは急停止せず、徐々にとまります)、完全停止した後に「scrollViewDidEndDecelerating:」がコールされます。
スクロール終了時の処理は、場合に応じてこの2つから選択すればよいと思います。
今回、これを利用した以下のようなサンプルアプリを作ってみました。

ViewControllerのViewにUITableViewとキャラクター画像のUIImageViewを貼付け、スクロールが開始すると、キャラクター画像を
アニメーションで徐々に透過し、スクロールが終了すると、今度はアニメーションで徐々に表示されるようにしました。
#import "ViewController.h" @interface ViewController () @end @implementation ViewController { NSArray *_fruitsArray; IBOutlet UITableView *_tableView; IBOutlet UIImageView *_charactor; } -(id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self){ _fruitsArray = @[@"apple",@"banana",@"grape",@"melon",@"strawberry",@"persimmon", @"kiwi fruit",@"cherry",@"plum",@"pear",@"pineapple",@"loquat"]; } return self; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [_fruitsArray count]; } -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *identifer = @"cell"; UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:identifer]; if(!cell){ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer]; } cell.textLabel.text = _fruitsArray[indexPath.row]; return cell; } -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [UIView animateWithDuration:0.5f animations:^{ _charactor.alpha = 0.0f; }]; } -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [UIView animateWithDuration:0.5f animations:^{ _charactor.alpha = 1.0f; }]; }
UITableViewのdelegateの設定は、storyboardで行っています。