20210116のSwiftに関する記事は8件です。

[Swift] core dataの基本的な実装をまとめてみた

はじめに

core dataについて色々調べ、とりあえず実装できた状態です。
自分なりにまとめてみます。
tableviewcontrollerと、遷移先の項目を追加するview2つを作成します。
core data以外の操作は省略します?‍♂️

実装と解説

1 プロジェクト作成

まずはじめにプロジェクトを立ち上げる際に、User Core Dataを選択します。
すると、ファイル名.xcdatamodeldというファイルが追加されます、

2 tableviewcontrollerと追加用のviewcontrollerの実装

イメージはこんな感じです。
スクリーンショット 2021-01-16 21.04.39.png

Core Dataの設定

①のファイルを選択すると、Core Dataの設定ができます。
②でEntityを追加します。
③が追加されます。Entityは今回データを入れておいて保管する場所になります。
今回はメモ内容の保存に使いますので、Memoという名前にしてみます。
Entityは大文字からの名前をつけるのが無難だそうです。
スクリーンショット 2021-01-16 8.04.43.png
④の+を押すと、⑤のAttributeがが追加できます。これは今回追加するデータになります。
今回はメモの内容を保存するnameをstring型、チェックマークの状態を保存するcheckをBoolean型で設定します。
型はTypeから設定できます。
スクリーンショット 2021-01-16 8.16.40.png
各項目のOptionalのチェックを外しておいてください。
ここで一旦ビルドしておきます。しておかないと後々エラーになるそうです。
スクリーンショット 2021-01-16 8.25.35.png

コード

保存の処理

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NavigationBarのBarButtonItemにテキスト量に応じたサイズで吹き出しメッセージを表示する。

はじめに

アプリ起動時にボタンの機能を簡単に紹介できる吹き出しが欲しいと思い、
NavigationBarのボタンからポップ表示できる吹き出しメッセージを作成。

完成形
スクリーンショット 2021-01-16 15.37.06.png

準備

スクリーンショット 2021-01-16 17.07.04.png

MainStoryBoardでNavigationControllerを追加します。
追加は、ViewControllerを選択して「Editor」→「Enbed In」→「Navigation Controller」

コード

吹き出しviewの三角矢印部は後から追加されるため、完成したviewは指定したサイズより大きくなる。
今回のように上側矢印の場合はviewのzeroポイントに中身のパーツを配置すると
矢印上にパーツが配置されることになる。
それを防ぐために吹き出しのViewControllerのセーフエリアを取得してパーツを配置。

ViewContoller.swift
import 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)
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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だなーという感覚です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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のリポジトリにPush

1.Xcodeでプロジェクトの作成

1スクリーンショット 2021-01-16 2.44.47.png
はじめにXcodeにて新しくプロジェクトを作成します。
Create a new Xcode project を選択

2スクリーンショット 2021-01-16 2.45.30.png
iOS→Appを選択し、Next

3スクリーンショット 2021-01-16 2.46.04.png
poduct Name: TestApp
Team: 任意
Organization Identifier: 任意
Bundle Identifier: 任意
Interface: Storyboad
Life Cycle: UIKit App Delegate
Language: Swift
→Next
(プロジェクト自体は触らないので今回ここはなんでもいいです)

4スクリーンショット 2021-01-16 2.55.45.png
任意のフォルダを選択し、Create
※ Create Git repository on my Mac は選択しない

5スクリーンショット 2021-01-16 2.56.38.png
こうなればOK

2.GitHubでリポジトリの作成

6スクリーンショット 2021-01-16 2.58.05.png
GitHubでログインし、初期画面の左上にある緑色のNewをクリック

7スクリーンショット 2021-01-16 2.59.21.png
Ownerを自身に選択し(任意)、Repository nameにTestAppと入力(任意)し,
緑色のCreate repositoryをクリック
※チェックボックスはどれも選択しない

8スクリーンショット 2021-01-16 3.01.25.png
こうなればOK

3.ターミナルでGitHubのリポジトリにPush

9スクリーンショット 2021-01-16 3.04.28.png
ターミナルを開き、
cd
と入力し、Xcode上のTestApp(黄色いフォルダのアイコン)をターミナル上へドラッグ&ドロップしてEnter

10スクリーンショット 2021-01-16 3.06.56.png
git init と入力しEnter

11スクリーンショット 2021-01-16 3.09.25.png
git add .と入力しEnter

12スクリーンショット 2021-01-16 3.24.08.png
git commit -m "first commit"と入力しEnter
(""の中は任意)

13スクリーンショット 2021-01-16 3.24.43.png
git branch -M mainと入力しEnter

14スクリーンショット 2021-01-16 3.25.10.png
↓の画像の赤枠の箇所を打ち込む(コピペ)

18スクリーンショット 2021-01-16 3.01.25のコピー.png

15スクリーンショット 2021-01-16 3.25.27.png
git push -u origin mainと入力しEnter

16スクリーンショット 2021-01-16 3.25.42.png

17スクリーンショット 2021-01-16 3.26.59.png
GitHubのrepositoryのページを更新し、こうなっていればOK

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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-16 13.02.48.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】Firebaseど素人のFirebase入門 認証編

Firebaseど素人だったので、Firebase学習の為に公式ドキュメントを見て、学びをサンプルアプリという形でアウトプットしました。
まずはAuthentication(認証)に挑戦!

作ったサンプルアプリのデモ

イメージ.GIF

現在使用中のユーザーメールアドレスがラベルに表示されるアプリです。

・メールアドレスとパスワードを使った新規登録(サインアップ)
・登録済みのアカウントの認証(サインイン)
・サインアウト
・匿名認証

この機能の実装を行いました。

まずはFirebaseに登録

Firebase公式ドキュメントに沿って、Firebase登録とFirebase SDKをアプリに追加する。
Firebase を iOS プロジェクトに追加する

アプリ内でFirebaseの初期化を行う

AppDelegate.swiftにFirebaseをインポート

AppDelegate.swift
import Firebase

didFinishLaunchingWithOptions内でFirebaseApp.configure()を宣言

AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        FirebaseApp.configure()
        return true
    }

使用するログインプロバイダのステータスを変更する

今回はメール/パスワード認証と匿名認証をログインに使用するので、Firebaseに登録したアプリのプロジェクト内AuthenticationSign-in method
メール/パスワード
匿名
こちらの二つのステータスを有効に変更します。
スクリーンショット 2021-01-15 15.43.08.png

ViewControllerにFirebaseをインポート

作成を進めるファイルにもFirebaseをインポートする。

AuthViewController.swift
import 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.swift
    var authHandle: AuthStateDidChangeListenerHandle?

2.viewWillAppearaddStateDidChangeListener(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
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

以下、画像ファイルを開くための例。

スクリーンショット 2021-01-16 9.30.09.png

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)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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をインスタンス化したものを利用しているのかということです。
notificationCenterNotificationCenter.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型の変数を使えるようになりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む