2014年4月アーカイブ

次の機能を持つローディング画面を実装します。
 ・ローディング画面表示中に裏で読み込み処理やViewの追加を行なえる
  ⇒当然の機能ですね。裏の画面で処理が止まってしまうpresentViewController:animated:completion:を用いる事はできません。
  ⇒裏で新規にViewをaddSubView:してもローディング画面の上に表示されないようにする必用があります。

 ・画面を跨いでの表示と非表示が行なえる
  ⇒シングルトンとして実装し、仕様側からは「表示」と「非表示」のみ制御すれば良いという実装をします。

上記の仕様で一番難しいのが裏でViewを新規に追加してもローディング画面の前面には表示しない。という項目ではないでしょうか。
presentViewController:animated:completion:を使う事ができない、また表示する画面にインスタンスを置かないという事からUIWindowのrootViewController.viewにaddSubView:する事が考えられますが、この場合後に追加したViewが最前面に表示されてしまいます。
実際にローディング処理を行なう各画面でViewを追加するごとにローディング画面を最前面に配置し直すという処理を書いても実現できますが、とても煩雑になってしまいます。
できれば「ローディング画面は常に最前面」というのはローディング画面の仕様なのですから、ローディング画面中に実装してしまいたいものです。

ローディング画面を常に最前面に配置し続ける機能を実装するため、キー値監視(KVOと表記される事もあります)という技術を用います。
キー値監視とは特定のインスタンス中にあるプロパティの変化を監視し、プロパティが変化したタイミングで監視側インスタンスにコールバックを受けるという仕組みです。

キー値監視を使ってローディング画面の親Viewを監視し、subviewsの数が変更された際にローディング画面を最前面に配置し直すという処理を実装する事により目的を達成します。

以下コードとなります。
ローディング画面のデザインはxibファイルで行なっています。
実際に監視しているプロパティは親View.layer.sublayersです。
これはUIView.subviewsの変更がKVCに準拠しておらず監視不可能なためです。

LoadingViewController.h

#import <UIKit/UIKit.h>


// ローディング画面(シングルトン)

@interface LoadingViewController : UIViewController


+ (LoadingViewController*)sharedController;

+ (void)deleteInstance;

+ (void)dismiss;


- (void)show;


@end


LoadingViewController.m


#import "LoadingViewController.h"


#define SUPERVIEW_SUBVIEWS @"SuperViewSubViews"


@interface LoadingViewController ()

{

BOOL _isAddView;

}


@end


@implementation LoadingViewController


static LoadingViewController* _LoadingViewControllerInstance = nil;

bool _canLoadingViewControllerDelete = NO;


+ (LoadingViewController*)sharedController

{

@synchronized(self) {

        if (!_LoadingViewControllerInstance) {

            _LoadingViewControllerInstance = [[self alloc] init];

        }

    }

    return _LoadingViewControllerInstance;

}


+ (void)deleteInstance

{

if (_LoadingViewControllerInstance) {

        @synchronized(_LoadingViewControllerInstance) {

            _canLoadingViewControllerDelete = YES;

[_LoadingViewControllerInstance.view.superview.layer removeObserver:_LoadingViewControllerInstance forKeyPath:@"sublayers"];

[_LoadingViewControllerInstance.view removeFromSuperview];

            [_LoadingViewControllerInstance release];

            _LoadingViewControllerInstance = nil;

            _canLoadingViewControllerDelete = NO;

        }

    }

}


+ (void)dismiss

{

[UIView animateWithDuration:0.2 animations:^(void) {

_LoadingViewControllerInstance.view.alpha = 0.0;

}completion:^(BOOL finished) {

[LoadingViewController deleteInstance];

}];

}


- (id)init

{

if (self = [super init]) {

_isAddView = NO;

}

return self;

}


- (void)show

{

if (_isAddView) return;

CGSize winSize = [[CCDirector sharedDirector] winSize];

AppController* delegate = (AppController*)[UIApplication sharedApplication].delegate;

UINavigationController* rootViewController = delegate.navController;

[rootViewController.view addSubview:self.view];

self.view.frame = CGRectOffset(self.view.frame, (winSize.width - self.view.frame.size.width) / 2, 0);

_isAddView = YES;

self.view.alpha = 0.0;

[UIView animateWithDuration:0.2 animations:^(void) {

self.view.alpha = 1.0;

}completion:^(BOOL finished) {

}];

// 親レイヤを監視し、サブレイヤ追加によりローディング画面が隠れる場合は最前面に配置し直す

[self.view.superview.layer addObserver:self forKeyPath:@"sublayers" options:0 context:SUPERVIEW_SUBVIEWS];

}


- (void)observeValueForKeyPath:(NSString *)keyPath

                      ofObject:(id)object

                        change:(NSDictionary *)change

                       context:(void *)context

{

    if (context == SUPERVIEW_SUBVIEWS) {

        [self.view.superview bringSubviewToFront:self.view];

    }

else {

[super observeValueForKeyPath:keyPath

ofObject:object

  change:change

  context:context];

}

}


@end