LoginSignup
0
0

More than 5 years have passed since last update.

[Swift] macOSでFirebaseを使う ~ Realtime Database用準備編

Last updated at Posted at 2018-07-17

前回の続きです

Realtime Database

実装に移る前に、mscOSプログラマがFirebaseの機構を理解するためのたった一つの言葉を書いておきます。

FirebaseはすごいKVC/KVO

これであなたはFirebaseをほとんど理解しました。

作るもの

個人用のメモ

仕様

  1. 認証済みユーザーのみ読み書きできる
  2. ほかのユーザーのメモは読み書きできない
  3. メモは「作成日時」「内容」で構成される

ごくシンプルに。

Realtime Databaseの設定

仕様1.2.を実現するためにFirebase側で設定を行います。
Firebase consoleへ移動します。
「Detabase」から「Realtime Database」の「データベースを作成」を選択します。
スクリーンショット (7).png

セキュリティールールはすぐ変更しますので、どちらを選んでも構いません。
スクリーンショット (8).png

「ルール」に移動しルールを以下のように設定します。
スクリーンショット_2018-07-15_11_31_57.jpg

ルール
{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null",

    "users": {
      "$uid": {
        ".read": "$uid == auth.uid",
        ".write": "$uid == auth.uid",
      }
    }
  }
}

やっていることは

  1. データベースへのアクセスは認証済みでなければならない
  2. users\$uid以下へのアクセスはuidが$uidのユーザーのみ許可

です。
rootの read, writeはこれでいいのか自信がないです。

Realtime Databaseの概要

Realtime Databaseの構造は、rootを根とする一つの巨大なDictionanyです。
Swift的にはこうなっていると考えると分かりやすいです。

protocol DatabaseValue {}

extension String: DatabaseValue {}
extension Int: DatabaseValue {}
extension Float: DatabaseValue {}
extension Double: DatabaseValue {}
extension Bool: DatabaseValue {}

extension Array: DatabaseValue where Element: DatabaseValue {}
extension Dictionary: DatabaseValue where Key == String, Value: DatabaseValue {}

struct RealtimeDatabase {

    let root: DatabaseValue
}

詳細な仕様が見つけられてないので、もしかすると使用可能な型がまだあるかもしれません。

各要素へはKeypathでのアクセスのが可能です。

// Realtime Database のrootのReference
let root = Database.database().reference()

// キーによるアクセス
let config01 = root.child("room1").child("config")

// キーパスによるアクセス。セパレータは / 
let config02 = root.child("room1/config")

値の設定はKVCのように行います。

root.child("room1").child("config").setValue(["limit": 1000])

値の取得は普通には出来ず、KVOのObserverのような設定が必要です。


// "room1/config"の変更を監視
let handler = root.child("room1").child("config").observe(.childChanged) { snapshot in

    guard let dict = snapshot.value as? [String: Any] else { return }
    guard let limit = dict["limit") as? Int else { return }

    print(limit)
}

オブザーバはそのままでは値の取得が大変面倒くさい可読性が落ちるので、後ほどextensionを作ります。

スキーマ定義

フリーダムなNoSQLなので必要はないのですが、スキーマ定義はしておかないとあとで死にます問題があります。

root ─ Users ┬ $uid ┬ $id ┬ memo (String)
             │      │   └ date (TimeInterval)
             │      │
             │      ├ $id ...
             │
             ├ $uid ...

$uidはユーザー固有のidです。
$idはメモにユニークなidです。

各ユーザーが個別のメモのリスト(実際には辞書)を持っているようになっている。
各メモはメモ自身と作成日時を持っています。

Modelの定義

メモのモデルを作っておきます。

Memo.swift
struct Memo {

    // realtime database の key
    let dataKey: String

    let memo: String

    let date: TimeInterval
}

extension Memo: Equatable {

    static func == (lhs: Memo, rhs: Memo) -> Bool {

        return lhs.dataKey == rhs.dataKey
    }
}

dataKeyはスキーマ定義の$idに当たるものです。
持っておくと便利なこともあったりなかったり。

ユーザーインターフェイスの作成

メモの表示はViewベースのNSTableViewを使います。

NSTableCellView

まずはNSTableCellViewのサブクラスMemoCellViewを作ります。

MemoCellView.swift
import Cocoa

class MemoCellView: NSTableCellView {

    @IBOutlet private var memoField: NSTextField!

    var memo: Memo? {

        didSet {

            textField?.objectValue = memo.map { Date(timeIntervalSince1970: $0.date) }

            memoField.stringValue = memo?.memo ?? ""
        }
    }

    static var identifier: NSUserInterfaceItemIdentifier {

        return NSUserInterfaceItemIdentifier("memo")
    }

    override var backgroundStyle: NSView.BackgroundStyle {

        didSet {

            switch backgroundStyle {

            case .dark:
                memoField.textColor = .white

            case .light, .raised, .lowered:
                memoField.textColor = .controlTextColor
            }
        }
    }
}

日時の表示にはNSTableCellViewtextFieldを利用します。
memoが設定されたらそれを表示するだけです。

NSViewController

次にNSViewControllerのサブクラスMemoViewControllerを作ります。
xib付きで作成してください。

これもCocoaBindingsを利用します。

MemoViewController.swift
import Cocoa
import FirebaseAuth
import FirebaseDatabase

class MemoViewController: NSViewController {

    private var userRef: DatabaseReference?
    private var handles: [DatabaseHandle] = []

    private var memos: [Memo] = []

    @IBOutlet private var memoTable: NSTableView!

    @objc dynamic private var memo: String = ""

    @objc dynamic private var canPost: Bool {

        return memo != ""
    }

    override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {

        if key == #keyPath(canPost) {

            return [#keyPath(memo)]
        }

        return []
    }
}

謎のプロパティがありますが、とりあえず無視してください。

memosプロパティの内容をmemoTableに表示するだけです。

見た目

UIは次のようにしました。

スクリーンショット_2018-07-15_11_03_57-2.jpg

ちょっとアレですが、そこは目をつむってください。

一番上のNSTextFieldvalueFile's Ownermemoをbind、Continuously Updates Valueをチェック。
PostボタンのEnabledFile's OwnercanPostをbind。アクションは後ほど。
Sign outボタンのアクションをFirst RespondersignOut:に接続。
File's OwnermemoTableアウトレットにNSTableViewを接続。
NSTableViewdelegateおよびdataSourceFile's Ownerに接続。
NSCellViewCustom ClassMemoCellViewに、identifiermemoに設定。
NSCellView内にNSTextFieldを配置して、これをMemoCellViewmemoFieldアウトレットに接続。
元から付いてるNSTextFieldDateFormatterを設定。

AutolayoutもしくはAutoresizingはいい感じに設定してください。

NSTableViewDelegate & NSTableViewDataSource

NSTableViewDelegateおよびNSTableViewDataSourceは以下の通りです。

MemoViewController.swift
extension MemoViewController: NSTableViewDelegate {

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {

        guard let memoView = tableView.makeView(withIdentifier: MemoCellView.identifier, owner: nil) as? MemoCellView else {

            fatalError("Can not get MemoCellView.")
        }

        memoView.memo = memos[row]

        return memoView
    }
}

extension MemoViewController: NSTableViewDataSource {

    func numberOfRows(in tableView: NSTableView) -> Int {

        return memos.count
    }
}

エラー処理をはしょってますが、普通の実装です。特に問題ないでしょう。

メインウインドウ

作ったMemoViewControllerMainWindowに貼り付けます。

MainWindowController.swift
import Cocoa

class MainWindowController: NSWindowController {

    override var windowNibName: NSNib.Name {

        return NSNib.Name("MainWindowController")
    }

    override func windowDidLoad() {

        super.windowDidLoad()

        self.contentViewController = MemoViewController()
    }
}

気になるようでしたら前回MainWindowController.xibに貼り付けたボタンは外してください。
外さなくても問題ないです。

次回

Realtime Databaseへのアクセスを行います。

macOSでFirebaseを使う ~ Realtime Database用準備編

macOSでFirebaseを使う ~ Authentication編

macOSでFirebaseを使う ~ Realtime Database編

macOSでFirebaseを使う ~ おまけ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0