【ObjectiveC】（indicator）インジケーターをハンドリングして表示させるには？

## はじまり

どうやら用意していたインジケーターの作りが悪く、サブスレッドで動くような書き方になってた。。。どないしよう？？


### iOS13 以降からの挙動がおかしい
個人的にインジケーターなどのビューをiOS13 から mainスレッドしか動かないようになってしまい、落ちるようなことが発生しましたので、以下のように、アクションが発生（ボタンを押したり、イベントが発生したり）したところに簡易的に handerを挟み込みをおこなうことでできないかと作ってみました。

> iOS13 からなのかは、他の方かのご指摘や根拠となるサイトなどがあれば記事などをご紹介いただきたい。

##### iOS13 以降のサブスレッドで書いてしまっていたなどで困った不具合が起きた時の対応

> 一番ベースのビューコントローラークラスに　以下のメソッドを追加

## 動作環境
> Xcode 11.3
> iOS 13.3

#### ObjectiveCの場合

以下はあくまで、どの画面にも書きたい場合に設定をする

~~~ObjectiveC:BaseLoadViewController.h

@interface BaseLoadViewController : UIViewController

#pragma mark - UIActivityIndicatorView
/// 一番手前にあるフロントビュー
@property (retain,nonatomic) UIView *loadingFrontView;
/// インジケーターなどを乗せるベースビュー
@property (retain,nonatomic) UIView *loadingBaseView;
/// インジケーターなど
@property (retain,nonatomic) UIActivityIndicatorView *activityIndicatorView;

// 基本的なライフサイクル
/// ロードビューの生成
- (void)createLoadView;
/// ロードビューのアニメーション開始
- (void)loadViewStartAnimating;
/// ロードビューのアニメーション終了
- (void)loadViewStopAnimating;
/// ロードビューを取り除く
- (void)removeLoadView;

// 表題にあるインジケーターをハンドリングして表示させる方法
/// ロードビューのイベント発生時の開始処理 * ロードビューを終了するタイミングを指定する必要があります。
- (void)loadViewStartAnimatingHandler:(dispatch_block_t)nextHandler;

// 簡易的にイベント発生時のロードビューのサイクルを行う方法
/// イベント発生時のロードビューの開始および終了処理
- (void)loadViewEventHandler:(dispatch_block_t)eventHandler;

@end 

~~~

~~~ObjectiveC:BaseLoadViewController.m
#import "BaseLoadViewController.h"

@implementation BaseLoadViewController

#pragma mark - UIActivityIndicatorView
@synthesize loadingFrontView;
@synthesize loadingBaseView;
@synthesize activityIndicatorView;

/// 表示準備
- (void)viewDidLoad {
    [super viewDidLoad];
}

/// 表示直前
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (loadingFrontView == nil) {
        [self createLoadView];
    }
}

/// 表示直後
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}

/// 非表示直前
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

/// 非表示直後
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self removeLoadView];

}
/// ロードビューの生成：準備
- (void)createLoadView {
    // 一番上のフロントビューがある場合は、一度、削除しておく
    if (loadingFrontView != nil) {
        [self removeLoadView];
    }

    /***
     * 表示したいビューを用意する部分
     ***/
    // 表示するタッチイベントが発生できなくする（黒：透明度0.3 程度）のフロントビューを生成
    if(loadingFrontView == nil) {
        loadingFrontView = [[UIView alloc] initWithFrame:self.view.bounds];
    }

    // フロントビューがメインのビューに表示させる（ない場合とある場合で切り分ける）
    if (![self.view.subviews containsObject:loadingFrontView]) {
        // もしない場合は、追加する
        [self.view addSubview:loadingFrontView];
    } else {
        // もしある場合は、一番上に配置
        [self.view bringSubviewToFront:loadingFrontView];
    }

    // フロントビューが何らかの原因でα値は1.0に設定
    loadingFrontView.alpha = 1.0;
    
    // インジケーターを表示するベースビューを生成（必要がない場合もあります）
    if (loadingBaseView == nil) {
        loadingBaseView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 180 , 180)];
        loadingBaseView.layer.cornerRadius = 20.0;
        loadingBaseView.layer.borderColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8].CGColor;
        loadingBaseView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
        loadingBaseView.hidden = NO;
    }

    // ベースビューをフロントのビューに表示させる（ない場合とある場合で切り分ける）
    if (![loadingFrontView.subviews containsObject:loadingBaseView]) {
        [loadingFrontView addSubview:loadingBaseView];
    } else {
        // もしある場合は、一番上に配置
        [loadingFrontView bringSubviewToFront:loadingBaseView];
    }

    // ベースビューが何らかの原因でα値は1.0に設定
    loadingBaseView.alpha = 1.0;
    
    // インジケーターを生成（ここは、テキストを載せたり、メーターなどをカスタマイズしてもいい。ここでは、インジケーターを作成している）
    if(activityIndicatorView == nil) {
        activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
        activityIndicatorView.hidden = NO;
    }

    // 上記のビューをベースのビューに表示させる（ない場合とある場合で切り分ける）
    if (![loadingBaseView.subviews containsObject:activityIndicatorView]) {
        [loadingBaseView addSubview:activityIndicatorView];
    } else {
        // もしある場合は、一番上に配置
        [loadingBaseView bringSubviewToFront:activityIndicatorView];
    }

    // 上記のビューが何らかの原因でα値は1.0に設定
    activityIndicatorView.alpha = 1.0;

    /***
     * 位置やサイズを指定する部分
     * 上記に用意したビューをどこに表示するのか位置の設定
     ***/
    // 親のビューのサイズを基準にします。
    CGSize size = self.view.bounds.size;
    
    /***
     * もし、ナビゲーションビューがある画面の場合は、そのナビゲーションに
     * ビューを書くことができます。以下の書き方が少し変わります。
     ***/
    CGRect frame = self.view.bounds;
    frame.size.width = size.width;
    frame.size.height = size.height;
    // フロントビューの表示位置・サイズを設定
    [loadingFrontView setFrame:frame];

    // フロントビューの背景色の設定（backgroundColorでαを設定
    // ＊alpha で指定すると全体の色が透明感が出ます
    [loadingFrontView setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0.2]];
    
    // ステータスバーの高さの初期値を設定
    CGFloat statusBarHeight = 0;
    // ナビゲーションの高さの初期値を設定
    CGFloat navigationBarHeight = 0;

    // ナビゲーションコントローラーがある場合は、以下のように取得することができます。
    if (self.navigationController) {
        statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
        navigationBarHeight = 55;
    }
    /// 上記で用意した各ビューの高さ・幅を用意しておいて
    CGFloat loadingBaseWidth = loadingBaseView.frame.size.width;
    CGFloat loadingBaseHeight = loadingBaseView.frame.size.height;
    CGFloat indicatorWidth = activityIndicatorView.frame.size.width;
    CGFloat indicatorHeight = activityIndicatorView.frame.size.height;
    
    /// インジケーターの表示位置
    // 基本的にインジケーターはベースとなるビューの高さとインジケーターの高さを1/2にすることでセンターの位置が決まります。
    // ＊今回の場合、baseView.center = indicator.center でもよい。 その他、ボタンやメーターのような特殊なビューを載せる場合は、xibファイルで作ってもいいかも
    [activityIndicatorView setFrame:CGRectMake((loadingBaseWidth - indicatorWidth)/2,(loadingBaseHeight - indicatorHeight)/2, indicatorWidth, indicatorHeight)];
    /// ベースビューの表示位置
    // フロントビューのxのセンターの位置の設定
    // フロントビューのyのセンターの位置にナビゲーションがある場合とない場合とで高さの調整が必要
    // ＊baseView.center = indicator.center でもよい
    loadingBaseView.frame = CGRectMake((loadingFrontView.frame.size.width - loadingBaseWidth) / 2,
                                       (loadingFrontView.frame.size.height - loadingBaseHeight) / 2 - statusBarHeight - navigationBarHeight,
                                       loadingBaseWidth,
                                       loadingBaseHeight);
    /// インジケーターの色は、白に設定
    activityIndicatorView.color = [UIColor whiteColor];
    /// 準備前はフロントビューを非表示にしておくことで、インジケーターは見えなくなります。
    loadingFrontView.hidden = YES;
}

/// アニメーション（ローディング開始）
- (void)loadViewStartAnimating {
    /// インジケーターが表示されていない場合
    if (loadingFrontView.hidden == YES) {
        /// もし作成されてなければ生成、あれば位置の再調整
        [self createLoadView];
    }
    /// フロントビューを表示にする
    loadingFrontView.hidden = NO;

    /// アニメーションが止まっていればインジケーターを開始する
    if (![activityIndicatorView isAnimating]) {
        [activityIndicatorView startAnimating];
    }
}

/// アニメーション（ローディング終了）
- (void)loadViewStopAnimating {
    /// アニメーションが動いていれば、インジケーターを停止する
    if ([activityIndicatorView isAnimating]) {
        [activityIndicatorView stopAnimating];
    }
    /// フロントビューを非表示にする
    loadingFrontView.hidden = YES;
}

/// 外部呼び出し用：アニメーションを開始したのちにイベントをハンドリングする
- (void)loadViewStartAnimatingHandler:(dispatch_block_t)nextHandler {
    [self loadViewStartAnimating];

    if ([NSThread isMainThread]) {
        if (nextHandler) nextHandler();
    } else {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (nextHandler) nextHandler();
        });
    }
} // この場合は、ストップ処理をハンドリング側に設定をする必要がある。

/// 外部呼び出し用：アニメーションを開始し、イベントをハンドリングした後にアニメーションを終了する
- (void)loadViewEventHandler:(dispatch_block_t)eventHandler {
    [self loadViewStartAnimatingHandler:^{
        if (eventHandler) {
            eventHandler();
        }
        [self loadViewStopAnimating];
    }];
} // この場合は、アラートコントローラーなどの処理で、ハンドリング処理を行うような場合やストップ処理をハンドリング側に設定をする必要がある。

/// ロードビューを取り除く
- (void)removeLoadView {
    /// アニメーションを終了させる
    [self loadViewStopAnimating];

    /// インジケーターを取り除く
    if (activityIndicatorView != nil) {
        if ([loadingBaseView.subviews containsObject:activityIndicatorView]) {
            [activityIndicatorView removeFromSuperview];
        }
        activityIndicatorView = nil;
    }

    /// ベースビューを取り除く
    if (loadingBaseView != nil) {
        if ([loadingFrontView.subviews containsObject:loadingBaseView]) {
            [loadingBaseView removeFromSuperview];
        }
        loadingBaseView = nil;
    }
    
    /// フロントビューを取り除く
    if (loadingFrontView != nil) {
        if ([self.view.subviews containsObject:loadingFrontView]) {
            [loadingFrontView removeFromSuperview];
        }
        loadingFrontView = nil;
    }
}

@end 

~~~


呼び出す側のViewController

~~~ObjectiveC:HogeViewController.h
#import "BaseLoadViewController.h"

@interface HogeViewController : BaseLoadViewController

@end

~~~

~~~ObjectiveC:HogeViewController.m
#import "BaseLoadViewController.h"

@implementation BaseLoadViewController

- (IBAction)sampleButtonAction:(id)sender {
   [self loadViewEventHandler:^{
   // イベント処理
   }];
}

@end
~~~

シンプルに書き換えることができるけども、変更箇所が多いとなかなか大変名作業かもですね。


## 関連記事

【About】(http://qiita.com/sunstripe) - サンストライプ

---

## 制作チーム：サンストライプ

[![sunstripe_logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80600/322f2d60-f19d-af9e-2b6f-7290ca546848.png)](http://sunstripe.main.jp/)
http://sunstripe.main.jp/

(月１WEBコンテンツをリリースして便利な世の中を作っていくぞ！！ボランティアプログラマー/デザイナー/イラストレーター/その他クリエイター声優募集中！!)

### 地域情報 THEメディア
THE メディア 地域活性化をテーマに様々なリリース情報も含め、記事をお届けしてます！！
https://the.themedia.jp/

### ゼロからはじめる演劇ワークショップ
多様化の時代に向けて他者理解を鍛える

https://workshop.themedia.jp/

### プログラミングワークショップ・ウェブ塾の開講！！！
様々なテーマでプログラミングに囚われずに取り組んでいきます。
詳しくはこちら　↓　↓　↓　↓　↓　↓　↓　↓
[プログラミングサロン 月１だけのプログラミング学習塾](https://workshop.themedia.jp/posts/7057553?fbclid=IwAR32CfRQ7RvDOhvuogoSwqxDLMOU7Jvuzljlnx-TMc27qBPX1BnWsfLcBR8
)

### 協力応援 / 支援者の集い
## チーム：サンストライプ
#### プログラミングラボ
https://twitter.com/sunstripe2011

一緒にポートフォリオを作りませんか？現場の体験やそれぞれの立場から年齢関係なく作品を作りたい方々と一緒にチームを作って、作品を作っています。現場に行きたい人には、職場紹介や職場の体験や悩み相談なども受けております。
様々な職種からプログラミングの知識を得たい、デザインの知識を得たい、データーベースの知識を得たいという人が集まっております。
週１のミーティングにそれぞれの近況と作業報告して、たまにリモート飲み会などをしております！！

興味がある方は、DMに話しかけてみてください。

## トラストヒューマン
http://trusthuman.co.jp/
私たちは何よりも信頼､人と考えてます｡

｢コンサルティング｣と｢クリエイティブ｣の両角度から｢人材戦略パートナー｣としてトータル的にサポートします！！

キャリア教育事業
広域学習支援プラットフォーム『のびのび日和』
https://slc-lab.amebaownd.com/

#スポンサー募集


## ネリム
https://nerim.co.jp/
配信事業などを映像コンテンツなどの制作しております

## ビヨンドXプロジェクト

[ビヨンドXプロジェクト全体会議](https://www.youtube.com/playlist?list=PLaR6-YWiGWkYhEHWnt8f1z-JC0g3nwwC9)