Tag Archives: Library

SnoozeLocalNotificationは擬似的にUILocalNotificationのローカル通知でスヌーズ対応するライブラリです。

UILocalNotificationを使った通知の設定について — ios-practice 0.1 documentationでも書かれていますが、iOSのローカル通知にはスヌーズ機能はありません。

そのため、ユーザーが止める(アプリを立ち上げる)まで何度も繰り返し通知を出すことで擬似的なスヌーズを作成出来ます。
(iOS8以降ならもっと別の手段があるかも)

SnoozeLocalNotification は通知を多重登録 + スヌーズの通知のみをキャンセル機能を持ったライブラリです。

インストール

CocoaPodsからインストールできます。


pod "SnoozeLocalNotification"

使い方

擬似的なスヌーズなので、アプリをユーザーが起動したらスヌーズを解除する必要があります。

この擬似スヌーズには主に2種類の通知があります。

  • メインとなる通知
  • メインとなる通知に紐づくスヌーズ通知

メインの通知からアプリが開かれたら、関連するスヌーズはいらないのでキャンセル。同様にメインの通知が既に通知済みの状態でアプリが起動したら関連するスヌーズはキャンセルするという処理が必要になります。

これらの処理を

AppDelegate

に 書きます。

cancelSnoozeForNotification

ではその通知に関係あるスヌーズ通知(userInfoのkeyを見ます)を解除します。


- (<span class="hljs-built_in" style="color: rgb(38, 139, 210);">BOOL</span>)application:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">UIApplication</span> *) application didFinishLaunchingWithOptions:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">NSDictionary</span> *) launchOptions {
    <span class="hljs-built_in" style="color: rgb(38, 139, 210);">UILocalNotification</span> *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    [[SnoozeLocalNotificationCenter center] cancelSnoozeForNotification:localNotif]
    <span class="hljs-keyword" style="color: rgb(133, 153, 0);">return</span> <span class="hljs-literal">YES</span>;
}

- (<span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span>)applicationWillEnterForeground:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">UIApplication</span> *) application {
    [[SnoozeLocalNotificationCenter center] cancelUnnecessarySnooze];
}

スヌーズ通知の登録はシンプルで、メインとなるUILocalNotificationとスヌーズ感覚を指定すれば、その分だけスヌーズ登録してくれます。


<span class="hljs-comment" style="color: rgb(147, 161, 161);">// Schedule 4 notification.</span>
<span class="hljs-comment" style="color: rgb(147, 161, 161);">// fireDate -&gt; 10min -&gt; 30min -&gt; 60min</span>
<span class="hljs-built_in" style="color: rgb(38, 139, 210);">NSArray</span> *snoozeMinutes = @[@<span class="hljs-number" style="color: rgb(42, 161, 152);">10</span>, @<span class="hljs-number" style="color: rgb(42, 161, 152);">30</span>, @<span class="hljs-number" style="color: rgb(42, 161, 152);">60</span>];
<span class="hljs-built_in" style="color: rgb(38, 139, 210);">UILocalNotification</span> *localNotification = [[<span class="hljs-built_in" style="color: rgb(38, 139, 210);">UILocalNotification</span> alloc] init];
localNotification<span class="hljs-variable" style="color: rgb(181, 137, 0);">.fireDate</span> = [[<span class="hljs-built_in" style="color: rgb(38, 139, 210);">NSDate</span> date] dateByAddingTimeInterval:<span class="hljs-number" style="color: rgb(42, 161, 152);">1000</span>];
localNotification<span class="hljs-variable" style="color: rgb(181, 137, 0);">.alertBody</span> = <span class="hljs-string" style="color: rgb(42, 161, 152);">@"message"</span>;
[[SnoozeLocalNotificationCenter center] schedule:localNotification snoozeMinutes:snoozeMinutes];

API

細かいAPIはヘッダを見るといいと思います。


<span class="hljs-class"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">@interface</span> <span class="hljs-title" style="color: rgb(38, 139, 210);color: rgb(181, 137, 0);">SnoozeLocalNotificationCenter</span> : <span class="hljs-title" style="color: rgb(38, 139, 210);color: rgb(181, 137, 0);">NSObject</span></span>
+ (instancetype)center;

<span class="hljs-comment" style="color: rgb(147, 161, 161);">// schedule notification and snooze</span>
- (<span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span>)schedule:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">UILocalNotification</span> *) localNotification snoozeDateComponents:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">NSArray</span> *) dateComponentsList;
- (<span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span>)schedule:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">UILocalNotification</span> *) snoozeLocalNotification snoozeMinutes:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">NSArray</span> *) snoozeMinutes;

<span class="hljs-comment" style="color: rgb(147, 161, 161);">// cancel all snooze notifications</span>
- (<span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span>)cancelAllSnooze;
<span class="hljs-comment" style="color: rgb(147, 161, 161);">// cancel unnecessary notifications.</span>
<span class="hljs-comment" style="color: rgb(147, 161, 161);">/* e.g) notificationA -&gt; snoozeX -&gt; snoozeY
 * Notify notificationA, then user launch app.
 * snoozeX and snoozeY are unnecessary notifications.
 */</span>
- (<span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span>)cancelUnnecessarySnooze;
<span class="hljs-comment" style="color: rgb(147, 161, 161);">// cancel notification and snooze</span>
- (<span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span>)cancelSnoozeForNotification:(<span class="hljs-built_in" style="color: rgb(38, 139, 210);">UILocalNotification</span> *) aNotification;
<span class="hljs-keyword" style="color: rgb(133, 153, 0);">@end</span>

SnoozeLocalNotification はスヌーズのために大量に通知を登録しますが、iOSではローカル通知に登録出来る数に上限があるのであまり数を多くすると溢れて無駄な通知登録処理をすることになるので注意して下さい。

UITextFieldWithLimit はUITextFieldのサブクラスとして使えて文字数制限が出来るライブラリです。

iOS で文字数制限つきのテキストフィールドをちゃんと作るのは難しいという話 – blog.niw.at 等を読んだ事がある方は知っていると思いますが、日本語(IME)などを含めた文字数制限を実装するのはとても面倒です。

UITextFieldWithLimit はUITextFieldのサブクラスなので、クラスを置き換えるだけでうごきます。

gif

インストール

CocoaPodsでインストールできます。


pod &quot;UITextFieldWithLimit&quot;

使い方

以下で試すことが出来ます。

pod try UITextFieldWithLimit

基本的にはUITextFieldそのままですが、

maxLength

で制限する文字数を指定することが出来ます。(これはただのlength取ってるだけなのでまた正確な文字数とは別という話…)

limitDelegate

で文字数に達した時と、文字数を超えて入力された時のメソッドも定義出来ます。


- (void)viewDidLoad {
    [super viewDidLoad];
    self.limitedTextField = [[UITextFieldWithLimit alloc] init];
    self.limitedTextField.maxLength = @15;
    // optional
    self.limitedTextField.limitDelegate = self;
}
- (void)textFieldLimit:(UITextFieldWithLimit *) textFieldLimit didReachLimitWithLastEnteredText:(NSString *) text inRange:(NSRange) range {
    NSLog(@&quot;%s&quot;, sel_getName(_cmd));
}
- (void)textFieldLimit:(UITextFieldWithLimit *) textFieldLimit didWentOverLimitWithDisallowedText:(NSString *) text inDisallowedRange:(NSRange) range {
    NSLog(@&quot;%s&quot;, sel_getName(_cmd));
}

こういう制限は標準機能として欲しい感じです。。

JonathanGurebo/UITextFieldLimitの動きを元に日本語のケースを追加した感じです。

AKUAssetManager はUIImagePickerControllerの補助的なライブラリです。

iOS6以降 写真やGPSなどアクセスするのに許可が必要なものが増えてきていて、iOS8だとローカル通知やカメラ等もアクセス許可が必要です。

写真などはアクセスが無いときの表示はiOS側がやってくれるのでいいですが、カメラの場合は真っ黒な画面で撮影からキャンセルしないと戻れないというかなり微妙な事になります。

以前、iOS6でプライバシー &gt; 写真へのアクセス許可があるかどうかを判定する方法 | Technology-Gymというので書きましたが、許可がでているかは取得することができるので、許可がない場合はアラートを出して設定画面で変更してもらう必要があります。

AKUAssetManagerはこれのカメラと写真を対象に手助けするライブラリです。

screenshot

という感じで、カメラを呼ぶときに許可があるかどうかを見てから呼び出すことができ、また設定画面へ行くように促す機能が入っています。

iOS8からアプリの設定画面へアクセス出来るようになっているので、iOS8の場合は設定へ直接誘導してくれます。

インストール

CocoaPodsでインストールできます。


pod &quot;AKUAssetManager&quot;

使い方

pod try AKUAssetManager

で試すのが早いです。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.manager = [[AKUImagePickerManager alloc] init];
}
// if allow to use camera, present UIImagePickerController
- (IBAction)openCamera:(id)sender {
    [self.manager openCameraWithDelegate:self];
}
// if allow to use photo, open UIImagePickerController
- (IBAction)openPhoto:(id)sender {
    [self.manager openPhotoAlbumWithDelegate:self inView:sender];
}

AKUImagePickerManager

のインスタンスにはカメラや写真をUIImagePickerControllerで開く + 権限チェック をしてくれるメソッドがそれぞれ生えています。

また、権限チェックだけを使いたい場合はAKUCaptureManager.hAKUAssetManager.hを見るといいでしょう。

(*カメラと写真の権限はそれぞれ別となっていて、カメラの権限はiOS7以降でしか触れません)

まだ、日本語しか入ってないのでローカライズが必要な場合はpull requestするといいと思います。

OpenFileInWebView というシンプルなライブラリを書きました。

これはWebView指定したファイルパスを開くViewControllerを返すという単機能のライブラリで主にデバッグ等に使う感じです。

以下のように使えば、file.pdfというファイルを表示してるWebViewを見ることが出来ます。(WebViewはPDFやdocなども表示出るので簡単なプレビュー側がわりになります)

UIViewController *controller = [OpenFileInWebView viewControllerToOpenFile:[[NSBundle mainBundle] pathForResource:@&quot;file&quot; ofType:@&quot;pdf&quot;]];
[self.navigationController pushViewController:controller animated:YES];

使用例としては、PDFを生成してメールで送るような機能をつけるときにシミュレータ上でPDFを直接確認する用途などに使えます。

system(&quot;open ~&quot;)

でMac OSXの方で開いてもいいのですが、Releaseビルドに残ってたりすると面倒なので、WebViewで開いてます。

UITabBarControllerはタブバーのコントローラーですが、
タブバーは細かいデザインの調整を行う場合はUITabBarControllerのサブクラスを作って実装するパターンが多いと思います。

一つのStoryBoardに全てのタブの参照がある場合はInterface Builderなどでもある程度はデザインを設定できますが、StoryboardをViewController毎やタブ毎等に分けていた場合はInterface BuilderだけだとIBOutletを結べなくなります。

運用的にはタブやViewControllerでstoryboardファイルを分けた方が扱いやすいと感じるので、タブを関連付けるにはUITabBarControllerのサブクラスを作ってコードで結んでいく必要があります。

しかし、UITabBarControllerをそのまま実装すると、UITabItemのテキスト、アイコン…等意外とややこしいです。

タブバーの構成要素は以下のような感じですね。

  • タブバー背景画像
  • 選択してるタブの背景画像
  • 各タブのアイコン
  • 各タブのテキスト

実際にやることとしては以下のようになりますね。

  1. self.viewControllers に 各タブTopのViewControllerを入れる
  2. self.tabBar.items を走査して、各タブ(UITabBarItem)にタイトルやアイコンを設定
  3. タブバー全体の細かい調整

これを直に書くとforループ等がでてきてしまい余りキレイにはならないと思います。

- (void)setupTabs {
	// 1. 各タブのViewControllerを設定
    self.viewControllers = @[
        self.tabFactory.calendarNavigationController,
        self.tabFactory.graphNavigationController,
        self.tabFactory.reporterNavigationController,
        self.tabFactory.karadaNoteNavigationController,
        self.tabFactory.settingNavigationController,
    ];
	// 2. TabBarItemにタイトルなどの設定
    NSArray *tabBarItems = [[self tabBar] items];
    for (NSUInteger i = 0; i < [tabBarItems count]; i++) {
        MenuTabType menuTabType = (MenuTabType)i;
        UITabBarItem *tabBarItem = tabBarItems[i];
        tabBarItem.title = [self tabItemTitle:menuTabType];
        UIImage *tabBarItemSelectedIcon = [self selectedTabItemImage:menuTabType];
        UIImage *tabBarItemUnselectedIcon = [self unselectedTabItemImage:menuTabType];
        [self setTabBarItem:tabBarItem tabBarItemSelectedIcon:tabBarItemSelectedIcon tabBarItemUnselectedIcon:tabBarItemUnselectedIcon];
        [self setTitleTextColorWithTabBarItem:tabBarItem];
    }
	// 3. タブバー全体の設定
    [[self tabBar] setSelectionIndicatorImage:[UIImage imageNamed:@"tab_select_image.png"]];
    [[self tabBar] setBackgroundImage:[UIImage imageNamed:@"tab_backgroundimage.png"]];
    [[self tabBar] setSelectionIndicatorImage:[UIImage imageNamed:@"tab_select_image.png"]];
}

という感じになってしまいました。

そこで、これをパターン化して解決出来るようにするRoleTabBarControllerというライブラリを書きました。

RoleTabBarController

このライブラリが扱うパターンと言うのは<UITableViewDataSource>、UITableViewのDataSourceと基本的に同じです。

$ pod try RoleTabBarController

で試すことが出来ます。

詳しくはExampleを見てもらいたいのですが、先ほどの例はUITabBarController内に全て書いていましたが、UITableViewDataSourceと同じようにModelクラスに情報を出しやすくなります。

@implementation AppTabBarController
- (AppTabBarControllerModel *)model {
    if (_model == nil) {
        _model = [[AppTabBarControllerModel alloc] init];
    }
    return _model;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.dataSource = self;
}


#pragma mark - RoleTabBarController

- (NSUInteger)numberOfRoleTabBarController:(RoleTabBarController *) tabBarController {
    return 4;// 実際にはこれもモデルに出せる
}

- (UIViewController *)roleTabBarController:(RoleTabBarController *) tabBarController viewControllerAtIndex:(NSUInteger) index {
    return [self.model viewControllerAtType:(AppTabBarControllerType)index];
}

- (RoleTabBarItemObject *)roleTabBarController:(RoleTabBarController *) tabBarController tabBarItemObjectAtIndex:(NSUInteger) index {
    return [self.model tabBarItemObjectAtIndex:index];
}
@end

モデルクラスの方は、indexに応じたViewControllerやUITabBarItemの設定オブジェクトを返すだけですね。

 

typedef NS_ENUM(NSUInteger, AppTabBarControllerType) {
    AppTabBarControllerType_MAIN,
    AppTabBarControllerType_CALENDAR,
    AppTabBarControllerType_POST,
    AppTabBarControllerType_RANKING,
};

@implementation AppTabBarControllerModel {
}
- (UIViewController *)viewControllerAtType:(AppTabBarControllerType) type {
    switch (type) {
        case AppTabBarControllerType_MAIN:
            return [RoleViewController viewController:@"main"];
        case AppTabBarControllerType_CALENDAR:
            return [RoleViewController viewController:@"calendar"];
        case AppTabBarControllerType_POST:
            return [RoleViewController viewController:@"post"];
        case AppTabBarControllerType_RANKING:
            return [RoleViewController viewController:@"ranking"];
    }
    return nil;
}

- (RoleTabBarItemObject *)tabBarItemObjectAtType:(AppTabBarControllerType) type {
    switch (type) {
        case AppTabBarControllerType_MAIN:
            return [[RoleTabBarItemObject alloc] initWithTitle:@"MAIN" selectedImage:nil unselectedImage:nil];
        case AppTabBarControllerType_CALENDAR:
            return [[RoleTabBarItemObject alloc] initWithTitle:@"CALENDAR" selectedImage:nil unselectedImage:nil];
        case AppTabBarControllerType_POST:
            return [[RoleTabBarItemObject alloc] initWithTitle:@"POST" selectedImage:nil unselectedImage:nil];
        case AppTabBarControllerType_RANKING:
            return [[RoleTabBarItemObject alloc] initWithTitle:@"RANKING" selectedImage:nil unselectedImage:nil];
    }
    return nil;
}

- (RoleTabBarItemObject *)tabBarItemObjectAtIndex:(NSUInteger) index {
    return [self tabBarItemObjectAtType:(AppTabBarControllerType)index];
}
@end

RoleTabBarItemObjectはTabBarItemのタイトルと選択時のアイコン、非選択時のアイコンを設定出来るオブジェクトです。

基本的なデザインならこれで足りますが、TabBarItemのテキスト位置などを調整したい場合は、

- (void)roleTabBarController:(RoleTabBarController *) tabBarController willShowTabBar:(UITabBarItem *) tabBarItem atIndex:(NSUInteger) index;

 を実装して、そこでUITabBarItemを更新するようにするといいです。

RoleTabBarControllerは<UITableViewDataSource>のパターンを使ってUITabBarControllerを実装するパターンを提供するライブラリです。

ライブラリも100行行かない程度のシンプルなものなので、試してみるといいかもしれません。