LoginSignup
16
16

More than 5 years have passed since last update.

[iOS]UIAlertControllerで左寄せ/フォントサイズ変更/テキストカラー変更 (iOS11まで)

Last updated at Posted at 2016-09-06

重要(2018/10/13 追記)

更にコメントで情報を受けましたが、iOS12ではView構造が変わってしまって、このままのやり方では落ちてしまうようです。
iOS12の構造に合わせた形に修正すれば動くようになるとは思いますが、去年の追記のように、SubViewの構造を利用して辿っていくやり方自体があまりよろしくないのでしょうね(なので、iOS12に合わせた記事本文の修正・追記は行いません)。

もはや記事自体を残す意味もないかもしれませんが、一応、歴史の記録ということで(タイトルには「iOS11まで」とつけました)。

重要(2017/08/21 追記)

コメントで指摘を受けて気がついたのですが、UIAlertControllerのリファレンスには下記のように記載されており、本手法は(もともとダメだとわかっていた方法1に限らず)非推奨な方法である可能性が高いということが判明しました。

UIAlertController - UIKit | Apple Developer Documentation

Important
The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.

履歴を残すという意味と、私と同じ過ちを起こす可能性のある人を少しでも減らすためにも、記事そのものの削除は行いませんが、ご注意ください。
(今の所、この方法で作成した私のアプリはリジェクトされていませんが、今後もそうである保証はありません)。
私の確認不足で申し訳ありませんでした。

それにしても、一番肝心なオフィシャルリファレンスの確認が漏れているという詰めの甘さに、自分でがっかりです。。。

さて、ということで結局、本問題は未解決です。
今の所思いつくのは、この例(【Swift】カスタムUIAlertControllerを作ってみた)などを参考にUIAlertControllerを使わずに自前で作成するしかないのか、メッセージの本文だけであれば「IOS開発:UIALERTCONTROLLERにチェックボックスを付ける[OBJECTIVE-C]」や「【Xcode】UIAlertControllerで簡単進捗ダイアログ作成」のチェックボックスやインジゲーターと同じように独自に作ったUILabelをaddSubviewするか(Messageを空や改行にしてわざわざ別にUILabelを作ることになるというのが格好悪いですが)、とかですかね、、。

また良い方法が思いついたら記事にします。

はじめに

確認環境

XCode 7.3.1
iOS 8.1/iOS 9.3

普通に表示すると

    NSString *title = @"Title";
    NSString *message = @"UIAlertControllerで左寄せしたりフォントサイズを変更したりテキストカラーを変更したりするテスト";

    UIAlertController *alertController =
    [UIAlertController alertControllerWithTitle:title
                                        message:message
                                 preferredStyle:UIAlertControllerStyleAlert];
    // OK ボタンを表示する
    UIAlertAction *alertAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleCancel
                           handler:nil];
    [alertController addAction:alertAction];

    [vc presentViewController:alertController animated:YES completion:nil];

スクリーンショット 2016-08-28 22.03.30.png

最終ゴール

テキストが2行以上になると真ん中寄せって格好悪いですよね。
これを左寄せにしたい(フォントサイズとカラーはついで)と思ったのですが、プロパティ一発で済むかと思いきや、予想外に面倒臭かったのでメモ。

もともとやりたかったのは本文だけだったのですが、同じやり方でタイトルのカスタマイズも出来たり、ボタンの色の変更方法も調べられたので、最終的にここまで出来るようになりました(カスタマイズできるという例なのでデザイン性は無視してください).

スクリーンショット 2016-09-03 20.57.17.png

試行錯誤の軌跡

方法その1(非公開API)

最初に調べた方法。

    UIAlertController *alertController =
    [UIAlertController alertControllerWithTitle:title
                                        message:message
                                 preferredStyle:UIAlertControllerStyleAlert];
    // OK ボタンを表示する
    UIAlertAction *alertAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleCancel
                           handler:nil];
    [alertController addAction:alertAction];


    // ★★★ ここから追加 ★★★
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    [paragraphStyle setAlignment:NSTextAlignmentLeft];

    NSMutableAttributedString *messageText;

    messageText = [[NSMutableAttributedString alloc]
                   initWithString:message
                   attributes:@{
                                NSParagraphStyleAttributeName: paragraphStyle,
                                NSFontAttributeName : [UIFont systemFontOfSize:12],
                                NSForegroundColorAttributeName : [UIColor blueColor]
                                }
                   ];
    [alertController setValue:messageText forKey:@"attributedMessage"];
    // ★★★ 追加ここまで ★★★


    [vc presentViewController:alertController animated:YES completion:nil];

スクリーンショット 2016-08-28 22.03.59.png

完璧♪
って思ったら、Private APIだって、、、。ダメじゃん。。。

stackoverflow とか githubとかにも公開されていて、回答の評価も高いやり方のくせに。。。

方法その2

てことで、上記stackoverflowの回答を星が少ないのも読み込んで行って見つけた方法。
ネストがめちゃくちゃ深いですが、要は、Viewを辿って行ってメッセージを表示している(タイトルも同じやり方でやれるはず)UILabelのプロパティを直接設定してしまおうという方法。

    UIAlertController *alertController =
    [UIAlertController alertControllerWithTitle:title
                                        message:message
                                 preferredStyle:UIAlertControllerStyleAlert];
    // OK ボタンを表示する
    UIAlertAction *alertAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleCancel
                           handler:nil];
    [alertController addAction:alertAction];


    // ★★★ ここから追加 ★★★
    NSArray *viewArray = [[[[[[[[[[[[alertController view] subviews] firstObject] subviews] firstObject] subviews] firstObject] subviews] firstObject] subviews] firstObject] subviews];

    UILabel *alertTitle = viewArray[0];
    UILabel *alertMessage = viewArray[1];

    alertMessage.textAlignment = NSTextAlignmentLeft;
    alertMessage.textColor = [UIColor blueColor];
    alertMessage.font = [UIFont systemFontOfSize:20];
    // ★★★ 追加ここまで ★★★


    [vc presentViewController:alertController animated:YES completion:nil];

スクリーンショット 2016-08-29 0.04.35.png

ソースの分かりやすさはともあれ、これでいくはず!
、、、と思いいきや、左寄せはできたけど、フォントサイズ、色は変わらなかったなぁ。。。

どうやら、フォントと色の設定は、アラートを表示させた後(presentViewControllerを実行した後)に行う必要があるみたいです。

    NSArray *viewArray = [[[[[[[[[[[[alertController view] subviews] firstObject] subviews] firstObject] subviews] firstObject] subviews] firstObject] subviews] firstObject] subviews];

    UILabel *alertTitle = viewArray[0];
    UILabel *alertMessage = viewArray[1];


    [vc presentViewController:alertController animated:YES completion:nil];


    alertMessage.textAlignment = NSTextAlignmentLeft;
    alertMessage.textColor = [UIColor blueColor];
    alertMessage.font = [UIFont systemFontOfSize:12];

スクリーンショット 2016-08-28 22.03.59.png

これでちゃんと行きました♪

方法その2+α

ネストが深すぎて気持ち悪いのと(その代わり1行で書けるので一長一短だとは思いますが。どうせコピペで手で書くことはないだろうし)、今後の仕様変更でUIAlertControllerの構造が変わっても(ある程度)対応できるように、alertTitleとalertMessageの取得方法を動的にしてみる。
方法は、subviewを再帰的に辿ってUILabelを持つsubviewを返すメソッドを作って、それを使ってviewArrayを設定するようにする。
viewArrayの設定方法以外は方法その2と同じ。

- (NSArray *)viewArrayWhichHasUILabel:(UIView *)root {
    NSLog(@"%@", root.subviews);
    NSArray *_subviews = nil;

    for (UIView *v in root.subviews) {
        if ([v isKindOfClass:[UILabel class]]) {
            _subviews = root.subviews;
            return _subviews;
        }
        _subviews = [self viewArrayWhichHasUILabel:v];
        if (_subviews) {
            break;
        }
    }
    return _subviews;
}
    NSArray *viewArray = [self viewArrayWhichHasUILabel:[alertController view]];

    UILabel *alertTitle = viewArray[0];
    UILabel *alertMessage = viewArray[1];


    [vc presentViewController:alertController animated:YES completion:nil];


    alertMessage.textAlignment = NSTextAlignmentLeft;
    alertMessage.textColor = [UIColor blueColor];
    alertMessage.font = [UIFont systemFontOfSize:12];

スクリーンショット 2016-08-28 22.03.59.png
その2と同じ結果が得られました♪

問題発生 (iOS8では動かない)

......と思いきや問題発生。
ここまで iOS9.3 で確認していたのだけれども、念のため旧バージョンでも、と試してみたら 8.1 ではpresentViewControllerの後に設定していても、左寄せしか効いていなかった。。。
方法1であれば8.1でもちゃんと動いたけど、AppStoreにアップできないと意味がないしなぁ。。。

スクリーンショット 2016-08-29 0.04.35.png

結局のところ

結局、UIAlertControllerのサブクラスを作って、viewWillLayoutSubviews の中で設定を変更するようにすることで、iOS8.1/9.3どちらでも想定通りに動作するようになりました。設定の方法自体は今までと同じ。

CustomUIAlertController.h
- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];


    NSArray *viewArray = [self viewArrayWhichHasUILabel:[self view]];
    UILabel *alertTitle = viewArray[0];
    UILabel *alertMessage = viewArray[1];


    alertMessage.textAlignment = NSTextAlignmentLeft;
    alertMessage.textColor = [UIColor blueColor];
    alertMessage.font = [UIFont systemFontOfSize:12];
}

- (NSArray *)viewArrayWhichHasUILabel:(UIView *)root {
    NSLog(@"%@", root.subviews);
    NSArray *_subviews = nil;

    for (UIView *v in root.subviews) {
        if ([v isKindOfClass:[UILabel class]]) {
            _subviews = root.subviews;
            return _subviews;
        }
        _subviews = [self viewArrayWhichHasUILabel:v];
        if (_subviews) {
            break;
        }
    }
    return _subviews;
}
    NSString *title = @"Title";
    NSString *message = @"UIAlertControllerで左寄せしたりフォントサイズを変更したりテキストカラーを変更したりするテスト";

    CustomUIAlertController *alertController;    
    alertController = [CustomUIAlertController alertControllerWithTitle:title
                                        message:message
                                 preferredStyle:UIAlertControllerStyleAlert];

    // OK ボタンを表示する
    UIAlertAction *alertAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleCancel
                           handler:nil];
    [alertController addAction:alertAction];


    [vc presentViewController:alertController animated:YES completion:nil];

最終形

せっかくサブクラスにしたので、パラメータ化して汎用にして、ついでにタイトルやボタンの文字色変更にも対応してみました。

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

@interface CustomUIAlertController : UIAlertController

@property (nonatomic) UIColor *messageColor;
@property (nonatomic) UIFont  *messageFont;
@property (nonatomic) NSTextAlignment messageAlign;

@property (nonatomic) UIColor *titleColor;
@property (nonatomic) UIFont  *titleFont;
@property (nonatomic) NSTextAlignment titleAlign;

@property (nonatomic) UIColor *tintColor;


@end
-(CustomUIAlertController*)init{
    self = [super init];
    if(self)
    {
        _messageAlign = NSTextAlignmentCenter;
        _messageColor = nil;
        _messageFont = nil;

        _titleAlign = NSTextAlignmentCenter;
        _titleColor = nil;
        _titleFont = nil;

        _tintColor = nil;
    }
    return self;
}


- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];


    NSArray *viewArray = [self viewArrayWhichHasUILabel:[self view]];
    UILabel *alertTitle = viewArray[0];
    UILabel *alertMessage = viewArray[1];


    alertTitle.textAlignment = _titleAlign;
    if(_titleColor)
    {
        alertTitle.textColor = _titleColor;
    }
    if(_titleFont)
    {
        alertTitle.font = _titleFont;
    }



    alertMessage.textAlignment = _messageAlign;
    if(_messageColor)
    {
        alertMessage.textColor = _messageColor;
    }
    if(_messageFont)
    {
        alertMessage.font = _messageFont;
    }


    if(_tintColor)
    {
        self.view.tintColor = _tintColor;
    }
}

- (NSArray *)viewArrayWhichHasUILabel:(UIView *)root {
    NSLog(@"%@", root.subviews);
    NSArray *_subviews = nil;

    for (UIView *v in root.subviews) {
        if ([v isKindOfClass:[UILabel class]]) {
            _subviews = root.subviews;
            return _subviews;
        }
        _subviews = [self viewArrayWhichHasUILabel:v];
        if (_subviews) {
            break;
        }
    }
    return _subviews;
}
    NSString *title = @"Title";
    NSString *message = @"UIAlertControllerで左寄せしたりフォントサイズを変更したりテキストカラーを変更したりするテスト";

    //UIAlertController *alertController;
    CustomUIAlertController *alertController;


    alertController = [CustomUIAlertController alertControllerWithTitle:title
                                        message:message
                                 preferredStyle:UIAlertControllerStyleAlert];
    // OK ボタンを表示する
    UIAlertAction *alertAction =
    [UIAlertAction actionWithTitle:@"OK"
                             style:UIAlertActionStyleCancel
                           handler:nil];
    [alertController addAction:alertAction];


    // タイトルの表示設定
    alertController.titleAlign = NSTextAlignmentRight;
    alertController.titleFont = [UIFont boldSystemFontOfSize:32];
    alertController.titleColor = [UIColor redColor];

    // 本文の表示設定
    alertController.messageAlign = NSTextAlignmentLeft;
    alertController.messageFont = [UIFont systemFontOfSize:12];
    alertController.messageColor = [UIColor blueColor];

    // ボタン色設定
    alertController.tintColor = [UIColor greenColor];


    [vc presentViewController:alertController animated:YES completion:nil];

スクリーンショット 2016-09-03 20.57.17.png

参考

16
16
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
16