2010/10/31

DTMやってる人ならおなじみだとは思うけど、OSXになってからオーディオ周りが「Core Audio」で統一された。 iphoneで音を鳴らす方法はいくつかあるが、とりあえず概念的なものはここの記事が分かりやすい。 楽器アプリの作り方 CoreAudioは、以下のフレームワークから形成されている。

・AVFoundation:オーディオプレイヤーを利用するためのフレームワーク ・Audio Toolbox、Audio Unit:OSX独自のフレームワーク ・OpenAL:ゲームアプリケーション等、OpenGLのサウンド版みたいなもので3D音響が扱える

作成するアプリによって利用するフレームワークが変わってくる。

System Sound Service

Audio Toolboxに含まれる一番シンプルなサービスで、短いサウンドを再生するのに適している。 SystemSoundIDを割り当てて、オーディオファイル(.aif)を再生する。

Audio Queue

サウンドデータのバッファを複数用意してキューを作成し、コールバックで音を再生する。 再生する前に、メモリに必要最小限のデータを用意するので効率が良い。 しかしこの方式だとデータを用意する時間、いわゆる「レイテンシー」が発生する。

OpenAL

標準化されたAPIが多数用意されており、オーディオ管理には3つの要素から構成されている。 ・Buffer ・Source ・Listener 再生ファイルはリニアPCMの方式で受け渡す必要がある。

  2010/10/25

SafariでURLを開くのを調べてたけど、Google先生に聞いてもなかなか見つからなかった。

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://hoge.com/"]];   

openURLはNSURLのメソッドと思いきや、UIApplicationなんですねぇ。

  2010/10/18

gvim-kaoriyaのOSX版をいつも忘れてしまうのでメモ。

■macvim macvim-kaoriya

Windows版同様に複数ファイル開け、尚かつvimの癖に コピ&ペーストのショートカットも直接使えたりする。

  2010/10/18

Google Chromeのver 6だとBASIC認証のページにアクセスする度に、毎回ダイアログが表示されてしまいます。

パスワード保存のダイアログが表示されない

のが原因のようで、ver 7 betaでは解決されている模様。

ただ、https経由だとこのバグが修正されていないみたいだ。 うーん。

  2010/09/16

NSFetchedResultsControllerを利用して、UITableViewでセクション別にソートしてデータの更新/表示する処理。 相当ハマりましたが、少しずつ理解できたので更新。

参考サイト

NSFetchedResultsController でグルーピング(Section分け)

このサイトでも勉強しましたが、実際やってみると原因追及に相当時間がかかりました。

ハマるポイント

(1) fetchedResultsControllerとUITableViewのデータ整合性 (2) setSortDescriptorsとsectionNameKeyPathによるデータ整合性 (3) didChangeObjectのアニメーション処理によるデータ整合性 (4) managedObjectContextの管理ミス

managedObjectContextの取得

アプリケーションからmanagedObjectContextを取得する必要があります。

- (void)viewDidLoad {
    [super viewDidLoad];
    UserAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    self.managedObjectContext = [appDelegate managedObjectContext];
}

これで、クラス内のmanagedObjectContextのアクセスは

[self managedObjectContext]

で統一できます。

fetchedResultsControllerの実装

@synthesize fetchedResultsController=fetchedResultsController_;
@synthesize managedObjectContext=managedObjectContext_;
@synthesize userListViewController;

- (NSFetchedResultsController *)fetchedResultsController {
    if (fetchedResultsController_) {
        return fetchedResultsController_;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" 
                                              inManagedObjectContext:[self managedObjectContext]];
    [fetchRequest setEntity:entity];
    [fetchRequest setFetchBatchSize:20];
    
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"indexName" ascending:YES];
    NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, sortDescriptor2, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] 
                                                             initWithFetchRequest:fetchRequest
                                                             managedObjectContext:[self managedObjectContext]
                                                             sectionNameKeyPath:@"indexName" 
                                                             cacheName:nil
                                                             ];
    
    aFetchedResultsController.delegate = userListViewController;
    self.fetchedResultsController = aFetchedResultsController;
    
    [aFetchedResultsController release];
    [fetchRequest release];
    [sortDescriptor release];
    [sortDescriptors release];
    
    NSError *error = nil;
    if (![fetchedResultsController_ performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    return fetchedResultsController_;
} 

setSortDescriptorsとsectionNameKeyPathによるデータ整合性

上記の例では、データ取得の際、User EntityのindexNameとnameの順でソートしています。 indexNameは、データ一覧をセクション別にグルーピングする為に、nameの先頭1文字を保存したカラムです。

Userデータ一覧を先頭文字(indexName)でセクション分けして取得するには、sectionNameKeyPathにindexNameを設定します。 これは相当便利です。

ただし、setSortDescriptorsでソートした結果と、sectionNameKeyPathでソートした結果の並び順が必ず同じでなければなりません。

つまり、データ追加、削除、データ変更による並び替えを考慮して設計しなければいけません。 双方のセクション数、データ数、indexPathが狂ったらアウトです。

UITableViewのクラスにdelegate

fetchedResultsControllerを実装したクラスでは、ユーザ一覧のUITableViewを持たない設計にしているので(Userデータ一元管理)、 ユーザ一覧のクラスUserListViewControllerを、delegateとして設定しています。

aFetchedResultsController.delegate = userListViewController;

もし、ユーザ一覧とfetchedResultsControllerの実装が同じクラスであれば、通常通りselfです。 どちらにするかは悩みどころです。 fetchedResultsControllerとUITalbeViewは同じクラス内で実装した方が良かったかな?と思ったりもします。

UITableVIewDelegateメソッドの実装

aFetchedResultsController.delegateを設定しているので、これらメソッドが自動で呼び出されます。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[self.delegate.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [[[self.delegate.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
    User *user = [self.delegate.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = [[user valueForKey:@"name"] description];
    
    return cell;
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [NSString stringWithFormat:@"%@", [[[self.delegate.fetchedResultsController sections] objectAtIndex:section] name], nil];
}

データ取得は常に、self.delegate.fetchedResultsControllerを経由しています。 NSFetchedResultsControllerには、面倒なデータ取得メソッドが用意されているのでこれが重宝される理由かと思います。

didChangeObjectによるfetchedResultsControllerとUITableViewの整合性

UITalbeVIewにアニメーションをつけないのであれば、以下のコーディングで問題ないと思います。

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    UITableView *tableView = userTableView;
    [tableView reloadData];
}

が、UITalbeVIewアニメーションをつける場合、用意された雛形をそのまま利用するとアプリが落ちる事があります。

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    UITableView *tableView = userTableView;
    switch(type) {
            
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;
            
        case NSFetchedResultsChangeMove:
            [tableView reloadData];
            //[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            //[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

自分が陥ったのは、ソートやセクションの増減が発生した場合で、コメントアウトした部分が問題でした。 例えば、managedObjectContext上でAセクションが削除されても、 UITalbeViewのレンダリング途中でデータ不整合が起こりアプリが終了します。

NSManagedObjectContextの管理ミス

NSManagedObjectContextを利用していると、自分の設計やコーディングによりアプリが落ちる事が多々ありました(^^;)

NSManagedObjectContextのデータを変更するだけ(saveしない)で、fetchedResultsControllerが呼ばれてしまったり、色々な画面を行き来しているうちにメモリが解放されてたりと頭を悩ませます。

こればかりは経験値もありますが、シンプルなベストプラクティスな設計を身につけたいものです。

  2010/09/16

CoreDataの更新処理でかなりハマりました(>_<)

NSFetchedResultsControllerを利用してCoreDataの更新やフェッチ処理をしていますが、 ソートをかけた状態でデータ更新処理がうまくいかない事があります。

iphone 上記の項目名を変更する際、並び順が変わるような更新が発生した時にエラーになりました。

エラー内容

Assertion failure in -[UITableView _endCellAnimationsWithContext:]

エラーからするとUITableViewのnumberOfSectionsInTableView、numberOfRowsInSection等で更新処理が正常にいっていないようです。

原因

更新後にUITableViewのメソッドがfetchedResultsControllerを読んだ時点で、ManagedObjectContextの不整合が起きているものと思われます。

Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (5), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted). with userInfo (null)

更にエラーを読み解いてくと、NSManagedObjectContextObjectsDidChangeNotificationが発生した時に、UITableViewのsectionが0になってしまいます。 また更新前と更新後のUITableViewのrowsの数も異なっているようです。

参考ページ

で、同じようにハマっている人がいました。 ・Study CoreData 19 ~最悪のシナリオ~-[NSFetchedResultsController performFetch:] でクラッシュ

対策

うーん、ちょっと大変そう。 ログを追跡してわかりましたが、NSManagedObjectContextをsaveする以前に、NSManagedObjectのデータを変更し、ソートが発生するような一覧になった時にこの現象が起きるようです。 NSFetchedResultsControllerとUITableViewのメソッドで何が行われているか?もう少し理解が必要かも。

ちなみに、UITableViewをNSManagedObjectContextで管理せず、UITableView用のNSArrayで管理すれば問題は回避できそうだけど、何だか本末転倒な気がする。 NSManagedとNSArrayでわけて管理するとメモリの無駄使いや、混乱して違うバグを生む可能性も否めない。

とりあえず、didChangeObjectの処理に問題がありそうなので、そこから手をつけていくことにした。

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {
    UITableView *tableView = self.tableView;
    switch(type) {
            
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;
            
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

並び替えが発生するデーター更新の場合、NSFetchedResultsChangeMoveを経由することになり、どうやらdeleteRowsAtIndexPaths、InsertRowsAtIndexPathsの処理で不整合になるようです。

応急処置

        case NSFetchedResultsChangeMove:
           [tableView reloadData]
           break;

とりあえず、行を削除せずにUITableViewのデータをリロードする事で回避できましたが、今度はUITableViewのdidSelectRowAtIndexPathの処理で整合性が・・・

そこで、fetchedResultsControllerメソッドの雛形の部分で、fetchedResultsController_をキャッシュしているような箇所があったので、コメントアウトしてみた。

    if (fetchedResultsController_ != nil) {
        //return fetchedResultsController_;
    }

これで、didSelectRowAtIndexPathクリック後の処理もうまくいったが、毎回NSFetchRequestを発行する事になります・・・。

----2010/09/01追記 「return fetchedResultsController_」のコメントアウトは、しなくても良かったです。 (当然と言えば当然か・・・) fetchedResultsControllerを複数のクラスで記述したり、共有したりしていた自分のバグでした。

今回の例はセクションを利用していないので、セクションを利用する場合はUITableViewとの連携がもっと大変です。 肝は、

fetchedResultsControllerのソート順とUITableViewのソート順が常に一致している

ことです。

  2010/09/16

UITabBarControllerとUINavigationControllerでレイアウトをした際、 状況に寄ってUITableViewのセルがTabBarに隠れてしまうことがある。 iphone ※最下段のセルがTabBarに隠れてしまった

はてなでこんな記事があったが、 UITabBarController + UINavigationController + UITableViewという画面構成のときに、次の画面でタブバーを消すようにしていると、前の画面に戻ってきたとき、テーブルの一番下が隠れる。 うーん、個人的にはコーディングでNIBを位置調整するのはバグを生みそうだし、MVCに反してる気がする ・・・って事で、何とかIBだけで対処方法を模索する。

ちなみに、画面構成は以下の階層(親から順)になっている

画面構成

(1) Window <- UITabBarController - UIViewController(複数) (2) View <- Controller(Show Navigation Bar) - UIViewController (3) UITableView

Wants Full ScreenやResize View From NIB

まず、(1)のUIViewControllerは、レイアウトの高さは数値で設定できない。 iphone

ViewControllerのAttributesには、Wants Full ScreenやResize View From NIBのチェックがある。 iphone Wants Full Screenにチェックすると、UITableViewのセルはきっちり収まった。 が・・・今度は(2)のNavigation ControllerのTop Barの上部が隠れてしまう。

Simulated User Interface Elements

Simulated User Interface Elementsでも色々調整してみる。 iphone どうも上手くいかない。 というか、こいつの使い道がよくわからない。。。

UITableView単独をやめ、ViewにAutoSizingを設定する

(3)のレイアウトでUITableView単独で表示するのではなくUIView - UITableViewにしてやり、 UITableViewの高さをSize & Positioningで調整してみた。 (自分の場合、365pxくらいでした)

また、ViewSizeのAutoSizingがデフォルトで有効になっている。 iphone 赤い線をクリックすると無効になります。 この場合、オブジェクト内の縦線(高さ相当)を無効にしてみました。

多分、これで回避できるとは思うけど、各設定がちゃんと把握できてないのですっきりしないです。

  2010/09/13

おぉ、Core Dataでマイグレーションが使えるんだ!

参考ページ

(旧) Cocoaの日々 CoreData - マイグレーションCore Data Migration Problems?Core Dataの自動マイグレーション

確かにマイグレーションできないと管理が大変・・・と言うか、データモデルが少しでも変更されるとアプリが起動しなくなる(アプリを削除すれば起動するがデータが消える)のでマイグレーションは必須です。

とりあえず、今作成しているアプリで調査がたら試してます。

モデルバージョン

(1) xcdatamodelを開く。 (2) 設計>データモデル>モデルバージョンを追加 を選択する。 iphone

(3) xcdatamodelが追加される。 iphone

(4) 新規ファイル> ResourseからMapping Modelを選択する。 iphone

(5) ファイル名をつけて【次へ】進む。 iphone ※ファイル名はバージョンと絡めた方が、見た目はわかりやすいかも知れない

(5) ソースモデルの設定、ディスティネーションの設定を選択し【完了】する。 iphone ※ソースモデルは現在のバージョンのxcdatamodel、ディスティネーションは次のバージョンのxcdatamodel

(6) 作成したMapping Modelファイルを開く。 iphone

【差分を表示】をクリックすると、バージョンの差分が表示されている。 iphone

(7) 最新のxcdatamodelを開き、設計>データモデル>現在のバージョンを設定を選択する。 iphone

これで、モデルのバージョン移行が完了です。 ・・・と、アプリをビルドしてみると

落ちてしまいます

persistentStoreCoordinatorにoptionsを設定

CoreDataでプロジェクトを作成すると、persistentStoreCoordinator等の雛形を作成してくれる訳だけど、 なぜかマイグレーションで管理した場合は、以下のoptionsのコードを記述しないとエラーになってしまう。

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                         [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                         nil];

サンプルコード

initWithManagedObjectModelするときに、optionsを設定してやる。

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    NSLog(@"----- App persistentStoreCoordinator -----");
    if (persistentStoreCoordinator_ != nil) {
        return persistentStoreCoordinator_;
    }
    
    NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"iMusicScore.sqlite"]];
    
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                             nil];
    
    NSError *error = nil;
    persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    
    
    return persistentStoreCoordinator_;
}

これで、モデルのバージョン移行が完了です! 勿論、旧バージョンにも戻せます。

不明点

さて、うまく動作したはいいけど、モデルのクラスファイルが更新の仕方がわからないです。 今は、手動でモデルを置き換えてます。

ここら辺は、調査しないといけないとこです。 わかったら、またトピックにする予定。

  2010/09/11

iPhoneホームのようなページングが、かなりお手軽に作成できるのでちょっとびっくり。 iphone

基本的にはUIScrollViewだけで完結しますが、UIPageControlで現在のページ状況を表示できます。 ちなみにUIPageControlはUIViewの子です。 iphone

ポイントはUIScrollViewのframeのサイズを利用して横スクロールさせる、つまり映画のフレーム方式です。 Flash制作者の人ならよく使う技ですけど、ごり押しコードでなくもっとシステマティックですw

表示するページコンテンツをいつ追加するかはアプリによると思いますが、 とりあえず全ページ作られた状態でスクロールする方法で。

HomeViewController.h

まず、定義から。

@interface HomeViewController : UIViewController {
    UIScrollView *scrollView;
    UIView *pageView;
    UIPageControl *pageControl;
}

@property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
@property (nonatomic, retain) IBOutlet UIView *pageView;
@property (nonatomic, retain) IBOutlet UIPageControl *pageControl;
@end

UIViewControllerのViewにscrollViewとpageControlを追加します。 さらにscrollViewでページングで表示するViewをpageViewとしました。 あとはスクロールの機能を使うので、UIScrollViewDelegateの追加を。

HomeViewController.xib

(1)scrollViewでページングで表示するView(pageView)を追加する iphone ※PageViewは、IBOutlet *pageViewにバインド

このViewに1ページずつコンテンツを作ります。 サイズは320px/1ページになるかと思います。

(2) ViewにUIScrollViewとUIPageControlを配置する iphone ※ScrollViewは、IBOutlet *scrollViewにバインド ※PageControlは、IBOutlet *pageControlにバインド

(3) UIScrollViewのdelegateをFile's Ownerにバインドする iphone

delegateしておくことで自動的にscrollViewDidScrollが呼ばれます。 ちなみに、UIScrollViewのDelegateメソッドを調べると、

- (void)scrollViewDidScroll:(UIScrollView *)scrollView;                                               // any offset changes
- (void)scrollViewDidZoom:(UIScrollView *)scrollView __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2); // any zoom scale changes

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;                              // called on start of dragging (may require some time and or distance to move)
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; // called on finger up if user dragged. decelerate is true if it will continue moving afterwards

- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView;   // called on finger up as we are moving
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;      // called when scroll view grinds to a halt

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView; // called when setContentOffset/scrollRectVisible:animated: finishes. not called if not animating

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;     // return a view that will be scaled. if delegate returns nil, nothing happens
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2); // called before the scroll view begins zooming its content
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale; // scale between minimum and maximum. called after any 'bounce' animations

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;   // return a yes if you want to scroll to the top. if not defined, assumes YES
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView;      // called when scrolling animation finished. may be called immediately if already at top

これで、ズームもいけちゃうっぽい!?

次に実装です。

HomeViewController.m

#import "HomeViewController.h"

@implementation HomeViewController

@synthesize scrollView;
@synthesize pageView;
@synthesize pageControl;

- (void)viewDidLoad {
    [super viewDidLoad];

    scrollView.pagingEnabled = YES;  
    scrollView.contentSize = CGSizeMake(640, 0);
    scrollView.showsHorizontalScrollIndicator = NO;  
    scrollView.showsVerticalScrollIndicator = NO;  
    scrollView.scrollsToTop = YES;
    
    pageControl.numberOfPages = 2;
    pageControl.currentPage = 0;

    [scrollView addSubview:pageView];
}

- (void)scrollViewDidScroll:(UIScrollView *)sender {
    CGFloat pageWidth = scrollView.frame.size.width;  
    pageControl.currentPage = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;  
} 
@end

実は

scrollViewのcontentSizeの設定と、pagingEnabledを有効する

だけでページングできてしまうのです。素晴らしい! 始めはこれを知らずに、scrollViewの位置とかで計算しようとしてましたw

UIScrollView scrollViewDidScrollを利用して、pageControlに現在のページ情報を設定します。 scrollViewの幅を計算して出してるところが力技っぽいけど。 将来的には、ページング周りのAPIが充実してくれるとうれしいですね。 (実はもうあったり!?)

  2010/09/08

PHPというか、正規表現をよく忘れるのでメモ(^^;)

郵便番号チェック

preg_match("/^[0-9]{3}-[0-9]{4}$/", $value);

電話番号チェック

preg_match("/^[0-9]{2,4}-[0-9]{2,4}-[0-9]{3,4}$/", $value);

メールアドレスチェック

preg_match("/^\w+[\w\-\.]*@([\w\-]+\.)+\w{2,4}$/", $value);
preg_match("|^[0-9a-z_./?-]+@([0-9a-z-]+\.)+[0-9a-z-]+$", $value);

URLチェック

preg_match('/^(https?|ftp)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)$/', $url);

URLからドメイン取得

preg_match('@^(?:http://)?([^/]+)@i',$url, $matches);
$domain = $matches[1];

クレジットカード名義チェック

preg_match("/^[a-zA-Z\s]+$/", $value);

全角スペースマッチ

preg_replace("/^[  ]+/u", '', $value)

文字数チェック

preg_match("/^.{5,10}$/",$value);
mb_ereg("^.{5,10}$", $value);

半角カタカナチェック

mb_ereg("[ア-ン]",$value)

HTMLカラーチェック

preg_match("/^#[a-fA-F0-9]{6}$",$value)

jQueryでやる手もあるけど、いい加減、PHPでもライブラリ化しておいた方がいいな?

あと、簡単に結果をチェックするならこのサイトで。 reg_match()による正規表現チェッカー

pngファイルの軽量化
Google DriveのIconを再起的に削除
php-markdownでバニラPHPなコードブロック処理
laravel-ffmpeg を使う
2021年版 Ubuntu + certbot + Let's Encrypt でサーバ証明書設定
GihHub のデフォルトでない master ブランチを checkout する
マルチログインで未認証のリダイレクト
Homebrew で Redis をインストール
CSS だけでスムーズスクロール
EC-CUBE4 で Gmail の smtp を利用する
Amazon Linux 2 の amazon-linux-extras とは
UNIQUE カラムのバリデーションで自分自身を除外して更新
フォーム有効期限切れで Page Expired をリダイレクト
ログを日付でローテーションやクリアや削除
Homebrew で PHP8.0 から PHP7.4 にダウングレード
Big sur で zsh 移行と Homebrew アップグレード
Mac に minikube をインストール
途中から .gitignore に追加する
Larevel 6.x から Laravel 8.x にバージョンアップ
Composer で Allowed memory size (メモリ不足)エラー
Blade でカスタムクラスを利用する
git push git pull にブランチ指定せずに実行する
git pull や git push できなくなったとき
Docker のコンテナからホストOS に接続
Mac で ローカル IP アドレス(ipv4)のみを表示する
ホストOS から Docker の MySQLコンテナに接続
caching_sha2_password のエラー
node-config で環境設定ファイルを利用する
rootパスワードを初期化(再設定)する
Git から clone したときのエラー対処
Mac に MySQL をインストール
Mac に PostgreSQL をインストール
Laravel 環境構築 - Mac ネイティブ編
Firebase 入門 - Firebase とは
Firebase 入門 - CLI インストールとデータベースの設定
AWS 無料枠(t2.micro)で容量とメモリエラー
Cloud9 を起動する -初心者編-
gcloud で GCEインスタンスを起動してみる
AWS CLI と jq でインスタンス一覧を整形して表示
React と Laravel7 のプロジェクトを作成する
Homebrewインストール-2020年版
3直線で囲まれた範囲塗りつぶし
PuLP で線形最適化問題を解く
カスタムのペジネーションを作る
node-sass を使って sass をコンパイルする
Log ファサードでSQLログを分離して書き出す
いちから始める Docker - 複数のコンテナを使う - (2020年)
いちから始める Docker - docker-compose を使う - (2020年)
AWS ECR を使ってみる
Laravel7 でマルチ認証