- 投稿日:2021-01-16T21:08:39+09:00
[Swift] core dataの基本的な実装をまとめてみた
はじめに
core dataについて色々調べ、とりあえず実装できた状態です。
自分なりにまとめてみます。
tableviewcontrollerと、遷移先の項目を追加するview2つを作成します。
core data以外の操作は省略します?♂️実装と解説
1 プロジェクト作成
まずはじめにプロジェクトを立ち上げる際に、User Core Dataを選択します。
すると、ファイル名.xcdatamodeldというファイルが追加されます、2 tableviewcontrollerと追加用のviewcontrollerの実装
Core Dataの設定
①のファイルを選択すると、Core Dataの設定ができます。
②でEntityを追加します。
③が追加されます。Entityは今回データを入れておいて保管する場所になります。
今回はメモ内容の保存に使いますので、Memoという名前にしてみます。
Entityは大文字からの名前をつけるのが無難だそうです。
④の+を押すと、⑤のAttributeがが追加できます。これは今回追加するデータになります。
今回はメモの内容を保存するnameをstring型、チェックマークの状態を保存するcheckをBoolean型で設定します。
型はTypeから設定できます。
各項目のOptionalのチェックを外しておいてください。
ここで一旦ビルドしておきます。しておかないと後々エラーになるそうです。
コード
保存の処理
①
viewContext
は変更などを見にいき、操作もできるメソッドで、それを定数contextに入れる
②そのcontextをMemo(context: context)に入れる事で、Core DataであるMemoの変更や操作ができるようになる。
③それを定数memoに入れたので、memo.attributeの項目を操作できる
④最後にsaveContext()でデータを保存して完了!import UIKit class AddViewController: UIViewController { @IBOutlet weak var addTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() } @IBAction func addButton(_ sender: Any) { // persistentContainerは総監督の役割でcore dataに関連するNSManagedObjectContext・ // NS PesistentStoreCoordinator・NSpersistentStoreに指示を出せる。 // 以下のcontextはManagedObjectContextへの参照が含まれている。 // viewContextはNSManagedObjectContext型で、管理されているオブジェクトを操作したり、 // 変更を追跡したりする。 let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext // (context:)はモデル内の単一のエンティティを表すNSManagedObject(この場合はMemo)のサブクラスに対してのみ、 // 合法的に呼び出すことができる。この場合、Memoの中で変更があるかとかをcontextで追跡し、 // それを呼び出して定数に入れているイメージかな?ManagedObjectのサブクラスを初期化し、 // 指定されたNSManagedObjectContextに挿入できるようになる。 let memo = Memo(context: context) //memoはManagedObjectのサブクラスを初期化し、 //指定されたNSManagedObjectContextに挿入できるようになったので、 //以下のように値などを入れる事ができる。 memo.name = addTextField.text memo.check = false // 変更があれば保存する処理 (UIApplication.shared.delegate as! AppDelegate).saveContext() navigationController!.popViewController(animated: true) } }データを取得して、表示するコード
import UIKit class MemoTableViewController: UITableViewController { // 変数memosを用意して、core dataのMemo型を追加していくためにMemo型に指定する var memos : [Memo] = [] override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: TableViewCell.reuseIdentifier) } override func viewWillAppear(_ animated: Bool) { // データを取得する getData() tableView.reloadData() } // MARK: - Table view data source override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return memos.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.reuseIdentifier, for: indexPath) as! TableViewCell let memo = memos[indexPath.row] cell.configure(isCheck: memo.check, name: memo.name!) return cell } func getData() { // 変更箇所を見にいく let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext do { // あれば取得 memos = try context.fetch(Memo.fetchRequest()) } catch { print("読み込み失敗") } } }cellの内容
import UIKit class TableViewCell: UITableViewCell { @IBOutlet weak var label: UILabel! @IBOutlet weak var checkImage: UIImageView! static let image = UIImage(named: "check") static let reuseIdentifier = "Cell1" func configure(isCheck: Bool, name: String) { // checkマークの表示の処理 checkImage.image = isCheck ? TableViewCell.image : nil label.text = name } }データの内容を削除
editingStyle内で処理を書きます。
また同じように、viewContextで操作できるようにします。
context.delete
で削除できます。
あとは削除した状態をsaveContext()
で保存してあげれはOKです。override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext if editingStyle == .delete { let memo = memos[indexPath.row] context.delete(memo) (UIApplication.shared.delegate as! AppDelegate).saveContext() do { // あれば取得 memos = try context.fetch(Memo.fetchRequest()) } catch { print("読み込み失敗") } } tableView.reloadData() }checkマークの切り替え
!を先頭につける事で値が反転できます。
これをdidSeleceRowAtに書く事で、cellのタップ時に切り替える事ができます。override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let memo = memos[indexPath.row] memo.check = !memo.check tableView.reloadData() }最後に
まだまだ理解できていないところがありますが、実装段階での理解の手助けになれば幸いです。
間違っている箇所などありましたら遠慮なくご指摘ください。参考サイト
https://blog.codecamp.jp/programming-iphone-app-development-todo
- 投稿日:2021-01-16T17:41:46+09:00
NavigationBarのBarButtonItemにテキスト量に応じたサイズで吹き出しメッセージを表示する。
はじめに
アプリ起動時にボタンの機能を簡単に紹介できる吹き出しが欲しいと思い、
NavigationBarのボタンからポップ表示できる吹き出しメッセージを作成。準備
MainStoryBoardでNavigationControllerを追加します。
追加は、ViewControllerを選択して「Editor」→「Enbed In」→「Navigation Controller」コード
吹き出しviewの三角矢印部は後から追加されるため、完成したviewは指定したサイズより大きくなる。
今回のように上側矢印の場合はviewのzeroポイントに中身のパーツを配置すると
矢印上にパーツが配置されることになる。
それを防ぐために吹き出しのViewControllerのセーフエリアを取得してパーツを配置。ViewContoller.swiftimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. //ボタンの作成 let actionButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(actionTapped(sender:))) //ナビゲーションバーに右ボタンを追加(2つ以上追加する場合) navigationItem.setRightBarButtonItems([editButtonItem,actionButton], animated: true) //一つだけの場合 //navigationItem.rightBarButtonItem = actionButton } override func viewDidAppear(_ animated: Bool) { super .viewDidAppear(animated) //pop表示メソッドを実行 showPopGuide() } //ガイド用吹き出しのPop表示メソッド func showPopGuide() { //吹き出しを表示するターゲットを指定 if let target = navigationItem.rightBarButtonItems?[1] { let popVC = PopoverViewController() popVC.modalPresentationStyle = .popover //pop表示するViewのサイズを指定(今回は、文字量に応じて更新される) popVC.preferredContentSize = CGSize(width: 100, height: 100) //矢印を表示したいbarButtonの指定 popVC.popoverPresentationController?.barButtonItem = target //矢印の方向を制限する popVC.popoverPresentationController?.permittedArrowDirections = .any //デリゲートの設定 popVC.popoverPresentationController?.delegate = self popVC.text = "メッセージをポップ表示します。\nViewのサイズはテキストの量で\n自動調整されます。" //吹き出しを表示 present(popVC, animated: true, completion: nil) } } //actionButtonをタップしたときに実行されるメソッド @objc func actionTapped(sender:UIBarButtonItem){ print("ActionTapped") } } // MARK: - UIPopoverPresentationControllerDelegate extension ViewController: UIPopoverPresentationControllerDelegate { // noneを返すことで、iPhoneでもpopover表示ができるようになる func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { return .none } // Popoverの外をタップしたら閉じるべきかどうかを指定 func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool { //trueを返すと、viewの外をタップした時に閉じる return true } } // MARK: - PopoverViewController (ポップ表示するViewController) //ポップ用のViewController class PopoverViewController: UIViewController{ //表示するTextView let guideTextView = UITextView() var text:String = "" override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .cyan //textViewの設定 guideTextView.isEditable = false guideTextView.isSelectable = false guideTextView.backgroundColor = .clear guideTextView.text = text view.addSubview(guideTextView) //このViewController自体がタップされたことを受け取れるように設定 view.isUserInteractionEnabled = true //このViewControllerがタップされたときに実行するメソッドの指定(VCを閉じるのに使用) view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewTapped(sender:)))) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() //iOS13以降矢印のエリアを除外するにはSafeAreaを使うようにする //safeAreaが確定して使用できるようになるのはviewWillLayoutSubviews()以降 let safeArea = view.safeAreaInsets //textViewの大きさを設定 guideTextView.frame.size = preferredContentSize guideTextView.frame.origin = CGPoint(x:safeArea.left , y: safeArea.top) } //textViewの内容にサイズを合わせる(ViewControllerのプロパティをoverrideして書き換える) override var preferredContentSize: CGSize { get { if presentingViewController != nil { return guideTextView.sizeThatFits(presentingViewController!.view.bounds.size) } else { return super.preferredContentSize } }set { super.preferredContentSize = newValue } } //このView自体をタップした時に呼ばれるメソッド @objc func viewTapped(sender:UITapGestureRecognizer){ print("viewtapped") //view自体を閉じる dismiss(animated: true, completion: nil) } }
- 投稿日:2021-01-16T16:57:22+09:00
UITextFiledで一発でplaceHolderの色を選択できるExtensionを考えてみた
はじめに
どうもこんにちは、TOSHです。
UITextFieldを使用したときに、textColorはそのままセットできるのに、なぜか、placeHolderの色はそのままセットできないって感じた人ことある人は多いと思います。
ということで、placeHolderについてもTextColorと同じようにセットできるようなExtensionを考えてみました。通常の方法
通常、placeHolderの色を変更する場合は、
let color: UIColor = ~ここで任意の色を指定する~ textField.attributedPlaceholder = NSAttributedString( string: "placeHolderの中の文字", attributes: [NSAttributedString.Key.foregroundColor : color])まあ、別にそんなに大変ではないが、NSAttibutedStringを使うのかといった感じですよね。
Extensionを用いた実装方法
extension UITextField { var placeHolderColor: UIColor { set { self.attributedPlaceholder = NSAttributedString( string: self.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor : newValue]) } get { let defaultPlaceHolderGray = UIColor(red: 0, green: 0, blue: 0.0980392, alpha: 0.22) guard self.attributedPlaceholder?.length != 0, let placeHolderColor = self.attributedPlaceholder?.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor else { return defaultPlaceHolderGray } return placeHolderColor } } }extensionのなかで、Stored Propertyはできないので、Computed Propertyを使用します。
setterは簡単なのですが、意外と苦戦したのは、getter。
Place Holderのなかに文字が入っていない可能性もあるので、self.attributedPlaceholder?.length != 0でガードをする必要があります。あと、あくまで、attributedPlaceholderの1文字目を取っているだけなので、1文字目以降で文字が変わるようなAttributedString出会った場合は、うまく動いていないと思っていいでしょう。
ただ、セットすることはあってもなかなか取ってくることはないと思われるのでこれについてはあまり深く考えなくてもいい気がします。。。まとめ
実装していて気づいたことは、そもそも、Place Holderの色をセットしたが、中身をセットしないということはないので、textのセットと同時に行うことのできるNSAttributedStringを使用しているののだなと思いました。(Textの場合は、ユーザーが文字を入力するので、初期状態で、色を指定しておいて、Textを指定しないということがメインの使い方)
なので、せっかく作ったはいいんですけど、あまり使用されないようなExtensionだなーという感覚です。
- 投稿日:2021-01-16T16:43:45+09:00
XcodeのプロジェクトをGitHubにあげる手順
はじめに
この記事にはXcodeでプロジェクトを作成するところから、GitHubにプロジェクトをあげるところまでまとめました。
前提
・GitHubのアカウントを持っている
・ターミナルにgitをインストールしている
・ターミナル(git)にて初期設定が完了している環境
・git version 2.24.3
・macbookpro 16inch (2019)
・Xcode Version 12.3 (12C33)目次
1.Xcodeでプロジェクトの作成
2.GitHubでリポジトリの作成
3.ターミナルでGitHubのリポジトリにPush1.Xcodeでプロジェクトの作成
はじめにXcodeにて新しくプロジェクトを作成します。
Create a new Xcode project を選択
poduct Name: TestApp
Team: 任意
Organization Identifier: 任意
Bundle Identifier: 任意
Interface: Storyboad
Life Cycle: UIKit App Delegate
Language: Swift
→Next
(プロジェクト自体は触らないので今回ここはなんでもいいです)
任意のフォルダを選択し、Create
※ Create Git repository on my Mac は選択しない2.GitHubでリポジトリの作成
GitHubでログインし、初期画面の左上にある緑色のNewをクリック
Ownerを自身に選択し(任意)、Repository nameにTestAppと入力(任意)し,
緑色のCreate repositoryをクリック
※チェックボックスはどれも選択しない3.ターミナルでGitHubのリポジトリにPush
ターミナルを開き、
cd
と入力し、Xcode上のTestApp(黄色いフォルダのアイコン)をターミナル上へドラッグ&ドロップしてEnter
git commit -m "first commit"
と入力しEnter
(""の中は任意)
- 投稿日:2021-01-16T13:10:06+09:00
【SwiftUI】Mapビューを使用する
この記事は何?
iOS13では、SwiftUIでMapKitを扱うにはUIKit互換のビューでラップする必要がありました。iOS14で、SwiftUIネイティブなビューとしてMapが実装されたようです。
ここでは、「アップル本社の場所を地図上に表示するビュー」のコードを載せておきました。環境
- macOS11.1
- Xcode12.3
- Swift5.3
コード
SwiftUIのMapビューimport SwiftUI import MapKit struct MapView: View { @State var region = MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: 37.3351, longitude: -122.0088), span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)) var body: some View { Map(coordinateRegion: $region) } } struct MapView_Previews: PreviewProvider { static var previews: some View { MapView() } }プレビュー
- 投稿日:2021-01-16T10:29:51+09:00
【iOS】Firebaseど素人のFirebase入門 認証編
Firebaseど素人だったので、Firebase学習の為に公式ドキュメントを見て、学びをサンプルアプリという形でアウトプットしました。
まずはAuthentication
(認証)に挑戦!作ったサンプルアプリのデモ
現在使用中のユーザーメールアドレスがラベルに表示されるアプリです。
・メールアドレスとパスワードを使った新規登録(サインアップ)
・登録済みのアカウントの認証(サインイン)
・サインアウト
・匿名認証この機能の実装を行いました。
まずはFirebaseに登録
Firebase公式ドキュメントに沿って、Firebase登録とFirebase SDKをアプリに追加する。
Firebase を iOS プロジェクトに追加するアプリ内でFirebaseの初期化を行う
AppDelegate.swiftにFirebaseをインポート
AppDelegate.swiftimport Firebase
didFinishLaunchingWithOptions
内でFirebaseApp.configure()
を宣言AppDelegate.swiftfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() return true }使用するログインプロバイダのステータスを変更する
今回はメール/パスワード認証と匿名認証をログインに使用するので、Firebaseに登録したアプリのプロジェクト内
Authentication
のSign-in method
で
メール/パスワード
匿名
こちらの二つのステータスを有効に変更します。
ViewControllerにFirebaseをインポート
作成を進めるファイルにもFirebaseをインポートする。
AuthViewController.swiftimport Firebaseメール/パスワード認証
新規登録
新規登録には
createUser(withEmail:, password:, completion:)
を使用します。Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in guard let user = authResult?.user, error == nil else { print("登録に失敗しました:" ,error!.localizedDescription) return } print("登録に成功しました", user.email!) }サインイン
サインインには、
signIn(withEmail:, password:, completion:)
を使用します。Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in guard let user = authResult?.user, error == nil else { print("サインインに失敗しました:" ,error!.localizedDescription) return } print("サインインに成功しました", user.email!) }匿名ログイン
匿名ログインには
Auth.auth().signInAnonymously(completion:)
を使います。Auth.auth().signInAnonymously { (authResult, error) in guard let user = authResult?.user, error == nil else { print("匿名サインインに失敗しました:" ,error!.localizedDescription) return } print("匿名サインインに成功しました", user.uid) }サインアウト
signOut()
を呼ぶだけです。do { try Auth.auth().signOut() } catch let signOutError as NSError { print("サインアウトに失敗しました:", signOutError) }現在ログインしているユーザーを取得する
現在ログインしているユーザーを取得するには、Auth オブジェクトでリスナーを設定することをおすすめします。
とのことで、リスナーを設定してみました。
1.
AuthStateDidChangeListenerHandle
をグローバル変数として定義AuthViewController.swiftvar authHandle: AuthStateDidChangeListenerHandle?2.
viewWillAppear
でaddStateDidChangeListener(listener:)
をアタッチoverride func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) authHandle = Auth.auth().addStateDidChangeListener({ (auth, user) in //ログイン状態が変更された時の処理を書く }) }
viewWillAppear
でアタッチしたこのリスナーは、ユーザーのログイン状態が変更される度に呼び出されます。
変更される度に実行したい処理をここに書きます。
とても便利ですね。
ログインされてるなら別ページに遷移、ログイン無しなら登録ページを表示などの使い分けにも有効そうです。その他の方法では、
currentUser
でログインユーザーの状態も取得出来るようです。currentUser を使用することでも、現在ログインしているユーザーを取得できます。ユーザーがログインしていない場合、currentUser は nil です。
3.
viewWillDisappear
でリスナーを切り離します。override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) Auth.auth().removeStateDidChangeListener(authHandle!) }まとめ
Firebaseの公式ドキュメントはとても充実しており、日本語ページもあるので簡単にここまでは進めれました。
もっとしっかり公式ドキュメントを読み、理解を深めていきたいと思います。
何か間違いがありましたら、優しく指摘していただけると幸いです?♂️参考
iOS で Firebase Authentication を使ってみる
サンプリアプリのコード全体
import UIKit import Firebase class AuthViewController: UIViewController { @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var currentUserEmailLabel: UILabel! @IBOutlet weak var signUpButton: UIButton! @IBOutlet weak var signInButton: UIButton! var authHandle: AuthStateDidChangeListenerHandle? override func viewDidLoad() { super.viewDidLoad() signUpButton.layer.cornerRadius = 22 signInButton.layer.cornerRadius = 22 emailTextField.delegate = self passwordTextField.delegate = self } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) authHandle = Auth.auth().addStateDidChangeListener({ (auth, user) in if let currentUser = user { //もし、ユーザーが匿名で利用していたら if currentUser.isAnonymous { self.currentUserEmailLabel.text = "匿名で利用中" } else { self.currentUserEmailLabel.text = currentUser.email } } else { //ログインをしていない場合 self.currentUserEmailLabel.text = "現在ログイン中のユーザーはいません" } }) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) Auth.auth().removeStateDidChangeListener(authHandle!) } // MARK: - Button Actions //サインアウト @IBAction func didTapSignOut(_ sender: UIBarButtonItem) { do { try Auth.auth().signOut() } catch let signOutError as NSError { print("サインアウトに失敗しました:", signOutError) } } //サインアップ @IBAction func didTapSignUp(_ sender: UIButton) { if let email = emailTextField.text, let password = passwordTextField.text { Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in guard let user = authResult?.user, error == nil else { print("登録に失敗しました:" ,error!.localizedDescription) return } print("登録に成功しました", user.email!) } } } //サインイン @IBAction func didTapSignIn(_ sender: UIButton) { if let email = emailTextField.text, let password = passwordTextField.text { Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in guard let user = authResult?.user, error == nil else { print("サインインに失敗しました:" ,error!.localizedDescription) return } print("サインインに成功しました", user.email!) } } } //匿名サインイン @IBAction func didTapSignInAnonymously(_ sender: UIButton) { Auth.auth().signInAnonymously { (authResult, error) in guard let user = authResult?.user, error == nil else { print("匿名サインインに失敗しました:" ,error!.localizedDescription) return } print("匿名サインインに成功しました", user.uid) } } } // MARK: - TextField Delegate Methods extension LoginViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } }
- 投稿日:2021-01-16T09:52:19+09:00
Document-Based AppのmacOSアプリでメニューからファイルオープンするために必要なこと
画像ビューアを作ってみようかと思ったら初手で軽くハマったので、同じようなことでつまずく人もいるでしょう、ということで。
環境はXcode12.3。Document Appでプロジェクトを新規作成
これは当然なので説明は省略。
Document Typesに対象となるファイルの種類を登録
Target->Info->Document Typesにファイルを登録していく。
Classにはプロジェクト作成時に自動的に作成される"Document"クラスを指定。
IdentifierにはUTIというものを指定する。具体的にどんな文字列を指定すればいいかはApple本家の情報が見当たらないが、下記サイトに一覧が書かれている。
http://potting.syuriken.jp/potting_conv/understanding_utis_J/chapter4.html以下、画像ファイルを開くための例。
DocumentクラスをObjective-Cから利用できるようにする
SwiftベースのアプリではNSDocumentを継承した"Document"クラスがそのままでは使えないそうで、@objcを使った一行を追加する必要がある。(この情報がなかなか見つからなかった・・・)
具体的にはこんな感じ。@objc(Document) // 追加 class Document: NSDocument { ...Documentクラスの読み込みのメソッドに手を入れる
ここまでくればファイル選択まではできるので、あとは実際の読み込み処理を追加する。
デフォルトでは下記のメソッドが呼び出される。override func read(from data: Data, ofType typeName: String)ファイルパスでもらいたい場合は下記のメソッドをオーバーライドする。
override func read(from: URL, ofType: String)
- 投稿日:2021-01-16T09:37:52+09:00
NotificationCenterでイベント処理を行おうとしたらうまくいかなかった話
はじめに
NotificationCenter
を使ってクラス間で通知処理を行おうとしたらうまくいかなかったのでそのメモを残します。コピペで動くようにしてあるので、Playgroundなどで貼り付けて確認できます。うまくいかない場合
先にうまくいかない場合を紹介。ざっくり説明すると、
Sender
クラスはインスタンス化された時、test
という通知キーワードをpostします。
Receiver
クラスはインスタンス化された時、test
という通知キーワードを受け取れるようにし、受け取ったらtest()
を実行します。import Foundation class Sender { let notificationCenter = NotificationCenter() init() { notificationCenter.post(name: NSNotification.Name(rawValue: "test"), object: nil) } } class Receiver { let notificationCenter = NotificationCenter() init() { notificationCenter.addObserver(forName: .init(rawValue: "test"), object: nil, queue: nil, using: { [unowned self] notification in test() }) } func test() { print("通知") } } let receiver = Receiver() let sender = Sender()うまくいく場合
うまくいく場合を紹介します。うまくいかなかった場合との違いは
NotificationCenter
をインスタンス化したものを利用しているのかということです。
notificationCenter
→NotificationCenter.default
import Foundation class Sender { init() { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "test"), object: nil) } } class Receiver { init() { NotificationCenter.default.addObserver(forName: .init(rawValue: "test"), object: nil, queue: nil, using: { [unowned self] notification in test() }) } func test() { print("通知") } } let receiver = Receiver() let sender = Sender()原因
クラスでそれぞれ
NotificationCenter
をインスタンス化して使っているのが原因でした。クラス間をまたいで使っていないのでできないことは当たり前といえば当たり前ですが。おまけ
それでもインスタンス化したものを使いたい!という場合の対処法について記述します。レアケースかもしれませんが、
AVAudioPlayer
などでもこの考え方は活きるかと思います。もしかしたらこっちの方が一般的かも。import Foundation class Sender { let notificationCenter = NotificationCenter() func send() { notificationCenter.post(name: NSNotification.Name(rawValue: "test"), object: nil) } } class Receiver { let notificationCenter: NotificationCenter init(initNC : NotificationCenter) { self.notificationCenter = initNC notificationCenter.addObserver(forName: .init(rawValue: "test"), object: nil, queue: nil, using: { [unowned self] notification in test() }) } func test() { print("通知") } } let sender = Sender() let receiver = Receiver(initNC: sender.notificationCenter) sender.send()//通知先ほどのうまくいかなかった場合のソースコードの一部を以下のように変更しました。
Sender
クラスの通知を送る処理をinit()
ではなくsend()
で送るようにした。Receiver
クラスにNotificationCenter
型の変数を定義した。このようにすることでクラス間で共通の
NotificationCenter
型の変数を使えるようになりました。