Objective-Cでカスタムプロモーションダイアログを実装する

概要

アプリ内で期間限定キャンペーンや告知を表示するための、背景を半透明にし中央に画像を配置するモーダルダイアログをObjective-Cで作成します。画像タップでURLへ遷移し、✕ボタンで閉じる仕様とし、トランジションアニメーションもカスタマイズします。

1. ダイアログ本体

ヘッダファイル:CampaignDialogViewController.h

#import <UIKit/UIKit.h>

@interface CampaignDialogViewController : UIViewController <UIViewControllerTransitioningDelegate>

- (instancetype)initWithBannerImage:(UIImage *)banner
                        actionURL:(NSString *)url;

- (void)closeDialog;

@property (copy, nonatomic) NSString *actionURL;

@end

実装ファイル:CampaignDialogViewController.m

#import "CampaignDialogViewController.h"
#import "FadeTransitionAnimator.h"

@interface CampaignDialogViewController ()

@property (strong, nonatomic) UIImageView *bannerView;
@property (strong, nonatomic) UIButton *closeBtn;

@end

@implementation CampaignDialogViewController

- (instancetype)initWithBannerImage:(UIImage *)banner actionURL:(NSString *)url {
    self = [super init];
    if (self) {
        _actionURL = url;
        
        // 背景
        self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.6];
        
        // バナー画像
        CGFloat ratio = banner.size.height / banner.size.width;
        CGFloat w = [self scaled:300]; // 画面幅に応じたスケーリング
        CGFloat h = w * ratio;
        
        _bannerView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, w, h)];
        _bannerView.image = banner;
        _bannerView.contentMode = UIViewContentModeScaleAspectFill;
        _bannerView.layer.cornerRadius = 8;
        _bannerView.clipsToBounds = YES;
        _bannerView.center = self.view.center;
        [self.view addSubview:_bannerView];
        
        // 閉じるボタン
        _closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [_closeBtn setImage:[UIImage imageNamed:@"ic_close"] forState:UIControlStateNormal];
        [_closeBtn addTarget:self action:@selector(closeDialog) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:_closeBtn];
        
        // Auto Layout
        _closeBtn.translatesAutoresizingMaskIntoConstraints = NO;
        [NSLayoutConstraint activateConstraints:@[
            [_closeBtn.widthAnchor constraintEqualToConstant:[self scaled:28]],
            [_closeBtn.heightAnchor constraintEqualToAnchor:_closeBtn.widthAnchor],
            [_closeBtn.trailingAnchor constraintEqualToAnchor:_bannerView.trailingAnchor constant:-8],
            [_closeBtn.topAnchor constraintEqualToAnchor:_bannerView.topAnchor constant:8]
        ]];
        
        // タップジェスチャー
        _bannerView.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBannerTap:)];
        [_bannerView addGestureRecognizer:tap];
    }
    return self;
}

- (CGFloat)scaled:(CGFloat)value {
    return value * [UIScreen mainScreen].bounds.size.width / 375.0;
}

- (void)handleBannerTap:(UITapGestureRecognizer *)gesture {
    if (self.actionURL.length) {
        [self closeDialog];
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.actionURL]
                                       options:@{}
                             completionHandler:nil];
    }
}

- (void)closeDialog {
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - UIViewControllerTransitioningDelegate

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source {
    FadeTransitionAnimator *animator = [[FadeTransitionAnimator alloc] init];
    animator.presenting = YES;
    return animator;
}

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    FadeTransitionAnimator *animator = [[FadeTransitionAnimator alloc] init];
    animator.presenting = NO;
    return animator;
}

@end

2. トランジションアニメーター

FadeTransitionAnimator.h

#import <UIKit/UIKit.h>

@interface FadeTransitionAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) BOOL presenting;
@end

FadeTransitionAnimator.m

#import "FadeTransitionAnimator.h"

@implementation FadeTransitionAnimator

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.25;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    UIView *container = transitionContext.containerView;
    
    if (self.presenting) {
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        toVC.view.alpha = 0;
        [container addSubview:toVC.view];
        
        [UIView animateWithDuration:[self transitionDuration:transitionContext]
                         animations:^{
                             toVC.view.alpha = 1;
                         }
                         completion:^(BOOL finished) {
                             [transitionContext completeTransition:YES];
                         }];
    } else {
        UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        
        [UIView animateWithDuration:[self transitionDuration:transitionContext]
                         animations:^{
                             fromVC.view.alpha = 0;
                         }
                         completion:^(BOOL finished) {
                             [fromVC.view removeFromSuperview];
                             [transitionContext completeTransition:YES];
                         }];
    }
}

@end

3. 使用例

CampaignDialogViewController *dialog = [[CampaignDialogViewController alloc]
                                        initWithBannerImage:[UIImage imageNamed:@"summer_sale"]
                                        actionURL:@"https://example.com/campaign"];
dialog.modalPresentationStyle = UIModalPresentationOverFullScreen;
dialog.transitioningDelegate = dialog;
[self presentViewController:dialog animated:YES completion:nil];

このコードをViewControllerの適切なタイミング(例:viewDidAppear内で初回起動判定)で呼び出すことで、ユーザーに対してキャンペーン情報を効果的に提示できます。

タグ: Objective-C iOS UIKit ModalPresentation UIViewControllerTransitioningDelegate

6月16日 19:30 投稿