20200515のiOSに関する記事は5件です。

LaunchScreenの画像をVector(PDF)画像に置き換えてもアプリ起動時の画像が変わらなかった問題

結論

方法1

  • LaunchScreenに使っているAssetsの画像名を変更する

方法2

  • cacheを削除するコードを呼び出す(参考元)
    • 試してないので実際に効果あるかは未確認
filename.swift
import UIKit

public extension UIApplication {
    func clearLaunchScreenCache() {
        do {
            try FileManager.default.removeItem(atPath: NSHomeDirectory()+"/Library/SplashBoard")
        } catch {
            print("Failed to delete launch screen cache: \(error)")
        }
    }
}

ことの始まり

iOSのアプリ起動時に表示されるLaunchScreenだが、多くはアプリやブランド、企業のロゴ画像を表示しているだろう。

このロゴ画像が何かしらの理由で差し替えが必要になった際、単純にAssetsの画像を入れ替えるだけでは上手くいかないことがあった話である。

今回ロゴ画像にノイズが乗っており見栄えが悪かったので、ノイズのない画像をVector画像で用意してもらった。

そして単純にAssetsでファイルを入れ替えを行ったところ、アプリ起動時の画面ではなぜか古い画像が表示されたのである。

原因

このLaunchScreenで使用しているロゴ画像は、アプリの別画面でも使用していたので、そちらを確認したがそちらは正しく新しい画像が表示されていた。

結構前にもLaunchScreenの画像の表示が上手くいかないことがあったのを記憶していたこともあって、原因に思い当たる節があった。

それは… キャッシュ(cache) である。

調査

cacheなら期限とか時間置けば消えるだろうと思って大雑把に日付を進めてはアプリを起動して画像が新旧どちらが表示されるか確認したところ、

  • 約30日後 => cacheが残っている
  • 約60日後 => cacheが消えている

ロゴ画像を差し替えたいのに、実際にそのipaをリリースしてからcacheが消えるまで悠長に待つ開発者なんているだろうか?あほなの?



ならば、どうやったら即時反映されるか、弄り回した結果、どうもAssetsの画像名でcacheしているらしくAssetsの画像名を変更するがお手軽かつ最速だと判断した。


おまけでcacheなら意図的消してしまえばいいのでは?と思い調べたところcacheを消す話を書いているサイト(結論の方法2の参考元)を見つけたが、cacheがあることで高速起動している点もあるだろうことから、コードでcache消すのはあまりいい感じがしなかったので止めた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】バージョン判定およびアップデートの処理

UpdateStatus.m
typedef NS_OPTIONS(NSInteger, UPDATE_STATUS) {
    UPDATE_NONE                 = 0,  // 変更なし
    UPDATE_ANY,                       // 任意
    UPDATE_CONFIRM,                   // 確認
    UPDATE_FORCED,                    // 強制
};

はい!

これ以上に種類があれば追加していきます。

はじめに

アプリをリリースしていくとアップデートというのが付きっきりになっていきます。
毎回、現場でアップデートの切り出しで処理を書いていくのですがこれを定めておきたい!
という願望もありつつここに書いていきます。

アップデートの種類

  • 強制アップデート
  • 確認アップデート
  • 任意アップデート
  • 変更なし

この種類なのかなと思っております。

用語の整理

強制アップデート

強制的にアップデートを行う時に用います。

用途としては、DB(データベース)が大幅に書き換わった場合やアプリ自体が大幅に変わった場合にする方法です。

確認アップデート

アップデートを確認をした後に処理を行います。
強制アップデートよりは強制力がない。
特に、バージョンにより機能が使えないページもしくは機能が存在する場合はこれを用いる。

用途としては、機能がバージョンにより変わるアプリの場合、元々のユーザーがいた場合、アプリを利用できるようにする方法です。

任意アップデート

確認アップデートよりもそんなに必要がない時に用います。
特に、色の変更やフォントの変更などの細かな修正がある場合などに用います。

用途としては、小さなバグの回収などで機能が少し変わる場合、毎回アップデートをする必要がない場合は、ユーザーの意思でアプリを利用できるようにする方法です。

変更なし

今のバージョンとアプリのバージョンが同じ場合です。

用途としては、特に書く必要がないですが、何も行わずに次の処理に移行できます。

注意

そして、ここでは、ビルドバージョンを持って切り分けることを前提に書いております。
他にいい方法があったら教えてください。

今のビルドバージョンが 1.0.0 だったとして、ネットワークや何かのデータベースで強制的にバージョンアップをして欲しいという時にこの処理を書いていきます。

今回提案したいのは、

1.2.3 とした時・・・

  • 1つ目の1.の部分を 強制アップデート
  • 2つ目の2.の部分を 確認アップデート
  • 3つ目の3.の部分を 任意アップデート
  • つ目以降がある場合も任意アップデート
  • それ以外、全てが同じだった場合は、アップデートなし

として書き出した場合は以下のようになります。

UpdateStatus.h
@property (retain,nonatomic) NSDictionary *selectForceUpdateInfo;

UpdateStatus.m
/// アップデートステータスを取得する
- (UPDATE_STATUS)getUpdateStatus {
    /// アップデートの初期設定:アップデートなし
    UPDATE_STATUS updateStatus = UPDATE_NONE;
    NSString *requiredVersion = self.selectForceUpdateInfo[@"required_version"];
    NSArray *requiredVersions = [requiredVersion componentsSeparatedByString:@"."];
    // バージョン:Version
    // NSString *shortVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
    // NSArray *shortVersions = [shortVersion componentsSeparatedByString:@"."];
    // ビルドバージョン:Build
    NSString *bundleVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
    NSArray *bundleVersions = [bundleVersion componentsSeparatedByString:@"."];
    if ([requiredVersions count] > 0) {
        if ([requiredVersions[0] integerValue] == [bundleVersions[0] integerValue]) {
            if ([requiredVersions[1] integerValue] == [bundleVersions[1] integerValue]) {

                if ([requiredVersions[2] integerValue] == [bundleVersions[2] integerValue]) {
                    if ([requiredVersion isEqualToString:bundleVersion]) {

                    } else {
                        // 任意アップデート
                        updateStatus = UPDATE_ANY;
                    }
                } else {
                    // 任意アップデート
                    updateStatus = UPDATE_ANY;
                }
            } else {
                // 確認アップデート
                updateStatus = UPDATE_CONFIRM;
            }
        } else {
            // 強制アップデート
            updateStatus = UPDATE_FORCED;
        }
    }

    return updateStatus;
}

このメソッドから「UPDATE_STATUS」を元に、UIAlertController でアップデートの切り分けをしたり、アップデート画面を表示したり、ログイン画面に戻ったりというだし訳を行うことができます。

いろんな現場でもそのバージョンの設定があったり、Androidでは表現できなかったりすることもありますので、言い切れないですがこういうのがあったなって思い出してもらえたら幸いです。

ぜひ試してみてください!

履歴

・【iOS】バージョン判定およびアップデートの処理

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】バージョン判定およびアップデートの処理の定義

UpdateStatus.m
typedef NS_OPTIONS(NSInteger, UPDATE_STATUS) {
    UPDATE_NONE                 = 0,  // 変更なし
    UPDATE_ANY,                       // 任意
    UPDATE_CONFIRM,                   // 確認
    UPDATE_FORCED,                    // 強制
};

はい!

これ以上に種類があれば追加していきます。

はじめに

アプリをリリースしていくとアップデートというのが付きっきりになっていきます。
毎回、現場でアップデートの切り出しで処理を書いていくのですがこれを定めておきたい!
という願望もありつつここに書いていきます。

アップデートの種類

  • 強制アップデート
  • 確認アップデート
  • 任意アップデート
  • 変更なし

この種類なのかなと思っております。

用語の整理

強制アップデート

強制的にアップデートを行う時に用います。

用途としては、DB(データベース)が大幅に書き換わった場合やアプリ自体が大幅に変わった場合にする方法です。

確認アップデート

アップデートを確認をした後に処理を行います。
強制アップデートよりは強制力がない。
特に、バージョンにより機能が使えないページもしくは機能が存在する場合はこれを用いる。

用途としては、機能がバージョンにより変わるアプリの場合、元々のユーザーがいた場合、アプリを利用できるようにする方法です。

任意アップデート

確認アップデートよりもそんなに必要がない時に用います。
特に、色の変更やフォントの変更などの細かな修正がある場合などに用います。

用途としては、小さなバグの回収などで機能が少し変わる場合、毎回アップデートをする必要がない場合は、ユーザーの意思でアプリを利用できるようにする方法です。

変更なし

今のバージョンとアプリのバージョンが同じ場合です。

用途としては、特に書く必要がないですが、何も行わずに次の処理に移行できます。

注意

そして、ここでは、ビルドバージョンを持って切り分けることを前提に書いております。
他にいい方法があったら教えてください。

今のビルドバージョンが 1.0.0 だったとして、ネットワークや何かのデータベースで強制的にバージョンアップをして欲しいという時にこの処理を書いていきます。

今回提案したいのは、

1.2.3 とした時・・・

  • 1つ目の1.の部分を 強制アップデート
  • 2つ目の2.の部分を 確認アップデート
  • 3つ目の3.の部分を 任意アップデート
  • つ目以降がある場合も任意アップデート
  • それ以外、全てが同じだった場合は、アップデートなし

として書き出した場合は以下のようになります。

UpdateStatus.h
@property (retain,nonatomic) NSDictionary *selectForceUpdateInfo;

UpdateStatus.m
/// アップデートステータスを取得する
- (UPDATE_STATUS)getUpdateStatus {
    /// アップデートの初期設定:アップデートなし
    UPDATE_STATUS updateStatus = UPDATE_NONE;
    NSString *requiredVersion = self.selectForceUpdateInfo[@"required_version"];
    NSArray *requiredVersions = [requiredVersion componentsSeparatedByString:@"."];
    // バージョン:Version
    // NSString *shortVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
    // NSArray *shortVersions = [shortVersion componentsSeparatedByString:@"."];
    // ビルドバージョン:Build
    NSString *bundleVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
    NSArray *bundleVersions = [bundleVersion componentsSeparatedByString:@"."];
    if ([requiredVersions count] > 0) {
        if ([requiredVersions[0] integerValue] == [bundleVersions[0] integerValue]) {
            if ([requiredVersions[1] integerValue] == [bundleVersions[1] integerValue]) {

                if ([requiredVersions[2] integerValue] == [bundleVersions[2] integerValue]) {
                    if ([requiredVersion isEqualToString:bundleVersion]) {

                    } else {
                        // 任意アップデート
                        updateStatus = UPDATE_ANY;
                    }
                } else {
                    // 任意アップデート
                    updateStatus = UPDATE_ANY;
                }
            } else {
                // 確認アップデート
                updateStatus = UPDATE_CONFIRM;
            }
        } else {
            // 強制アップデート
            updateStatus = UPDATE_FORCED;
        }
    }

    return updateStatus;
}

このメソッドから「UPDATE_STATUS」を元に、UIAlertController でアップデートの切り分けをしたり、アップデート画面を表示したり、ログイン画面に戻ったりというだし訳を行うことができます。

いろんな現場でもそのバージョンの設定があったり、Androidでは表現できなかったりすることもありますので、言い切れないですがこういうのがあったなって思い出してもらえたら幸いです。

ぜひ試してみてください!

履歴

強制バージョンアップの作り方議論
【iOS】バージョン判定およびアップデートの処理の定義
【iOS】アプリ内にアップデート機能を導入する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「はじめてのDart言語[基礎文法1]」 ~私文男子の格闘第6話~

はじめに

(2020/5/15投稿)
最近iPad Proを購入し、非常に作業効率や勉強体験が変わって満足しています、私文男子です。

本日は前回紹介したDart Padを使って、簡単なコードを走らせてみるということをやってみたいと思います!
プログラミングの基礎中の基礎である

・Hello World
・算術演算
・変数/代入
・if文
・for文
・おまけのfizzbuzz

を紹介したいと思っています。

Dart言語とは?や、Dart Padって何?という部分は

はじめてのDart言語(0) ~私文男子の格闘第5話~

を参考にしてもらえるとお分かりいただけるかと思います。
それではやっていきましょう!
image.png

Hello World!

プログラミングにおける1丁目1番地とも言える「Hello World」
Hello WorldとはコンソールにHello Worldという文字列を出力します。その場合にはprintという指令を出します。
1回やってみましょう!

void main() {
  print("Hello World");
}
-----------------------------
///出力
Hello World

出力できたでしょうか?
ポイントとしては
・文字の出力にはprint()を使う
・文字列を扱うときはダブルクオーテーションで囲う("文字列")
・指令の最後にはセミコロンをつける(;)
でしたー!

算術演算

演算子に慣れる

プログラムにおいては簡単な計算もできます。
実際の数学と記号が違うことがあるので、違いを意識しながら学びましょう。

void main() {
  print(2+3); ///3+2=5(足し算)
  print(3-2); ///3-2=1(引き算)
  print(3*2); ///3×2=6(かけ算)
  print(6/2); ///6÷2=3(割り算)
  print(7%3); ///8÷3=2,,,1(剰余算)
}
-----------------------------
///出力
5
1
6
3
1

演算子を使ってうまくできましたでしょうか?
ポイントとしては
・実際の数学での×と÷*と/に置き換える必要がある。
割ったあまりを出力する演算子として%を用いる

優先順位

実際に計算するときはもっと複雑な場合が想定されます。
そこで演算子の優先順位や、かっこの扱いについて確認しておきます。

void main() {
  print(2 * 3 + 4 + 5); ///複数の演算子を使えるよ
  print(4 + 2 * 3 + 5); ///かけ算が優先されるよ
  print(4 + 5 + (2 + 1) * 2); ///カッコ内が先に計算されるよ
 }
------------------------------
///出力
15
15
15

ポイントとしては、
・複数の演算しを使うことができる
・演算の優先順位は* = / = % > + = -で考える
・計算の順序を変えたい場合は()を使って囲む
・カッコの中に同じカッコがあっても問題ない
といったところでしょうか。

変数/代入

プログラミング言語では、数字や文字列・論理値(truefalse)など(値と呼ぶ)を名前をつけた変数に"代入"することができます。
これも見た方が早いので書いてみましょう。

void main() {
//ageという数値型の変数を作り数字を代入する
int age = 21;
//nameという文字型の変数を作り文字列を代入する
String name = "ダート太郎";
//出力する
print("年齢: $age名前: $name");
}
-------------------------------
///出力
年齢: 21 名前: ダート太郎

ポイントとしては、
[変数の種類][変数名]=[代入する値];
のように、変数を宣言するときは String(文字型)やint(整数型)を最初に書き、変数名をその後に書く。

・最後の行に関しては、文字列の中(""の中)では、$ドルマークの後ろに変数の名前を付けたものを書くとその変数の中身を文字に変換して出力してくれる

といったところです。

条件分岐( if文 )

Dart言語でも、「if文」を使って条件によって異なる動作をすることができます。
習うよりまず、「年齢を入力して馬券が買えるか判断する」コードをみてみましょう。

void main() {
///年齢の変数ageに年齢を代入
  int age = 19;
///条件式
  if(age < 20) { ///もしageの値が20未満だったら
    print("$age歳は馬券買えません");
  }
  else { ///そのほかの条件だったら
    print("$age歳は馬券買えます");
  }
}

------------------------------
//出力
19歳は馬券買えません

いよいよプログラムチックになってきました。
この条件式では
20歳未満かどうか判断(ifの部分)→20歳未満なら馬券買えません

20歳以上なら馬券買えます(elseの部分)
というフローになっています。条件が面倒になってくるとフローチャートを書いておくと良いみたいです。

ポイントとしては、
if([条件式]){[条件式がtrueだったときの処理]}
 else{[条件式がfalseだったときの処理]}

という形で書く。

・条件によって結果を3種類以上に分けたい場合はelse if([条件式2]){...}といった具合に続ける。
else ifの意味としては、それじゃなければ...という感じでしょうか。

あとは、条件式について整理しておきます。

==: ///左右が同じ値
!=: ///左右が違う値
>: ///左のほうが大きい
<: ///左のほうが小さい
>=: ///左右同じか左のほうが大きい
<=: ///左右同じか左のほうが小さい

=(イコール)がひとつの時とふたつの時で意味が変わってくるので注意ですね!

繰り返し文( for文 )

繰り返しは、同じ処理を入力する数字を変えて行うことです。
今回はn回目の誕生日を20回まで行い、最後に「成人おめでとう」と出力するプログラムを書いてみます。

void main() {
  for(int i = 1; i <= 20 ; i = i + 1){
    print("$i回目の誕生日");
  }
  print("成人おめでとう!");
}
----------------------------
///出力
1回目の誕生日
2回目の誕生日
///...(省略)....
19回目の誕生日
20回目の誕生日
成人おめでとう!

分かりにくいかと思いますが、書き方の形としては
for([初期化式];[条件式];[更新式]){
[繰り返しやる処理];
}

となっています。
今回は
初期化式...i=1 →変数iは1からはじまるよ
条件式...i<=20 →変数iは20以下までだよ
更新式...i = i+1 →変数iで処理し終わった後、次にする処理はi+1の処理だよ
という意味を持っています。
上から処理を行っていくので、繰り返し文の次の中カッコに「成人おめでとう」を書いておくと最後に出力されます。

おまけ (fizzbuzz )

プログラムの特徴を比べる時によくfizzbuzzというプログラムが用いられます。
3で割れる数字をfizz / 5で割れる数をbuzz / どちらもわれる数(15で割れる数)をfizzbuzzと返します。
今回は1番シンプルなfor文で20まで回して、if文で分類していくプログラムで実装していきます。

void main(){
  for(int i = 1; i <= 20 ; i = i + 1){
    if(i % 15 == 0){
    print("fizzbuzz");
  }
    else if(i % 5 == 0){
      print("buzz");
    }
    else if(i % 3 == 0){
      print("fizz");
    }
    else{
      print(i);
    }
  }
}
-----------------------------------
///出力
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

fizzbuzzまでとりあえず習得できました!!

感想

最初の方は余裕と思っていたのですが、実際に書く言語と近い部分遠い部分があることが分かりました。

たくさんエラーが出ましたが、それでも試しに試すとなんとかいくものということもわかり、今後の自信にもなりました。
文法もう少しちゃんとやらないと無理そうなのでしばらく文法シリーズ続きます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIalertControllerが微妙に使いづらかったのでいい感じのAlertControllerを自作する!

はじめに

先日インターン先のタスクで共通で使う通知部分を実装する際、デフォルトのUIAlertControllerだとボタンのテキストをいじれなかったりとデザインに合わせることが難しかったので、一からUialertControllerに似た実装の仕方で作ってみたのでそれについてまとめてみようと思います!

github

https://github.com/ikemoti/OriginalUIAlertController

デザインの用件内容

・アラートのttitleやmessage部分のフォントやtextColorを変えたい
(デフォルトのアラートだと変更不可能)
・アラートの上部にバーナー画像を任意で追加可能にして欲しい
・ボタンの数を任意で選択できるようにしてフォントや文字色も選択可能にして欲しい
・なるべく使う際にUIAlerttControllerに似た使い方ができるようにして欲しい

完成図

①アニメーション部分

はじめに土台となるアラートのアニメーション部分を作成しました!

a
public class DialogAnimation: NSObject, UIViewControllerAnimatedTransitioning {
    // true: dismiss
    // false: present
    private let isPresenting: Bool
    init(isPresenting: Bool) {
        self.isPresenting = isPresenting
    }
    public func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.20
    }
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        if isPresenting {
            dismissAnimation(transitionContext)
        } else {
            presentAnimation(transitionContext)
        }
    }
    private func presentAnimation(_ transitionContext: UIViewControllerContextTransitioning) {
        guard let alert = transitionContext
            .viewController(forKey: UITransitionContextViewControllerKey.to) as? DialogController
            else { fatalError("ViewController is not defined sucessfully") }
        let container = transitionContext.containerView
        alert.backgroundView.alpha = 0
        alert.contentView.alpha = 0
        alert.contentView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
        container.addSubview(alert.view)
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            alert.backgroundView.alpha = 1
            alert.contentView.alpha = 1
            alert.contentView.transform = .identity
        }, completion: { _ in
            transitionContext.completeTransition(true)
        })
    }
//DissMissAnimaitonb部分は省略

ポイントとして
ただ単にViewを表示するだけだと,遷移前の画面が投下されないので表示されるViewをbackgroundViewとAlertViewに分割してbackgroundViewを透過させることで無事表示できるようになりました

②Controller部分について

a
//Controller部分
public class DialogController: UIViewController, UIViewControllerTransitioningDelegate {
    //DialogControllerのベースとなるView
    private(set) lazy var backgroundView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.black.withAlphaComponent(0.15)
        view.isOpaque = false
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    let contentView: UIView = .init()
    let contentStackView: UIStackView = .init()

    private let bannerView: UIImageView = .init()
    private var bannerAspectRatioConstraint: NSLayoutConstraint?
    private let textContainer: UIView = UIView()
    private let textStackView: UIStackView = UIStackView()
    private lazy var textStackViewTopAnchor: NSLayoutConstraint = {
        return textStackView.topAnchor.constraint(equalTo: textContainer.topAnchor, constant: 40)
    }()
    private lazy var textStackViewBottomAnchor: NSLayoutConstraint = {
        return textStackView.bottomAnchor.constraint(equalTo: textContainer.bottomAnchor, constant: -40)
    }()
    private let titleLabel: UILabel = .init()
    private let messageLabel: UILabel = .init()
    private let buttonStackview: UIStackView = .init()
    private let topBorder: UIView = .init()
    private var actions = [DialogAction]()

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        providesPresentationContextTransitionStyle = true
        definesPresentationContext = true
        modalPresentationStyle = UIModalPresentationStyle.custom
        transitioningDelegate = self
    }
    //簡易イニシャライザ
    //引数がnilかどうかでcontentViewが可変する
    public convenience init(title: String, message: String?, banner: UIImage?) {
        self.init(nibName: nil, bundle: nil)
        titleLabel.text = title
        messageLabel.isHidden = message == nil
        messageLabel.text = message
        bannerView.image = banner
        bannerView.isHidden = banner == nil
        textStackViewTopAnchor.constant = banner == nil ? 40 : 24
        textStackViewBottomAnchor.constant = banner == nil ? -40 : -24

        if let banner = banner {
            bannerAspectRatioConstraint
                = bannerView.heightAnchor.constraint(equalTo: bannerView.widthAnchor,
                                                     multiplier: banner.size.height / banner.size.width)
        }
    }

    required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func viewDidLoad() {
        super.viewDidLoad()
        setAttrributes()
        constructViews()
        setLayout()
    }

    @objc private func buttonEvent(sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }

    public func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DialogAnimation(isPresenting: true)
    }
//ボタン追加時のメゾット、二つ目のボタン追加時からボタンの追加前にborderを設置する
    public func add(action: DialogAction) {
        let button = UIButton()
        button.setTitle(action.title, for: UIControl.State())
        button.setTitleColor(action.titleColor, for: .normal)
        button.titleLabel?.font = action.titleFont
        button.heightAnchor.constraint(equalToConstant: 44).isActive = true
        button.addTarget(self, action: #selector(buttonEvent(sender:)), for: UIControl.Event.touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        if actions.count > 0 {
            let border = UIView()
            border.translatesAutoresizingMaskIntoConstraints = false
            border.widthAnchor.constraint(equalToConstant: 0.5).isActive = true
            border.heightAnchor.constraint(equalToConstant: 44).isActive = true
            border.backgroundColor = UIColor.gray
            buttonStackview.addArrangedSubview(border)
        }
        buttonStackview.addArrangedSubview(button)
        if let alertView = buttonStackview.arrangedSubviews.first {
            button.widthAnchor.constraint(equalTo: alertView.widthAnchor ).isActive = true
        }
        actions.append(action)
    }

    public func animationController(
        forPresented _: UIViewController,
        presenting _: UIViewController,
        source _: UIViewController
    ) -> UIViewControllerAnimatedTransitioning? {
        return DialogAnimation(isPresenting: false)
    }
}

Controller部分に関して引数の有無に合わせてのcontentViewの変化するようにしたり、
ボタンの追加時に個数によってボーダーを追加するような実装にしました!

③ボタンに関して

a
public class DialogAction {
    public enum ActionStyle {
        case `default`
        case cancel
        case destructive
        case custom(color: UIColor, font: UIFont)
    }
    private var handler: ((_ action: DialogAction) -> Void)?
    public private(set) var style: ActionStyle
    public private(set) var title: String
    public var titleColor: UIColor {
        switch self.style {
            case .default: return UIColor.black
        case .cancel: return UIColor.black
            case .destructive: return UIColor.red
            case let .custom(color, _): return color
        }
    }
    public var titleFont: UIFont {
        switch self.style {
            case .default: return UIFont.boldSystemFont(ofSize: 14)
            case .cancel: return UIFont.systemFont(ofSize: 14)
        case .destructive: return UIFont.boldSystemFont(ofSize: 14)
            case let .custom(_, font): return font
        }
    }
    public init(title: String, style: ActionStyle, handler: ((_ action: DialogAction) -> Void)?) {
        self.handler = handler
        self.style = style
        self.title = title
    }
}

ボタンに関しては通常はdefault、cancel、destructiveの三つですが今回自由にカスタムできるように作って欲しいとのことだったのでcustomを追加してフォントとテキストカラーを自由に選べるようにしました!

④完成時の実装例

a
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()}

    @IBAction func `default`(_ sender: Any) {
        let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert)
        let cancel = UIAlertAction(title: "キャンセル", style: .cancel,handler: nil)
        let ok = UIAlertAction(title: "OK", style: .default,handler: nil)
        alert.addAction(cancel)
        alert.addAction(ok)
        self.present(alert, animated: true, completion: nil)
    }


    @IBAction func Original(_ sender: Any) {
        let alert2 = DialogController(title: "タイトル", message:"メッセージ", banner:UIImage(imageLiteralResourceName:"Image" ) )
        let cancel2 = DialogAction(title: "キャンセル", style: .custom(color: UIColor.purple, font: UIFont.systemFont(ofSize: 16)), handler:nil )
        let ok2 = DialogAction(title: "Ok", style: .default,handler: nil)
        alert2.add(action: cancel2)
        alert2.add(action: ok2)
        self.present(alert2, animated: true, completion: nil)
    }

今回無事に実装できたのですが、animation関連がの知識不足を感じたので、また勉強していきたいなと思いました!?‍♂️

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む