読者です 読者をやめる 読者になる 読者になる

イッツァハローワールド

恥さらしていこうかなとか。

手作りモーダルダイアログ(もどき)を作る

iOS

僕はできるだけ標準のUIつかいたいひとなのですがゲームを作るにあたり、全画面カスタムUIを作る必要性を(勝手に)迫られたのでした。

ペーパーなプロトタイプやモックを作って見た目を吟味するまでは楽しいんだけど、いざ作るとなると結構壁がありました。
そんで手間のかかることかかること...

モーダルダイアログとか関数一発呼べばいいやつを画面から作るとか...
はじめは、どうすんだこれってかんじでした。


この記事では手作りのモーダルダイアログ(もどき)を作ったやりかたを備忘録します。

やったこと

  • ダイアログ用のビューをこしらえる
  • ダイアログ用のビューはディスプレイいっぱい描画して、下のレイヤーを触れなくしておく
  • 親ビューからの呼び出して、親ビューにダイアログ用のビューを重ねる
  • ダイアログのボタン押下でビューを消す

イメージはこんな感じ
f:id:hanamiju:20150211025725p:plain

絵に悪意はない('A`)
注目すべきはダイアログの周りも描画しているところ。
黒の透過40%で塗りつぶしています。これで重ねると陰影で立体感がでるわけ。

こんくらいならそんなに頑張らなくてもできる。

画像作る

f:id:hanamiju:20150211031152p:plain

今回冗談でWindows8テイスト作った。(8つかったことない...)

クラスをつくる

ViewController ... 親ビューのViewController
CustomDialogViewController ... ダイアログのViewController

Story Boardつくる

f:id:hanamiju:20150211033625p:plain

とりあえずこんなかんじ。
親ビュー左上のパワーボタンが押したら「終了しますか?」ってダイアログでるようにします。
ダイアログのOK,キャンセルボタン,右上xボタン押したらダイアログを閉じます。
iPhoneの電源オフはしませんw)

ここで注意すべきは、ダイアログ側のレイヤー構成です。
CustomDialogViewControllerのviewを透過してしまうと全体が透過されてしまうのでダイアログの絵も透過されてしまいます。
なので以下のことをします。

  1. CustomDialogViewControllerのviewはClear Color(alphaは1.0)にする
  2. CustomDialogViewControllerのviewの下にUIViewを敷き、こいつを透過する。(今回はDark Gray Colorのalpha0.4)
  3. ↑の上のレイヤーにダイアログのイメージ以下ボタン等をおく(↑のビューの中に置いてしまうとやっぱり透過します。)


レイヤー構成はこんなん↓(TransparentViewと他が同階層なのに注目)
f:id:hanamiju:20150211041804p:plain
(拡大縮小するとレイアウトが崩れるのでもう一個viewを挟んだりしている。)


あとはCustomDialogViewControllerを選択してStoryBoardID振っておきます。
今回は"CustomDialog"で。
f:id:hanamiju:20150211034354p:plain

Outletをつくる

ダイアログのTitleとContentsラベルは親でいじれるようにOutletにします。
xボタンもOutletにして表示非表示を切り替えられるようにしてもいいかもしれません。

コードを書く

ダイアログビューから書きます。

// CustomDialogViewController.h
#import <UIKit/UIKit.h>

@interface CustomDialogViewController : UIViewController
- (void)setContents:(NSString*)contents;
- (void)setTitle:(NSString *)title;
@end

ダイアログに表示する文字列のアクセッサメソッドを書きます。

// CustomDialogViewController.m

#import "CustomDialogViewController.h"
@interface CustomDialogViewController ()
{
    NSString* _title;
    NSString* _contents;
}
@property (strong, nonatomic) IBOutlet UIButton *mExitButton;
@property (strong, nonatomic) IBOutlet UILabel *mTitleLabel;
@property (strong, nonatomic) IBOutlet UILabel *mContentsLabel;

- (IBAction)okButtonPushed:(id)sender;
- (IBAction)cancelButtonPushed:(id)sender;
- (IBAction)exitButtonPushed:(id)sender;

@end

@implementation CustomDialogViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _mTitleLabel.text = _title;
    _mContentsLabel.text = _contents;
    // Do any additional setup after loading the view.
}

// OKボタン押下
- (IBAction)okButtonPushed:(id)sender {
    [self closeDialog];
}

// キャンセルボタン押下
- (IBAction)cancelButtonPushed:(id)sender {
    [self closeDialog];
}

// xボタン押下
- (IBAction)exitButtonPushed:(id)sender {
    [self closeDialog];
}

// 内容文字列入力
- (void)setContents:(NSString*)contents {
    _contents = contents;
}

// タイトル文字列入力
- (void)setTitle:(NSString *)title {
    _title = title;
}

// フェードアウトしながら閉じる
- (void)closeDialog
{
    // 0.3秒かけてダイアログを透過する。0.3秒たったらVC,viewを削除する。
    [UIView animateWithDuration:0.3f
                          delay:0.0f
                        options:UIViewAnimationOptionCurveEaseInOut
                     animations:^{
                         self.view.alpha = 0.0f;
                     }
                     completion:^(BOOL finished) {
                         // このへんで結果の通知とかもやればいいよ。
                         [self removeFromParentViewController];
                         [self.view removeFromSuperview];
                     }];
}
@end
// ViewController.m

#import "ViewController.h"
#import "CustomDialogViewController.h"

@interface ViewController ()
- (IBAction)powerButtonPushed:(id)sender;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (IBAction)powerButtonPushed:(id)sender
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    CustomDialogViewController* vc = [storyboard instantiateViewControllerWithIdentifier:@"CustomDialog"];
    
    // ダイアログの内容を書く
    [vc setTitle:@"Shutdown"];
    [vc setContents:@"電源落とすでー。"];
    
    // ダイアログを重ねる
    [self addChildViewController:vc];
    [self.view addSubview:vc.view];
    
}

setTitle, setContentsではCustomDialogViewControllerの文字列メンバ変数にダイアログに表示する内容を入力しています。
Outletに直接やろうぜってノリになりたいところですが、setTitleの時点でViewがロードされていないのでOutletたちはまだnilです。ですので、一旦メンバ(_title, _contents)に退避してダイアログクラスのViewDidLoadでラベルにテキストをいれるっていう方法をとります。

実行結果

ボタン押下前
f:id:hanamiju:20150211043657p:plain

ボタン押下後
f:id:hanamiju:20150211043726p:plain

うん。windowsっぽいな(目的が違う)

まとめ

  • そこそこ簡単なコードでつくれた。でももっと簡単な方法あると思う。教えて。
  • 上記コードだと結果の通知はNotificationとかでやんないといけない。呼び出し側毎に結果の通知を別々にしたいなら、あらかじめダイアログクラスにmessageをsetしてあげるとかの処理が必要です。
  • 見た目に満足した。