20220113のSwiftに関する記事は6件です。

Xcode + Swift でiPhone用 地図アプリ。(22日目) 

昨日に続いて、swiftで地図アプリ作成。 参考サイト: 今日やった事 ・位置情報取得 ・現在地を画面中央に移動 ・アプリアイコン設定 ・拡大・縮小ボタン追加と実装 ・ロングタップでピンを打つ 実行画面(iPhone11) ※現在地は画面外 ほぼの解説サイトの情報を、参考にそのまま実装してるので特筆すべきことは特にないですが、 Swiftの「Info.plist」が、kotlin(android studio)でいうところの「AndroidManifest.xml」にあたるとか、ストーリーボード(Main.storyboard)が、kotlinの「activity_main.xml」になるとか、何となくそんな認識です…。 基本的な事が何となくわかったので、明日は、独自にボタンを作って実装できればと思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

列挙型(enum)について

列挙型(enum)について 履歴 2022年1月13日 初版 開発環境 Swift5.5以降 Xcode13 概要 列挙型とは、識別子の集合である。 識別子は、何かを区別する際に用いるもので、例えば、トランプのカードの絵柄は「ハート」「ダイヤ」「クラブ」「スペード」の4種類であり、 カードの番号(1〜13)もまた、カードを識別するために用いる識別子となる。 従って、トランプは「絵柄」と「番号」といったカードを区別をするための識別子をもって、一意に特定できることとなる。 カードの絵柄はジョーカーを除けば「ハート」「ダイヤ」「クラブ」「スペード」の4種類しかない。この4種類の絵柄の集合が、「トランプの絵柄」と言い換えることができる。 以下、ジョーカーを除いて考えるとして、 トランプのカードが絵柄を持つ時、次のPlayingCardSuitのいずれかが該当することになる。 列挙型は、enumで始まり名前を記述する。クラスや構造体同様、列挙型の命名は大文字から始まるアッパーキャメルケースである。 enum PlayingCardSuit { case heart case diamond case club case spade } カードの構造体を作る場合は、次のようになる。 struct PlayingCard { // カードの絵柄 let suit: PlayingCardSuit // カードの番号 let rank: Int } スペードの4を示す時、インスタンス生成はPlayingCardSuitのspade指定する。 指定の方法は、列挙型名.識別子である。 let spade4 = PlayingCard(suit: PlayingCardKind.spade, rank: 4) 利点 列挙型を型として利用する場合、列挙された識別子以外の値は存在しない。 PlayingCardとして指定されたsuitは、必ず4種類のうちのどれかに該当する。 Swift言語はswitchを利用する時、その型が取りうる値すべてを網羅していない限りdefault句が必要となる。 もし、カードの種類を「1はハート」「2はダイヤ」「3はクラブ」「4はスペード」とInt型でマッピングしたとしても、switch文は1〜4以外のdefaultも考慮しなければならない。 例として、PlayingCardのsuitがInt型であった時のswitch文を記す。 switch spade4.suite { case 1: print("♥") case 2: print("♦") case 3: print("♣") case 4: print("♠") default: print("?") } すべての値を網羅していない状態でdefault句がない場合はコンパイルエラーとなるので、必ず書かなければならない。 ただ、この場合はカードの種類(4種類しかない)から考えればdefaultは存在しないはずである。しかし、文法上は記述をしなければエラーになる。 もちろん、suiteがInt型である以上、1〜4以外の値が入らないとも限らない。 カードの設定としては不適切な値が許容されてしまうため、数値を使ったカードの種類のマッピングは避けるべきと考える。 しかし、列挙型を利用している場合は、列挙型のすべてを取り上げていれば、defaultが必要にならない。 そして、列挙型である以上、この4種類以外の値は取り得ることがない。 switch spade4.suite { case PlayingCardSuit.heart: return "♥" case PlayingCardSuit.diamond: return "♦" case PlayingCardSuit.club: return "♣" case PlayingCardSuit.spade: return "♠" } 省略可能なケース 列挙型として指定しているプロパティ、あるいは列挙型を引数としている関数などは、該当する値は指定した列挙型であることが確定している。 例えば、先のカードの構造体のkindは必ずPlayingCardSuitのいずれかが入ることがわかっている。 struct PlayingCard { let suite: PlayingCardSuit // ← heart、diamond、club、spadeのいずれか let rank: Int } このカードのsuiteに値を代入する時、列挙型名を省略することができる。 let spade4 = PlayingCard(suite: .spade, rank: 4) もちろん、switch文でもspade4のsuiteはPlayingCardSuitであることがわかっているため、case句のPlayingCardSuitは省略することができる。 switch spade4.suite { case .heart: return "♥" case .diamond: return "♦" case .club: return "♣" case .spade: return "♠" } ただし、型が定まっていない場合は型を省略することはできないので注意をする。 列挙型の反復(for文で使いたい) 列挙型で、内部の識別子をfor文で利用した時には、そのenumをCaseIterableに準拠するようにする。 enumは、クラスや構造体同様、他のプロトコルに準拠させる記述も可能である。 CaseIterableに準拠されることで、配列などで使うfor文が利用できるようになる。 enum PlayingCardSuit: CaseIterable { case heart case diamond case club case spade } print(PlayingCardSuit.allCases.count) // 4 for suite in PlayingCardSuit.allCases { print(suite) // heart、diamond、club、spadeの順に表示される } rawValue 列挙型の識別子には識別子に値を持たせることができる。これをrawValueと呼ぶ。生の値。 識別子に値を持たせるものであり、識別子を別の値で利用するための手段と思って良い。つまり、代入可能な値ではない。 例えば先程のカードの種類を数値で表す……すなわち「1はハート」「2はダイヤ」「3はクラブ」「4はスペード」のようなケースを作成する場合、rawValueを使う。 rawValueを指定する場合は、列挙体名: の後ろに指定したい型名を書く。rawValueはCaseIterableと並行して記述しても問題はない。 enum PlayingCardSuit: Int, CaseIterable { case heart = 1 case diamond = 2 case club = 3 case spade = 4 } var spade6 = PlayingCard(suite: .spade, rank: 6) print(spade6.suite.rawValue) //4 が表示される rawValueはget(値の取得)は可能だがset(値の再代入)はできない。以下のようにrawValueを設定することはできない。 ※suiteを列挙型で変更することはできる。rawValueの変更ができない。 spade6.suite = PlayingCardSuit.heart // 可能 spade6.suite.rawValue = 2 // 不可能 インスタンスを作る時にrawValueを用いて識別子を定めることができる。 ただし、rawValueで識別子を定めた列挙型を作ろうとする時、オプショナルになることに注意する。 (もし、PlayingCardKind(rawValue: 99999)と指定した場合、存在しない識別子を指定することになる) let heartKind: PlayingCardKind? = PlayingCardKind(rawValue: 1) if let heartKind = heartKind { let heart10 = PlayingCard(kind: heartKind, rank: 10) print(heart10.kind) } ※rawValueは、識別子に対応する値であり、同じ列挙型内の他の識別子と同じrawValueを持たせることはできない。 rawValueで指定できる型 rawValueで指定できる型はString、Character、Int、Float、Doubleである。 それ以外の型はエラーとなる。 // DoubleもFloatもどちらでもOK enum PlayingCardKind: Double, CaseIterable { case heart = 0.0 case diamond = 1.0 case club = 2.0 case spade = 3.0 } Boolにするとエラーになる(指定できない型) Associated Values 列挙型の識別子に関連した値(Associated Values)を持たせることもできる。 これまでのPlayingCardは内部にkindとrankをもたせた構造体で考えていたが、Associated Valuesを使うことで列挙型だけで管理することもできる。 代入ではなく、その識別子に関連した値を設定することができる。 Associated Valuesを使うには、列挙型の定義時に、Associated Valuesの型を丸括弧で括って指定する必要がある。 case 識別子(型)のように書き、複数ある場合はcase 識別子(型, 型, 型)とカンマ区切りで書いていく。 実際に、PlayingCardSuiteにrankの値が関連付けられるようにしてみる。 enum PlayingCardSuit { case heart(Int) case diamond(Int) case club(Int) case spade(Int) } 変更に伴って、PlayingCardの情報も変更する。 「ダイヤの10」のカードを作ろうとする時、次のように記述する。 struct PlayingCard { let card: PlayingCardSuit } let diamond10 = PlayingCard(card: PlayingCardSuit.diamond(10)) Associated Valuesは、switch文で分岐した時に取得することができる。 つまり、switch文のcase句で、定数として取り出すことで、case内で利用ができるものである。プロパティのようには扱えない。 switch diamond10.card { case .heart(let rank): print("♥の\(rank)です") case .diamond(let rank): print("♦の\(rank)です") case .club(let rank): print("♣の\(rank)です") case .spade(let rank): print("♠の\(rank)です") } case文でのみ対応が可能である理由は、識別子ごとに関連する値の型、数をが指定できるためである。 つまり、共通化ができないものであるので、switchで分岐してそれぞれの識別子ごとに値を取り出すことしかできない。 例えば、Joker(赤と黒の2枚)を種類として追加すると、列挙型は次のようになる。※enumの中にenumをネストことはできる。 enum PlayingCardSuit { case heart(Int) case diamond(Int) case club(Int) case spade(Int) case joker(JokerKind) enum JokerKind { case red case black } } Jokerの赤を作るには次のような文になる。 let jokerRed = PlayingCard(card: PlayingCardSuit.joker(.red)) カードの種類がjokerの時は、値に「jokerの赤かjokerの黒」のどちらかの値が入っている。 ハート、ダイヤ、クラブ、スペードの時は値は「Int型」である。 case句の定数に入る値の型がjokerの時だけ異なることに注意する。 switch jokerRed.card { case .heart(let rank): print("♥の\(rank)です") case .diamond(let rank): print("♦の\(rank)です") case .club(let rank): print("♣の\(rank)です") case .spade(let rank): print("♠の\(rank)です") case .joker(let jokerKind): switch jokerKind { case .red: print("ジョーカーの赤です") case .black: print("ジョーカーの黒です") } } rawValueとAssociated Valuesは、同時に利用することができない。 また、Associated Valuesが設定されている場合は、CaseIterableも使えなくなる。 次の2つの列挙型は、両方ともエラーになるので注意をすること。 // エラーになる enum Hoge: CaseIterable { case fuga(Int) case piyo(Int) } // エラーになる enum Hoge: String { case fuga(Int) = "FUGA" case piyo(Int) = "HOGE" } rawValueとAssociated Valuesの違いは、rawValueはenumで定義した値が常に利用されるということであり、 Associated Valuesはインスタンスを作る際に異なる値を設定することができるという違いがある。 つまり、カードのランクのようにカードによって値が異なるのであればAssociated Valuesであるし、 ジョーカーのredとblackはどのような場合でもredは赤でありblackは黒であるという状況であればredとblackに対応する値はrawValueで定義することになる。 変数、関数の追加 列挙型は、ストアドプロパティは定義することができないがコンピューテッドプロパティは定義することができる。 また、関数(メソッド)や比較演算子の定義も可能である。 次のPlayingSuiteは、Equatableに準拠させ、構造体同士で同じか異なるかの比較を可能とした。 絵柄を取得するため、コンピューテッドプロパティとしてsuiteを定義し、 ランクを取得するために、同じくコンピューテッドプロパティでrankを定義した。 変数suiteでは、処理内部のswitch文にあるcase句に定数を定義していないが、Associated Valuesが不要であれば(_)としてもよいし、書かないことも可能である。 enum PlayingCardSuit: Equatable { // Equatableの「等しい」の条件 static func == (lhs: PlayingCardSuit, rhs: PlayingCardSuit) -> Bool { return lhs.suite == rhs.suite } case heart(Int) case diamond(Int) case club(Int) case spade(Int) case joker(JokerKind) // redは赤の意味しかなく、blackは黒の意味しかない。 enum JokerKind: String { case red = "赤" case black = "黒" } var suite: String { switch self { case .heart: return "♥" case .diamond: return "♦" case .club: return "♣" case .spade: return "♠" case .joker: return "ジョーカー" } } var rank: String { switch self { case .heart(let rank), .diamond(let rank), .club(let rank), .spade(let rank): return String(rank) case .joker(let jokerKind): return jokerKind.rawValue } } // メソッドの定義 func printMessage() { switch self { case .heart, .diamond, .club, .spade: print("絵柄は\(self.suite)で、ランクは\(self.rank)です") case .joker: print("絵柄は\(self.suite)で、色は\(self.rank)です") } } } 使用例は次のようになる。 struct PlayingCard { let card: PlayingCardSuit } let diamond10 = PlayingCard(card: PlayingCardSuit.diamond(10)) let jokerRed = PlayingCard(card: PlayingCardSuit.joker(.red)) diamond10.card.printMessage() // 「絵柄は♦で、ランクは10です」と表示される jokerRed.card.printMessage() // 「絵柄はジョーカーで、色は赤です」と表示される // Equatableによる比較 print(diamond10.card == jokerRed.card ? "同じ絵柄です" : "異なる絵柄です") // 「異なる絵柄です」と表示される 実際のコードでよく使う、Errorに準拠するEnum Swift言語ではErrorに準拠した値を受け取る型がしばしば登場する。 Errorはプロトコルであるため、そのまま利用することができず、Errorに準拠した型を自分で定義することが多い。 enum SampleError: Error { case hogehogeError(String) case fugafugaError(Int, String) var message: String { switch self { case .hogehogeError(let message): return message case .fugafugaError(let status, let message): return message + ":" + String(status) } } } let error1 = SampleError.hogehogeError("エラーメッセージ") print(error1.message) //「エラーメッセージ」と表示される let error2 = SampleError.fugafugaError(10, "エラー番号付き") print(error2.message) // 「エラー番号付き:10」と表示される Resultで使う 列挙型のResult型は、成功時はsuccess識別子にSuccess型の値をAssociated Valuesに詰め込み、 失敗時はfailure識別子にErrorに準拠した型の値をAssociated Valuesに詰め込むことができる列挙型である。 列挙型Resultの中身は次のようになっている(抜粋)。 failureに設定できるのはErrorに準拠していなければならないため、先程のSampleErrorのようなErrorに準拠した型を作っておくと良い。 処理に成功した時はsuccessを使い、処理に失敗した時はfailureと、その失敗の理由を列挙型の識別子などで定義しておくと、エラーの処理が扱いやすくなる。 let error1 = SampleError.hogehogeError("エラーメッセージ") let error2 = SampleError.fugafugaError(10, "エラー番号付き") // 様々なResultの作成 let result1: Result<String, SampleError> = .failure(error1) let result2: Result<String, SampleError> = .failure(error2) let result3: Result<String, SampleError> = .success("成功") switch result1 { //「エラーメッセージ」と表示される case .success(let message): print(message) case .failure(let error): print(error.message) } switch result2 { // 「エラー番号付き:10」と表示される case .success(let message): print(message) case .failure(let error): print(error.message) } switch result3 { // 「成功」と表示される case .success(let message): print(message) case .failure(let error): print(error.message) } 所感 enumはわかるととても便利。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebase Remoete Configで強制アップデート

FirebaseのRemote Configを使用して強制アップデートを実装したので記事にします。 強制アップデートとは アプリの新しいバージョンをリリースした時に、ユーザに必ずアップデートをして欲しい場合があります。 例えば、以下のような場合です。 既存の機能に大幅な変更が加わった場合 致命的なバグを修正した場合 この時、ユーザがアプリを立ち上げた際、ユーザにアップデートを促しアップデートを完了させるまでアプリの使用を事実上不可能にする処理が強制アップデートです。 アプリの使用を不可能にする処理としては、以下のようなアラートを出し、アプリストアへの遷移以外できなくすることなどが挙げられます。(ストアからアップデートせずに戻ってきても再度表示される) また、強制アップデートは最初に公開した時に入れとかないと強制アップデートさせたくてもさせることができない場面が出てくるので、できるだけ最初のバージョンで入れておきたい機能です。 Firebase Remote Configとは Firebase Remote Configはアプリの動作や外見を変更するパラメータをFirebaseに持たせることができるクラウドサービスです。 アプリが通信処理を行いこのパラメータにアクセスすることにより、アプリ自身を変更することなく動作や外見を変更することができます。 今回の強制アップデートの場合、アプリの最新のバージョン情報などをFirebase Remote Configに設定しておきます。 実装 ここからは実際にどのような手順で実装するのか見ていきます。 Firebase側 まずFirebaseのコンソールから左下、「エンゲージメント」の中にある「Remote Config」を選びます。 初めての場合はRemote Configのトップページが表示されます。「構成を作成」を押すと最初のパラメータを入力することができるので、バージョン情報を入力して保存しましょう。 また、それぞれ項目は以下の通りです。 パラメータ名 アプリ側でパラメータにアクセスするためのキー データ型 パラメータのタイプ Description パラメータの説明 Default value 設定するパラメータ 保存すると以下のような画面になります。 この状態ではまだ変更が適用されていません。右上の「変更を公開」ボタンを押すと変更が適用されて、設定したパラメータをアプリで使用できるようになります。 また、新たにパラメータを追加することも可能です。 私の場合は、current_versionの他にrequire_force_update(強制アップデートさせるかどうか)とapp_store_url(app storeのURL)も追加しています。 最後に「変更を公開」ボタンを押してFirebase側の設定は完了です。 アプリ側 次はアプリ側の実装についてです。 まず全体のコードを載せてしまいます。 RemoteConfigProvider.swift import FirebaseRemoteConfig /// Firebaseのコンソールで設定したパラメータ名に対応 enum ConfigKey: String { case currentVersion = "current_version" case forceUpdateRequired = "require_force_update" case storeUrl = "app_store_url" static func makeDefaults() -> [String: Any] { [ currentVersion.rawValue: "", forceUpdateRequired.rawValue: false, storeUrl.rawValue: "" ] } } final class FirebaseRemoteConfigProvider { private let remoteConfig = RemoteConfig.remoteConfig() /// パラメータを取ってくる func fetchConfig(completion: (() -> Void)? = nil) { // 取得するパラメータのデフォルトを設定 remoteConfig.setDefaults(ConfigKey.makeDefaults() as? [String: NSObject]) // FetchとActivateを一括で行う remoteConfig.fetchAndActivate(completionHandler: { status, error in switch status { case .successUsingPreFetchedData, .successFetchedFromRemote: completion?() case .error: if let error = error { print(error.localizedDescription) } @unknown default: fatalError() } }) } /// RemoteConfigから取ってきたパラメータを取得 func getConfig(key: ConfigKey) -> RemoteConfigValue { remoteConfig.configValue(forKey: key.rawValue) } } ForceUpdateManager.swift import Combine import FirebaseRemoteConfig final class UpdateCheckManager { static let shared = UpdateCheckManager() private let remoteConfigProvider = FirebaseRemoteConfigProvider() private var cancellable: AnyCancellable? private init() {} func setup() { observeApplicationDidBecomeActive() } /// アプリがActiveになった際にアップデートチェック private func observeApplicationDidBecomeActive() { cancellable = NotificationCenter.Publisher(center: .default, name: UIApplication.didBecomeActiveNotification, object: nil) .sink(receiveValue: { [weak self] _ in FirebaseRemoteConfigProvider().fetchConfig(completion: { self?.forceUpdateIfNeeded() }) }) } /// requireForceUpdateがtrueかつ現在のバージョンが最新のバージョンと異なる場合に強制アップデート private func forceUpdateIfNeeded() { let localVersionString = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String let requireForceUpdate = remoteConfigProvider.getConfig(key: .forceUpdateRequired).boolValue if requireForceUpdate, let currentVersionString = remoteConfigProvider.getConfig(key: .currentVersion).stringValue, localVersionString != currentVersionString { guard let storeUrlString = remoteConfigProvider.getConfig(key: .storeUrl).stringValue, let storeUrl = URL(string: storeUrlString) else { return } // 強制アップデートのアラートを出す } } } AppDelegate.swift import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. UpdateCheckManager.shared.setup() return true } ... } 各クラスの説明は以下の通りです。 クラス 機能 UpdateCheckmanager 強制アップデートに関する処理を担当 FirebaseRemoteConfigProvider Firebase Remote Configとの通信とパラメータ取得 ConfigKey Firebase Remote Configのパラメータ名を定義 コードを見ていただくとわかりますが、UpdateCheckManagerでdidBecomeActiveを監視しています。アプリがアクティブになった際は、FirebaseRemoteConfigProviderにFirebaseのコンソールで設定したパラメータを取ってきてもらいます。 また今回は、「アプリがアクティブになった際に強制アップデートのアラートを出す」という方針を取っております。これにより、ストアに移動してアップデートせずに戻ってきた場合でも再度アラートを出すことができます。 まとめ Firebase Remote Configを使用した強制アップデートの実装についてまとめました。 強制アップデートの実装については、他にも強制アップデート用のライブラリを利用する等の方法があるようなので、作成するアプリに適した方法を使用してください。 参考文献 Firebase Remote Configを使ってみる Implementing Force Update Feature using Firebase Remote Config in iOS
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebase Remote Configで強制アップデート

FirebaseのRemote Configを使用して強制アップデートを実装したので記事にします。 強制アップデートとは アプリの新しいバージョンをリリースした時に、ユーザに必ずアップデートをして欲しい場合があります。 例えば、以下のような場合です。 既存の機能に大幅な変更が加わった場合 致命的なバグを修正した場合 この時、ユーザがアプリを立ち上げた際、ユーザにアップデートを促しアップデートを完了させるまでアプリの使用を事実上不可能にする処理が強制アップデートです。 アプリの使用を不可能にする処理としては、以下のようなアラートを出し、アプリストアへの遷移以外できなくすることなどが挙げられます。(ストアからアップデートせずに戻ってきても再度表示される) また、強制アップデートは最初に公開した時に入れとかないと強制アップデートさせたくてもさせることができない場面が出てくるので、できるだけ最初のバージョンで入れておきたい機能です。 Firebase Remote Configとは Firebase Remote Configはアプリの動作や外見を変更するパラメータをFirebaseに持たせることができるクラウドサービスです。 アプリが通信処理を行いこのパラメータにアクセスすることにより、アプリ自身を変更することなく動作や外見を変更することができます。 今回の強制アップデートの場合、アプリの最新のバージョン情報などをFirebase Remote Configに設定しておきます。 実装 ここからは実際にどのような手順で実装するのか見ていきます。 Firebase側 まずFirebaseのコンソールから左下、「エンゲージメント」の中にある「Remote Config」を選びます。 初めての場合はRemote Configのトップページが表示されます。「構成を作成」を押すと最初のパラメータを入力することができるので、バージョン情報を入力して保存しましょう。 また、それぞれ項目は以下の通りです。 パラメータ名 アプリ側でパラメータにアクセスするためのキー データ型 パラメータのタイプ Description パラメータの説明 Default value 設定するパラメータ 保存すると以下のような画面になります。 この状態ではまだ変更が適用されていません。右上の「変更を公開」ボタンを押すと変更が適用されて、設定したパラメータをアプリで使用できるようになります。 また、新たにパラメータを追加することも可能です。 私の場合は、current_versionの他にrequire_force_update(強制アップデートさせるかどうか)とapp_store_url(app storeのURL)も追加しています。 最後に「変更を公開」ボタンを押してFirebase側の設定は完了です。 アプリ側 次はアプリ側の実装についてです。 まず全体のコードを載せてしまいます。 RemoteConfigProvider.swift import FirebaseRemoteConfig /// Firebaseのコンソールで設定したパラメータ名に対応 enum ConfigKey: String { case currentVersion = "current_version" case forceUpdateRequired = "require_force_update" case storeUrl = "app_store_url" static func makeDefaults() -> [String: Any] { [ currentVersion.rawValue: "", forceUpdateRequired.rawValue: false, storeUrl.rawValue: "" ] } } final class FirebaseRemoteConfigProvider { private let remoteConfig = RemoteConfig.remoteConfig() /// パラメータを取ってくる func fetchConfig(completion: (() -> Void)? = nil) { // 取得するパラメータのデフォルトを設定 remoteConfig.setDefaults(ConfigKey.makeDefaults() as? [String: NSObject]) // FetchとActivateを一括で行う remoteConfig.fetchAndActivate(completionHandler: { status, error in switch status { case .successUsingPreFetchedData, .successFetchedFromRemote: completion?() case .error: if let error = error { print(error.localizedDescription) } @unknown default: fatalError() } }) } /// RemoteConfigから取ってきたパラメータを取得 func getConfig(key: ConfigKey) -> RemoteConfigValue { remoteConfig.configValue(forKey: key.rawValue) } } ForceUpdateManager.swift import Combine import FirebaseRemoteConfig final class UpdateCheckManager { static let shared = UpdateCheckManager() private let remoteConfigProvider = FirebaseRemoteConfigProvider() private var cancellable: AnyCancellable? private init() {} func setup() { observeApplicationDidBecomeActive() } /// アプリがActiveになった際にアップデートチェック private func observeApplicationDidBecomeActive() { cancellable = NotificationCenter.Publisher(center: .default, name: UIApplication.didBecomeActiveNotification, object: nil) .sink(receiveValue: { [weak self] _ in FirebaseRemoteConfigProvider().fetchConfig(completion: { self?.forceUpdateIfNeeded() }) }) } /// requireForceUpdateがtrueかつ現在のバージョンが最新のバージョンと異なる場合に強制アップデート private func forceUpdateIfNeeded() { let localVersionString = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String let requireForceUpdate = remoteConfigProvider.getConfig(key: .forceUpdateRequired).boolValue if requireForceUpdate, let currentVersionString = remoteConfigProvider.getConfig(key: .currentVersion).stringValue, localVersionString != currentVersionString { guard let storeUrlString = remoteConfigProvider.getConfig(key: .storeUrl).stringValue, let storeUrl = URL(string: storeUrlString) else { return } // 強制アップデートのアラートを出す } } } AppDelegate.swift import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. UpdateCheckManager.shared.setup() return true } ... } 各クラスの説明は以下の通りです。 クラス 機能 UpdateCheckmanager 強制アップデートに関する処理を担当 FirebaseRemoteConfigProvider Firebase Remote Configとの通信とパラメータ取得 ConfigKey Firebase Remote Configのパラメータ名を定義 コードを見ていただくとわかりますが、UpdateCheckManagerでdidBecomeActiveを監視しています。アプリがアクティブになった際は、FirebaseRemoteConfigProviderにFirebaseのコンソールで設定したパラメータを取ってきてもらいます。 また今回は、「アプリがアクティブになった際に強制アップデートのアラートを出す」という方針を取っております。これにより、ストアに移動してアップデートせずに戻ってきた場合でも再度アラートを出すことができます。 まとめ Firebase Remote Configを使用した強制アップデートの実装についてまとめました。 強制アップデートの実装については、他にも強制アップデート用のライブラリを利用する等の方法があるようなので、作成するアプリに適した方法を使用してください。 参考文献 Firebase Remote Configを使ってみる Implementing Force Update Feature using Firebase Remote Config in iOS
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Xcode]括弧内の範囲を確認したい時

概要 VisualStudioなら括弧を色分けする便利な機能がありますがXcodeには残念ながらありません。 本文 少し手間ですが、最初の括弧か最後の括弧にマウスカーソルを重ねてダブルクリックをすると括弧の中身が選択状態になるので確認しやすくなります。 補足 デフォルトの配色では選択範囲の色が濃すぎたり薄すぎたりして見づらい時もあります。 Xcode > Preferences > Themesタブ > ウィンドウ下側のSelectionで見やすい色に調節するとだいぶ快適になります
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】UITableView のチュートリアルやってみた

目的 Swift + storyboard での開発を経験したい。 UITableView の実装を経験したい。 流れ プロジェクト作成 storyboard の作成 TableView の配置 TableView の接続 TableViewCell の作成 ViewController.swift の作成 実行・確認 1. プロジェクト作成 2. storyboard の作成 画面左側の Main.storyboard を選択 画面右上にある 「+」 を押す 3. TableView の配置 検索タブに 「UITable」 と、打ち込む 「UITableView」 を、スマホ画面まで ドラッグ&ドロップ UITableView を画面いっぱいに引き伸ばす( =上下左右の余白を 0 にする → 「add Constraints」 をクリック ) 下図のように、UITableView が画面いっぱいに広がっていれば、UITableViewの作成は完了 下図の赤丸部分を押す 4. TableView の接続 接続を行う。 赤矢印のように、ドラッグ&ドロップ を行う。 「data Source」 と 「View Controller」 「delegate」 と 「View Controller」 接続後、接続状況を確認 5. TableViewCell の作成 TableView の配置 を参考に、「TableViewCell」 を下図のように配置する TableViewCell の 「Identifier」 を 「Cell」 にする 6. ViewController.swift の作成 ViewController.swift にプログラムを追加していく TabView の 「dataScore」 「delegate」 を ViewContoroller に継承する ViewController.swift import UIKit // UITableViewDelegate, UITableViewDataSource を追加 class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } } エラーが発生する。 エラーの詳細を確認し、「Fix」を押す 押すと、以下のように ①と② が追加される また、 let weeksName = ["月", "火", "水", "木", "金", "土", "日"] を追加する ViewController.swift import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let weeksName = ["月", "火", "水", "木", "金", "土", "日"] // ①: Fix を押すと自動で追加される func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { } // ②: Fix を押すと自動で追加される func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } } ①,② の説明 と, プログラムの追加 ①.swift // 描画するセルの数 // ①: Fix を押すと自動で追加される func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return weeksName.count // 7 } ②.swift // 描画するセルを生成する // ②: Fix を押すと自動で追加される func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // セルを作成, Identifier が "Cell" となっているセルを呼び出す let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = weeksName[indexPath.row] // 表示する文字列 return cell } 完成したプログラム ViewController.swift import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let weeksName = ["月", "火", "水", "木", "金", "土", "日"] // Fix を押すと自動で追加される① func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return weeksName.count // 7 } // Fix を押すと自動で追加される② func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // セルを作成, Identifier が "Cell" となっているセルを呼び出す let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = weeksName[indexPath.row] // 表示する文字列 return cell } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } } 7. 実行・確認 Main.storyboard への TableView, TableViewCell の配置 ViewController.swift へのプログラム追加 が完了したら、次は実行を行う。 実行機(iPhone) を選択する。 今回は iPhone12mini を選択。 画面左上の 「◀︎」 を押して、プログラムを実行する 実行結果を確認。 予想通りの実行結果が得られた。 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む