だいたい47度

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

PageTop

無限にスクロールできるUIScrollView

結構簡単に作れました。

ページャブルなUIScrollViewに3ページだけスペースを用意し、ページがめくれると順次ページを繰り上げる。それを繰り返すことで無限にスクロールできるようにしました。

新しいページはEndlessScrollViewDelegateプロトコルを作成し、- (UIView *)viewOfPage:(int)pageでページ数を指定して供給します。ページの左右端を指定するときは、viewOfPageでnilを返してあげると、普通にスクロールしてbounceするようになります。
endlessScroll

//EndlessScrollViewDelegate.h
@protocol EndlessScrollViewDelegate
- (UIView *)viewOfPage:(int)page;
@end

//EndlessScrollView.h
@interface EndlessScrollView : UIScrollView

@property (nonatomic, strong) UIView *pageView1;
@property (nonatomic, strong) UIView *pageView2;
@property (nonatomic, strong) UIView *pageView3;

@property (nonatomic, weak) id endscrollDelegate;

- (void)createFirst3Pages;

@end

//EndlessScrollView.m
#import "EndlessScrollView.h"

@implementation EndlessScrollView
{
  int _centerPageNum;
  BOOL _isLoadableScrollView;
}

- (id)initWithFrame:(CGRect)frame
{
  self = [super initWithFrame:frame];
  if (self) {
   self.contentSize = CGSizeMake(frame.size.width * 3, frame.size.height);
   self.pagingEnabled = YES;
   self.showsHorizontalScrollIndicator = NO;
   self.scrollsToTop = NO; // ステータスバーのタップによるトップ移動禁止
   self.delegate = self;
  }
  return self;
}

- (void)createFirst3Pages
{
  self.pageView1 = [self.endscrollDelegate viewOfPage:1];
  self.pageView2 = [self.endscrollDelegate viewOfPage:2];
  self.pageView3 = [self.endscrollDelegate viewOfPage:3];
  self.pageView1.frame = [self correctRectOfPageView:1];
  self.pageView2.frame = [self correctRectOfPageView:2];
  self.pageView3.frame = [self correctRectOfPageView:3];
  [self addSubview:self.pageView1];
  [self addSubview:self.pageView2];
  [self addSubview:self.pageView3];

  if (self.pageView2 == nil) {
   _isLoadableScrollView = NO;
   self.contentSize = CGSizeMake(self.frame.size.width * 1 + 1,//bounceさせる1
    self.frame.size.height);
  } else if (self.pageView3 == nil) {
   _isLoadableScrollView = NO;
   self.contentSize = CGSizeMake(self.frame.size.width * 2, self.frame.size.height);
  }else {
   _isLoadableScrollView = YES;
  }

  _centerPageNum = 2;
}

- (CGRect)correctRectOfPageView:(int)pageViewNum
{
  float x = self.frame.size.width * (pageViewNum - 1);
  return CGRectMake(x, 0, self.frame.size.width, self.frame.size.height);
}

#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
  if (_isLoadableScrollView == NO) {
   return;
  }

  CGFloat position = scrollView.contentOffset.x / scrollView.bounds.size.width;
  if (position >= 2.0f) {
   int nextPage = _centerPageNum + 1;
   UIView *nextView = [self.endscrollDelegate viewOfPage:nextPage + 1];

   if (nextView) {
    [self addSubview:nextView];

    nextView.frame = self.pageView3.frame;
    self.pageView3.frame = self.pageView2.frame;
    self.pageView2.frame = self.pageView1.frame;

    [self.pageView1 removeFromSuperview];
    self.pageView1 = self.pageView2;
    self.pageView2 = self.pageView3;
    self.pageView3 = nextView;
    self.contentOffset = CGPointMake(self.bounds.size.width, 0);

    _centerPageNum = nextPage;
   }
  } else if (position <= 0.0) {
   int nextPage = _centerPageNum - 1;
   UIView *nextView = [self.endscrollDelegate viewOfPage:nextPage - 1];

   if (nextView) {
    [self addSubview:nextView];

    nextView.frame = self.pageView1.frame;
    self.pageView1.frame = self.pageView2.frame;
    self.pageView2.frame = self.pageView3.frame;

    [self.pageView3 removeFromSuperview];
    self.pageView3 = self.pageView2;
    self.pageView2 = self.pageView1;
    self.pageView1 = nextView;
    self.contentOffset = CGPointMake(self.bounds.size.width, 0);

    _centerPageNum = nextPage;
   }
  }
}

@end


- (void)scrollViewDidScroll:(UIScrollView *)scrollViewで制御しちゃっているので、- (UIView *)viewOfPage:(int)pageが呼び出された時にnilを返す処理が重いときついです。didEnd系はページを複数枚一気にめくろうとするときに対処出来ません。頑張ればいろいろできるけど、とりあえずこれで目的は達成。

使う方は、普通にEndlessScrollViewをaddしてあげてcreateFirst3Pagesを呼び出すだけでOK。もちろんEndlessScrollViewDelegateのviewOfPageの実装も忘れずに。

PageTop

iOSモダルパネルライブラリUAModalPanelのよくやる使い方

iOS開発でモダルを使いたいとき、UAModalPanelが便利です。

基本的にはUAModalPanelの子クラスを作り、contentViewプロパティに描画したいViewをaddしていけばOK。showFromPointを実行すればモダルがそのポイントから出現します。githubの他、cocoapodsからもDLできます。iOSオープンソースライブラリ徹底活用にも出ています。

ただ、ちょいちょい癖があって使い方がわからないことがあるので、よく使う部分をメモします。

1) Xibで扱いたい。
InterfaceBuilderでパネルの中身を作りたい場合。MyModalPanelというクラスを作って、同名のxibファイルがあるとします。xibではFile's OwnerをMyModalPanelとし、MyModalPanel.hで定義したbaseViewプロパティがObjectsの大本Viewになるようにしておきます。

//MyModalPanel.m
- (id)initWithFrame:(CGRect)frame
{
   if ((self = [super initWithFrame:frame])) {
       [[NSBundle mainBundle] loadNibNamed:@"MyModalPanel" owner:self options:nil];
      float topMargin = 200;
      float bottomMargin = frame.size.height - topMargin - _baseView.frame.size.height;
      float leftMargin = (frame.size.width - _baseView.frame.size.width) / 2;

      self.shouldBounce = NO;
      self.margin = UIEdgeInsetsMake(topMargin, leftMargin, bottomMargin, leftMargin);
      self.padding = UIEdgeInsetsMake(0, 0, 0, 0);
      self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
      self.cornerRadius = 0;

      [self.contentView addSubview:_baseView];
      [_baseView setFrame:self.contentView.frame];
   }
}


topMarginを適当に調整すれば、良い感じの位置に表示させてくれます。paddingはなしにするのが僕は好き。initで指定しなくても、showFromPointを実行する前にself.marginを変えてあげれば任意の場所にパネルを描画できます。

2) 左上のバツボタンを消したい。
左上にバツボタンが出てしまいますが、アプリとあまりにも感じが違う場合は消してしまいたいです。

このボタンはcloseButtonプロパティで指定されるボタンなので、単純にそれを消せばOK。他に消すボタンを作った場合は、このcloseButtonの消す処理を呼んであげるのが良いです。パネルをアニメーションで消したりとかの処理をきちんとやってくれるので。

//MyModalPanel.mのinit
   [self.closeButton setHidden:YES];

//MyModalPanel.m
- (IBAction)myCloseButtonTapped:(id)sender
{
   [self.closeButton sendActionsForControlEvents:UIControlEventTouchUpInside];
}



3) パネルの大きさを動的に変えたい。
パネル内のボタンを押したら、パネルをニョっと拡大させたいことがあります。そんなときは、パネルの中身の拡大&marginの修正&再描画が必要です。

//tapSomeButton
[UIView animateWithDuration:0.2f
   animations:^{
      self.contentView.frame = //お好みのサイズ
   }
   completion:^(BOOL finished){
      self.margin = //対応したエッジ
      [self layoutSubviews];//忘れずに
   }];


marginの修正をanimationの中で行うと、ちょっと描画が変になることがあるので、completionの中でやっています。見た目上はcompletionの中身がなくても問題ないですが、拡張した領域でのタップ感知などのためにはmarginの修正が必要です。やっておきましょう。

便利なライブラリがあると開発がとても楽ですね。

PageTop

iOSデータベース周りの本を読んでみた(1) Professional iPhone and iPad Database Application Programming

せっかくだからゲーム以外のアプリも作ってみようと思いましたが、その場合、コンテンツに魅力がないと面白くなさそうです。基本的な動きはゲームよりも簡単ですし。

そのためデータベース連携周りを学習することにしました。この本がよさげ。例えば、取得したデータ量が多すぎて画像描画が遅くなった時対応、などのTipsも豊富そうです。400ページ弱。
Professional iPhone and iPad Database Application Programming (Wrox Professional Guides)Professional iPhone and iPad Database Application Programming (Wrox Professional Guides)
(2010/10/26)
Patrick Alessi

Objective-Cの文法は分かっている人向けの本です。大きく3パート構成で、パート1ではデータを使ったアプリの作成方法、パート2ではCoreDataの使い方、パート3ではWebサービスとの連携をやるようです。ちょっと古いのでARCなどには対応しておらず、そこはこっちで補完して読む必要があります。

Chapter 1:Introducing data-driven applications
第1章では、単純なアプリを作ります。XcodeやMVC(Model-View-Controller)の説明の後に開発開始。NSStringのNSArrayを持つModelクラス(NSObjectの子クラス)を作成し、それを使ってUITableViewを描画します。UITableViewDelegateの話をしておしまい。意外とHello World的なこともやるんだな。

Chapter 2:The iPhone and iPad database: SQLite
第2章では、オープンソースライブラリSQLiteを使います。SQLiteは、アプリ組み込み型のリレーショナルデータベース管理システムです。データを1つのファイルに保存し、それにSQL文でアクセスしますので、軽量なデータであればMySQLなどより使い勝手がよいです。sqlite3 hogehoge.dbコマンド1つでデータベースを作れて、あとは普通にクエリを実行できるのですね。GUIもあるし、敷居は低そう。

データベースへの接続は専用クラスを作ります。.dbファイルをアプリ内に入れておいて、それにアクセスします。クエリの実行は、プリペアードステートメントを作成し、その結果をstepで一行ずつ読み込むことで行います。プリペアードステートメントで引数的に扱う部分は?にしておいて実行時に変更することができます。データの読み出し関数はsqlite3_column_textなど色々種類があります。一覧が本にもありますが本家を見てみてもいいかも。

//DBAccess.m
sprite3 *database;

- (void)initializeDB
{
 NSString *path = [[NSBundle mainBundle] pathForResource:@"database" ofType:@"db"];
 if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) {
 } else {
  sqlite3_close(database);
  //エラー処理
 }
}

- (NSMutableArray *)someQuery
{
 NSMutableArray *ret = [[NSMutableArray alloc] init];
 const char *sql = "SELECT hogehoge....";
 sqlite3_stmt *statement;
 //プリペアードステートメント
 int sqlResult = sqlite3_prepare_v2(database, sql, -1, &statement, NULL);
 if (sqlResult == SQLITE_OK) {
  while (sqlite3_step(statement) == SQLITE_ROW) {
   //statementからデータを取り出して、Modelクラスに格納
   //ModelクラスをretにaddObject
  }
  sqlite3_finalize(statement); //リソースのrelease
 }
 return ret;
}

結構単純でいいですね。使ってみよう。

Chapter 3: Displaying your data: the UITableView
第3章では、UITableViewについて掘り下げます。UITableViewおよび関連クラスは、DBから取得したデータを一覧で表示するのによく使うのでよいですね。

まずUITableViewをカスタマイズしていきます。subviewなどを持たせてリッチなtableにしていきます。途中でdequeueの説明などが入りますが、恥ずかしながらよく分かっていなかったので学習できてよかったです。Stringで指定するIdentifierはcellのstyleを指定してcellを再利用しているものなのですね。普通のscrollviewとは違いますね。

複数のsubviewをCellに持たせると、パフォーマンスに影響が出ます。これに対応する方法としてdrawRect:をオーバーライドする方法を説明しています。なお、カスタムにdrawRectする場合セル内のアニメーションは非推奨なので、ユーザが編集できるCellにはこの方法は使えません。また、一度にdrawRectしたものは同一とみなされるため、その中に複数のボタンを含めることはできません。他にパフォーマンスを上げる方法として、Cellが透過しないようにする(opaque = YES)のは基本みたいですね。opaqueがNOになっているものはInstrumentsのCoreAnimation、Color Blended Layersで確認できます。

//ViewCell.m
- (void)drawRect:(CGRect)rect
{
 //文字列
 [string drawAtPoint:CGPointMake(0,0)
  forWIdth:120
  withFont:[UIFont systemFontOfSize:18]
  minFontSize:12
  actualFontSize:NULL
  lineBreakModel:UILineBreakModeTailTruncation
  baselineAdjustment:UIBaselineAdjustmentAlignBaselines];

 //画像
 [image drawInRect:CGRectMake(0,0,0,0)];
}

Indexについても説明があります。アドレス帳の右のアルファベットのようなもの。ここって開発者が決めれるんですね、へー。更にsearch barを付ける方法も詳細に書いています。

Chapter 4: iPad interface elements
第4章では、iPadに視点を移します。前半はUISplitViewControllerとUIPopoverControllerを説明しながらサンプルアプリを作っていきます。目新しいところはなし。UIPopoverControllerを使ったことなかったので、ViewControllerを引数に渡してinitするのが新鮮でした。

中盤はGesture Recognizerの話。対応するUIGestureRecognizerの子クラスをviewにaddしてあげることで、swipeやpinch動作を簡単に認識してくれるクラスです。以前ゲームを作ったときはこれがなくて一つ一つ書くのが面倒だった……。しかしこれiPadの章にあるべきなのか?

//ViewController
- (void)viewDidLoad
{
 [super viewDidLoad];
 UISwipeGestureRecognizer *swipeLeft = [UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeToLeft:)];
 swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
 [self.view addGestureRecognizer:swipeLeft];
}

- (void)swipeToLeft:(UIGestureRecognizer *)sender
{
}

最後にiTunesを介したPCとのファイルシェアリングの話。plistでApplication supports iTunnes file sharingを設定した後、アプリの中でデータをXML化させて保存させています。個人的にあまり興味がないので読み飛ばし。


長くなってきたので一旦ここで切ります。ちょうどPart1の終わりだし。次はとうとうCoreDataに入るようです。

PageTop

MPFlipViewControllerの使い方

ゲームばっかり作っていて、UIKitを使ったアプリを作っていないことに思い至ったので学習中です。基本は下記をざっと読んで理解。遷移周りをもう少し詳しく書いてあるとよかったですが、読みやすいしイメージはつきました。

iPhoneプログラミングUIKit詳解リファレンスiPhoneプログラミングUIKit詳解リファレンス
(2010/01/12)
所 友太
iPhone/iPad/iPod touch プログラミングバイブル iOS 6/Xcode 4対応 (smart phone programming bible)iPhone/iPad/iPod touch プログラミングバイブル iOS 6/Xcode 4対応 (smart phone programming bible)
(2012/10/22)
布留川英一

んで本題。

その中でFlipBoardみたいな動きをするライブラリMPFlipViewControllerを使って遊ぼうとしたのですが、使い方がよくわからなかったので概略をメモります。

動作イメージと導入
コードと動作イメージはgithub。Modified BSD Licenseです。
cocoapodsを使う場合は

pod `MPFlipViewController`, `= 0.0.2`


概略
親UIViewControllerの下で子UIViewControllerを動かす形になります。上述の動作イメージで言うと、遷移する部分(MATRIXの写真と説明文のあたり)が子UIViewControllerにあたります。

親UIViewControllerは、MPFlipViewControllerDelegateとMPFlipViewControllerDataSourceの2つのプロトコルを導入します(もちろん他クラスに導入しても良いですが)。単純に使うなら、プロトコルで定義されている関数はほぼsampleのままでよいです。一点、flipViewController: viewControllerBeforeViewControllerとflipViewController: viewControllerAfterViewControllerの、nilを返す条件のみ修正する必要があります。ここは、ページめくりができない条件(一番前にきている・一番後ろにきている)においてnilを返すようにしてあげればOKです。

子の方は普通のUIViewControllerです。これがFlipされるたびに作成され、描画されます。サンプルではcontentViewWithIndexメソッドで子UIViewControllerを作成しています。

よって、flipしたときのnil条件とcontentViewWithIndexメソッドを変えてあげれば、単純に自分のアプリに導入出来ます。たとえば、InterfaceBuilderで作ったTestViewControllerを無限にflipできるようにする場合は、以下の様なコードになります。

#pragma mark - MPFlipViewControllerDataSource protocol
- (UIViewController *)flipViewController:(MPFlipViewController *)flipViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
 int index = self.previousIndex;
 index--;
 self.tentativeIndex = index;
 return [self contentViewWithIndex:index];
}

- (UIViewController *)flipViewController:(MPFlipViewController *)flipViewController viewControllerAfterViewController:(UIViewController *)viewController
{
 int index = self.previousIndex;
 index++;
 self.tentativeIndex = index;
 return [self contentViewWithIndex:index];
}

- (UIViewController *)contentViewWithIndex:(int)index
{
 TestViewController *page = [[TestViewController alloc] initWithNibName:@"TestViewController" bundle:nil];
 [self addChildViewController:page];
 [page didMoveToParentViewController:self];
 page.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
 return page;
}


ページ数を事前に指定しないでいいのもいいですね。DB連携などもしやすそう。

ちなみに下記の本でも紹介されています。この本はライブラリがいっぱい見れて便利。

iOSオープンソースライブラリ徹底活用iOSオープンソースライブラリ徹底活用
(2012/12)
菊田 剛

商品詳細を見る

PageTop

エキスパートObjective-Cプログラミングがすごい

エキスパートObjective-Cプログラミングは上級者向けのObjective-C本だ。正直ボクは全部は理解できてない。が、とても勉強になったし今後もなりそうだ。

内容としては、ARC、Blocks、GCDについてそれぞれ扱って200ページ程度。ARC(Automatic Reference Counting)は、retainやreleaseをコンパイラ側で実装してもらう仕組み。BlocksはObjective-Cの関数ポインタのようなもの。GCD(Grand Central Dispatch)はマルチスレッド。

それぞれ概念からきちんと説明してくれるが、気がつくとドップリと深いところに話が進んでいる。たとえば、ARCの説明をするとき、最初にObjective-Cのメモリ管理について説明してくれるが、この説明はどこで読んだものよりもわかりやすい。なるほどなぁと思って読み進めると、retainで何をやっているかからretainの実装はどうやっているか、という話に進んでいく。しまいには公開されていないNSObjectのretainの実装をデバッガを使って予想するところまで話が進む。そこからARCの説明に戻ってくるとまた非常に親切で読みやすい……といった感じだ。

ARCについては、使っていなかったことを公開するくらい便利そう。そもそもcocos2dばかり使っていたから、どうもARCを入れるのが面倒だった。cocos2dのライブラリがARC対応していないから、一々ARCを指定しなくてはならないのだ。ただ、いまはARC対応したcocos2dもあるみたいだし、次は是非使おうと思った(「DiegoTristanと黒白猫の開発日記 」さん参照)。
追記:office-ashipさんを参照してやったら、現行のアプリをARC対応するのは20分もあれば終わりました。概ね自分のアプリのautoreleaseなどを外していく作業。

Blocksについては、cocos2dで使いまくっているので難なく。やはり実装に話が及ぶと難しいが、いままでおまじないと思っていた__BLOCKの実装などがわかるとなるほどなぁと感心する。

GCDについては、難しそうなので避けていたが読んでみると非常にスッキリと頭に入ってきた。いままでのアプリでも使うべきところが幾つかあって、直さなきゃと思った。GCDの後半は個別の話に進んでいくので、実装しながら読んだほうがよさそう。

わかりやすい概念図と、深い実装のところが並んでいるので、開発でちょっと迷った時に参照できるよう横に置いておきたい本。

エキスパートObjective-Cプログラミング -iOS/OS Xのメモリ管理とマルチスレッド-エキスパートObjective-Cプログラミング -iOS/OS Xのメモリ管理とマルチスレッド-
(2011/11/18)
坂本 一樹

商品詳細を見る

PageTop
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。