20210909のSwiftに関する記事は5件です。

Compositional Layoutsで水平方向のスクロール出来た!けど、垂直方向にもスクロール出来てしまう時の対処法

現状 解決策 以下のコードをCollectionViewに対して設定することで解決しました。 ViewController collectionView.isScrollEnabled = false 検証したこと 以下のdelegateメソッドを呼んで水平スクロールと垂直スクロールの時の挙動を調べた。 ViewController func scrollViewDidScroll(_ scrollView: UIScrollView) { print(#function) } 検証結果 水平スクロールの時は呼ばれていない。 垂直スクロールの時は呼ばれている。 →垂直スクロールが要らないのでスクロールの処理を無効にすることで解決 GitHub 最後に この方法が最善とは思わないので、もし他の方法があれば教えていただきたいです。 お願いします!!!!!!!!!!!!!!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画面をタップしたことを検知して処理をさせる。(UITapGestureRecognizer())

今回の内容 コードと簡単解説 UITapGestureRecognizer(target: self, action: #selector(メソッド名))でタップ時の処理を作成します。 viewなど.addGestureRecognizer(インスタンス)でタップした時に処理が働くようになります。 ViewController import UIKit class ViewController: UIViewController { @IBOutlet weak var detectionResultLabel: UILabel! var gradient = CAGradientLayer() var tapCount = 0 override func viewDidLoad() { super.viewDidLoad() gradient.frame = CGRect(x: self.view.frame.minX, y: self.view.frame.minY, width: self.view.frame.size.width, height: self.view.frame.size.height) let tapDetection = UITapGestureRecognizer(target: self, action: #selector(screenTap)) view.addGestureRecognizer(tapDetection) } @objc func screenTap(){ tapCount += 1 detectionResultLabel.text = "\(tapCount)回、画面をタップしました" gradient.colors = [ UIColor(red: CGFloat.random(in: 0...255) / 255, green:CGFloat.random(in:0...255) / 255, blue: CGFloat.random(in: 0...255) / 255, alpha: CGFloat.random(in: 0.0...1.0)).cgColor, UIColor(red: CGFloat.random(in: 0...255) / 255, green:CGFloat.random(in: 0...255) / 255, blue: CGFloat.random(in: 0...255) / 255, alpha: CGFloat.random(in: 0.0...1.0)).cgColor ] gradient.startPoint = CGPoint(x: 0.5, y: 0.5) gradient.endPoint = CGPoint(x: 0.5, y: 0) self.view.layer.insertSublayer(gradient, at: 0) } } 終わり ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画面をタップしたことを検知して処理をさせる。(UITapGestureRecognizer( ))

今回の内容 コードと簡単解説 UITapGestureRecognizer(target: self, action: #selector(メソッド名))でタップ時の処理を作成します。 viewなど.addGestureRecognizer(インスタンス)でタップした時に処理が働くようになります。 ViewController import UIKit class ViewController: UIViewController { @IBOutlet weak var detectionResultLabel: UILabel! var gradient = CAGradientLayer() var tapCount = 0 override func viewDidLoad() { super.viewDidLoad() gradient.frame = CGRect(x: self.view.frame.minX, y: self.view.frame.minY, width: self.view.frame.size.width, height: self.view.frame.size.height) let tapDetection = UITapGestureRecognizer(target: self, action: #selector(screenTap)) view.addGestureRecognizer(tapDetection) } @objc func screenTap(){ tapCount += 1 detectionResultLabel.text = "\(tapCount)回、画面をタップしました" gradient.colors = [ UIColor(red: CGFloat.random(in: 0...255) / 255, green:CGFloat.random(in:0...255) / 255, blue: CGFloat.random(in: 0...255) / 255, alpha: CGFloat.random(in: 0.0...1.0)).cgColor, UIColor(red: CGFloat.random(in: 0...255) / 255, green:CGFloat.random(in: 0...255) / 255, blue: CGFloat.random(in: 0...255) / 255, alpha: CGFloat.random(in: 0.0...1.0)).cgColor ] gradient.startPoint = CGPoint(x: 0.5, y: 0.5) gradient.endPoint = CGPoint(x: 0.5, y: 0) self.view.layer.insertSublayer(gradient, at: 0) } } 終わり ご指摘、ご質問などありましたら、コメントまでお願い致します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] FirebaseAuthenticationを使用したユーザー登録機能メモ

はじめに iOSアプリ開発の学習で、FirebaseAuthenticationを使用してログイン機能を実装しました。 実装した機能の一部抜粋ではありますが、備忘録的に残しておきます。 この記事に書いたこと createUserメソッドを使用したユーザー登録機能 signInメソッドを使用したユーザーログイン機能 sendEmailVerificationメソッドを使用した確認メール送信機能 ※Firebaseの導入については本記事では触れません。 実装 UserAuthManager ユーザーの認証を受け持つクラスを作成します。 外部サービスのimportをあちこちに記述するのはあまりよろしくないので、 import FirebaseAuth等の記述はこのファイルのみで完結できるように設計します。 UserAuthManager.swift import Foundation import FirebaseAuth import Firebase final class UserAuthManager { static let shared: UserAuthManager = .init() private init(){} // ~~~~~ 処理を実装 ~~~~~ } UserAuthError(エラー型の用意) サーバーからエラーが返ってくるケースに備えて作成しておきます。 UserAuthManager.swift enum UserAuthError: Error, LocalizedError { case createUserError(errorMessage: String) case userLoginError(errorMessage: String) case sendEmailVerificationError(errorMessage: String) case checkEmailVerification(errorMessage: String) var errorDescription: String? { switch self { case .createUserError(let message): return "ユーザー作成エラー:\(message)" case .userLoginError(let message): return "ログインエラー:\(message)" case .sendEmailVerificationError(let message): return "確認メール送信エラー:\(message)" case .checkEmailVerification(let message): return "メールアドレス検証エラー:\(message)" } } } createUser(ユーザーの新規作成) UserAuthManager.swift func createUser(email: String, password: String, completion: @escaping (Result<Bool, UserAuthError>) -> Void) { Auth.auth().createUser(withEmail: email, password: password) { authResult, error in if let error = error { completion(.failure(UserAuthError.createUserError(errorMessage: error.localizedDescription))) return } guard authResult != nil else { completion(.failure(UserAuthError.createUserError(errorMessage: "入力された情報では登録できませんでした"))) return } completion(.success(true)) } } Result クロージャーでResult<Bool, UserAuthError>を返していますが、これはユーザー登録出来たかどうか以外特に渡したいパラメータも無かったので成功時Bool型にしています。 ただ失敗時は基本的にError型が返されるのでfalseが使われることがないのはちょっと気になっているところです。 もっと良い書き方がありそう。 error サーバーから返ってきた結果について何パターンか処理を分けます。 errorが返ってきた場合(errorがnilでない) この場合はResultでUserAuthErrorを返します。この時、呼び出し元にエラーの原因(通信状況?アドレス被り?など)も知らせる必要があるのでUserAuthError.createUserError(errorMessage: error.localizedDescription)としてサーバーから受け取ったエラーメッセージも付けて返します。 errorは無いがauthResultも無い場合 errorが返ってきてないのにも関わらずauthResultが無い場合は、サーバー側も想定外のエラーとなるので、アプリ側でエラーメッセージを用意してUserAuthErrorを返します。 エラーもなくauthResultがある場合 登録成功しているので.success(true)を返します。 signIn(ログイン) UserAuthManager.swift func login(email: String, password: String, completion: @escaping (Result<Bool, UserAuthError>) -> Void) { Auth.auth().signIn(withEmail: email, password: password) { authResult, error in if let error = error { completion(.failure(UserAuthError.userLoginError(errorMessage: error.localizedDescription))) } guard authResult != nil else { completion(.failure(UserAuthError.userLoginError(errorMessage: "入力された情報ではログインできませんでした"))) return } completion(.success(true)) } } やっていることはcreateUserとほぼ同じです。 sendEmailVerification(メールアドレスの確認) 不正なメールアドレスの使用を防ぐため、登録したメールアドレスに確認用のメールを送信する機能です。 受けとったメールアドレスのリンクをタップすることで、currentUser.isEmailVerifiedというパラメータがfalseからtrueに変わります。trueであれば確認済みの状態ということになります。 UserAuthManager.swift func sendEmailVerification(completion: @escaping (Result<Bool, UserAuthError>) -> Void) { guard let user = Auth.auth().currentUser else { return } user.sendEmailVerification { error in if let error = error { completion(.failure(UserAuthError.sendEmailVerificationError(errorMessage: error.localizedDescription))) } else { completion(.success(true)) } } } func checkEmailVerificationResult(completion: @escaping (Result<Bool, UserAuthError>) -> Void) { guard let user = Auth.auth().currentUser else { return } user.reload { error in if let error = error { completion(.failure(UserAuthError.checkEmailVerification(errorMessage: error.localizedDescription))) } else { completion(.success(user.isEmailVerified)) } } } 関数を2つ用意しています。 sendEmailVerification()はcurrentuserのメールアドレスに確認用のメールを送信するためのものです。 エラーなく送信完了の場合はtrueを返し、エラーがある場合はUserAuthErrorを返します。 checkEmailVerificationResult()はメールアドレスの確認が終了しているか否かをチェックするためのものです。 エラーが返された場合(チェック自体が出来なかった場合)はUserAuthErrorを返し、エラーがなければisEmailVerifiedのBool値を返します。 補足 sendEmailVerificationはcurrentUserが持つメソッドなので、登録完了したユーザーに対して行うことになるという点に注意 Routing(おまけ) アプリの起動経路の道中でユーザのログイン状態によって画面遷移先を変えます。 細かいところは省略していますが、userLoginCheckという関数を用意してcurrentuserを取得してログイン状態のチェック、及びメール確認の有無を見ています。 // Routerなどの画面遷移処理を受け持つクラス func showRoot() { UserAuthManager.shared.userLoginCheck { result in switch result { case .success(let user): // ログインしている場合 if user.emailVerified { // さらにメールの確認も済んでいる場合は、ログイン後のトップページ等へ } else { // メールの確認が済んでいない場合は、メールの確認処理を受け持つ画面へ } case .failure: // ログインしていない場合 // ログイン・新規登録の画面へ } } } UserAuthManager.swift // UserAuthManagerクラスに関数を用意 func userLoginCheck(completion: @escaping (Result<User, UserAuthError>) -> Void) { let user = Auth.auth().currentUser guard let userEmail = user?.email, let userEmailVerified = user?.isEmailVerified else { return completion(.failure(UserAuthError.loginFailure)) } completion(.success(User(emailAddress: userEmail, emailVerified: userEmailVerified))) } 終わりに このようにユーザー認証用のクラスを用意しておくことで、ViewからUserAuthManager.shared.hogehoge { クロージャー }という形で呼び出せます。 FirebaseAuthは個人アプリ開発などでもお世話になると思いますので、今後さらに理解を深めていければと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftで覚えるデザインパターンその3

はじめに Swiftで覚えるデザインパターン第3弾です。 Swiftで覚えるデザインパターン Strategy Observer Decorator Factory Method Abstract Factory Singleton Swiftで覚えるデザインパターンその2 Command Adaptor Facade Template Method Swiftで覚えるデザインパターンその3 ← 今ココ Iterator Composite State Compound Iteratorパターン 概要 集約したいくつかのオブジェクト(配列やコレクション) = アグリゲートに順次アクセスできるようにする。 試しに作ったもの 大学の学部生と修士生の履歴書のデータをボタン一つで一括表示するような画面を作りました。 ※長くなるので最後の方切れてます。 クラス図 Iteratorには各要素にアクセスできるようなメソッドを定義します。 ひとまず、次の要素を返すnext()と次の要素があるかを調べるhasNext()を定義しています。 必要によっては、一番最初の要素を返すfirst()や逆に最後の要素を返すlast()とかを定義しても良さそうです。 Iteratorが全てのアグリゲートに統一した方法でアクセスする方法を定義すれば、アグリゲートの形式を考える必要がなくなります。 また、新しいAggregateを作ってもそれ用のIteratorを追加し、同様にViewControllerでこれまでのAggregateと同様に呼び出せば良いので改修するのも簡単にできます。 ちなみに今回はModelを定義していますが、必ずしもIteratorパターンに必要なものではありません。 実装 Modelの定義 protocol StudentModel { /// 氏名 var name: String { get } /// 年齢 var age: Int { get } /// 大学名 var universityName: String { get } /// 志望動機 var motivation: String { get } } // Masterの場合も同様 struct BachelorModel: StudentModel { /// 氏名 let name: String /// 年齢 let age: Int /// 大学名 let universityName: String /// 志望動機 let motivation: String } Aggregate周り protocol Aggregate { /// イテレーターを作成 func createIterator() -> Iterator } // Masterも同様なものを作成する class Bachelor: Aggregate { var models = [BachelorModel]() init() { models.append(BachelorModel(name: "山田", age: 22, universityName: "〇〇大学", motivation: "頑張ります!")) // 同様にいくつか追加 } func createIterator() -> Iterator { return BachelorIterator(bachelor: self) } } Iterator周り protocol Iterator { /// 次の要素があるか func hasNext() -> Bool /// 次の要素を取得する func next() -> StudentModel? } // Masterの場合も同様 class BachelorIterator: Iterator { private var bachelor: Bachelor? private var index = 0 init(bachelor: Bachelor) { self.bachelor = bachelor } func hasNext() -> Bool { guard let models = bachelor?.models else { return false } return !(index >= models.count) } func next() -> StudentModel? { guard let models = bachelor?.models else { return nil } let bachelor = models[index] index += 1 return bachelor } } IteratorViewController.swift class IteratorViewController: UIViewController { /// 学部生 private let bachelor = Bachelor() /// 修士生 private let master = Master() @IBAction func didTapListUpButton(_ sender: Any) { listUpStudents() } /// 学部生と修士学生をリストアップ func listUpStudents() { let bachlorIterator = bachelor.createIterator() let masterIterator = master.createIterator() listUpStudents(from: bachlorIterator) listUpStudents(from: masterIterator) } /// 学生をリストアップ func listUpStudents(from iterator: Iterator) { while iterator.hasNext() { guard let model = iterator.next() else { return } print(model.name + " " + String(model.age) + "歳です。\n") print(model.universityName + "出身です。\n") print(model.motivation) print("==============") } } } Compositeパターン 概要 オブジェクトをツリー構造に構成して扱う。 このとき、クライアントは個別のオブジェクトとオブジェクトのコンポジションを同じものとして扱うことができる 登場人物は以下の3つ Component:コンポジションの全てのオブジェクト用のインターフェースを定義する Composite:子要素を持つコンポーネントの振る舞いを定義し、子要素を格納する Leaf:子要素を持たない要素。コンポジション内の全ての要素の振る舞いを実装する 試しに作ったもの ツリー構造になりそうなもので浮かんだのが家系図だったので、家系図っぽくなるような家族のリストアップを行う画面を作りました。 画面自体には何かを表示すると言うことはせず、画面を開いた時に家族の一覧をコンソールに表示するようなものとなります。 田中家は通常パターンで家族-メンバーの関係で表示するようにしています。 鈴木家は、佐藤家から婿入りしてきたメンバーを盛り込んでいるので、階層化されています。 正直作った時にもっとわかりやすい例があったなと後悔... クラス図 CompositeViewControllerがクライアントとしてComponentにアクセスします。 クライアントは、Family(Composite)にも、Person(Leaf)にもアクセスすることができます。 Componentをインターフェースとして定義し、Family、Personの両方でComponentを実装します。 CompositeであるFamilyの方では、listUp()を再起的に呼び出すことで、要素がLeafであればLeaf用のリストアップを行い、要素が再度Compositeであれば再度メソッドを呼び出すようにします。 実装 Component protocol Component { func listUp() } CompositeとLeaf /// 家系(Compositeに該当する) class Family: Component { /// 全ての家族 var family = [Component]() /// 名字 private let familyName: String init(familyName: String) { self.familyName = familyName } func listUp() { print("\(familyName)家\n") for person in family { person.listUp() } } } /// 個人(leafに該当する) class Person: Component { /// 氏名 private let name: String init(name: String) { self.name = name } func listUp() { print(name) } } CompositeViewController.swift class CompositeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupFamily() } /// 家族の設定 func setupFamily() { let tanakaFamily = Family(familyName: "田中") tanakaFamily.family.append(Person(name: "田中一郎")) tanakaFamily.family.append(Person(name: "田中二郎")) tanakaFamily.family.append(Person(name: "田中三郎")) let suzukiFamily = Family(familyName: "鈴木") suzukiFamily.family.append(Person(name: "鈴木一花")) suzukiFamily.family.append(Person(name: "鈴木ニ菜")) suzukiFamily.family.append(Person(name: "鈴木三春")) // 婿入り let satoFamily = Family(familyName: "佐藤(旧姓)") satoFamily.family.append(Person(name: "佐藤一")) suzukiFamily.family.append(satoFamily) tanakaFamily.listUp() print("=============") suzukiFamily.listUp() print("=============") } } Stateパターン 概要 オブジェクトの内部状態が変化した時、オブジェクトがその振る舞いを変更することができる。 状態そのものをクラスとすることで、if分やswitch分の分岐を追加する必要がなくなる。 試しに作ったもの 車を想定した画面を作ってみました。 パーキング、バック、ドライブのモードがあり、それぞれの状態でブレーキ、アクセルを押した時のリアクションが変わります。 モードのボタンを押すと、押したボタンが黄色になります。 それぞれの状態で、アクセル→ブレーキの順にボタンを押した時の状態です。 パーキング状態 ドライブ状態 バック状態 クラス図 Stateは全ての状態クラスの共通プロトコルで、全クラスで同じインターフェースを実装するので交換が可能になる。 ここではStateViewControllerが俗に言うContextと呼ばれる、多数の内部状態を持つクラスになる。 実装 State protocol State { /// アクセル func run() /// ブレーキ func stop() } // Back, Runningの場合も同様 class ParkingState: State { func run() { print("動きません") } func stop() { print("既に停止しています") } } StateViewController.swift class StateViewController: UIViewController { /// 停車状態 private let parkingState = ParkingState() /// 戻る状態 private let backState = BackState() /// 走行状態 private let runningState = RunningState() /// 状態フラグ(初期値は停車状態) private var state: State = ParkingState() override func viewDidLoad() { super.viewDidLoad() setup() } // 各モードの切り替え @IBAction func didTapParkingButton(_ sender: Any) { setState(with: parkingState) // ボタンの色状態を変える処理は省略 } @IBAction func didTapBackButton(_ sender: Any) { setState(with: backState) } @IBAction func didTapDriveButton(_ sender: Any) { setState(with: runningState) } // 各アクション @IBAction func didTapBreakButton(_ sender: Any) { state.stop() } @IBAction func didTapRunButton(_ sender: Any) { state.run() } /// 準備 func setup() { parkingButton.backgroundColor = .yellow setState(with: parkingState) } /// 状態をセット /// - Parameter state: State func setState(with state: State) { self.state = state } } Compoundパターン 概要 デザインパターンを複数組み合わせたパターン。 書籍以外に参考にできるものはないかを検索してもなかなか出てこなかった。 どうやらデザインパターンを組み合わせてアーキテクチャにしたものといった表現もあり、MVCも一種のCompoundパターンのようです。 作ったもの オリジナルのものを考えるのが大変だったので、参考書籍の部分からSwiftでも実装できそうな部分をなるべく抽出して作成しました。 DJの操作画面みたいなもので、BPMの調整ができます。 クラス図 必要そうな部分だけ抜き取りました。 View:画面の構成をする。画面の各パーツの状態を監視し、状態によって処理を変えたりする。 Controller:Viewでのユーザの操作/入力を受け取り、Modelにとってどのような意味を持つかを解釈してModelに渡す。 Model:画面の状態やデータを取り扱う。Controllerから受け取った状態によって値を更新し、更新後はViewのオブザーバーに通知を送る。 監視や通知といっているので、Observerパターンを利用していることはわかりやすいかと思います。 また、今回の例ではあまり恩恵を得られていないかもしれませんが、DJViewがControllerInterfaceを参照していますが、これによってBPMControllerではない別のControllerを使いたいとなった時に交換が容易になります。 これは俗に言うStrategyパターンを用いています。 実装 全部載せようとすると長くなりそうなのでリポジトリからコードをご覧いただければと思います。 おさらい パターン 特徴 Iterator 集約したオブジェクトにアクセスして、読み込みや操作可能にする Composite オブジェクトをツリー構造にして扱うことで、呼び出す側で親/子を気にしないで呼び出せる State オブジェクトの状態をクラスにすることで、オブジェクトが振る舞いを変更可能にする Compound デザインパターンを組み合わせたパターン 今回も4つのデザインパターンについて学びました。 今回のデザインパターンもGitHubのリポジトリで公開していますので、具体的な実装をみたい方がいらっしゃったらご覧ください。 今回のパターンはPatterns3のディレクトリ内に含まれています。 ここまで長かった... デザインパターンって多いし、中にはこれなんか似てね?みたいなのもあるかと思います。 引き続き、本記事でもご指摘やアドバイス等があればぜひコメントをいただきたく思います。 いただいたコメントを踏まえて、学びをより深いものにして行けたらと思っております✨ 一旦デザインパターンを学ぶシリーズはここまでにしようと思います。 参考書籍にはおまけで他のパターンもありましたが、そちらはぜひご自分の目でご確認いただければと思います。 これまで読んでくださった方も、これだけ読んだよという方も、この記事にたどり着いてくださりありがとうございました。 参照 共通 Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本 Iteratorパターン デザインパターン「Iterator」 - Qiita Compositeパターン Swiftで学ぶデザインパターン13 (Compositeパターン) - しめ鯖日記 Stateパターン State – 野生のプログラマZ Compoundパターン 【デザインパターン】コンパウンドパターン【事前学習編1】 【Swift】MVCについて分かりやすいサンプルが無かったのでSwiftでMVCモデルで簡易アプリ書いてみた【MVCパターン】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む