2011/04/28 | 18:58 pm

UIScrollView で視差をつける

ここ半年以上、ウェブから離れていたので、その間に溜め込んだTIPSのメモ。
Objective-Cは厳格だけど楽しいです。あとXcode4からデバックが相当楽になりました。

と、余談は置いといて、UIScrollView でスクロールしたときに、表示されているそれぞれに視差をつけて動かすとき、真ん中を基準にして単純に可動範囲の横幅を足すのが一番楽でした。

explanation

iPadだと、実際の横幅は1024pxになるところを、表示させているOBJECT_Aには、可動域を1280pxとして動かす。
こうすると、OBJECT_Aが背景と同期していないように動いて、まるで視差があるように見えます。
何個かオブジェクトを配置して、別々の値を設定しておくと、なかなか面白い動きをしてくれます。
多分、GUINNESS WORDL RECORDSのアプリとかに近い動きになるはず。

今回やった方法は、UIScrollViewをベースのUIViewに配置して、UIScrollViewDelegateからscrollViewDidScrollで吐き出される値をそれぞれのオブジェクトに渡すというもの。

[ BaseView.h ](← UIViewControllerにaddSubview:されたUIViewと想定)

#import 

@interface BaseView : UIView  {
	// 基本となるUIScrollView
	UIScrollView	*scrollView;
	NSMutableArray	*pages;
}

- (void)initObject;
- (int)currentPage;

@end

[ BaseView.m ]

#import "BaseView.h"
#import "PageView.h"

#define NUM_OF_PAGES	5	// 5ページあると想定

@implementation BaseView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
		[self initObject];
    }
    return self;
}

- (void)initObject {
	CGRect rect = CGRectMake( 0.0f, 0.0f, 1024.0f, 768.0f );

	scrollView = [[UIScrollView alloc] initWithFrame:rect];
	scrollView.pagingEnabled = YES;
	scrollView.delegate = self;
	scrollView.contentSize = CGSizeMake( NUM_OF_PAGES * rect.size.width, rect.size.height );
	[self addSubview:scrollView];

	pages = [[NSMutableArray alloc] initWithCapacity: NUM_OF_PAGES];
	for( int i = 0; i < NUM_OF_PAGES; i++ ) {
		PagaView *page = [[UIPageView alloc] initWithFrame:rect];

		if( page.superview == nil ) {
		CGRect viewFrame = page.frame;
		viewFrame.origin.x = viewFrame.size.width * i;
		viewFrame.origin.y = 0;

		page.frame = viewFrame;

		[pages addObject:page];
		[scrollView addSubview:page];
	}
}

- (int)currentPage {
	CGFloat pageWidth = scrollView.frame.size.width;
	int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;

	return page;
}

- (void)scrollViewDidScroll:(UIScrollView *)sv {
	int page = [self currentPage];

	if( page < 0 ) return;
	if( page >= NUM_OF_PAGES ) return;

	PageView *view = [pages objectAtIndex:page];
	if( view.superview != nil )
		[view adjustObjectPosition:sv.contentOffset.x pageIndex:page];	// この値を元に視差をつける
}

[ PageView.h ]

#import 

@interface PageView : UIView {
	// 今回は、それぞれのオブジェクトに画像を当てはめる想定
	UIImageView		*aImage;
	UIImageView		*bImage;
	UIImageView		*cImage;
}

- (void)setupPageObject;
- (void)adjustObjectPosition:(float)xpos pageIndex:(int)index;
- (UIImageView *)makeImageView:(CGRect)rect withImageName:(NSString *)imageName

@end

[ PageView.m ]

#import "PageView.h"

// それぞれの差異の値を設定
#define DIFF_A	500.0f
#define DIFF_B	300.0f
#define DIFF_C	100.0f

@implementation UIPageView

- (void)setupPageObject {
	CGRect aRect = CGRectMake( 90.0f, 140.0f, 815.0f, 510.0f );
	CGRect bRect = CGRectMake( 80.0f, 100.0f, 450.0f, 190.0f );
	CGRect cRect = CGRectMake( 300.0f, 20.0f, 160.0f, 90.0f );

	aImage = [self makeImageView:aRect withImageName:@"aImage.png"];
	[self addSubview:aImage];
	bImage = [self makeImageView:bRect withImageName:@"bImage.png"];
	[self addSubview:bImage];
	cImage = [self makeImageView:cRect withImageName:@"cImage.png"];
	[self addSubview:cImage];
}

- (void)adjustObjectPosition:(float)xpos pageIndex:(int)index {
	float pos = xpos - ((page + 1) * 1024.0f);

	// 真ん中を基点としたそれぞれのX座標の差異
	float moveValue_a = -((pos / 1024.0f) * DIFF_A) - (DIFF_A - 90.0f);
	float moveValue_b = -((pos / 1024.0f) * DIFF_B) - (DIFF_B - 80.0f);
	float moveValue_c = -((pos / 1024.0f) * DIFF_C) - (DIFF_C - 300.0f);

	// 各画像にX座標を適応
	aImage.frame = CGRectMake( moveValue_a, 140.0f, 815.0f, 510.0f );
	bImage.frame = CGRectMake( moveValue_b, 100.0f, 450.0f, 190.0f );
	cImage.frame = CGRectMake( moveValue_c, 20.0f, 160.0f, 90.0f );
}

- (UIImageView *)makeImageView:(CGRect)rect withImageName:(NSString *)imageName {
	NSString *path = [[NSBundle mainBundle] pathForResource:imageName ofType:@""];
	UIImage *img = [[UIImage alloc] initWithContentsOfFile:path];

	UIImageView *imgView = [[UIImageView alloc] initWithFrame:rect];
	imgView.image = img;

	[img release];

	return imgView;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        [self setupPageObject];
    }
    return self;
}

- (void)dealloc {
	[aImage removeFromSuperview];
	[aImage release];
	aImage = nil;

	[bImage removeFromSuperview];
	[bImage release];
	bImage = nil;

	[cImage removeFromSuperview];
	[cImage release];
	cImage = nil;

	[super dealloc];
}

@end

こーゆーのは、Flashで培ったごまかしの技術が生かされる気がします。

COMMENT

CATEGORY

POSTED

jam

2010/07/31 | 3:24 am

iPad x 3D Hologram

おお、その手があったか。と言っても全然やり方わからないスけど。。。
なんという事でしょう(← ビフォーアフター風)

N-3D DEMO from aircord on Vimeo.


aircord labo
via Fubiz

COMMENT

POSTED

jam

2010/07/30 | 0:17 am

Movie のメモリリーク of iPad

MoviePlayerController と MoviePlayerViewController どちらでも、
FullScreen ではなく、画面内に埋め込んで再生している状態で「DONE」ボタンを押すか、ちゃんと最後まで見ない状態で強制的に止めるとメモリが残ってるぽい。。。

Instruments 使って計ってみたところ、終了時のメモリの下げ幅が最後まで見た場合と強制的に終わらせた場合だと半分くらいちがうという結果。。。汗
[moviePlayer retainCount] でリファレンスカウンタの値を調べても release した時点で 4(← 謎)を吐き出す。
そもそも、なんで増えてるのかしら??
[moviePlayer stop] を呼び出すと一気に増えるのは何故??

例えば、電子カタログ系とかの構造を作るとして、スクロールでページ展開していく場合、インラインで指定エリアの中でムービーを流す予定のページで、途中で遷移したときに前のページのムービーが流れっぱなしは避けたいところ。
なので、UIScrollViewDelegate の scrollViewDidEndDecelerating: などでスクロールとページ状況を検知して前後のページの状態を初期化する方法を取ってムービーを終了させると発生してしまう模様。。うう。。

やはりちゃんとフルスクリーン表示させて「DONE」ボタンなりでちゃんと終了させないとダメっぽい。

[ソースコード]
(※親の mファイルが setMovie: を呼ぶ想定で
stopMovie が遷移したときの強制終了用のメソッドです)

- (void)setMovie:(NSString *)movieName movieArea:(CGRect)frameSize pageName:(NSString *)page {
	NSBundle *bundle = [NSBundle mainBundle];
	fileName = [[NSString alloc] initWithString:[bundle pathForResource:movieName ofType:@"m4v"]];
	currentPage = page;

	movieButton = [[UIButton alloc] initWithFrame:frameSize];
	[movieButton addTarget:self action:@selector(movieButtonTouched:) forControlEvents:UIControlEventTouchDown];
	[movieButton setBackgroundImage:[UIImage imageNamed:@"movie_capture.jpg"] forState:UIControlStateNormal];
	[self addSubview:movieButton];
}

- (void)movieButtonTouched:(id)sender {
	[movieButton removeFromSuperview];
	[self playMovie];
}

- (void)playMovie {
	moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileURLWithPath:fileName]];
	[moviePlayer.view setFrame:CGRectMake( 0, 0, self.frame.size.width, self.frame.size.height )];

	[moviePlayer play];
	[self addSubview:moviePlayer.view];

	isPlayMovie = YES;

	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification  object:moviePlayer];
}

- (void)clearMovieAsset {
	[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:moviePlayer];

	moviePlayer.initialPlaybackTime = -1;

	[moviePlayer pause];
	[moviePlayer stop];
	[moviePlayer.view removeFromSuperview];
	[moviePlayer release];

	[self addSubview:movieButton];

	isPlayMovie = NO;
}

- (void)moviePlayerDidFinish:(NSNotification *)aNotification {
	[self clearMovieAsset];
}

- (void)stopMovie {
	if( isPlayMovie ) {
		[self clearMovieAsset];
	}
}

Build and Analyze でも leak はしていないけど、Instruments のランタイムで見てると顕著にメモリリークしてます。
うーん、うーん。。。
リファレンスカウンタ方式とガベージコレクタ方式があるから、ガベージコレクタの方も調べなければ。

COMMENT

CATEGORY

POSTED

jam

2010/07/21 | 13:57 pm

Objective-CでFlashの非同期処理みたいなの

Objective-C で UIViewController を入れ子にしたときに、子UIViewController から親UIViewController の Function を呼びたいとき。
かなり前に書いてたのに、公開忘れてたので今公開。
こーゆーの中々覚えられないアフォーなのでメモ。。。

—————————-
AppDelegate
- ParentController
- ChildController
—————————-
という関係性があると仮定して、このときに ChildController が ParentController のメソッドを呼ぶ場合、
ParentController と ChildController に記述することス。

まず親側の .hファイルには子ファイルの .h ファイルをインポートして @interface 時に Delegate を加える。

//  ParentController.h
//
#import <UIKit/UIKit.h>
#import "ChildController.h"

@interface ParentController : UIViewController <ChildControllerDelegate> {
	ChildController	*childController;
}

@property (nonatomin, retain) ChildController *childController;

// 呼び出される Function を定義
- (void)calledFromChild;

@end

親側の .m ファイルで子の Delegate をセットする

//  ParentController.m
//

@implementation ParentController

@synthesize childController;

- (void)calledFromChild {
	NSLog(@"calledFromChild");
}

- (void)viewDidLoad {
	[super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
	[super didReceiveMemoryWarning];
}

- (void)viewWillAppear:(BOOL)animated {
	ChildController *child = [[ChildController alloc]  initWithNibName:@"ChildController" bundle:nil];
	self.childController = child;
	[self.childController setDelegate:self];

	[self.view insertSubview:childController.view atIndex:0];
}

- (void)viewDidUnload {
	[super viewDidUnload];
}

- (void)dealloc {
	[super dealloc];
}

@end

次に子側の .h ファイルには protocol で Delegate と @optional で呼び出すメソッドを設定

//  ChildController.h
//

#import <UIKit/UIKit.h>

@protocol ChildControllerDelegate;

@interface ChildController : UIViewController {
	UIButton *checkButton;	// InterfaceBuilder で配置したボタンと仮定
	id <ChildControllerDelegate> delegate;
}

@property (nonatomic, retain) IBOutlet UIButton *checkButton;
@property (nonatomic, assign) id <ChildControllerDelegate> delegate;

- (IBAction)clickCheckButton:(id)sender;	// InterfaceBuilder で checkButton と関連づける

@end

@protocol ChildControllerDelegate <NSObject>

@optional
- (void)calledFromChild;	// ParentController で定義したメソッドと同じ名前

@end

それで子側の .m ファイルで delegate で親側のメソッドを実行

//  ChildController.m
//  

#import "ChildController.h"

@implementation ChildController

@synthesize checkButton, delegate;

#pragma mark -

// 実際に呼び出すメソッド
- (IBAction)clickCheckButton:(id)sender {
	if( [delegate respondsToSelector:@selector(calledFromChild)] )
		[delegate calledFromChild];
}

- (void)viewDidLoad {
	[super viewDidLoad];
}

- (void)viewDidUnload {
	checkButton = nil;
	[super viewDidUnload];
}

- (void)dealloc {
	[checkButton release];
	[super dealloc];
}

@end

これで、checkButton に関連づけられたボタンにタッチしたときに
Xcode の[実行]-[コンソール]で表示されるウィンドウに「calledFromChild」って表示されます。
子側の処理を待ってから、親側でごにょごにょするときに使える。

COMMENT

CATEGORY

POSTED

jam

JUN NAKAJIMA
Tokyo, Japan.
member of Onawatobi.
contact@brooklyn.jp