【Library】写真のグルーピングやフィルタリングが可能な ALAssetsLibrary用ライブラリ

2014年5月31日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

ALAassetsLibraryから取得した写真を月ごとにグルーピングしたり、JPEGだけあるいはスクリーンショットだけを抜き出すといった処理が行えるライブラリを作りました。このライブラリではモデルクラスのみを提供しています(ビューはありません)。



基本


クラス図はこんな感じ。

左の3クラス(LKAssetsLibrary,LKAssetsGroup,LKAsset)がALAssetsLibraryの主要クラスのラッパーとなっていて便利メソッドが用意されている。

LKAssetの定義
@interface LKAsset : NSObject

// Properties (Status)
@property (assign, nonatomic, readonly) BOOL deleted;

// Properties (Image)
@property (weak  , nonatomic, readonly) UIImage* thumbnail;
@property (weak  , nonatomic, readonly) UIImage* aspectRatioThumbnail;
@property (weak  , nonatomic, readonly) UIImage* fullScreenImage;
@property (weak  , nonatomic, readonly) UIImage* fullResolutionImage;

// Properties (Date number)
@property (assign, nonatomic, readonly) NSTimeInterval timeInterval;
@property (assign, nonatomic, readonly) NSInteger dateTimeInteger;  // yyyyMMddHH

// Properties (ALAsset property)
@property (strong, nonatomic, readonly) NSURL* url;
@property (strong, nonatomic, readonly) CLLocation* location;
@property (strong, nonatomic, readonly) NSDate* date;
@property (strong, nonatomic, readonly) NSString* fileExtension;    // upper string JPG, PNG, ...
@property (assign, nonatomic, readonly) CGSize size;
@property (assign, nonatomic, readonly) LKAssetType type;

// Properties (Filter)
@property (assign, nonatomic, readonly) BOOL isJPEG;
@property (assign, nonatomic, readonly) BOOL isPNG;
@property (assign, nonatomic, readonly) BOOL isScreenshot;
@property (assign, nonatomic, readonly) BOOL isPhoto;
@property (assign, nonatomic, readonly) BOOL isVideo;
  :
ちなみに fullScreenImageとfullResolutionImageは回転している写真を適正に補正してくれる。

使い方の例。
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(_assetsLibraryDidSetup:)
                                                 name:LKAssetsLibraryDidSetupNotification
                                               object:nil];
    self.assetsLibrary = [LKAssetsLibrary assetsLibrary];
    [self.assetsLibrary reload];
}

ALAssetsLibraryと異なり blocksではなく通知ベースで使う。アセットの取得が済むと LKAssetsLibraryDidSetupNotification が通知されるので、後は LKAssetsGroupとLKAssetを取り出して使っていく。

- (void)_assetsLibraryDidSetup:(NSNotification*)notification
{
    [self.tableView reloadData];
}

グループのタイトルとサムネイルをテーブルのセルへ表示する例
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GroupCell" forIndexPath:indexPath];

    LKAssetsGroup* assetsGroup = self.assetsLibrary.assetsGroups[indexPath.row];   
    cell.imageView.image = assetsGroup.posterImage;
    cell.textLabel.text = assetsGroup.description;
    return cell;
}

アセット(画像)のサムネイルをUICollectionViewCellへ表示する例
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    AssetCell* cell = (AssetCell*)[collectionView dequeueReusableCellWithReuseIdentifier:@"AssetCell"
                                                                           forIndexPath:indexPath];
    LKAsset* asset = [self.assetsCollection assetForIndexPath:indexPath];
    cell.imageView.image = asset.thumbnail;
    return cell;
}


コレクション


さてこのライブラリの売りは基本で紹介したクラスを利用して作られた LKAssetsCollectionにある。LKAssetsCollectionは様々な条件を満たすアセットの集合を取り扱うことができる。例えばLKAssetsCollectionを使うと「月ごと」に分類された 「JPEGとPNG画像」を「日付降順でソート」したアセットの配列を作ることができる。

LKAssetsCollection
   .entries
   |
   |--LKAassetsCollectionEntry(2014年5月)
   |   .assets
   |    |
   |    |--LKAsset(JPEG画像 2014/05/21 10:00)
   |    |--LKAsset(PNG 画像 2014/05/11 10:00)
   |    |--LKAsset(JPEG画像 2014/05/01 10:00)
   |    :
   |--LKAassetsCollectionEntry(2014年4月)
   |   .assets
   |    |
   |    |--LKAsset(JPEG画像 2014/04/21 10:00)
   |    |--LKAsset(PNG 画像 2014/04/11 10:00)
   |    |--LKAsset(JPEG画像 2014/04/01 10:00)
   |    :
   :

LKAssetsCollectionの配下には LKAssetsCollectionEntryの配列があり、これがさらに LKAssetsの配列を持つ。

一旦コレクションが作れれば後は indexPathで末端の LKAssetを取り出せるので UITableView や UICollectionView で簡単に使うことができる。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    AssetCell* cell = (AssetCell*)[collectionView dequeueReusableCellWithReuseIdentifier:@"AssetCell"
                                                                           forIndexPath:indexPath];
    LKAsset* asset = [self.assetsCollection assetForIndexPath:indexPath];
    cell.imageView.image = asset.thumbnail;
    return cell;
}

LKAssetsCollectionのインターフェイス。
@interface LKAssetsCollection : NSObject

@property (nonatomic, weak  , readonly) LKAssetsGroup* group;
@property (nonatomic, strong, readonly) NSArray* entries;   // 

@property (nonatomic, strong, readonly) id <LKAssetsCollectionGrouping> grouping;
@property (nonatomic, strong) id <LKAssetsCollectionFilter> filter;
@property (nonatomic, strong) id <LKAssetsCollectionSorter> sorter;

+ (instancetype)assetsCollectionWithGroup:(LKAssetsGroup*)group grouping:(id <LKAssetsCollectionGrouping>)grouping;

@end

@class LKAsset;
@interface LKAssetsCollection (NSIndexPath)
- (LKAsset*)assetForIndexPath:(NSIndexPath*)indexPath;
@end

コレクションに含めるアセットを決める条件は3種類指定することができる。

・グルーピング  (例)年ごと、月ごと、日ごと、時ごと
・フィルター   (例)JPEGのみ、PNGのみ、スクリーンショットのみ、ほか
・ソーター    (例)日付昇順・降順

上記の処理を定義するためにそれぞれプロトコルが定義されている。
 LKAssetsCollectionGrouping
 LKAssetsCollectionFilter
 LKAssetsCollectionSorter
これらを実装するインスタンスを LKAssetsCollectionへ渡すことで条件に合致するアセットの集合ができあがる。

初期化の例
LKAssetsCollectionGrouping* grouping = [LKAssetsCollectionGrouping assetsCollectionGroupingWithType:self.groupingType];
   
self.assetsCollection = [LKAssetsCollection assetsCollectionWithGroup:self.assetsGroup
                          grouping:[LKAssetsCollectionDateGrouping groupingWithType:self.groupingType]];
self.assetsCollection.filter = [LKAssetsCollectionGenericFilter filterWithType:LKAssetsCollectionGenericFilterTypeJPEG|LKAssetsCollectionGenericFilterTypePNG];
self.assetsCollection.sorter = [LKAssetsCollectionDateSorter sorterAscending:NO];

LKAssetsCollectionDateGrouping や LKAssetsCollectionGenericFilter、LKAssetsCollectionDateSorter は先程のプロトコルの実装クラスになる。これらは標準で提供される。なおグルーピングと、ソースとなる LKAssetsGroupは初期設定しかできず後からは変更できない(immutable)。一方、フィルターとソーターは後から変更を適用することが可能。

LKAssetsCollectionDateGrouping で指定可能なグルーピング単位
typedef NS_ENUM(NSInteger, LKAssetsCollectionGroupingType) {
    LKAssetsCollectionGroupingTypeAll      = 0,
    LKAssetsCollectionGroupingTypeYearly   = 11,          // 年ごと
    LKAssetsCollectionGroupingTypeMonthly  = 12,          // 月ごと
    LKAssetsCollectionGroupingTypeWeekly   = 13,          // 週ごと(月曜始まり〜日曜までを1週間とみなす)
    LKAssetsCollectionGroupingTypeDaily    = 14,          // 日ごと
    LKAssetsCollectionGroupingTypeHourly   = 15,          // 時ごと
};

LKAssetsCollectionGenericFilter で指定可能なフィルタ条件。ビットORで複数指定が可能。
typedef NS_ENUM(NSUInteger, LKAssetsCollectionFilterType) {
    LKAssetsCollectionFilterTypePhoto        = (1 << 0),
    LKAssetsCollectionFilterTypeVideo        = (1 << 1),
    LKAssetsCollectionFilterTypeJPEG         = (1 << 2),
    LKAssetsCollectionFilterTypePNG          = (1 << 3),
    LKAssetsCollectionFilterTypeScreenShot   = (1 << 4),
    LKAssetsCollectionFilterTypeAll          = 0xFFFFFFFF,
};

コレクションクラスは、グルーピング・フィルター・ソーターの各プロトコルに準拠したインスタンスを利用するので、標準以外の処理を行わせたい場合はプロトコルに適合したクラスを用意すればいい(もちろん標準提供のこれらのクラスをサブクラス化しても良い)。


その他

グループやアセットの変更通知

グループやアセットが追加されたり、変更や削除された場合、LKAssetsLibraryはそれを検出して内部で持っているグループをそれに合わせて更新する。アセットの増減があった場合はコレクションにも変更が自動的に反映される。

また処理後に独自の通知も出す。
// Notifications (Update)
// store updated group into userInfo[LKAssetsLibraryGroupsKey]
extern NSString * const LKAssetsLibraryDidInsertGroupsNotification;
extern NSString * const LKAssetsLibraryDidUpdateGroupsNotification;
extern NSString * const LKAssetsLibraryDidDeleteGroupsNotification;

// Notifications (keys)
extern NSString * const LKAssetsLibraryGroupsKey;

NSNotificationの userInfoから LKAssetsLibraryGroupsKeyをキーにして追加・変更のあった(あるいは削除された)LKAssetsGroupインスタンスを取得できる。

- (void)_assetsLibraryDidInsertGroup:(NSNotification*)notification
{
    NSArray* groups = notification.userInfo[LKAssetsLibraryGroupsKey];
    NSLog(@"%s|inserted: %@", __PRETTY_FUNCTION__, groups);
}

iCloud上の写真

iCloud上で作成した共有アルバムの写真は、サムネイルは入手できるものの実体画像は取得できない場合がある。これは明示的にダウンロードする必要がある為。標準の写真アプリで閲覧(ダウンロード)してある場合はそのキャッシュが使える(表示できる)。最初のバージョンではこのケースに対応できていない(デモプログラムでもその場合、サムネイルは表示されても詳細が表示できない)。フォトストリームは自動的にダウンロードされるので大丈夫なようだ。



デモプログラム


付属のデモプログラムを実行すると最初にグループの一覧が表示される。
これは LKAssetsLibrary.assetsGroup(NSArray)を並べたもの。

グループを1つ選んでタップすると中の写真が一覧される。
グルーピング(ALL | Year | Month | Week | Day | Hour)、並び順(Ascending | Descending)、種別(All | JPEG | Screen | Video)が選べて、その場で表示が変わる。ここで先ほど説明した LKAssetsCollection を使っている。

なおビデオはサムネイルは表示されるが、詳細では表示されない(ビデオ再生を実装していないだけ)。


その他


グルーピングの Weeklyの処理はいまいちイケていないので少し遅い。大量の写真がある場合、デモでも他のグループに比べて一瞬遅れるのが分かるかもしれない。


インストール


CocoaPodsを使う。

$ pod install LKAssetsLibrary

今までプラベートなリポジトリしか使っていなかったので CocoaPodsの公式へ登録するのは初めて。先日公開された Trunkを早速使った。この辺りは下記のブログが参考になった。

CocoaPods Trunkを利用したライブラリの追加方法


ライセンス


MIT です。基本好きに使って下さい。


- - - -
ようやくモデルができた。次は選択画面を作る。これは複数選択可能なイメージピッカーみたいなやつになる予定。

Evernote投稿アプリStackOneの無料版が出ました(広告なし!)

2014年5月21日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

StackOneの無料版が出ました。

広告はなしの完全無料です。
有料版との違い
・ルールの追加ができません
・フォント種類/サイズ設定ができません
・投稿画面のカラー設定ができません

それ以外は有料版とまったく同じ。「サクサク投稿」はもちろん、約40種類の予約語が使えるヘッダ・フッタのカスタマイズ、1日分の投稿まとめなどStackOneならではの機能が健在です。

有料版紹介ページ


有料版レビューより
「動作が可愛い」
「衝撃がデカイです」
「手放せません」
「サクサクです」
「ホームに置いて活躍しています」
「使いやすい」
「素晴らしいアップデート」
「最高です」
「日記を残すのが楽しくなるアプリ」


新感覚のエバーノート投稿アプリを是非お試し下さい。
(サクサクですよ)

Static cell を隠す

2014年5月13日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

Storyboardで作った Staticな Cellを実行時に消には UITableViewDelegateの2つのメソッドを使う。

例)セクション[2]を隠す

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 2) {    // セクション[2]内おセルをすべて非表示
        cell.hidden = YES;
    }
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 2) {    // セクション[2]内のセルの高さをすべて0に
        return 0;
    } else {
        return [super tableView:tableView heightForRowAtIndexPath:indexPath];
    }
}


高さを0にするだけだと下のセルに重なって表示されてしまうので合わせてhiddenにもしておく。これはクリッピングすれば不要かもしれない。


追記)セクションヘッダがある場合はこれも高さ0にしてやる。
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    if (section == 2) {
        return 0;
    } else {
        return [super tableView:tableView heightForHeaderInSection:section];
    }
}

【Library】Task Completion ライブラリ

2014年4月29日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

Task Completionが数行で使える小さなライブラリ。



Task Completion や実装については下記を参考のこと。


iOS7からは10分→3分と短くなっている。本格的な処理はiOS7から導入されたバックグラウンド送受信を使い、こちらはそこまで大げさではないちょっとした処理の延長で使う位置づけになったのだと思う。

使い方は簡単で基本1行のみ。AppDelegateの中で setupメソッドを1回呼ぶ。
#import "LKTaskCompletion.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [LKTaskCompletion.sharedInstance setup];
    :
}

バックグラウンドのタスクが終了した時はできれば下記を呼んでおく。
[LKTaskCompletion.sharedInstance endBackgroundTask];

バックグラウンド処理を走らせたくないときは enabledプロパティを NOにしておく。
LKTaskCompletion.sharedInstance.enabled = NO;


サンプルプログラムがついていて、実行すると1秒単位で1行を消しこむ処理が走る。

残件がバッジに表示されていて、ホーム画面へ戻っても処理がバックグラウンドで行われているのがわかるようになっている。


CocoaPods対応 & MITライセンス





lakesoftのサポートページを開設しました

| Published in | 0 コメント

このエントリーをはてなブックマークに追加

自作アプリは lakesoft というブランドで出していますがそのサポートサイトを Facebookページに開設しました。


今後アプリのサポートはここで一括して行います。今までアプリごとにページを用意していたりしたのですが、一人でやるにはさすがにしんどくなってきたのでまとめることにしました。

ツイッターアカウントも用意しています。


ロゴは湖と太陽をイメージして作ってみました。



ちなみに github にもページがあります。


- - - - -
作って出すだけではなかなかダウンロードされないので、このブログもそうですが、自分のブランディングを少しづつですが進めてアプリの認知に役立てたいと考えています(もちろんサポート機能としても)。


アプリ【StackOne】最新バージョン 1.2 リリース「色とりどり」アピアランス対応

2014年4月25日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

エバーノート投稿アプリ【StackOne】の最新バージョン 1.2 がリリースされました。


今回の目玉はアピアランスのカスタマイズ対応です。ルール毎にベースカラーが変更できるようになりました。選べる色は25色。合わせてフォントの種類とサイズも指定できるようにしました。




バージョン 1.2 の内容


  • アピアランスカスタマイズ(ベースカラー、フォント種類、フォントサイズ)
  • ジェスチャーの追加
  • 日時置換オプションの追加
  • バグ修正

アピアランスカスタマイズ


ベースカラー、フォント種類、フォントサイズが変更できるようになりました。いずれもルール毎に設定が可能です。そうです、ルールを切り替えるとその場で色もフォントも変わるのです。


ルール毎に色を変えると視認性が良くなるし、さらには投稿毎の気分も盛り上げてくれますよ。選べるカラーは25色。フラットデザインにマッチする色を用意しました。


フォントはインストール済みのものが様々なバリエーションで選べます。



※現バージョンではダウンロードフォントには対応していません。

カラー変更はルールのみで設定可能です。フォント種類とフォントサイズは全体のデフォルト設定が可能です(ルールでは初期値がデフォルトに従うようになってる)。


ジェスチャーの追加


投稿画面のルール周りにジェスチャーを2つほど追加しました。

(1)長押し

ルールを長押しするとそのルールの設定画面が開くようになりました。設定後は投稿画面へ戻ります。

(2)ダブルタップ

ルールをダブルタップするとあらかじめ設定しておいたルールへジャンプします。もう一度ダブルタップすると直前のルールへ戻ります。ダブルタップを繰り返すとルール間を行ったり来たりするようになりました。
ジャンプ先は設定画面で指定が可能です。デフォルトでは「新規ノート」になります。




タイトル日時置換オプションの追加


タイトル置換に初回投稿日時を使う設定を追加しました。例えばローテーションを毎日 5:00とした時、4/6 23:50に初回投稿、4/7 4:30に最終投稿とした場合、タイトルの %date は 4/6となります。

ローテーション:毎日 5:00
4/6 23:50 初回:タイトル %date → 4/6
 4/7 04:30 投稿:タイトル %date → 4/6

今までだと 4/7投稿は 4/7になっていましたが、これを初回投稿の日時を基準に置換するようになりました。日をまたぐローテーション設定になっている場合でもタイトルを前日のものとして記録ができます。またこれは日単位に限らず他の時間単位でも同じです。

この設定の初期値はONになっており、全体の設定で切り替えられるほか、各ローテーションでも個別に変更が可能です。





・この設定がOFFの場合は先ほどの例だと %dateは 4/7 となります(今までの動作)。
・日時以外(いは従来どおり最新の情報が使われます。
・この処理はバージョンアップ後にローテーションが発生した時から適用されます。バージョンアップ直後などは適用されていないのでご注意下さい。


バグ修正


・ & があると送信エラーになる問題が修正されました。
・投稿画面でルールをフリック変更した時にノートブックとタグが変更されない問題が修正されました。


----


色とりどりになった StackOne を是非お試し下さい




アプリ【StackOne】最新バージョン 1.1 リリース「さくさく投稿」

2014年4月7日月曜日 | Published in | 9 コメント

このエントリーをはてなブックマークに追加

エバーノート投稿アプリ【StackOne】の最新バージョン 1.1 がリリースされました。



今回の目玉はいくつかありますが、その一つは『オフライン対応』です。以前のバージョンではサーバへの送信が終わるまでは次の入力ができなかったのですが、送信部分を非同期に改良することで送信後に待たされないサクサク投稿が可能になりました。

バージョン 1.1 の内容


  • 投稿画面のデザインを一新
  • オフライン投稿に対応
  • URLスキーム対応
  • バグ修正

新デザイン


投稿画面のデザインを一新しました。


多少なりとも以前のバージョンに慣れた人には申し訳ないのですが、使い勝手を改良するにはどうしても必要と判断して思い切って変えました(クレームが来るのは覚悟しています x_x 。。

最大の変更理由は『ルール』の切替を簡単にできるようにする為です。以前までルール名はナビゲーションバーに表示していましたが、このバージョンから下のツールバーへ移動しました。ここを左右フリックするとルールが切り替えられます。以前のレイアウトのままでの切替も考えたのですが、ナビゲーションバーのタイトルをフリックするというのはどうも感覚的ではないし、片手持ちの時に遠くて指が届かず、試行錯誤の末に真ん中のツールバーへ配置することに決めました。送信ボタンも合わせて下へ移動して一番押しやすい場所に配置しました。代わりに従来下のツールバーにあった写真やリマインダをナビゲーションバーへ配置してあります。写真をよく使っていたユーザの方は慣れるまで使いづらいかもしれません(私自身がそうでした)。


オフライン投稿


投稿は一旦送信ボックスに入り、オンライン時に自動的に送信されます。




送信ボックス導入によってオフラインだけでなくオンライン時も非同期な送信が可能になりました。これによって比較的大きな画像があっても送信を待たされることが無い ”さくさく” 投稿が可能になりました。

実装の話を少しすると内部では自作のキューライブラリを使っています。

このライブラリはいわゆるキュー(FIFO)の簡易ライブラリで、特徴的なのはアーカイブ可能なオブジェクトを渡すと処理が終わるまでディスクに保存しておいてくれるところです(永続化)。投稿が実行されると投稿内容を保持するモデルクラスをこのキューへ入れて、メインスレッドではすぐに次の投稿入力画面に切り替えます。一方別スレッドで動く処理がこのキューを監視していて、諸条件(オンラインなど)が揃ったらサーバへの送信を自動的に開始します。処理が終わったらキューから投稿を削除して、次の投稿モデルを取り出します。

また送信ボックスはちょっとしたミニアニメーションを仕込んでいて増減の様子がさりげなくわかるようになっています。ここのアニメーションには Canvas というライブラリを利用しました。

このライブラリは良く出来ていて用意されているビュークラスの上にアニメーションしたい画像等を配置すれば移動やズーム、フェードイン・アウトなどの動きを簡単につけることができます。アイコンやボタンなど主にコントロール系のパーツでユーザの目を引きたい時で使うと効果的だと思います。StackOneでは送信ボックスに投稿が入った時に ZoomOut、送信が終わった時にMorphを使っています(どくんどくん、と少し波打つように見える)。


URLスキーム


このバージョンから URLスキームに正式対応しました。テキストおよびルールの指定が可能です。
書式 jp.lakesoft.StackOne://text=テキスト&rule=ルールID
ruleパラメータは省略可能です。

URLはルール設定画面で取得することもできるので、この機能を活用してみて下さい。




その他


StackOneの特徴はなんといっても『ルール』です。『ルール』については別に説明ページを設けたのでもし興味を持ったらこちらを読んでみて下さい。




それと連絡事項が1件あります。頻繁に投稿するとエバーノートの1時間当たりの投稿上限に達して送信に失敗する場合があります。
エラー内容:
Code=19 "Operation denied because the calling application has reached
 its hourly API call limit for this user."

送信BOXに投稿が溜まってしまうことになりますが、この場合はしばらくしてから再送信してみてください。この課題はエバーノートの仕様であるため変更はできないのですが、アプリ側で少しでも改善できるよう検討中です。


- - - - - - -
エバーノートへの投稿がもっと楽しくなる新世代投稿アプリ【StackOne】を是非お試し下さい。







人気の投稿(過去 30日間)