- 投稿日:2021-02-28T19:51:35+09:00
TodoAPPでRxSwift入門[part1]
概要
最近RxSwiftを勉強し始めて現在理解していることを備忘録として残せたらいいなと思い記事にします。
そもそもRxSwiftのRxとはRx(Reactive X)とは、「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している拡張ライブラリです。
Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」ということに尽きると思います。 値の変化というのは変数値の変化やUIの変化も含まれます。 例えばボタンをタッチする、という動作もボタンのステータスが変わったと捉えることができRxを使って記述することができます。とのことです。
詳しくは以下のサイトを参照してください。
入門!RxSwift
RxSwiftについてようやく理解できてきたのでまとめることにした(1)今回使用するライブラリ
RxSwift
pod 'RxSwift'
--- ObservableなどのRx系の本領的なものを使うのに必要
RxCocoapod 'RxCocoa'
--- UIKitでRxを使うのに必要
RxDataSourcespod 'RxDataSources'
--- tableViewなどのdatasourceの扱いを楽にしてくれる
Firestorepod 'Firebase/Firestore'
--- todoの保存をするのに必要以上のライブラリを使用していきます。
認証機能は?と思われたかもしれないですが今回は省かせていただきます。
また、今回の記事はfirebaseやpodは導入済みのものとして話を進めさせていただきます。
StoreManager
まずはこのアプリのもっとも重要なTodoの追加や取得の処理を書いていきます。
StoreManager
という名前のファイルを作ってください。StoreManager.swiftimport Foundation import FirebaseFirestore import RxSwift class StoreManager { static let shared = StoreManager() private let store = Firestore.firestore() }こんな感じで定義してください。
ここは説明するまでもないと思うので説明を省略します。次に
Todo
を保存する関数を作ります。
extension
で拡張すると可読性が上がるので拡張します。StoreManager.swiftextension StoreManager { // firestore にtodoのデータを保存 func insertTodoToFireStore(title: String, detail: String) { let data: [String: Any] = [ "title": title, "detail": detail, "createdAt": Timestamp() ] store.collection("todos").addDocument(data: data) } }ここも特に説明はいらないと思いますは簡単に説明しておきます。
title
,detail
を受け取り[String: Any]
型のDictionary
にして各値を格納します。そして、todos
というコレクション名のコレクションにデータを保存します。次にデータを取得する関数を作ります。
StoreManager.swift// firestore からtodosのデータ取得 func fetchTodosFromFirestore() -> Single<[TodoModel]> { Single.create { [weak self](single) -> Disposable in self?.store.collection("todos").getDocuments { (snapshots, error) in guard let docs = snapshots?.documents, error == nil else { single(.failure(CustomError.error(message: "Failed To Fetch Todos From Firestore"))) return } let todos = docs.map { TodoModel(data: $0.data()) } single(.success(todos)) } return Disposables.create() } }ここがこのクラスのキモですね。
戻り値にSingle<[TodoModel]>
とありますね。
Single
はRxSwiftをインポートしていないと使えないので忘れないでください。
また、現時点ではTodoModel
というものが定義されていないのでエラーになると思いますが気になる人はとりあえずTodoModelを定義しておいてください。
Single
について説明していきます。
Single
はSuccessとErrorを流し、Completedを流さないものになります。
successとはnextを一回だけ流れるものでerrorはそのままですね。
ほーという感じだと思いますがcomplete処理がいらなかったらSingleを使うという感じでいいんじゃないでしょうか。Singleの他にもMaybe、Completableがあります。
詳しいことは以下の記事を参照してください。
RxSwift 3.3.0で追加された3つのUnit(Single, Maybe, Completable)次に
guard let文
の中でエラー処理を書いています。
ここは説明なしでもわかると思います。
そして、取得した値をTodoModel
に加工してsuccess
で流してあげます。最後に
Disposables.create()
ですが、これはストリームのライフサイクルを管理するためのものです。
disposeされたときに、ここに書いた処理が実行されます。
Disposable
についてTodoModel
先ほど飛ばした
TodoModel
はこんな感じで書きます。TodoModel.swiftimport Foundation import FirebaseFirestore struct TodoModel { let title: String let detail: String let createdAt: Timestamp init(data: [String: Any]) { title = data["title"] as? String ?? "" detail = data["detail"] as? String ?? "" createdAt = data["createdAt"] as? Timestamp ?? Timestamp() } }最後に
part1はここまでになります。
正直まだ理解しきれていないこともあり、調べながらになりますが理解できているところとあまり理解できていないとこがわかってoutputって大切だなと思いました。
また、あまり理解できずに書いてしまっているところや誤解釈してしまっているところがありましたらご指摘の方お願いいたします。[part2]はViewModelの作成をしていきます。
TodoAPPでRxSwift入門[part2]
- 投稿日:2021-02-28T19:51:35+09:00
RxSwiftでTodoAPP入門[part1]
概要
最近RxSwiftを勉強し始めて現在理解していることを備忘録として残せたらいいなと思い記事にします。
そもそもRxSwiftのRxとはRx(Reactive X)とは、「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している拡張ライブラリです。
Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」ということに尽きると思います。 値の変化というのは変数値の変化やUIの変化も含まれます。 例えばボタンをタッチする、という動作もボタンのステータスが変わったと捉えることができRxを使って記述することができます。とのことです。
詳しくは以下のサイトを参照してください。
入門!RxSwift
RxSwiftについてようやく理解できてきたのでまとめることにした(1)今回使用するライブラリ
RxSwift
pod 'RxSwift'
--- ObservableなどのRx系の本領的なものを使うのに必要
RxCocoapod 'RxCocoa'
--- UIKitでRxを使うのに必要
RxDataSourcespod 'RxDataSources'
--- tableViewなどのdatasourceの扱いを楽にしてくれる
Firestorepod 'Firebase/Firestore'
--- todoの保存をするのに必要以上のライブラリを使用していきます。
認証機能は?と思われたかもしれないですが今回は省かせていただきます。
また、今回の記事はfirebaseやpodは導入済みのものとして話を進めさせていただきます。
StoreManager
まずはこのアプリのもっとも重要なTodoの追加や取得の処理を書いていきます。
StoreManager
という名前のファイルを作ってください。StoreManager.swiftimport Foundation import FirebaseFirestore import RxSwift class StoreManager { static let shared = StoreManager() private let store = Firestore.firestore() }こんな感じで定義してください。
ここは説明するまでもないと思うので説明を省略します。次に
Todo
を保存する関数を作ります。
extension
で拡張すると可読性が上がるので拡張します。StoreManager.swiftextension StoreManager { // firestore にtodoのデータを保存 func insertTodoToFireStore(title: String, detail: String) { let data: [String: Any] = [ "title": title, "detail": detail, "createdAt": Timestamp() ] store.collection("todos").addDocument(data: data) } }ここも特に説明はいらないと思いますは簡単に説明しておきます。
title
,detail
を受け取り[String: Any]
型のDictionary
にして各値を格納します。そして、todos
というコレクション名のコレクションにデータを保存します。次にデータを取得する関数を作ります。
StoreManager.swift// firestore からtodosのデータ取得 func fetchTodosFromFirestore() -> Single<[TodoModel]> { Single.create { [weak self](single) -> Disposable in self?.store.collection("todos").getDocuments { (snapshots, error) in guard let docs = snapshots?.documents, error == nil else { single(.failure(CustomError.error(message: "Failed To Fetch Todos From Firestore"))) return } let todos = docs.map { TodoModel(data: $0.data()) } single(.success(todos)) } return Disposables.create() } }ここがこのクラスのキモですね。
戻り値にSingle<[TodoModel]>
とありますね。
Single
はRxSwiftをインポートしていないと使えないので忘れないでください。
また、現時点ではTodoModel
というものが定義されていないのでエラーになると思いますが気になる人はとりあえずTodoModelを定義しておいてください。
Single
について説明していきます。
Single
はSuccessとErrorを流し、Completedを流さないものになります。
successとはnextを一回だけ流れるものでerrorはそのままですね。
ほーという感じだと思いますがcomplete処理がいらなかったらSingleを使うという感じでいいんじゃないでしょうか。Singleの他にもMaybe、Completableがあります。
詳しいことは以下の記事を参照してください。
RxSwift 3.3.0で追加された3つのUnit(Single, Maybe, Completable)次に
guard let文
の中でエラー処理を書いています。
ここは説明なしでもわかると思います。
そして、取得した値をTodoModel
に加工してsuccess
で流してあげます。最後に
Disposables.create()
ですが、これはストリームのライフサイクルを管理するためのものです。
disposeされたときに、ここに書いた処理が実行されます。
Disposable
についてTodoModel
先ほど飛ばした
TodoModel
はこんな感じで書きます。TodoModel.swiftimport Foundation import FirebaseFirestore struct TodoModel { let title: String let detail: String let createdAt: Timestamp init(data: [String: Any]) { title = data["title"] as? String ?? "" detail = data["detail"] as? String ?? "" createdAt = data["createdAt"] as? Timestamp ?? Timestamp() } }最後に
part1はここまでになります。
正直まだ理解しきれていないこともあり、調べながらになりますが理解できているところとあまり理解できていないとこがわかってoutputって大切だなと思いました。
また、あまり理解できずに書いてしまっているところや誤解釈してしまっているところがありましたらご指摘の方お願いいたします。[part2]はViewModelの作成をしていきます。
- 投稿日:2021-02-28T18:30:04+09:00
【SwiftUI】contextMenuで長押ししたときにメニューを出す
先日リリースしたアプリに使用した技術をひとつずつ解説しています。
私のアプリはこちら。
contextMenuとは
任意のビューに対し、長押ししたときにメニューを出す機能を追加するメソッド。
実際の動作
実際にアプリを触らないとわかりづらいかもしれませんが、長押しすることでメニューが表示されています。また、メニューの外側をタップするとメニューが閉じるようになっています。
iOSアプリでよく見かける機能ではないでしょうか。ソースコード
ContentView.Swiftimport SwiftUI struct ContentView: View { let evs = ["イーブイ", "ブースター", "シャワーズ", "サンダース", "エーフィ", "ブラッキー", "グレイシア", "リーフィア", "ニンフィア"] let colors1: [Color] = [Color(#colorLiteral(red: 0.7254902124, green: 0.4784313738, blue: 0.09803921729, alpha: 1)), .red, .blue, .yellow, Color(#colorLiteral(red: 0.8446564078, green: 0.5145705342, blue: 1, alpha: 0.5)), .black, Color(#colorLiteral(red: 0.6872052062, green: 0.9808971643, blue: 1, alpha: 1)), Color(#colorLiteral(red: 0.5843137503, green: 0.8235294223, blue: 0.4196078479, alpha: 1)), .pink] let colors2: [Color] = [.white, .white, .white, .black, .black, .yellow, .blue, Color(#colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)), Color(#colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1))] @State var evID = 0 var body: some View { ZStack{ Circle() .foregroundColor(colors1[evID]) .frame(width: 250) VStack{ Image(systemName: "sparkles") //このビューを長押しするとメニューが現れるようにする //ここからがコンテキストメニュー .contextMenu(menuItems: { ForEach(0..<9) { n in Button(action: { evID = n }, label: { Text(evs[n]) }) } }) //ここまでがコンテキストメニュー Text(evs[evID]) .padding() } .font(.title) .foregroundColor(colors2[evID]) } } }「長押しするとメニューが出る」という機能を追加したいビューに対して
.contextMenu
を書きます。
そして、contextMenuの引数であるmenuItems
の{}内が、表示されるメニューの内容になります。menuItemsには何を書く?
contextMenuで
Text
やSF Symbolsを使ったImage
を表示させることができます。しかし、書いたものを自動的にボタンにする、という機能はありません。上のサンプルアプリのようにcontextMenuにボタンを表示させたい場合は、menuItemsにはButtonビューを書くのが良いでしょう。まとめ
簡単なコードでおもしろくて便利な機能を実現できます。ぜひ、SwiftUIでcontextMenuを使ってみてください。
よかったらTwitterのフォローをお願いします。
https://twitter.com/masayoshi_tozan
- 投稿日:2021-02-28T14:12:21+09:00
アルバイト先のiOSアプリをリファクタリングした
アルバイト先の会社で、プロジェクトの整備、リファクタリングをしようということになりました。
アプリのプロジェクトはこれまでオフショアで海外に渡ったり、業務委託やアルバイトに渡ったりと、カオス状態でした。
今回は、まとまった時間を設けてこのレガシープロジェクトを少しでも改善しようということだったので、最近学んだ内容をアウトプットするいい機会だと思って色々やってみました。
「これまでそんな事もやってなかったのか」と思われるかもしれませんが、
誤りや改善点等ありましたら、ご指摘いただければ幸いです。GitLabからGitHubへの移行
これまでGitLabで管理されていたプロジェクトをGitHubに移行することになりました。
私はREADMEやWikiの整備を行いました。
具体的には以下を含めました。
README
- 開発環境
- ライブラリ管理
- セットアップ方法
- API仕様書
Wiki
- 簡単なディレクトリ構成
- 主な使用ライブラリ
- Gitやコードの命名規則
また、ブランチの運用も変更しました。
- Main(リリース用)
- Staging(テスト用)
- Mainからブランチを切って、作業用ブランチはStagingにマージ
- Stagingでテスト完了後にリリースバージョンのタグをつけてマージ
- Mainをリリース
rbenv で ruby のバージョンを指定
CocoaPods は ruby で作られたツールです。
チーム開発を行う場合、各々の PC の ruby のバージョンが異なるとライブラリのインストールに失敗したり、上手く動かないことがあるので rbenv を使って ruby のバージョンを統一しました。
bundler など他にもツールが出てきて一気に理解するのは大変でしたが、以下の記事では丁寧に解説されています。
Cocoapods と Carthage を共存させた iOS プロジェクトの始め方
SwiftLint の導入
SwiftLintを導入しました。
ルールに関しては、業務委託の方が普段使用されている.swiftlint.yml ファイルが導入されました。
それに少しルールを追加しただけで、かなり緩めにルールが設定されています。
下記の記事を参考にさせていただきました。
Swiftの静的解析ツール「SwiftLint」のセットアップ方法
IQKeyboardManager の導入
これに関しては、本当に入れるだけで障壁はほぼありませんでした。
リソース管理、R.swiftの導入
他言語対応されているはずなのに、NSLocalizedString が使用されずにハードコーディングされている箇所があったりと、こちらもカオス状態でした。
この機会なのでリソース管理のライブラリを入れようということで、導入がSwiftGenより簡単なR.swiftを導入しました。
注意点としては、生成された R.generated ファイルが、SwiftLint の制約エラーを起こすことがあるので、R.generatedファイルは .swiftlint.yml ファイルの excluded: に含める必要があります。
また、“キーワードが被っているという問題で、生成をスキップします“というような警告が出たので、リソース側の整理が必要でした。
現在は文字列しかほぼ書き換えられてないので、今後は画像、ストーリーボード、セルなども書き換える必要があります。
下記の記事を参考にさせていただきました。
リテラルが結構使用されていたのでこちらも。
R.Swiftでは文字リソースで「%@」を使うと、その部分を引数にとる関数を自動生成する
継承が想定されないクラスには final をつける
Swift ではクラスを継承することができますが、自作のクラスで他のクラスから継承しないものには final をつけるべきです。
自分が継承されない想定で作ったクラスを他の人が継承する実装にしてしまったというようなことがチーム開発ではあり得るので、それを防ぐためには final を適切に設定することが必要です。
アクセスレベルを適切に設定
変数やメソッドなどには private などのアクセスレベルを設定することができます。 例えばクラス内でのみ使用し、クラスの外から呼ばれることのないメソッドは private にすることができます。
final と同様に、アクセスレベルも適切に設定することが必要です。
他人がプロジェクトのコードを読むときに「このメソッドは private だから外から呼ばれることはないんだ」と判断できるので、迅速な理解に繋がるというメリットもあると思います。
下記の記事で、非常にわかりやすく説明されています。
delegate メソッドを extension に分ける
ViewController の定義時に全ての Delegate を継承せず適切に extension で分けました。
主に UITableViewDelegate や UICollectionViewDelegate
、 UITextFieldDelegate のメソッドがそのままクラス内に書かれており、定義されているメソッドが delegate メソッドなのか独自のメソッドなのか分かりづらい状態でした。こちらは自分で簡単にまとめたものを参考にしました。
不要なコメントアウト、 print デバッグの削除
使用されていないコードのコメントアウトや、 print 文がそのまま残されている状態でした。
print 文の中で可能なものは、下記の記事を参考に os.Logger で置き換えられました。
忙しい人向けの Explore logging in Swift - #WWDC20
例:
let logger = Logger() // 省略 do { // 処理 } catch { logger.error(error) }今後やりたいこと
可能なところは StackView を使用
UI のメンテナンスの際は、強力な StackView である方がメンテナンスがやりやすいので、
可能なところは StackView を使用したいです。可能なところStoryboard Reference を使用
Storyboard が入り組んでいると、メンテナンスがやりづらいと思います。
特にチーム開発ではコンフリクトの原因にもなるので、可能なところは StoryboardReference で Storyboard を分けるべきだと思います。
SwiftFormat の導入
機械的にコーディングルールを適用させれば楽になるかと思います。
ネストの解消
可読性の低いネストが散見されるので、可能な限り guard 文などで可読性を改善したいです。
まとめ
本来であれば、重複した処理を共通化したり、クラスの抽象化をしたりと、より高度なことをやりたいですが、現状の私のレベルで出来ることはこのくらいでした。
やるべき事は山積みですが、引き続きプロジェクトに貢献していきたいです。