20210412のiOSに関する記事は10件です。

App Storeリリース時に提出するスクショを作成する時に役立つサイト集

はじめに いつも、検索かけるのでQiitaにまとめます。完全自分用です。 端末の画像の入手 Marketing Resources and Identity Guidelines | Apple Developer 提出に必要なスクリーンショットの枚数とサイズの確認 スクリーンショットの仕様 | App Store Connect ヘルプ Simulatorのスクリーンショット方法 // ターミナルにて xcrun simctl io booted screenshot --mask black <画像名>.<拡張子> スクリーンショット後の端末の角やノッチ部分の加工を行う bannerkoubou 画像の背景を削除する 画像の背景を削除 - remove.bg JPGに変換したり、PNGに変換したりなど iloveIMG | 画像編集用オンラインツール おわりに 良かったら参考にして下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】UserDefaultsをもう少しちゃんと理解する

「UserDefaults消したーーーーい」と思って調べるとこのようなコードと出会った。 let appDomain = Bundle.main.bundleIdentifier UserDefaults.standard.removePersistentDomain(forName: appDomain!) なんとなくBundle Identifierを取ってきて指定すると消せるんだなというのはわかるが、UserDefaultsを消すときにBundle Identifierが必要なのかとか色々考えてしまったのでUserDefaultsについて調べてみました。 バージョン Xcode: v11.3 Swift: v.5.3.2 iOS: v14.4 対象読者 iOSアプリ開発初心者 UserDefaultsをなんとなく使っている人 内容 UserDefaultsとは UserDefaultsはユーザーごとの設定など保存するために提供されたKey-Valueストアです。また、UserDefaultsに保存された値は、削除しない限り永続的に保存され続けます。 ご存知ではあると思いますが、ユーザー個人のデータを保存する簡易的なデータベースみたいな感じですね。 Bundle IdentifierとUserDefaultsの関係は? 本題です。 結論から言うと、UserDefaultsを介して保存されるアプリ毎の設定を格納するファイル名にBundle Identifierが使われるという関係です。つまり、UserDefaultsはデータの保存領域で、Bundle Identifierはその領域の識別子として使われるという関係です。 つまりどういう事? ここからはコードを使って説明します。 UserDefaultsは初めて書き込みが行われた時にアプリのRoot/Library/Preferencesに Bundle Identifier名.plistというファイルをデバイス内に生成します。 まず、アプリのRoot/Library/PreferencesにBundle Identifier名.plistが生成されていないことを確認しましょう。 Library/PreferencesまでのパスはFileManagerを使うことで取得できます。 ViewController.swift class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() getFileNamesFromPreferences() // 何も出力されない } func getFileNamesFromPreferences() { // Libraryまでのファイルパスを取得 let filePath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0] // filePathにPreferencesを追加 let preferences = filePath.appendingPathComponent("Preferences") // Library/Preferences内のファイルのパスを取得 guard let fileNames = try? FileManager.default.contentsOfDirectory(at: preferences, includingPropertiesForKeys: nil) else { return } // Library/Preferences内のファイル名を出力 fileNames.compactMap { fileName in print(fileName.lastPathComponent) } } } Bundle Identifier名.plistを生成してみる UserDefaultsに値を保存することでBundle Identifier名.plistが生成されるか実際に確認してみましょう。 ViewController.swift import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() getFileNamesFromPreferences() // 何も出力されない + UserDefaults.standard.set("Value1", forKey: "test") // UserDefaultsに値を保存 + getFileNamesFromPreferences() // com.test.UserDefaults.plist } func getFileNamesFromPreferences() { // Libraryまでのファイルパスを取得 let filePath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0] // filePathにPreferencesを追加 let preferences = filePath.appendingPathComponent("Preferences") // Library/Preferences内のファイルのパスを取得 guard let fileNames = try? FileManager.default.contentsOfDirectory(at: preferences, includingPropertiesForKeys: nil) else { return } // Library/Preferences内のファイル名を出力 fileNames.compactMap { fileName in print(fileName.lastPathComponent) } } } お手元で実行してみるとプロジェクトのBundle Identifier名.plistが保存されていることがわかると思います。 ちなみにpreferencesの値を使ってターミナルからも確認できます。 removePersistentDomain(forName:) UserDefaultsに書き込みを行った際に生成される.plistファイルはBundle Identifier名でドメイン(簡単に言うと領域, 区画みたいなやつ)として認識されるみたいで、ドメイン名を指定することでドメイン内の値をすべて消すことができるみたいです。 ViewController.swift import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() getFileNamesFromPreferences() // 何も出力されない UserDefaults.standard.set("Value1", forKey: "test") // UserDefaultsに値を保存 getFileNamesFromPreferences() // com.test.UserDefaults.plist + UserDefaults.standard.removePersistentDomain(forName: "com.test.UserDefaults.plist") // ドメイン内の値を全て削除 + getFileNamesFromPreferences() // com.test.UserDefaults.plist } func getFileNamesFromPreferences() { // Libraryまでのファイルパスを取得 let filePath = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0] // filePathにPreferencesを追加 let preferences = filePath.appendingPathComponent("Preferences") // Library/Preferences内のファイルのパスを取得 guard let fileNames = try? FileManager.default.contentsOfDirectory(at: preferences, includingPropertiesForKeys: nil) else { return } // Library/Preferences内のファイル名を出力 fileNames.compactMap { fileName in print(fileName.lastPathComponent) } } } 実行結果からわかるように、.plistファイル自体は削除されないみたいです。 終わりに 今回はUserDefaultsの内部構造に踏み込んでみました。 iOSの勉強は初めてからずっと心にあったモヤモヤが解消されてよかったです。 みなさんの参考になれば幸いです。 参考文献 swift - アプリ内で保存したファイルの一覧を取得したい - スタック・オーバーフロー Swift5でDocumentディレクトリのファイルにアクセスする - Qiita 【Swift】URLでパスを扱うときに便利なプロパティとメソッド - Qiita UserDefaults under the hood ??‍ | by Aaina jain | Swift India | Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

業務でSwiftUIを使って画面構築してみた話

業務の中でSwiftUIを使って画面構築出来る機会がありましたので、実際に構築し終えての所感をまとめておきたいと思います。 やったこと 昨年、アプリの対応バージョンがiOS13以上となったので、SwiftUIが導入できるようになりました。 ちょうどUITableViewControllerを利用して構築されていたヘルプページの改修を予定していたので、こちらをSwiftUIへ置き換えてみました。 簡単に構成を示しておきます。 画面構成 今回作成したViewは、Objective-Cで作成したUIKitのViewとSwiftのUIKitのViewに挟まれた場所にありました。 この構成が後に悩みのタネとなります。 クラス構成 MVPベースに作成していますが、Presenterの持つBindingされた値をSwiftUIのViewクラスへ渡すような構成となっているため、実質的にはMVVMのようになっています。 今回作成した画面の遷移元がUINavigationControllerを利用したViewControllerだったため、SwiftUIのNavigationViewを利用しての画面遷移実装ができませんでした(後述)。 ですので、画面遷移の実装はViewControllerで行わせつつ、レイアウト部分をSwiftUIのViewクラスで実装しています。 ? 良い点 コードがシンプル 今回作成したViewに関連する箇所のコードのみを抽出すると、以下のようになっていました。 UIKit: ViewController = 60行(UI関連のみ) + Storyboard(XML) SwiftUI: 75行(Preview除く) Storyboardを使わなくなる分、SwiftUIクラス側の記述が大きくなるかと想像していましたが、2〜3割くらいコードが増えるだけで済んでいます。 作業においては、レイアウト作成中にStoryboard/Xib ⇔ Swiftファイルを行き来する必要がないため、集中しやすいと感じました。 純粋なSwiftのコードで記述できるので、複数人で同一Viewを編集するような状況になってもStoryboard/Xibのような厄介なコンフリクトが起きる心配もないですね。 Previewでレビュー効率を上げる SwiftUIリリース時に導入されたPreviewの機能では、複数のPreviewを表示できます。 APIのレスポンスに応じて表示の切り替えを行うViewでは各StateごとのPreviewを表示することで開発効率を上げることができます。 また、レビュー時にも各Previewを参照することでレイアウトを確認できるので、モックの準備などが不要になり非常に効率的かと思います。 Previews.swift struct Sample_Previews: PreviewProvider { static var previews: some View { // プレビューを3つ並べる SampleView(presenter: successPresenter) SampleView(presenter: loadingPresenter) SampleView(presenter: failedPresenter) } // ローディング成功時のプレビュー static var successPresenter: SamplePresenter { let userInterface = SampleViewController() let presenter = SamplePresenter(userInterface: userInterface) presenter.state = .success(itemList: itemList) return presenter } // ローディング中のプレビュー static var loadingPresenter: SamplePresenter { let userInterface = SampleViewController() let presenter = SamplePresenter(userInterface: userInterface) presenter.state = .loading return presenter } // ローディング失敗時のプレビュー static var failedPresenter: SamplePresenter { let userInterface = SampleViewController() let presenter = SamplePresenter(userInterface: userInterface) presenter.state = .failed return presenter } // サンプル表示用のデータモデル static let itemList: [Item] = [ Item(id: 1, title: "問い合わせ1") ] } 表示例 ? 困る点 効率的な記述のできるSwiftUIですが、一方で既存アプリへの導入時にはちょっと困ったこともあります。 UINavigationControllerとNavigationViewの互換性がない UINavigationController&ViewControllerで構成されたViewからSwiftUIで構成されたViewへ遷移させたい場合、UINavigationControllerの内容をNavigationViewに引き継ぐことができません。 UINavigationControllerを使用したViewからNavigationViewを使用したSwiftUIのViewへ単純に遷移させてしまうと、NavigationBarが2重に表示されるような状態になります。 このようなケースにおいては、SwiftUIのNavigationViewとNavigationLinkを使わず、ViewControllerからUIHostingControllerを利用してSwiftUIのViewを呼び出していくのがベターかと思います。 Combine前提にしている処理が多々 例としてアラートのメソッドを挙げますが、こちらにはBinding型の引数があり、表示のコントロールを行うのにはController層、Presenter層などからBindingされた値を渡してあげる必要があります。 public func alert<Item>(item: Binding<Item?>, content: (Item) -> Alert) -> some View where Item : Identifiable public func alert(isPresented: Binding<Bool>, content: () -> Alert) -> some View UIKitだとViewControllerからpresentしてあげるだけでしたが、要領が変わっているので設計上注意が必要です。 SwiftUIでは、多くのクラスでデータバインディングを前提とした構成を採用しているため、現状ではMVVMが一番スムーズに導入出来るアーキテクチャなのではないかと思います。 (ラクマアプリはMVPアーキテクチャベースとなっているため、このViewだけイレギュラーな構成となってしまいました?) UIKitではデフォルトで用意されている部品が一部ない ActivityIndicator Accessory付きのButton PageControl(iOS13で非対応) etc... UIKitに存在した上記の部品たちはSwiftUIでは用意されていないので、UIKitのクラスをSwiftUIで使えるようラップして使えるようにするか、自作する必要があります。 下記のようなLoadingViewはSwiftUI用に新規作成しています。 Previews.swift struct LoadingView: View { var text: String = "読み込み中..." var body: some View { ZStack { Color(UIColor.clear) VStack { ActivityIndicator(style: .large) Text(text) .bold() .foregroundColor(Color(ColorPalette.textWhite)) .multilineTextAlignment(.center) }.frame(minWidth: 130, idealWidth: 130, minHeight: 130, idealHeight: 130) .compositingGroup() .background(RoundedRectangle(cornerRadius: 10) .fill(ColorPalette.indicatorBackground)) .shadow(radius: 6) .edgesIgnoringSafeArea(.all) } .transition(AnyTransition.opacity.animation(.easeOut(duration: 0.3))) } } struct ActivityIndicator: UIViewRepresentable { typealias UIViewType = UIActivityIndicatorView var style: UIActivityIndicatorView.Style = .medium func makeUIView(context: Context) -> UIActivityIndicatorView { let indicator = UIActivityIndicatorView(style: style) indicator.color = .white return indicator } func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) { uiView.startAnimating() } } iOS13⇔iOS14間で表示差分がある 細かなところですが、iOS13とiOS14の間で表示の異なる部分がありました。下記に一例を示します。 このあたりは実装時の確認や検証をしっかり行っていく必要がありますね。 表示時のアニメーションの有無 iOS13でListを使用した画面を表示する場合、デフォルトでのアニメーションが挿入されませんでした。 iOS13 iOS14 Listのデザイン iOS13ではListのgroupごとにカード型のような表示となっていますが、iOS14では従来のUITableViewの表示に準ずるデザインとなっています。 iOS13 iOS14 Previewが重い XcodeでのPreview利用時にObjective-Cを利用しているライブラリのビルドが頻繁に走るため、表示の更新に分単位の時間がかかることがありました。 (このあたりはM1搭載のMacでは速くなっているのでしょうか?) あとがき 既存アプリへのSwiftUIの導入にはいくつかの障壁がありますが、新規作成のViewであったり独立性の高い機能であればSwiftUIを積極的に選択肢に入れるのもアリかと思います? また、今後の導入に備えて既存のObjective-Cのコードを駆逐しておいたり、MVVMなどのアーキテクチャを導入しておいたりしておくことも大事かと思います。 導入のメリットも大きいですし、今後のUI構築はSwiftUIへシフトしていく可能性も高いと思いますので、積極的に使っていきたいですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ViewControllerのライフサイクルについて

はじめに ViewControllerのライフサイクルとオーバーライド時の適切な処理を備忘録として書きます。 ViewControllerのライフサイクル 以下のようになっており、上から順に呼ばれます。 ① func loadView() ↓ ② func viewDidLoad() ↓ ③ func viewWillApper(_:) ↓ ④ func viewWillLayoutSubviews(_:) ↓ ⑤ func viewDidLayoutSubviews(_:) ↓ ⑥ func viewDidAppear(_:) ↓ ⑦ func viewWillDisappear(_:) ↓ ⑧ func viewDidDisappear(_:) ①loadView() viewを生成するメソッドであり、1度だけ呼ばれます。 Storyboardやxibなど、InterfaceBuilderを使用している場合は このメソッドをオーバライドしてはいけません。 loadView() | Apple Developer Documentation ・適切な処理 コードでviewを作成する場合などに適しています。 その際には、super.loadView()は呼びません。 ②viewDidLoad() viewがメモリに読み込まれた後に1度だけ呼ばれます。 viewDidLoad() | Apple Developer Documentation ・適切な処理 viewに対する追加の初期化処理やネットワーク通信など1度だけ行う何らかの処理などが適しています。 基本的な初期化の処理は大体ここで行いますが、このタイミングではviewのframeが確定されていないので正確なframeを取得することが出来ません。 viewDidLayoutSubviews(_:)以降にviewのサイズに関連した処理を書く必要があります。 ③viewWillAppear(_:) viewが画面に表示される直前に呼ばれます。 loadView()やviewDidLoad()と違って 遷移やTab Bar切り替え時など画面が表示されようとする度に呼び出されます。 一方でホーム画面からの復帰や、コントロールセンターを閉じたりした時には呼ばれません。 viewWillAppear(_:) | Apple Developer Documentation ・適切な処理 viewが画面に表示される前に実行する必要のある処理や 画面を表示するたびにviewを更新したい場合などに適しています。 ④viewWillLayoutSubviews(_:) viewがSubviewをレイアウトする前に呼ばれます。 デフォルト実装では何もしません。 viewWillLayoutSubviews(_:) | Apple Developer Documentation ⑤viewDidLayoutSubviews(_:) viewがSubviewをレイアウトした後に呼ばれ、デフォルト実装では何もしません。 viewDidLoad()の部分で書いたように これ以降のタイミングではないと正確なframeは取得できません。 viewDidLayoutSubviews(_:) | Apple Developer Documentation ・適切な処理 viewのサイズに関係する処理などが適しています。 ⑥viewDidApper(_:) viewが画面に表示された後に呼ばれます。 このメソッドが呼ばれたタイミングでは、すでにviewが描画されている状態なので ここでviewの生成を行えば、おかしな事になったりするので注意が必要です。 viewDidAppear(_:) | Apple Developer Documentation ・適切な処理 アニメーション開始、動画の再生やデータのフェッチなど viewが画面に表示された後、すぐに必要となる処理を行うのに適しています。 ⑦viewWillDisappear(_:) viewが表示されなくなる直前に呼ばれます。 具体的には、画面遷移やTab Barによる切り替え時に呼ばれます。 viewWillDisappear(_:) | Apple Developer Documentation ⑧viewDidDisappear(_:) 遷移などでviewが完全に表示されなくなった時に呼ばれます。 viewDidDisappear(_:) | Apple Developer Documentation ・適切な処理 何らかの終了処理などをここで行うのに適しています。 おわりに loadView()やviewDidLoad()以外のメソッドはライフサイクルの中で複数回呼ばれるので、viewの追加や重い処理などには注意が必要です。 間違っていればコメントして下さると有り難いです! 参考資料 ViewControllerのライフサイクル iOS開発におけるUIViewControllerのライフサイクルイベントまとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】添字付けについて

はじめに 今回は添字付け(subscript)についてみていきたいと思います。 subscript subscriptとは、複数個のプロパティがある時に、配列のように、アクセスできるようにする機能です。配列以外のリストやデータ構造でも利用できます。 struct FoodMenu { let menu = ["menu1", "menu2", "menu3"] var submenu = ["submenu1", "submenu2", "submenu3"] var count: Int { menu.count + submenu.count } subscript(i: Int) -> String { get { return i < 3 ? menu[i] : submenu[i-3] } set { if i > 2 && i < 6 { submenu[i-3] = newValue } } } } var foodMenu = FoodMenu() for i in 0..<foodMenu.count { print(foodMenu[i]) } //menu1 //menu2 //menu3 //submenu1 //submenu2 //submenu3 foodMenu[i]としているので、subscribeのgetが呼ばれます。 i < 3 ? menu[i] : submenu[i-3]から、出力結果からもわかるように、配列menuと配列submenuが繋がったような配列foodMenuになっていますね。 次はsetを使ってみます。 var foodMenu = FoodMenu() foodMenu[1] = "set1" foodMenu[5] = "set5" for i in 0..<foodMenu.count { print(foodMenu[i]) } //menu1 //menu2 //menu3 //submenu1 //submenu2 //set5 "set1"は条件i > 2 && i < 6を満たさないのでsetできていないのがわかります。 また、以下のように、subscriptに対してもstaticを使うこともでき、その場合はFace[2]のようにアクセスすることができます。 struct Face { static subscript(i: Int) -> String { let index = (i < 0) ? 0 : (i > 4 ? 4 : i) let faces = ["?", "?", "?", "?", "?"] return faces[index] } } print(Face[3]) // ? おわりに おわりです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】mutatingとstaticについて

はじめに 今回はメソッドやプロパティによってインスタンスの値が変更される場合に用いられる、mutatingと全てのインスタンスから共通して利用したい場合に用いられるstaticについてみていきましょう。 mutating 先ほど、メソッドやプロパティによってインスタンスの値が変更される場合に用いられる、mutatingと言いましたが、実はこれ、構造体(struct)についてのみつけるキーワードになります。つまり、クラスには必要ありません。 例を見ていきましょう。 struct Clock { var hour = 0 var min = 0 mutating func advance(min: Int) { let m = self.min + min if m >= 60 { self.min = m % 60 // インスタンスの値が変更されている let t = self.hour + m / 60 self.hour = t % 24 // インスタンスの値が変更されている } else { self.min = m // インスタンスの値が変更されている } } mutating func inc() { self.advance(min: 1) } } プロパティを直接書き換えるメソッドのadvance(min:)にmutatingをつけています。さらに、そのメソッドを呼び出しているinc()メソッドにもmutatingが必要です。 mutatingをつけていないと以下のようなエラーがでてきてしまいます。 static staticがついたメソッドをタイプメソッドといいます。 staticは全てのインスタンスから共通して利用したい場合に用いられます。 struct SimpleDate { var year: Int var month: Int var day: Int static func isLeap(_ y: Int) -> Bool { return (y % 4 == 0) && (y % 100 != 0 || y % 400 == 0) } static func daysOfMonth(_ m: Int, year: Int) -> Int { switch m { case 2: return Self.isLeap(year) ? 29 : 28 // isLeap(year)だけでもいい case 4, 6, 9, 11: return 30 default: return 31 } } func leapYear() -> Bool { return SimpleDate.isLeap(year) // 構造体名.タイプメソッド } } staticのついたプロパティやメソッドにアクセスするときは、構造体名.タイプメソッドなどのようにしますが、同じ構造体内でstaticプロパティやメソッドどうしにアクセスする場合はSelf.タイプメソッドやSelfの省略もできます。 //isLeapもdaysOfMonthもstaticで同じ構造体なので、構造体名やSelfは書かなくても良い static func daysOfMonth(_ m: Int, year: Int) -> Int { switch m { case 2: return isLeap(year) ? 29 : 28 case 4, 6, 9, 11: return 30 default: return 31 } } しかし、daysOfMonthメソッドのstaticをとると以下のようなエラーになり、Self.isLeap()にするか、SimpleDate.isLeap()にしないといけません。 同様の理由で、leapYear()メソッドには構造体名またはSelfが必要になります。 おわりに おわりです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter×FirebaseでPJ始めるときにやること色々

FirebaseとFlutterを用いて開発することが多いのですが、毎回リリース直前になってFLAVORを分けたくなったり、セキュリティルールを検討し始めたりしてしまいます。 とくに複数人で開発していると、FLAVOR分けは他の人の開発の手を止めてしまう原因にもなってしまいますので、この辺の初期設定を脳死で出来るように手順化しておきます。 筆者は普段からWindowsとMacOSの両方で開発していますが、iOSのリリースも考えているなら、Macで全てやってしまうのがいいと思います。 今回使用したリポジトリは以下です。 この記事でやってること FirebaseプロジェクトをFlavor毎(Development/Production)に作成し、Flutterプロジェクトと紐づける セキュリティルールやCloud Functions、Hostingのための環境構築をDockerで行う LPやプライバシーポリシーをデプロイするためのFirebase Hostingの設定 Lint(pedantic)の導入方法 Flutter SDKのバージョン固定(fvmを使用) 筆者の環境 Windows > flutter doctor Doctor summary (to see all details, run flutter doctor -v): [√] Flutter (Channel stable, 1.22.5, on Microsoft Windows [Version 10.0.19041.867], locale ja-JP) [√] Android toolchain - develop for Android devices (Android SDK version 30.0.1) [!] Android Studio (version 4.1.0) X Flutter plugin not installed; this adds Flutter specific functionality. X Dart plugin not installed; this adds Dart specific functionality. [!] VS Code (version 1.54.3) X Flutter extension not installed; install from https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [!] Connected device ! No devices available ! Doctor found issues in 3 categories. MacOS Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 1.22.4, on macOS 11.2.3 20D91 darwin-x64, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2) [✓] Xcode - develop for iOS and macOS (Xcode 12.4) [!] Android Studio (version 4.1) ✗ Flutter plugin not installed; this adds Flutter specific functionality. ✗ Dart plugin not installed; this adds Dart specific functionality. [!] VS Code (version 1.53.0) ✗ Flutter extension not installed; install from https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [!] Connected device ! No devices available ! Doctor found issues in 3 categories. 事前準備 以下のことを事前に行っている前提で進めます Firebaseのスキーマ設計 Android StudioとXCodeのインストールおよびFlutterでの環境構築 VSCodeのインストール(Flutter extensionsは必須ではない) Android StudioからFlutterプロジェクト作成 Dockerが使える環境が整っている ※プロジェクト作成時にパッケージ名を決めるが、com.example のドメインは予め避けておいたほうが良い。自分が取得しているドメイン名や、GitHubのドメイン名(io.github.<ユーザー名>)に変更しておく。 また、このパッケージ名は設定を行う際によく使用するので控えておく。Firebaseに登録する際に躓かないように、英字、数字、ピリオドのみを使用して決めておく方が良い(_アンダースコアと-ハイフンはそれぞれiOSなら_、Androidなら-が使用不可なので、OS毎に別のパッケージ名となってしまうことに注意する) 今回はDevelopmentという開発環境と、Productionという本番環境の2環境を用意することにする。 Flavorごとのビルド設定 主に以下を参考に手順化しました。本記事では解説は行わないので、理由が知りたくなったらご参考ください。 環境設定 - KosukeSaigusa/recipe-app Flutterで環境ごとにビルド設定を切り替える — iOS編 - medium iOS側の設定 open ios/Runner.xcworkspaceでXCodeからプロジェクトを開く Runner>PROJECT>RunnerのDeployment Targetを10.0以上に変更しておく(現在のfirebase_coreのバージョンだと、ここが低いとビルドに失敗する) XCodeのRunnerの部分をクリックし、Manage Schemes...をクリックする 出てきた画面の左下の「+」をクリックしてTarget,Nameが以下のようなものを追加する Target Name Runner Development Runner Production 次にRunner>PROJECT>RunnerのConfigurationの下部の「+」をクリックし、設定を追加する。 Duplicate "Debug" ConfigurationをクリックしてDebug-Developmentを追加し、Duplicate "Release" ConfigurationをくりっくしてRelease-Productionを追加する。 次に左ペインで右クリックしNew File...をクリックする。Configuration Setting Fileを選択し、NEXTをクリックする。 Configuration Setting Fileの内容は以下の通り。2回繰り返す。 ファイル名 保存場所 Group Target Development.xcconfig ios/Flutter Flutter Runnerにチェック Production.xcconfig ios/Flutter Flutter Runnerにチェック Debug-Development.xcconfig ios/Flutter Flutter Runnerにチェック Release-Production.xcconfig ios/Flutter Flutter Runnerにチェック 各ファイルを以下のように編集する Development.xcconfig FLUTTER_FLAVOR=Development PRODUCT_BUNDLE_IDENTIFIER=<パッケージ名>.development DISPLAY_NAME=<開発環境のアプリ名> FLUTTER_TARGET=lib/main.dart Production.xcconfig FLUTTER_FLAVOR=Production PRODUCT_BUNDLE_IDENTIFIER=<パッケージ名> DISPLAY_NAME=<アプリ名> FLUTTER_TARGET=lib/main.dart Release-Production.xcconfig #include "Release.xcconfig" #include "Production.xcconfig" Debug-Development.xcconfig #include "Debug.xcconfig" #include "Development.xcconfig" Runner>PROJECT>RunnerのConfigurationsを以下のように設定します。 その後、Runner > PROJECT > Runner > Build Settings > Packaging の中の Product Bundle Identifierを以下のように、正しいパッケージ名を設定しておきます。 Runner > TARGET > Runner も同様に設定しておく。 次にAndroid Studioからinfo.plistを開き最後の行(</dict>と</plist>)の手前に以下を追記する。 info.plist <key>FlutterFlavor</key> <string>$(FLUTTER_FLAVOR)</string> <key>CFBundleDisplayName</key> <string>$(DISPLAY_NAME)</string> XCodeに戻り、Runner>Runner>Info.plistをクリックし、Bundle nameをリリースする際のアプリ名に変更する。(これはリリース前に行っても良い) FirebaseのGoogleService-Info.plistを追加する前に先に設定を行う。 Runner>Runnerで右クリックし、New Groupをクリック 名前をFirebaseとしておく Runner > Targets > Runner > Build Phases を開きNew Run Script Phaseをクリックする 作成されたRun Scriptをダブルクリックし、適当な名前に変更しておく(例えばRun Script for Firebase) 以下をコピーしてスクリプト欄に貼り付ける。 if [[ "${CONFIGURATION}" == "Debug-Development" ]]; then rm $PRODUCT_NAME/GoogleService-Info.plist cp $PRODUCT_NAME/Firebase/GoogleService-Info-Development.plist $PRODUCT_NAME/GoogleService-Info.plist echo "GoogleService-Info-Production.plist copied." elif [[ "${CONFIGURATION}" == "Release-Production" ]]; then rm $PRODUCT_NAME/GoogleService-Info.plist cp $PRODUCT_NAME/Firebase/GoogleService-Info-Production.plist $PRODUCT_NAME/GoogleService-Info.plist echo "GoogleService-Info-Production.plist copied." elif [[ "${CONFIGURATION}" == "Release" ]]; then rm $PRODUCT_NAME/GoogleService-Info.plist cp $PRODUCT_NAME/Firebase/GoogleService-Info-Production.plist $PRODUCT_NAME/GoogleService-Info.plist echo "GoogleService-Info-Production.plist copied." else echo "configuration didn't match to Development, Staging or Production" echo $CONFIGURATION exit 1 fi また、Output Fileに$SRCROOT/Runner/GoogleService-info.plistを追加しておく。 Android側の設定 android/app/build.gradle buildTypes { debug { debuggable true // 後の鍵の設定のために追加。現状はコメントアウトしておく // signingConfig signingConfigs.debug } release { // 後の鍵の設定のために追加。現状はコメントアウトしておく // signingConfig signingConfigs.release } } flavorDimensions "app" productFlavors { development { dimension "app" resValue "string", "app_name", "<開発環境のアプリ名>" applicationIdSuffix ".development" } production { dimension "app" resValue "string", "app_name", "<本番環境のアプリ名>" } } Android Studioでのbuild構成の編集 この項目は複数人での開発を行う場合には全員が行う必要がある項目です。 下図の「構成の編集」をクリックします 出てきたダイアログの左上にある「+」をクリックし、新規構成の追加からFlutterを選択します。 すでに存在するmain.dartのDart entrypointをコピーして新規作成した名称未設定のDart Entrypointにペーストします。 以下のように設定します。 名前(Name) 追加引数(Additional Arguments) Build flavor Debug-Development --debug --flavor development --dart-define=FLAVOR=development development Release-Production --release --flavor production --dart-define=FLAVOR=production production Firebase Projectの作成 Firebase ProjectをFlavor毎に作成します。 プロジェクト名-dev、プロジェクト名-prod などと分けておくとわかりやすいです。 iOSアプリの追加 下図のiOSをクリックします。 dev側はiOSのバンドルIDをパッケージ名.development、本番側はパッケージ名を入力し、アプリのニックネームはわかりやすい名前を入力しておきます。 アプリを登録 をクリックすると、GoogleService-Info.plistをダウンロードできるので、GoogleService-Info-Development.plistにリネームし、XCodeから Runner>Runner>Firebaseへコピーする 本番環境もプロジェクトを作成し、同様にiOSアプリを追加し、GoogleService-Info.plistをダウンロードしたのちに、GoogleService-Info-Production.plistにリネームし、XCodeから Runner>Runner>Firebaseへコピーしておく。 この時点でAndroid StudioからiOSでDebug-Developmentでデバッグビルドできることを確認しておく。 Androidアプリの追加 同様にしてAndroidアプリも追加しておく。 Androidパッケージ名を、dev側はパッケージ名.development、本番側は単にパッケージ名を入力し、アプリのニックネームを適当に入力する。 アプリを追加をクリックすると、google-service.jsonがダウンロードできるので、ダウンロードし、Android Studio から以下のようにフォルダを切って格納しておく。 またこのタイミングでAndroid側のFirebaseを使う設定と、flutterのパッケージの設定をしておきます。 Firebaseの指示に従って、以下をandroid/build.gradleに追加します。 android/build.gradle ・・・ buildscript { ext.kotlin_version = '1.3.50' repositories { google() // なければ追加する jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.3.4' // なければ追加する } } allprojects { repositories { google() // なければ追加する jcenter() } } ・・・ 加えて、android/app/build.gradleにも以下を追加します。 android/app/build.gradle ・・・ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply plugin: 'com.google.gms.google-services' // 追加する ・・・ そして、Firestore/Authenticationを使う前提として、pubspec.yamlに以下を追加します。 ※バージョンは公式ページを参照してください。 pubspec.yaml dependencies: flutter: sdk: flutter firebase_core: ^0.4.5 firebase_analytics: ^5.0.16 firebase_auth: ^0.14.0+9 cloud_firestore: ^0.12.11 追加したらpub getを実行してください。(私はいつも何故か2回実行しないとうまくいきません。) 下図のようにpubspec.yamlを開くと、右上にPub getの文字があるのでそちらをクリックします。 現在のバージョンではflutterのfirebaseパッケージを組み込むと必ずエラーが発生しますので、以下の対処をしておきます。 minSdkVersionを16から21に変更しておく defaultConfigにmultiDexEnabled trueの行を追加する。 参考:アプリの圧縮、難読化、最適化 - Android Developpers (公式) app/build.gradle defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "tech.tokkuengineer.flutter_firebase_sample" minSdkVersion 21 targetSdkVersion 29 versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true } setting.gradleに以下を追加 参考:【flutter】firebase登録時にsetting.gradeでのエラー - Qiita android/setting.gradle def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ここまで出来たら、エミュレータをAndroidのイメージに変えてDebug-Developmentでビルドできるか確認します。 Dockerでのfirebase環境の構築 に入る前に、Firebaseプロジェクトの設定を行っておきます。 Firebase プロジェクトの画面からFirestoreをクリック Cloud Firestoreの画面からデータベースの作成をクリック データベースの作成画面でモードを選択 ※本番環境モードで開始すると、セキュリティルールを適切に設定するまでFirestoreの書き込みが全て拒否されます。開発環境であれば、まずはテストモードで開始することをお勧めします。 Cloud Firestoreのロケーションを選択する。好みではありますが、asia-northeast1を選択しておけば良いかと思います。 Dockerを使った環境構築 ここからはVSCodeで作業していきます。 firebase/Dockerfile Dockerfileとdocker-compose.yamlファイルを作成し、以下のように記載します。 GitHubリポジトリに用意してあります。 firebase\firebase-cli\Dockerfile FROM node:latest # Define working directory WORKDIR /workdir # Firebase toolsのインストール RUN apt-get -y update && apt-get install -y sudo openjdk-11-jdk RUN npm install -g firebase-tools # Expose ports ENV HOST 0.0.0.0 EXPOSE 4000 EXPOSE 5000 EXPOSE 5001 EXPOSE 8080 EXPOSE 9005 docker-compose.yaml version: "3" services: firebase-cli-container: build: ./firebase-cli container_name: firecli-container volumes: - ../firebase:/workdir これらの準備が出来たら、cd firebaseでフォルダを移動し、docker-compose upコマンドでコンテナをビルドします。 PS C:\Users\XXXX\AndroidStudioProjects\flutter_firebase_sample\firebase> docker-compose up Building firebase-cli-container failed to get console mode for stdout: The handle is invalid. => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 32B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/node:latest 1.0s => [1/3] FROM docker.io/library/node:latest@sha256:096cbc2667f5d1507d59d 0.0s => CACHED [2/3] WORKDIR /workdir 0.0s => CACHED [3/3] RUN npm install -g firebase-tools 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:9e31128f3f52b134c129a9f91f13f5bb91c3c9cd482f3 0.0s => => naming to docker.io/library/firebase_firebase-cli-container 0.0s Successfully built a9f91f13fc9e311c31128f32fb1349c9cd482f3525bb9c15ecb15b874b35c88a WARNING: Image for service firebase-cli-container was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Creating firecli ... done Attaching to firecli firecli exited with code 0 成功したら、以下のコマンドでコンテナを起動します。 docker-compose run -p 4000:4000 -p 5000:5000 -p 5001:5001 -p 8080:8080 -p 9005:9005 firebase-cli-container /bin/bash 起動したら、コンテナ内に接続されているので、そのままfirebaseの初期設定をします。 まずはfirebaseにログインします。 > firebase login i Firebase optionally collects CLI usage and error reporting information to help improve our products. Data is collected in accordance with Google's privacy policy (https://policies.google.com/privacy) and is not used to identify you. ? Allow Firebase to collect CLI usage and error reporting information? (Y/n) そのままEnterを押すとURLが表示されるので、Ctrlを押しながらクリックして開きます。 アカウント選択の画面で、ログインするユーザをクリックします。 Firebase CLIからのアカウントアクセスのリクエスト画面が出るので、許可をクリックします。 以下のような画面が出たら成功です。 VSCodeに戻って(コンテナ内のコンソールに戻って)firebase initを実行します。 以下のような画面でFiresotre、Functions、Hosting、Emulatorsを選択します。 ※選択するにはスペースを押します。 後は基本的にデフォルトで進めます。 途中Firebaseプロジェクトを聞かれるので、先の手順で作成したプロジェクトのdev側を選択しておきます。 firebase initが完了したらfirebase.jsonを以下のように修正します。 firebase.json { "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, "functions": { "predeploy": [ "npm --prefix \"$RESOURCE_DIR\" run lint", "npm --prefix \"$RESOURCE_DIR\" run build" ], "source": "functions" }, "hosting": { "public": "public", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ] }, "emulators": { "functions": { "host":"0.0.0.0", "port": 5001 }, "firestore": { "host":"0.0.0.0", "port": 8080 }, "hosting": { "host":"0.0.0.0", "port": 5000 }, "ui": { "enabled": true, "host":"0.0.0.0", "port":4000 } } } ここまで出来たらエミュレータを起動してみます。 コンテナ上で以下のコマンドを実行します。 firebase emulators:start ブラウザでhttp://localhost:4000 にアクセスし、以下のような画面が表示されたら成功です。 Firebase Hostingの設定 Firebase Authenticationを使ってユーザー認証を行っていると、App Storeへの申請時にプライバシーポリシーが必要となります。 せっかくFirebaseを使っているので、プライバシーポリシーとついでにアプリのLPをFirebase Hostingで用意しましょう。 プライバシーポリシー、利用規約作成 ついでにLP作成 とはいえ、上記のDockerでの環境構築の手順で環境構築は済んでいます。 firebase/publicがドキュメントルートになりますので、ここにLPなりプライバシーポリシーなりを作成して、デプロイすればOKです。 実際のコンテンツ作成はこの記事では割愛しますが、デプロイは以下のコマンドを用いて可能です。 firebase deploy --only hosting また別のFirebaseプロジェクトにデプロイする場合は、 firebase projects:list でプロジェクトの一覧を表示したあと、表示されたProject IDを指定して以下のコマンドを実行するとプロジェクトを切り替える事ができます firebase use <Project ID> この後firebase deployコマンドでデプロイすればプロジェクトが切り替わった状態でデプロイされます。 Lintの追加 これも後から入れるよりも最初に入れた方が良いので、pedanticを導入します。 pubspec.yamlに以下を追加します。 pubspec.yaml dev_dependencies: pedantic: ^1.11.0 こちら(公式リポジトリ)にある最新のanalysis_options.yamlをコピーしてプロジェクトの直下に置いておきます。 本記事執筆時点では1.11が最新でした。 analysis_options.yaml もちろんお好みでカスタムてOKです。(私はいつもこの方法でコピーするだけにしてます。) Flutter SDKのバージョン固定 共同開発を行っていると、Flutter SDKのバージョンを固定したくなる場合があります。これもプロジェクトの初期の段階で導入を済ませてしまった方が良いです。 バージョン固定するためにfvmというパッケージを使います。 fvmを導入するために、FlutterにバンドルされているDartとは別にDartをインストールします。 公式のインストール方法 Windows 管理者権限でPowerShellを開き、Chocolatoryを以下のコマンドでインストールします Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) インストールされたらchoco -vでバージョンが表示されるか確認しておきましょう。 そのままdart-sdkをインストールします。 choco install dart-sdk choco upgrade dart-sdk インストールと同時にPATHも通っていると思いますが念のため以下のコマンドで確認します。 PS C:\WINDOWS\system32> dart --version Dart SDK version: 2.10.4 (stable) (Wed Nov 11 13:35:58 2020 +0100) on "windows_x64" インストール時のメッセージに従って一度PowerShellのプロンプトを再起動しておきます。 ※またPub Cacheの方のPATHが正常に通らないので、手動で該当のフォルダを開いた後、パスを手でコピーしてインストール時に追加された環境変数のPATHを上書いておきましょう。 MacOS Terminalを開いて以下のコマンドを実行します brew tap dart-lang/dart brew install dart また各shellに合わせてPATHを通しておきます。 インストール時のメッセージに従って.zshrcなどに追記してください。 MacでのPATHの通し方が分からない方はこちらのような記事を参考にしてください。 fvmの導入 WindowsならPowerShell、MacOSならTerminalを開いて以下のコマンドを実行します。 pub global activate fvm 次にプロジェクトのルートに行き、以下のコマンドを実行します stableとなっているところを好きなバージョンに変更してもかまいません fvm install stable fvm use <version> このようにすると、Windowsなら.fvmフォルダの下に以下のようなファイルが出来上がります。 fvm_config.json { "flutterSdkVersion": "2.0.4" } これをgitの管理下にして共有しておくことで、他の共同開発者は、fvmをインストールした後にプロジェクトルートで fvm install とだけ打てば指定したバージョンがインストールされます。 MacOSの場合、.fvmの下にfvm_config.jsonに加えてflutter_sdkというシンボリックリンクが作成されます。 これをAndroid StudioのPreferences -> Languages & Frameworks -> Flutter -> SDKに指定すれば、pub getやビルドする際に、このflutter sdkを使うようになります。 共同開発者にWindowsユーザーがいる場合はこのシンボリックリンクは.gitignoreに追加して共有しないようにしておきます。(Windowsマシンでプロジェクトを開けなくなります) Windowsの場合は、シンボリックリンクが作成されないので、以下のような手順で手動でSDKを変更します。 C:\Users\XXXX\fvm\versionsの下に、インストールされたバージョン名のフォルダがありますのでコピーしてAndroid StudioのPreferences -> Languages & Frameworks -> Flutter -> SDKに指定します。 Android StudioのPreferences -> Languages & Frameworks -> Dartを開き、C:\Users\XXXX\fvm\versions\<指定したバージョン>\bin\cache\dart-sdkを指定し「OK」を押します。 これで、WindowsでもfvmのFlutter SDKを見に行くようになります。 まとめ 長々と手順化しましたが、これらはプロジェクトを作成して初期のタイミングで行った方が良いかと思います。 下手したらこれだけで1日潰れそうですね・・・ 参考 Flutter アプリに Firebase を追加する(公式) 環境設定 - KosukeSaigusa/recipe-app 開発者ごとでなく、プロジェクトごとにFlutterのバージョンを管理する fvmを使ってFlutter SDKのバージョンを切り替える - Qiita [BUG] Can't load Kernel binary: Invalid kernel binary format version. アプリの圧縮、難読化、最適化 - Android Developpers (公式) docker でfirebaseを試したメモ - Qiita Docker入門(第六回)〜Docker Compose〜 Docker compose ことはじめハンズオン - Qiita Firebase CLI の設定 - KosukeSaigusa/recipe-app
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】RxSwift勉強してみたPart7

はじめに 前回 今回はOptionalな値が流れるストリームに対してさまざまなことをできるようにする拡張ライブラリーであるRxOptionalをみていこうかと思います。 RxOptional filterNil() nilをはじいてくれるオペレーター Observable<String?> .of("100", nil, "200") .filterNil() //Observable<String?> -> Observable<String> .subscribe { print($0) } //100 //200 replaceNilWith() nilを置き換えることができるオペレーター Observable<String?> .of("100", nil, "200") .replaceNilWith("300") .subscribe { print($0) } //100 //300 //200 errorOnNil() nilが検出されたときにエラーをながすオペレーター Observable<String?> .of("100", nil, "200") .errorOnNil() .subscribe { print($0) } //100 //Found nil while trying to unwrap type<Optional<String>> filterEmpty() 空の配列や辞書をはじいてくれるオペレーター Observable<[String]> .of(["1"], [], ["2", "3"]) .filterEmpty() .subscribe { print($0) } //["1"] //["2", "3"] おわりに RxSwiftって拡張ライブラリーたくさんありますね!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】RxSwift勉強してみたPart6

はじめに 前回 今回はWKWebViewを使った以下のような簡単なアプリを作ります。 GitHub WebViewRxSwiftフォルダにあります 実装 import UIKit import RxSwift import RxCocoa import RxOptional import WebKit final class WKWebViewController: UIViewController { @IBOutlet private weak var webView: WKWebView! @IBOutlet private weak var progressView: UIProgressView! private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() setupWebView() } private func setupWebView() { let loadingObservable = webView.rx.observe(Bool.self, "loading") .filterNil() .share() loadingObservable .map { return !$0 } .bind(to: progressView.rx.isHidden) .disposed(by: disposeBag) loadingObservable .bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible) .disposed(by: disposeBag) loadingObservable .map { [weak self] _ in return self?.webView.title } .bind(to: navigationItem.rx.title) .disposed(by: disposeBag) webView.rx.observe(Double.self, "estimatedProgress") .filterNil() .map { return Float($0) } .bind(to: progressView.rx.progress) .disposed(by: disposeBag) let url = URL(string: "https://www.google.com/") let urlRequest = URLRequest(url: url!) webView.load(urlRequest) } } 解説 filterNil() RxOptionalライブラリを利用することで使えるようになります。 nilの場合は値を流さず、nilでない場合はアンラップして値をながすオペレーターです。 RxOptionalを使わない場合 Observable<String?> .of("1", nil, "3") .filter { $0 != nil } .map { $0! } .subscribe { print($0) } RxOptionalを使う場合 Observable<String?> .of("1", nil, "3") .filterNil() .subscribe { print($0) } share() ColdなObservableをHotなObservableに変換するオペレーター 以下のコードはtextFieldへのテキスト入力を監視し、ストリームの途中で値を加工して複数のlabelへbindしています。 let text = textField.rx.text .map { text -> String in print("call") } text .bind(to: label.rx.text) .disposed(by: disposeBag) text .bind(to: label2.rx.text) .disposed(by: disposeBag) text .bind(to: label3.rx.text) .disposed(by: disposeBag) ここで、textFieldに123と入力すると、"call"は9回呼ばれます。値を入力するたびにmapが3回呼ばれるためです。これは、データーベースアクセスするもの、通信処理が発生するものでは好ましくありません。 なぜこの現象が起こるのかをみていきたいと思います。 textField.rx.textはControlProperty<String?>として定義されています。 ControlPropertyはUI要素のプロパティで使われ、メインスレッドで値が購読されることが保証されているColdなObservableです。 ColdなObservableとは、subscribe(bind)した時点で読み込まれ、複数回subscribeするとその都度ストリームが生成されるという仕組みです。 今回は3回subscribe(bind)したので、3個のストリームが生成されます。すると、値が変更されたときにオペレーターが3回実行されてしまいます。 これを解決するには複数回購読してもオペレーターを一回実行するだけで済ませれば良さそうです。つまり、ColdなObservableをHotなObservableに変換します。そのためにshare()と言うオペレーターを使います。 let text = textField.rx.text .map { text -> String in print("call") } .share() text .bind(to: label.rx.text) .disposed(by: disposeBag) text .bind(to: label2.rx.text) .disposed(by: disposeBag) text .bind(to: label3.rx.text) .disposed(by: disposeBag) こうすることで、123と入力したときに"call"が三つだけ出力されていたら成功です。 おわりに 次回 RxSwift少しずつわかってきました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ハマり】ApplePush通知証明書の更新

この記事を書く理由 この前、実務で「iOSのpush通知証明書の更新をやっておいて」と無茶振りされました。 iOSの開発等をほとんどやった事がなかった + 手順書等が存在しなかった為、0から調べて手順をまとめてみました。 色々とハマったので僕と同じような方の参考になれば嬉しいです! ※iOS Developer等のページのUIはコロコロ変わるようなので、微妙にUIの文言等が変わってくるかも知れません。 また今回僕がハマった箇所を記載するので、他の人に当てはまらない箇所もあるかと思います。 前提 Amazon SNSを利用したプッシュ通知を利用しているアプリケーションのpush通知証明書の更新です。 プッシュ通知を確認するところまで書いていきます。 やっていく! 大まかな流れ
 ①証明書の要求ファイル(CSRファイル)を作成
。 ②余分な証明書の削除 ← ※この項目はほとんどの方が必要ないかと思います。 ③Developer Center でCSRを利用して、証明書を作成。 ④作成した証明書からp12ファイルを書き出す。
 ⑤p12ファイルをAWS SNSで使用するように設定。 ⑥プッシュ通知が送信されることの確認
。 上記流れでやっていきます。 ①証明書の要求ファイル(CSRファイル)を作成
。 ・CSRファイルの作成
・MacPCのキーチェーンアクセスを開く 
・[キーチェーンアクセス」→「証明書アシスタント」→「認証局に証明書を要求」をクリック。
 ・「ユーザーのメールアドレス(Apple Developerのもの)」を入力。(「通称」は任意のもの(秘密鍵の名前になります。)、「CAのメールアドレス」は空欄でOK) 
・「要求の処理」は「ディスクに保存」を選択し「鍵ペア情報を設定」にチェック。「続ける」をクリック。
 ・保存先の選択が出るので任意の場所を選択し「保存」をクリック。
 ・「鍵ペア情報」画面を確認して「続ける」をクリック。
・「設定結果」画面が出るので「完了」をクリック。 ②余分な証明書をの削除。 ・証明書は2つまでしか作成できない為、有効期限が切れている方の証明書を削徐する。
(※証明書が2つある場合のみ。※有効期限が切れていない方を削除するとプッシュ通知が飛ばなくなるので注意。) 
・Developer Centerで[Certificates]の有効期限が切れている方の証明書をクリック。
 ・Removeボタンをクリック
・確認モーダルが出るので、再度Removeボタンをクリック。
 ※今回ハマったところは、なぜか同じ有効期限の証明書があり、どちらを削除すべきか迷った。→ AWS SNSに設定されている証明書の有効期限の日付 + 時間と証明書の日付 + 時間を見比べて、使用していない方を削除した。 ③Developer Center でCSRを利用して、証明書を作成。 ・①で作成したCSRファイルを利用して証明書の作成
・「Certificates」をクリックし、「Certificates」の隣にある「+」をクリック。
 ・「Create a New Certificate」で「Apple Push Notification service SSL (Sandbox & Production)」にチェックをいれ、右上の方の「Continue」をクリック。 
・AppleIDが聞かれるので、該当するIDを選択。(※1つしか選択できないはず。) 
・「Choose File」をクリックして、1) で作成した「CSRファイル」を選択し、「Continue」をクリック。 ④作成した証明書からp12ファイルを書き出す。 ・p12ファイの作成
・③で作成したAPNs用証明書(.cer) の「Download」をクリックして「aps.cer」をローカル保存
 ・「aps.cer」をダブルクリックして、MaPCキーチェーンアクセスを開く。 
・Apple Push Serviceの証明書がキーチェーンアクセスに登録されるので、証明書を右クリックして「・・・を書き出す」から [p12]ファイルを選択 ※キーチェーンの「すべての項目」ではp12が選択できないので、「自分の証明書」から表示
  ※パスワード空はNG 
・任意の名前を付けて任意の場所に保存(※日本語名NG) ⑤p12ファイルをAWS SNSで使用するように設定。 ・AWS SNSに④で作成したp12ファイルを設定
・AWS SNSから[アプリケーション]を開き、[Mobile] → [プッシュ通知]を開く。
 ・[プラットフォームアプリケーション] → 使用しているプラットフォームアプリケーションを選択。
 ・[編集]をクリック。
 ・編集画面にて、[サンドボックスでの開発に使用されます]にチェック。 (おそらくテスト環境で使用する場合にチェックする。)[プッシュ証明書タイプ]「iOS」を選択。[証明書]で④で作成したp12ファイルを選択。
 ・証明書とプライベートキーそれぞれに文字列が入力されたのを確認して、[変更の保存]をクリック。
 ・証明書の有効期限が更新されていることを確認する。 ⑥プッシュ通知が送信されることの確認
。 ・[プラットフォームアプリケーション] → 使用しているプラットフォームアプリケーションを選択。
 ・エンドポイントの検索フォームにテスト用のプッシュ通知を送信したいデバイスのデバイストークンを入力。 ・検索したエンドポイントを選択して、[メッセージの発行]をクリック。
 ・[配信プロトコルごとにカスタムペイロード]を選択して、[メッセージの発行]をクリック。
 ・選択したデバイスにプッシュ通知が送信される事を確認する。 以上で僕の環境では無事にpush通知が送信されて、本番環境でも問題なく動作しました! 指摘等ありましたら、何でもお願い致します!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む