20211014のSwiftに関する記事は9件です。

【Swift】UIAlertControllerやUIAlertActionを手軽に実装できるライブラリ「Alertift」の操作

SwiftでUIAlertControllerやUIAlertActionを実装するとコード量が多く見辛いというデメリットが有る。 そんな悩みを解決してくれるのが「Alertift」というライブラリ。 基本情報 Alertiftの基本情報。 CocoaPodsまたはCarthageで導入可能。 実装コード 実際に実装すると以下のようになる。 Alertift.alert(title: "タイトル", message: "メッセージ") .action(.default("OK")) { [unowned self] in print("OK") } .action(.destructive("削除")) { [unowned self] in print("delete") } .action(.cancel("キャンセル")) .show(on: self) 最後の.show(on: self)がないと表示されないので注意が必要。 UIAlertController.Style.actionSheetを実装したい場合は以下の通り。 Alertift.actionSheet(title: "タイトル", message: "メッセージ") .action(.default("OK")) { [unowned self] in print("OK") } .action(.destructive("削除")) { [unowned self] in print("delete") } .action(.cancel("キャンセル")) .show(on: self) UITextFieldを組み込みたい時は以下の通り。 Alertift.alert(title: "タイトル", message: "メッセージ") .textField { textField in textField.placeholder = "" } .handleTextFieldTextDidChange { textField, index in print(textField.text ?? "") } .action(.cancel("キャンセル")) .show(on: self) 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UITableViewCellをスワイプアクションでアラート表示

今回の内容 UITableViewCellでスワイプアクションをした時に、削除機能を行う前にアラートで削除するか確認する様にする。 コード extension SearchViewController:UITableViewDelegate{ func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let deleteAction = UIContextualAction(style: .destructive, title: "") { action,_,_ in let alert = UIAlertController(title: "確認", message: "選択中のデータを削除しますか?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "削除", style: .destructive, handler: { _ in //配列内の値を削除など tableView.deleteRows(at: [indexPath as IndexPath], with: .automatic) tableView.reloadData() })) alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)) self.present(alert, animated: true, completion: nil) } deleteAction.image = UIImage(systemName: "trash") return UISwipeActionsConfiguration(actions: [deleteAction]) } } 終わり ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】ユーザーレビューを促す訴求ダイアログを表示する方法

Swiftで開発したiOSアプリでユーザーレビューを促すダイアログを表示させる方法。 公式リファレンスの解説 SKStoreReviewController An object that controls the process of requesting App Store ratings and reviews from users. ユーザーにAppStoreの評価とレビューを要求するプロセスを制御するオブジェクト。 Overview Use the requestReview() method to indicate when it makes sense within the logic of your app to ask the user for ratings and reviews within your app. このメソッドを使用して、アプリのロジック内でユーザーにアプリ内の評価とレビューを求めることが理にかなっている場合を示します。 実装コード 必要なのは2行だけ。 import StoreKit SKStoreReviewController.requestReview() ただしダイアログを表示できるのは365日で最大3回まで。 どのタイミングで表示させるか工夫しないと、効果を得られないので注意が必要。 またユーザーは設定画面からレビュー訴求ダイアログの非表示設定が可能であるため、必ず表示されるとは限らない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift: HealthKitに体温データを入力する。できるだけ公式ドキュメントだけを見て。

まずは公式ドキュメントをちゃんと読む人間になろうと思いたち、Apple公式ドキュメントだけを元にHealthKitにアクセスを試みました。 環境 XCode Version 13.0 iOS target 14.0 参考(公式) 前説 https://developer.apple.com/documentation/healthkit/about_the_healthkit_framework XCodeでHealthKitを有効化する https://developer.apple.com/documentation/healthkit/setting_up_healthkit プライバシーデータへアクセスするためのプロジェクト設定 https://developer.apple.com/documentation/healthkit/protecting_user_privacy Swiftでヘルスケアデータにアクセス https://developer.apple.com/documentation/healthkit/authorizing_access_to_health_data プロジェクト設定 HealthKitの有効化(プロジェクト設定の「Signing & Capabilities」にHealthKitを追加 アクセス要求用のメッセージ定義 今回は体温データの入力のみなのでPrivacy - Health Update Usage Description アプリが読み出しする場合はPrivacy - Health Share Usage Descriptionが必要になります 参考: Protecting User Privacy (developper.apple.com) これらのDescriptionは13文字以上必要なようです。これに関しては調査力が足りず、StackOverflowのお世話になりました。 https://stackoverflow.com/questions/37863093/exception-nsinvalidargumentexception-nshealthupdateusagedescritption コード 今回は1つのクラス内でHealthKitへの参照を完結させます setup() HKHealthStoreはドキュメントによれば、無闇に生成せず保持し続けるのがいいようです。 You need only a single HealthKit store per app. These are long-lived objects; you create the store once, and keep a reference for later use. Setting Up HealthKit (developper.apple.com) エラーチェックで呼び出し側の処理を変えることなども考慮し、setup()メソッドでStoreの生成を行います。(エラーチェックが不要であればinit()内で生成してました) postBodyTemperature() 以下の順番で処理を行います 体温データに関するアクセス許可取得 アクセス許可状態の確認 体温データの保存 出来上がったClass import Foundation import HealthKit enum BodyTemperatureUnit{ /// 摂氏 case degreeCelsius /// 華氏 case degreeFahrenheit } class HealthCareRepository{ let allTypes = Set([HKObjectType.quantityType(forIdentifier: .bodyTemperature)!]) /// HKHealthStoreはアプリケーションあたり1インスタンス。1回生成したらそれを使い続ける必要あり var store:HKHealthStore? = nil func setup() -> Bool{ /// ipadではヘルスケア使えない /// https://developer.apple.com/documentation/healthkit/setting_up_healthkit /// Ensure HealthKit’s Availability if (HKHealthStore.isHealthDataAvailable() == false){ // ヘルスデータが無効状態 return false } /// ヘルスケア機能があり、有効である場合生成する self.store = HKHealthStore() return true } func postBodyTemperature(_ value:Double, unit:BodyTemperatureUnit, completion:@escaping (Bool, Error?) -> Void) -> Void{ /// https://developer.apple.com/documentation/healthkit/authorizing_access_to_health_data /// Request Permission from the User /// toShare: Write要求 /// read: Read要求 self.store!.requestAuthorization(toShare: allTypes, read: nil){ (success, error) in if !success{ completion(success, error) return } /// https://developer.apple.com/documentation/healthkit/authorizing_access_to_health_data /// Check for Authorization Before Saving Data let status = self.store!.authorizationStatus(for: .quantityType(forIdentifier: .bodyTemperature)!) switch status{ case .notDetermined: // "If you have not yet requested permission" // ここに入ることはないはず print("Not determined") completion(false, HKError(HKError.errorAuthorizationNotDetermined)) return case .sharingDenied:// If the user has denied permission // ユーザーが許可しなかった場合 print("Sharing Denied") completion(false, HKError(HKError.errorAuthorizationDenied)) break case .sharingAuthorized: // ユーザーが許可した場合 print("Sharing Authorized") break @unknown default: print("Unknown status.") break } // Datetime let now = Date() // 摂氏 or 華氏 let hkUnit:HKUnit switch unit { case .degreeCelsius: hkUnit = .degreeCelsius() case .degreeFahrenheit: hkUnit = .degreeFahrenheit() } let quantity = HKQuantity(unit: hkUnit, doubleValue: value) let obj = HKQuantitySample(type: .quantityType(forIdentifier: .bodyTemperature)!, quantity: quantity, start: now, end: now) self.store!.save(obj, withCompletion: completion) } } } 結果 適当なUI作って上記クラスを試した結果、シミュレータ上ではありますが無事に体温データをヘルスケアに登録することができました。大抵のことは公式Documentに書いてあることも実感できました。次回はUI予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ハッカソン】初めてモバイルハッカソンに参加してのいろいろ

はじめに 2021年10月23日〜10月24日に開催のモバイルハッカソンに参加して学んだことやチーム開発でのことをまとめていきます。 ハッカソン初参加でこれを書いている時点(10月14日)では何すればいいんだろうって感じですね ゆるく書いていくので、ハッカソンってこんな感じなんだってぐらいで見ていただければと思います。 10月14日 4人のチーム編成が行われた。 言語の技術スタックは大体以下のような感じ ・自分: Swift ・Aさん: Ruby on Rails, Python ・Bさん: HTML, JavaScript, PHP ・Cさん: Laravel, Vue, React どうでしょうか?みなさんバラバラですね笑 早速、Discordで自己紹介、作るアプリのテーマ決め。 slackのmiroというサービスで、課題重視、難易度の軸を作り、四人で出したアプリ案を分けて作るアプリの方向性を決めた。 10月15日 特になし 10月16日 10月17日 10月18日 10月19日 10月20日 10月21日 10月22日 10月23日(本番1日目) 10月24日(本番2日目) おわりに
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ハッカソン】初めてハッカソンに参加してのいろいろ

はじめに 2021年10月23日〜10月24日に開催のモバイルハッカソンに参加して学んだことやチーム開発でのことをまとめていきます。 ハッカソン初参加でこれを書いている時点(10月14日)では何すればいいんだろうって感じですね ゆるく書いていくので、ハッカソンってこんな感じなんだってぐらいで見ていただければと思います。 10月14日 4人のチーム編成が行われた。 言語の技術スタックは大体以下のような感じ ・自分: Swift ・Aさん: Ruby on Rails, Python ・Bさん: HTML, JavaScript, PHP ・Cさん: Laravel, Vue, React どうでしょうか?みなさんバラバラですね笑 早速、Discordで自己紹介、作るアプリのテーマ決め。 slackのmiroというサービスで、課題重視、難易度の軸を作り、四人で出したアプリ案を分けて作るアプリの方向性を決めた。 10月15日 10月16日 10月17日 10月18日 10月19日 10月20日 10月21日 10月22日 10月23日(本番1日目) 10月24日(本番2日目) おわりに
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Core Dataでのデータ更新時にnewBackgroundContextを使う場合にはperform/performAndWait内でそのコンテキストに対して実行する

はじめに Core Dataの並列実行時にXcodeのデバッグオプションとして-com.apple.CoreData.ConcurrencyDebug 1が存在する。このオプションを有効にしておくとCore Dataの間違った使い方をすると実行時エラーになって止めてくれる。 アプリをリリースしていると、動いているんだけどたまにクラッシュするエラーログがあるが、そういうものを事前に見つけてくれる(いや、オプションとかなくても開発時からクラッシュしてくれたらもっと良い気がするんだけど)。 実際にどんな使い方が間違っているかについて書こうと思う。AppleのサンプルコードであるHandling Different Data Types in Core Dataを見たらnewBackgroundContextでperformのちょうどよいサンプルがある。 最初に結論 NSPersistentContainerのメソッドnewBackgroundContextを使う場合 perform内でコンテキストへの命令を実行する必要がある fetchRequest自体の組み立てはperform/performAndWait外でも別スレッドでもなんでも良い performだけでなくperformAndWaitでも同じ NSPersistentContainerのプロパティviewContext perform/perform関係なくエラーが出ない 表現を変えると 別スレッドでDB操作する際 newBackgroundContext()で取得したコンテキストへの命令はすべてperform/performAndWait内で行う サンプルを観察した内容からの推測 viewContextを別スレッドから操作してもいい? Appleのサンプルではエラーにならなかったけどメインキュー(メインスレッド)以外で使うのは避けたほうが良い メインキューでnewBackgroundContext()のコンテキストを操作していい? この記事で紹介しているAppleのサンプルHandling Different Data Types in Core Dataではperformを前提にそうしているので問題なさそう NSPersistentContainerのnewBackgroundContext()メソッドでの結果 perform内でコンテキストへの命令を実行する必要がある NSPersistentContainerのnewBackgroundContext()メソッドで作成したCore Dataコンテキストで保存する場合はコンテキストのperformを実行することになるが、そのためのfetchRequestを実行するのもperform内である必要がある。 AppleのサンプルコードであるHandling Different Data Types in Core Dataから、サンプルコードを引用したものを以下に示す。ちなみに引数として渡されるcontextはNSPersistentContainerのnewBackgroundContext()メソッドで作成したもの。 // サンプルのまま。問題ない。 static func generateSampleDataIfNeeded(context: NSManagedObjectContext) { context.perform { // 1. performクロージャの中でcontextに対してfetchRequestを実行させる必要がある guard let number = try? context.count(for: Book.fetchRequest()), number == 0 else { return } for day in stride(from: 1, to: 365, by: 7) { dateComponents.day = day self.generateOneNewBook(with: dateComponents, context: context) } do { try context.save() } catch { print("Failed to saving test data: \(error)") } } } 上記サンプルコードは問題ないが、問題あるようにしたのが次のコード。performメソッドの外でコンテキストのcountを呼び出すものを示す。 // 駄目なパターン static func generateSampleDataIfNeeded(context: NSManagedObjectContext) { // 1. performクロージャ外でcontextに対してメソッドを実行させると駄目。 guard let number = try? context.count(for: Book.fetchRequest()), number == 0 else { return } context.perform { for day in stride(from: 1, to: 365, by: 7) { dateComponents.day = day self.generateOneNewBook(with: dateComponents, context: context) } do { try context.save() } catch { print("Failed to saving test data: \(error)") } } } ちなみにfetchRequestが駄目なわけではない。念の為下記に問題ないパターンとして示す。 // 問題ないパターン static func generateSampleDataIfNeeded(context: NSManagedObjectContext) { // 1. fetchRequestを組み立てるだけなのはまったく問題ない。 let request = Book.fetchRequest() context.perform { guard let number = try? context.count(for: request), number == 0 else { return } for day in stride(from: 1, to: 365, by: 7) { dateComponents.day = day self.generateOneNewBook(with: dateComponents, context: context) } do { try context.save() } catch { print("Failed to saving test data: \(error)") } } } performAndWait内でもコンテキストへの命令を実行する必要がある ここまで書いて、「peformメソッドは非同期実行されるから、つまり呼び出し元スレッドでcontext.countしてperformで別スレッドに切り替えてるからや!」という人もいるかもしれない。それはそれで正しいとは思うものの、じゃあサンプルのperformを同期メソッドのperformAndWaitにしてみてもやはり駄目なので結果performとperformAndWaitには違いが見られない。サンプルを下記に示す。 // サンプルのperformをperformAndWaitにしたが関係ない static func generateSampleDataIfNeeded(context: NSManagedObjectContext) { // 1. performAndWaitクロージャ外でcontextに対してメソッドを実行させると駄目。 // ここはメインスレッド guard let number = try? context.count(for: Book.fetchRequest()), number == 0 else { return } context.performAndWait { // 2. ここのスレッドもメインスレッド // メインスレッドでperformAndWaitするやつはいないとは思う。 for day in stride(from: 1, to: 365, by: 7) { dateComponents.day = day self.generateOneNewBook(with: dateComponents, context: context) } do { try context.save() } catch { print("Failed to saving test data: \(error)") } } } NSPersistentContainerのviewContextプロパティでの結果 perform viewContextは何やってもエラーにしてくれない。viewContextでもエラーを出したら良いのになあ。 performの場合のサンプルを下記に示す。 static func generateSampleDataIfNeeded(context: NSManagedObjectContext) { // 1. performAndWaitの外に出してcontextに対してfetchRequestする guard let number = try? context.count(for: Book.fetchRequest()), number == 0 else { return } context.perform { // 2. ここは別スレッドなんだけどなあ for day in stride(from: 1, to: 365, by: 7) { dateComponents.day = day self.generateOneNewBook(with: dateComponents, context: context) } do { try context.save() } catch { print("Failed to saving test data: \(error)") } } } performAndWaitでも結果は変わらない。別スレッドから呼び出してもエラーにならない。ドキュメントにはThe main queue’s managed object context.って書いてあるんだったら厳し目でも良い気がする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【詳解】Firebase iOS SDKをSwift Package Manager (SwiftPM)で導入する

? FirebaseのSwift Package Manager対応 Xcode 11よりSwift Package Manager (以下、SwiftPM)でライブラリなどの依存をXcodeのGUI上で管理できるようになりました。 かねてよりbetaとしてSwiftPM対応していたFirebaseもv8.0.0よりついに正式に対応されました SwiftPMはApple純正ツールであり、Xcodeとシームレスにライブラリ管理を実現できる点はCocoaPodsやCarthageと一線を画します。 ビルドのための下準備が不要で、プロジェクトのビルドを実行すれば依存パッケージもそのフローの中で追加されます。 そのため、特に新規のライトなiOSプロジェクトなんかでは第一の選択肢に上がってくるのではないでしょうか。 そんな、SwiftPMを用いてFirebase iOS SDKを導入する手順を示したいと思います! ? 導入手順 もちろん、GoogleのFirebase Documentationにもしっかりとドキュメントが存在しています。 Install Firebase with Swift Package Manager  |  Firebase Documentation 基本的にはそれを読めばOKなのですが、色々と端折られていたり、実際のユースケースにおいてつまづく箇所なども少なくありません。 そういったよくあるシチュエーションの手順も含めた情報として機能すればと思い、筆を走らせています。 ? 今回のシチュエーションとゴールについて 今回は、よくあるDebug / Releaseの2つのBuild Configurationを持ったプロジェクトに対し、Firebaseのサービス群の中でもほとんどのアプリで入れることになるであろう、Google AnalyticsとCrashlyticsを導入することをゴールとしたいと思います。 それでは、さっそくいってみましょう! 1. SwiftPMでのPackage追加 実は、SwiftPMでのPackage追加方法には下記の2つの方法があります。 Xcode上からGUIで導入する Package.swift に記述して導入する 今回は、Xcode上からGUIで導入する手順で説明します。 Package.swift を用いたこの手順については、公式のFirebase Documentationに記載されているのでそちらを参考にして導入後、次の手順からお読みください まずは、Firebaseを導入したいプロジェクトファイルをXcodeで開き、メニューバーよりAdd Packagesを選択します。 すると、下記のようなウィンドウが現れるので、右上のサーチバーにフォーカスを合わせ、下記のGitのURLをペーストします。 https://github.com/firebase/firebase-ios-sdk.git すると、firebase-ios-sdkが表示されるので、Dependency RuleとAdd to Projectに任意のものを選択して右下のAdd Packageを選択します。 なお、Dependency RuleはCocoaPodsやCarthageと同じようなメジャーバージョンが上がらない最新を入れるための記述 Up to Next Major Version や直接BranchやCommitを指定することができるなど柔軟なルールが選択可能です。 今回は、デフォルトのUp to Next Major Versionかつminimumに8.0.0を指定した状態とします。 詳しい内容が気になる方はこちらをご覧ください。 Adding Package Dependencies to Your App | Apple Developer Documentation Add Packageを選択後、インジケーターが表示されパッケージのフェッチが行われるので、しばらく待ちます。 フェッチが完了すると、Package Productを選択する画面が表示されます。 今回は、FirebaseAnalyticsWithoutAdIdSupportとFirebaseCrashlyticsにチェックし、Add Packageを選択します。 FirebaseAnalyticsWithoutAdIdSupportは、IDFAが不要(iOS 14.5のApp Tracking Transparencyの対応が不要)なFirebaseAnalyticsのパッケージです。 詳しくは下記のDocumentationをご覧ください。 Get started with Google Analytics  |  Firebase Documentation すると、Project Navigatorの下部にPackage Dependenciesという欄が表示され、諸々の依存がプロジェクトにインストールされます。 これで、この手順は完了となります。 ここまで一行もコードを書いてないですね 2. Firebaseプロジェクトを開発用/本番用の2つ作成する Google AnalyticsやCrashlyticsといった解析系サービスは、開発用と本番用で分けておくことに越したことはありません。 開発中のAppの各種イベントやクラッシュがプロダクトの方に紛れてしまっては、解析や集計に支障が出る恐れがあるからです。 そこで、今回は開発用および本番用の2つのFirebaseプロジェクトを作成していきます。 2.1 開発(Debug)用のBundle Identifierを作成する Firebaseプロジェクトを作る前に、やっておくことがあります。 それは、Debug用のBundle IdentifierをRelease用と分けることです。 これによって、異なるFirebaseプロジェクトを設定することが可能になります。 下記の画像にならって、DebugコンフィグのBundleIDを書き換えます。 一応手順をテキストでも起こしておきます。 Project Navigatorからプロジェクトを選択 TARGETS下のAppを選択 Build Settingsタブを選択 検索バーから bundle identifier と入力し、Return Packaging配下のProduct Bundle IdentifierのPulldownを選択 Configurationごとの設定が可能になるので、DebugのBundleIDのsuffixに.debugをつける(任意の名前でOKです) Appの中にExtensionなど複数のTargetが存在する場合は、同じ手順をTarget分、繰り返します。 この際、watchOSに対応しているアプリなどはWatck Kit Companion Bundle IdentifierといったInfo.plistに直接BundleIDを記述している場合がありえます。その場合、DebugコンフィグのBundleIDを分けると突合できずにBuildに失敗してしまうので、User Defined Settingsに変数を作成して定義し、Info.plistのValueにはそれを指定し、定義した変数の値はConfigurationごとに設定するといった手順が必要になる場合があるので、覚えておくと良いでしょう。 2.2 Firebaseプロジェクトの作成 / Google Analyticsの追加 この手順は開発用と本番用で2回同じ動作を行います。それぞれ読み替えて手順を行ってください。 Firebaseコンソールを開き、プロジェクトを追加を選択します。 開発用は<プロジェクト名>-dev、本番用は<プロジェクト名>といった名前で作成し続行を選択しましょう。(もちろん名前は任意です) 続いて、Google Analyticsを有効にするか聞かれるので今回は導入するためチェックのまま続行します。 Google Analyticsのアカウントをpulldownより選択します。アカウントがなければ新しいアカウントを作成を選択して新規作成します。 選択できたらプロジェクトを作成を選択するとプロジェクトの作成が完了します。 この画面が表示されたら続行を押すとプロジェクトのコンソール画面が開きます。 コンソール画面が開いたらサイドバーよりDashboardを選択しAnalyticsの画面に遷移します。 そして、iOSを選択してアプリを追加します。 iOSアプリへのFirebaseの追加の画面に遷移するので、それぞれの項目で必要な手順を以下の表に示します。 Firebase上に表示されてる手順に従わず、必ず下記表の手順を実施するようにしてください。 この手順1はFirebase上の案内通りに行ってOKです。 続いて、手順2ではファイルをダウンロードしたら、開発用はGoogleService-Info-Debug.plistに、本番用はGoogleService-Info-Release.plistにリネームしておきます。 プロジェクトへの追加はまだやらなくて良いです。 手順3、4、5は何もせず次の手順へ進んでOKです。 一応、上記の手順を表にまとめましたので、参考にしてください。 No. 手順 1 アプリの登録 Firebase上の手順通り 2 設定ファイルのダウンロード GoogleService-Info.plistをダウンロードし次へファイルは開発用はGoogleService-Info-Debug.plistに本番用はGoogleService-Info-Release.plistにリネームしておく(後の手順でプロジェクトにファイルを配置するのでここではしない) 3 Firebase SDK の追加 何もせず次へ 4 初期化コードの追加 何もせず次へ 5 次のステップ コンソールに進むを選択 これでFirebaseプロジェクトの作成及びAnalyticsの追加は完了です。 2.3 Crashlyticsの追加 こちらの手順も開発用/本番用のそれぞれのプロジェクトで実施してください。 サイドバーからCrashlyticsを選択し、Crashlyticsの画面に遷移したらCrashlyticsを有効にするを選択します。 手順はこれだけです。(実際にクラッシュを飛ばす手順は後で示すので何もしなくて良いです) これで、Firebase側の設定は全て完了となります。 3. アプリ上でFirebaseを起動する 最後に実際にプロジェクトをビルドしてFirebaseによる解析を開始する手順を示します。 3.1 GoogleService-Info.plistの配置 Analyticsの追加の際のFirebase上の手順には、下記のようにGoogleService-Info.plistファイルをXcodeプロジェクトのルートに移動してくださいという説明がありましたが、この手順を本手順では実施しませんでした。 その理由は、開発用と本番用の2つのplistファイルを同居させたいからでした。 まずは、2.2の手順で実施しておいたリネーム済みの開発用 (GoogleService-Info-Debug.plist) と本番用 (GoogleService-Info-Release.plist) の2つのplistファイルをプロジェクトに追加します。 この際、Xcodeプロジェクトのメインターゲットのルートディレクトリから Firebase というディレクトリを切って、その中にドラッグアンドドロップします。 なお、このディレクトリの名前や構成は任意のもので構いません。 下記のように配置できればOKです。 続いて、配置したファイルがビルド時にターゲットのルートに配置されるようなScriptを配置します。 Build Phases上でRun Script Phaseを追加することによって、環境ごとのビルド時に該当のplistファイルがルートに配置されます。 以下のようにして、Run Script Phaseをプロジェクトに追加します。 Scriptはこんな感じです。 (ディレクトリ構成を任意のものにした場合はパスを読み替えて記述してください) if [ "${CONFIGURATION}" == "Release" ]; then cp "${PROJECT_DIR}/${PROJECT_NAME}/Firebase/GoogleService-Info-Release.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist" else cp "${PROJECT_DIR}/${PROJECT_NAME}/Firebase/GoogleService-Info-Debug.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist" fi このScriptを貼り付け、下記のようになっていればOKです。(Run Scriptの名前は任意です) なお、本手順は下記の記事を参考にさせていただきました 開発版とリリース版でFirebaseのGoogleService-Info.plistを切り替える - Qiita 3.2 Google Analytics用の設定の追加 さて、再掲ですが、こちらのドキュメントには注釈 (Product Specific Considerations) が書いてあります。 Install Firebase with Swift Package Manager  |  Firebase Documentation 案内の通り、-ObjC linker flagを追加していきます。 Google Analytics requires adding the -ObjC linker flag to your target's build settings. 下記のようにして、 Other Linker Flags に -ObjC flag を追加してあげればOKです。 3.3 Crashlytics用の設定の追加 つづいて、Crashlytics用の設定を追加していきます。 Product Specific Considerations には下記のような記載があります。 Crashlytics requires you to upload debug symbols. You can use a run script for Xcode to automatically upload debug symbols post-build. Find the run script here: ${BUILD_DIR%Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run 3.1の手順と同じやり方でRun Script Phaseを追加します。 下記のようなScriptが設定できていればOKです。 また、Crashの解析にはdSYMファイルが必要となりますが、DebugビルドはデフォルトではdSYMを生成する設定となっていません。 下記の手順でDebugビルドでもdSYMを生成するように debug information format を DWARF with dSYM File に変更しましょう。 3.4 アプリ起動時にFirebaseを起動させる こちらの手順も先ほど実施しなかった手順となります。 AppDelegate の起動時に最初に通るDelegateメソッドの didFinishLaunchingWithOptions に下記のコードを加えます。 AppDelegate.swift import Firebase // Firebase関連の処理を呼びさせるようにimport ... final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // Firebaseの起動 FirebaseApp.configure() return true } } SwiftUI Appを利用しているプロジェクトでは、AppDelegateがない場合もあるかと思います。 App.swift の中に下記のような設定を加えることで、AppDelegateにライフサイクル処理を委譲できるオプションがあるので、それを利用するのが良いでしょう。 SampleApp.swift import SwiftUI import Firebase @main struct SampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate var body: some Scene { WindowGroup { ContentView() } } } final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { // Firebaseの起動 FirebaseApp.configure() return true } } これで全ての準備が完了です!お疲れ様でした。 4. 動作検証 お待たせしました。いよいよ動作検証に入っていきます。 4.1 Google Analyticsの動作確認 アプリを起動するだけでAnalytics上にイベントが自動的に飛ぶようになっています。 まずはプロジェクトをDebugコンフィグでBuildしてRunしましょう。 しばらくしたら、Firebaseコンソール上からDashboardを開いて確認すると、データの反映を確認できると思います。 (反映されない場合は、アプリをバックグラウンドにしてからフォアグラウンドに復帰させるなどの手順をお試しください) 念の為、Releaseコンフィグでも同様にちゃんとデータが反映されるか確認するのが安全です。 first_open のイベントが飛んでいるのが確認できます 4.2 Crashlyticsの動作確認 Crashlyticsの確認には、Crashを発生させないことには話になりません。 そこで簡単なCrashの発生方法を示しておきます。 UIKit Appの場合 UIKitは公式に書いてあるやり方でやるのがいいでしょう。 Get started with Firebase Crashlytics  |  Firebase Documentation ViewController.swift import UIKit final class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let button = UIButton(type: .roundedRect) button.frame = CGRect(x: 20, y: 50, width: 100, height: 30) button.setTitle("Test Crash", for: []) button.addTarget(self, action: #selector(self.crashButtonTapped(_:)), for: .touchUpInside) view.addSubview(button) } @IBAction func crashButtonTapped(_ sender: AnyObject) { let numbers = [0] let _ = numbers[1] } } SwiftUI Appの場合 SwiftUIのケースも載せておきます。 ContentView.swift import SwiftUI struct ContentView: View { var body: some View { Button( action: { let numbers = [0] let _ = numbers[1] }, label: { Text("Test Crash") } ) } } 上記の実装を行ったら、実際にBuildしてRunし、一度Debugは中止します。 その後、Debugせずに普通にアプリを起動させ、このCrashを意図的に発生させるようにボタンをタップします。 そしてさらに、もう一度アプリを起動ししばらく待ちます。 Crashをさせた後、再度アプリを起動してしばらく経過させるのがコツとなります。このタイミングでCrashの情報がFirebase上に送信されます。もし送信されてなさそうであればアプリをバックグラウンドにしてからフォアグラウンドに復帰させるなどの手順をお試しください。 下記のようにCrashlyticsの画面に該当のクラッシュ情報が送信されたのが確認できました! Crashlyticsも同様にReleaseビルドでも動作検証をしておくことをオススメします。 おわりに いかがだったでしょうか。 だいぶ詳細に説明したのでかなり長くなってしまいましたが、公式にはない実際のユースケースに沿った手順も含めて示すことができたので、ハマりやすい罠もだいぶ踏み抜けたような気がします。 この資料がどなたかの役に立てば幸いです!お読みいただきありがとうございました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIImageの画像の大きさを変えるExtension(拡張)を作る

結論から書くとこんな感じ。 import UIKit extension UIImage { func resize(size _size: CGSize) -> UIImage? { let widthRatio = _size.width / size.width let heightRatio = _size.height / size.height let ratio = widthRatio < heightRatio ? widthRatio : heightRatio let resizeSize = CGSize(width: size.width * ratio, height: size.height * ratio) UIGraphicsBeginImageContextWithOptions(resizeSize, false, 0.0) draw(in: CGRect(origin: .zero, size: resizeSize)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage } } 使い方 class BottomButtonView: UIView { var button: UIButton? init(frame: CGRect, width: CGFloat) { super.init(frame: frame) button = UIButton(type: .system) //                                                                      ↓これ button?.setImage(UIImage(named: "")?.resize(size: .init(width: width * 0.4, height: width * 0.4)), for: .normal) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } }   拡張機能なので、元々UIImageで準備されている関数に欲しい関数がなかったので新しく関数を加えました。 class BottomButtonView: UIViewみたいな感じでUIViewのクラスを作成しているのは、storyboardなしでMVVMの練習も兼ねて行ったためです。 誰かの参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む