20200814のiOSに関する記事は10件です。

新しいmapを使ってみた

はじめに

Apple Developer DocumentationをみるとMapKit中にstruct Mapが増えています。
どうもSwiftUIで直接mapが表示できるようになったようです。
実際に作ってみました。
サンプルプロジェクトはgithubにアップしてあります。

コードの解説

サンプルプロジェクトのポイントを紹介します。

Mapを表示する

@State var coordinate = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 36.0, longitude: 138), latitudinalMeters: 2000000, longitudinalMeters: 2000000)
Map(coordinateRegion: $coordinate)

Mapを表示するには、表示したい位置情報(coordinate)を指定すると表示できます。

ピンを表示する

ピンを表示するには、MapAnnotationProtocolを実装する必要があるようです。
今回は、下記のような構造体を用意しました。(ネーミングが悪くてすみません)

struct AnnotationItemStruct:Identifiable{
    // ID(識別子)
    let id = UUID()
    // 緯度経度
    let coordinate:CLLocationCoordinate2D
}

次にピンの配列を作成します。

@State var annotationItems: [AnnotationItemStruct] = []

最後にピンに位置情報を追加します。

let annotationItem = AnnotationItemStruct(coordinate: coordinate)
self.annotationItems.append(annotationItem)

実際にピンをmapに表示できるようにmapを修正します。

Map(coordinateRegion: $coordinate, annotationItems: annotationItems) { annotationItem in
    // annotationItemsから1つ取りだした情報からピンを打つ
    MapPin(coordinate: annotationItem.coordinate)
}

参考

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

CocoaPodsインストール時に発生する「ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES」の意味と対応方法2つ

はじめに

CocoaPodsをインストールしたときに、「ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES」に関する警告が表示されることはありませんか?
この記事では、その意味と対応方法を2つご紹介します。

発生する事象

pod install を実行した場合、以下のようなメッセージが表示されることがあります。
以下は、MyAppというプロジェクト名・ターゲット名のアプリの例です。

Terminalに表示される警告メッセージ

[!] The `MyApp [Debug]` target overrides the `ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES` build setting defined in `Pods/Target Support Files/Pods-MyApp/Pods-MyApp.debug.xcconfig'. This can lead to problems with the CocoaPods installation
    - Use the `$(inherited)` flag, or
    - Remove the build settings from the target.

[!] The `MyApp [Release]` target overrides the `ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES` build setting defined in `Pods/Target Support Files/Pods-MyApp/Pods-MyApp.release.xcconfig'. This can lead to problems with the CocoaPods installation
    - Use the `$(inherited)` flag, or
    - Remove the build settings from the target.

環境

  • Xcode: 11.5
  • Swift: 5
  • CocoaPods: 1.9.3

説明

対応方法のみ知りたい方は、読み飛ばしてください :ok_woman:

警告メッセージの日本語訳

MyApp [Debug/ Release] (最終的なアプリバンドル )のターゲットが、 Pods/Target Support Files/Pods-MyApp/Pods-MyApp.xxx.xcconfig で定義された、 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES (Swift標準ライブラリ を常に埋め込む)というビルド設定を、オーバーライドしています。
これにより、CocoaPodsのインストールで問題が発生する可能性があります。」

つまり、CocoaPodsで管理しているビルド設定を、アプリのターゲットのビルド設定が上書きしちゃってるけどいいの?という注意喚起をしてくれています。

CocoaPodsで管理しているビルド設定
Screen Shot 2020-08-14 at 11.55.03.png

アプリのターゲットのビルド設定
Screen Shot 2020-08-14 at 11.57.07.png

ビルド設定「ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES 」とは?

ターゲットがSwiftコードを含まない場合でも、常にターゲットの製品にSwift標準ライブラリを埋め込むかどうかを設定します。YESに設定されている場合、アプリにSwift標準ライブラリを埋め込むようにXcodeに指示します。
例えば、

  • ターゲットがSwiftを含む他の製品を埋め込んでいる場合や、
  • Swiftを含まないがSwiftを含む製品をテストしているテストターゲットの場合

には、この設定を有効にすべきです。

Xcode Help 参照

Swift標準ライブラリとは?

Swift標準ライブラリは、Swiftプログラムを書くための機能のベースレイヤーを定義しています。
Int, Doubleなどの基本的なデータ型や、printのようなグローバル関数などが含まれています。
【Apple開発者ドキュメント】Swift Standard Library

ターゲットがSwiftコードを含んでいる場合に、アプリバンドルにコピーされます。
なお、製品がSwiftコードを含んでいるかどうかは、実行ファイル上でotool -Lを実行することで確認することができます。otool -Lの結果の中にSwiftライブラリが表示されていれば、製品はSwiftを使用していることになります。
【Apple開発者ドキュメント】Technical Q&A QA1881: Embedding Content with Swift in Objective-C

対応方法

対応方針

CocoaPods側で管理されている、「ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES」というビルド設定を、最終的なアプリバンドル のターゲットに引継ぎます。

1. Xcode上で変更する方法

メリット

変更方法が視覚的にわかりやすい。

デメリット

チームプロジェクトの場合、チームメイト全員が各自で pod install したあとに、確実にこの対応をするようにしなければならない。

方法

  1. 設定に移動
  2. ターゲットを選択
  3. 「Build Setting」を選択
  4. 「All」と「Combined」を選択
  5. "Always Embed Swift Standard Libraries" を検索
  6. 「$(inherited)」を値に入力、もしくは項目自体を削除。

Screen Shot 2020-08-14 at 21.02.40.png

Screen Shot 2020-08-11 at 22.01.22.png

2. Podfileに記述する方法

メリット

一度Podfileに記述すれば、自動的に pod install  したときに自動的に実行される。
個人差が発生しない。

デメリット

特になし?
人によっては見慣れないこと。

方法

以下の文をPodfileに追記した上で、 pod install を実行する。

Podfileに追加する文

post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = '$(inherited)'
        end
    end
end

【stack overflow】What's ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES with CocoaPods, Swift 3 and Xcode 8
【blog】CocoaPods - Always Embed Swift Standard Libraries

おわりに

以上、CocoaPodsインストール時に発生する「ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES」の意味と対応方法2つについて、説明しました。
対応方法が書かれた記事はよく見かけるのですが、結局これってどういう意味なの?を説明した記事がなかなか見つからなかったので、両方をこの機会にきちんと調べてみました。
どなたかのお役に立てたら嬉しいです。

参考リンク

Xcode Help
【Apple開発者ドキュメント】Swift Standard Library
【Apple開発者ドキュメント】Technical Q&A QA1881: Embedding Content with Swift in Objective-C
【stack overflow】What's ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES with CocoaPods, Swift 3 and Xcode 8
【blog】CocoaPods - Always Embed Swift Standard Libraries

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

iOSのネットワーク接続状態の検出にはReachabilityよりシンプルなNWPathMonitorを使いましょう。

iOSでネットワーク接続状態の取得といえばReachabilityを使うといった印象が強くありませんか。

Reachabilityを使うには、まず
https://developer.apple.com/library/archive/samplecode/Reachability/Introduction/Intro.html
からApple の提供しているサンプルコードをダウンロードして・・・なんて手間がありましたが、もっと手軽に利用できるApple標準のクラスがありました。

簡単にネットワーク接続のモニタリングが可能

NWPathMonitor

ネットワークの接続状態やネットワークインターフェースなどの情報をモニタリングできるNetwork frameworkのクラス。iOS 12.0以降から利用可能。
https://developer.apple.com/documentation/network/nwpathmonitor

実装ステップ

利用箇所でまずフレームワークをインポートしておきましょう。

import Network

モニタリング用クラスのインスタンス化

解放されるとコールバックが受け取れないので、ViewControllerのプロパティなどとして保持しておきましょう。

let monitor = NWPathMonitor()

requiredInterfaceType引数を指定して、ネットワークインターフェース別にモニタリングすることも可能です。

let wifiMonitor = NWPathMonitor(requiredInterfaceType: .wifi)

サポートしているインターフェースのタイプは以下に記載があります。
https://developer.apple.com/documentation/network/nwinterface/interfacetype

ネットワーク接続状態の変更をハンドリングする

monitor.pathUpdateHandler = { path in
    if path.status == .satisfied {
        // Connected
    }
}

後述しますが、モニタリングは通常バックグラウンドキューで指定して実行されます。
クロージャー内でUIを操作する場合には、メインスレッドで行うようにしましょう。

ネットワーク接続状態のモニタリングを開始する

モニタリングイベントを配信するためのDispatchQueueを定義して、モニタリングを開始します。

private let queue = DispatchQueue(label: "com.sample")
monitor.start(queue: queue)

ソースコード全体

import UIKit
import Network

final class ViewController: UIViewController {

    private let monitor = NWPathMonitor()
    private let queue = DispatchQueue(label: "com.sample")

    override func viewDidLoad() {
        super.viewDidLoad()

        monitor.pathUpdateHandler = { path in
            if path.status == .satisfied {
                print("Connected")
            } else {
                print("Not Connected")
            }
        }

        monitor.start(queue: queue)
    }
}

まとめ

動画で載せたデモアプリの実装ですが、ViewController内を30行前後で書くことができました。
iOS12以降対応ですし、実用性もばっちりですね。
「ネットワーク接続状態の検出にはNWPathMonitor!」が定着すれば良いなと思います。

以上です :tada:
ありがとうございました。

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

【iOS】Pythonista3でQiitaのトレンドを表示してくれるWidgetを作った。【Python】

はじめに

iPhone, iPadユーザーの皆さん、iOS端末上でPythonでの開発ができるPythonista3というアプリはご存知でしょうか?
Pythonを実行するだけであれば個人的にはCarnets - Jupyterがおすすめですが、
Pythonista3では各種ショートカットやウィジェットなどといったアブリ外から使用可能な機能の開発や、ゲームを作ることも出来ます。

今回はそんなPythonista3を使って
Qiitaのトレンドを表示してくれて、クリックするとSafariでそのページを開いてくれるウィジェット
を作成したので紹介したいと思います。
qwid.png

やったこと

今回のウィジェットの場合、Example/Widget下にあるLauncher.pyの内容がほとんどそのまま使えるので、それをベースに、

・QiitaのトップページからトレンドのタイトルとURLを取得してくる機能
・取得した情報を一時的に保存しておく機能
・前回の情報の取得から一定時間上経過していたら再度Qiitaにアクセスし情報を更新する機能

を追加しました。

コード

列数、折り畳んだ状態での行数、フォントサイズの変数をインポート文の後にまとめておいたので、お好みでレイアウトを調節してください。
窮屈&押しにくくなりますが、行数を5以上にすれば全デイリートレンド(30件)を表示できます。

qiita_trends_widget.py
import re
import requests
import appex, ui
import os
from math import ceil, floor
import webbrowser
import pickle
import time

COLS = 1
ROWS = 3
fontsize = 12

def get_trend():
    trends = []
    text = requests.get( 'https://qiita.com/').text
    titles = re.findall('title":".{10,100}?",',text)
    urls = re.findall('uuid":".{10,50}?",',text)
    for i in range(30):
        trends.append({'title':'', 'url':''})
        trends[i]['title'] = titles[i][18:-7]
        trends[i]['url'] = 'https://qiita.com/items/'+urls[i][17:-7]
    trends.append({'lastUpdate':time.time()})
    with open("trends.pickle", "wb") as f:
            pickle.dump(trends, f)
    return trends


class LauncherView (ui.View):
    def __init__(self, shortcuts, *args, **kwargs):
        row_height = 110 / ROWS
        super().__init__(self, frame=(0, 0, 300, ceil(len(shortcuts[:-1]) / COLS) * row_height), *args, **kwargs)
        self.buttons = []
        for s in shortcuts[:-1]:
            btn = ui.Button(title=' ' + s['title'], name=s['url'], action=self.button_action, bg_color='#73c239', tint_color='#fff', corner_radius=7, font=('<System-Bold>',fontsize))
            self.add_subview(btn)
            self.buttons.append(btn)

    def layout(self):
        bw = (self.width - 10) / COLS
        bh = floor(self.height / ROWS) if self.height <= 130 else floor(110 / ROWS)
        for i, btn in enumerate(self.buttons):
            btn.frame = ui.Rect(i%COLS * bw + 5, i//COLS * bh, bw, bh).inset(2, 2)
            btn.alpha = 1 if btn.frame.max_y < self.height else 0

    def button_action(self, sender):
        webbrowser.open(sender.name)

def main():
    widget_name = __file__ + str(os.stat(__file__).st_mtime)
    v = appex.get_widget_view()
    if v is None or v.name != widget_name:
        try:
            with open("trends.pickle", "rb") as f:
                SHORTCUT = pickle.load(f)
        except:
            SHORTCUT = [{'lastUpdate':time.time() - 86400}]
            with open("trends.pickle", "wb") as f:
                pickle.dump(SHORTCUT, f)
        SHORTCUTS = get_trend() if time.time() - SHORTCUT[-1]['lastUpdate'] > 1800 else SHORTCUT #前回から1800秒(30分)以上経っていたら更新
        v = LauncherView(SHORTCUTS)
        v.name = widget_name
        appex.set_widget_view(v)

if __name__ == '__main__':
    main()

まとめ

Pythonista3はいいぞ(使えるパッケージ増えるといいなあ…)

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

【iOS】Pythonista3でQiitaのトレンドを表示してくれるウィジェットを作った。【Python】

はじめに

iPhone, iPadユーザーの皆さん、iOS端末上でPythonでの開発ができるPythonista3というアプリはご存知でしょうか?
Pythonを実行するだけであれば個人的にはCarnets - Jupyterがおすすめですが、
Pythonista3では各種ショートカットやウィジェットなどといったアブリ外から使用可能な機能の開発や、ゲームを作ることも出来ます。

今回はそんなPythonista3を使って
Qiitaのトレンドを表示してくれて、クリックするとSafariでそのページを開いてくれるウィジェット
を作成したので紹介したいと思います。
qwid.png

やったこと

今回のウィジェットの場合、Example/Widget下にあるLauncher.pyの内容がほとんどそのまま使えるので、それをベースに、

・QiitaのトップページからトレンドのタイトルとURLを取得してくる機能
・取得した情報を一時的に保存しておく機能
・前回の情報の取得から一定時間上経過していたら再度Qiitaにアクセスし情報を更新する機能

を追加しました。

コード

列数、折り畳んだ状態での行数、フォントサイズの変数をインポート文の後にまとめておいたので、お好みでレイアウトを調節してください。
窮屈&押しにくくなりますが、行数を5以上にすれば全デイリートレンド(30件)を表示できます。

qiita_trends_widget.py
import re
import requests
import appex, ui
import os
from math import ceil, floor
import webbrowser
import pickle
import time

COLS = 1
ROWS = 3
fontsize = 12

def get_trend():
    trends = []
    text = requests.get( 'https://qiita.com/').text
    titles = re.findall('title&quot;:&quot;.{10,100}?&quot;,',text)
    urls = re.findall('uuid&quot;:&quot;.{10,50}?&quot;,',text)
    for i in range(30):
        trends.append({'title':'', 'url':''})
        trends[i]['title'] = titles[i][18:-7]
        trends[i]['url'] = 'https://qiita.com/items/'+urls[i][17:-7]
    trends.append({'lastUpdate':time.time()})
    with open("trends.pickle", "wb") as f:
            pickle.dump(trends, f)
    return trends


class LauncherView (ui.View):
    def __init__(self, shortcuts, *args, **kwargs):
        row_height = 110 / ROWS
        super().__init__(self, frame=(0, 0, 300, ceil(len(shortcuts[:-1]) / COLS) * row_height), *args, **kwargs)
        self.buttons = []
        for s in shortcuts[:-1]:
            btn = ui.Button(title=' ' + s['title'], name=s['url'], action=self.button_action, bg_color='#73c239', tint_color='#fff', corner_radius=7, font=('<System-Bold>',fontsize))
            self.add_subview(btn)
            self.buttons.append(btn)

    def layout(self):
        bw = (self.width - 10) / COLS
        bh = floor(self.height / ROWS) if self.height <= 130 else floor(110 / ROWS)
        for i, btn in enumerate(self.buttons):
            btn.frame = ui.Rect(i%COLS * bw + 5, i//COLS * bh, bw, bh).inset(2, 2)
            btn.alpha = 1 if btn.frame.max_y < self.height else 0

    def button_action(self, sender):
        webbrowser.open(sender.name)

def main():
    widget_name = __file__ + str(os.stat(__file__).st_mtime)
    v = appex.get_widget_view()
    if v is None or v.name != widget_name:
        try:
            with open("trends.pickle", "rb") as f:
                SHORTCUT = pickle.load(f)
        except:
            SHORTCUT = [{'lastUpdate':time.time() - 86400}]
            with open("trends.pickle", "wb") as f:
                pickle.dump(SHORTCUT, f)
        SHORTCUTS = get_trend() if time.time() - SHORTCUT[-1]['lastUpdate'] > 1800 else SHORTCUT #前回から1800秒(30分)以上経っていたら更新
        v = LauncherView(SHORTCUTS)
        v.name = widget_name
        appex.set_widget_view(v)

if __name__ == '__main__':
    main()

まとめ

Pythonista3はいいぞ(使えるパッケージ増えるといいなあ…)

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

【Flutter】超簡単!要素を画面いっぱいに表示する方法【Expanded】

この記事を読んで習得できること

Flutterの画面作成で「画面いっぱいに要素を表示したい」っていう問題を解決する力(多分)

結論

Expandedを使って要素を隙間いっぱいに大きくする

こんな感じ

スクリーンショット 2020-08-14 15.14.30.png

とりあえず、書いてみる

main.dart
class _MainViewControllerState extends State<MainViewController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Container(
            decoration: BoxDecoration(color: Colors.blueAccent),
          ),
          Container(
            decoration: BoxDecoration(color: Colors.greenAccent),
          ),
        ],
      ),
    );
      ),
    );
  }
}

スクリーンショット 2020-08-14 15.00.42.png

?

要素が吹っ飛んでしまった。
これは、高さが何も指定されてないためであり、仕方がないこと。

高さを指定してみる

なので、heightをContainerにつけてみる。

main.dart
class _MainViewControllerState extends State<MainViewController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Column(
      children: [
        Container(
          decoration: BoxDecoration(color: Colors.blueAccent),
          height: 300,
        ),
        Container(
          decoration: BoxDecoration(color: Colors.greenAccent),
          height: 300,
        ),
      ],
    ));
  }
}

スクリーンショット 2020-08-14 15.05.36.png

Containerが表示されるようになったが、高さ指定をしてしまっているので、
これだと不都合がことが起きる。

青いContainerを画面いっぱいに引き伸ばしてみる

上での問題を解消するために、ContainerをExpandedで囲ってみる。

main.dart
class _MainViewControllerState extends State<MainViewController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: Container(
              decoration: BoxDecoration(color: Colors.blueAccent),
            ),
          ),
          Container(
            decoration: BoxDecoration(color: Colors.greenAccent),
            height: 200,
          ),
        ],
      ),
    );
  }
}

スクリーンショット 2020-08-14 15.19.47.png

すると、いい感じに画面いっぱいに要素が表示されるようになる。
とりあえずやりたいことは終わり。

Expandedを複数入れると均等に要素を広げてくれる

一番上の画像のような要素表示は、以下のソースで行ってくれた。

main.dart
class _MainViewControllerState extends State<MainViewController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Container(
            decoration: BoxDecoration(color: Colors.orangeAccent),
            height: 100,
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(color: Colors.yellowAccent),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(color: Colors.blueAccent),
            ),
          ),
          Container(
            decoration: BoxDecoration(color: Colors.greenAccent),
            height: 100,
          ),
        ],
      ),
    );
  }
}

複数のExpandedが要素内にあると、それらが均等の大きさになるように自動で調整してくれる。
こういうところが、Flutterの好きなところなんだよなあ…
(他のをあまり知らない)

最後に

この要素内に、Textとかを入れたりすると、要素の横幅がぶっ壊れてしまう。
これの解消方法はまた今度。

main.dart
class _MainViewControllerState extends State<MainViewController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Container(
            decoration: BoxDecoration(color: Colors.orangeAccent),
            height: 100,
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(color: Colors.yellowAccent),
              child: Text("test"),
            ),
          ),
          Expanded(
            child: Container(
              decoration: BoxDecoration(color: Colors.blueAccent),
            ),
          ),
          Container(
            decoration: BoxDecoration(color: Colors.greenAccent),
            height: 100,
          ),
        ],
      ),
    );
  }
}

スクリーンショット 2020-08-14 15.27.23.png

ソース

https://github.com/taichi6930/flutterstudyapp/tree/develop

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

TFCoreMLで変換したモデルの出力がおかしい時の対処法

WWDC2020あたりを境に、tfcoremlで変換したGANの出力が全部真っ白になりました。

対処法

Core ML Tools 4.0をインストールして変換し直す。

pip install coremltools==4.0b2

tfcoremlに比べて、変換メソッドのオプションがシンプルに変わっています。

import coremltools as ct
image_input = ct.ImageType(shape=(1, 256, 256, 3,),
                             scale=2/255,bias=[-1,-1,-1])

coreml_model = ct.convert(
        yourmodel,
        inputs=[image_input],
        )
coreml_model.save("yourmodel.mlmodel")

これで出力されました。

IMG_8701.JPG

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
MLBoysチャンネル
Medium

相棒
note

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

ReactNative × FirestoreでiOSアプリを作成する手順とまとめ

■概要

CloudFirestoreとは

Firebaseが提供している、NoSQLベースのデータベースのうちの一つ(MBaasの一つ)

RealtimeDatabase → CloudFirestoreに進化!

Q、何が改善されてる?
A、データモデルの改善、クエリ強化されている。

Firebaseの機能とデータベース

image.png

■準備

  • Firebaseプロジェクトのセットアップ

    Firebase公式ページからコンソール画面に移動して、新規アプリ作成。

    プロジェクトIDとロケーションは後で変更が出来ないので、注意!

    ※国内向けサービスの場合、asia-northeast1(東京)or asia-northeast2(大阪)

  • バックエンド側のセットアップ

    • Firebase CLIをインストールする。これを使うと、Firebaseを使った開発がより効率的になる。
    npm install firebase-tools
    
    • デプロイを可能にするためにセットアップする。
    npm firebase login
    
  • Firestoreをセットアップ

    最初はテストモードで開始。

    ReactNative%20%C3%97%20Firestore%E3%81%A6%E3%82%99iOS%E3%82%A2%E3%83%95%E3%82%9A%E3%83%AA%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B%E6%89%8B%E9%A0%86%E3%81%A8%E3%81%BE%E3%81%A8%E3%82%81%20dc05f9092d8940bb80ec7f9bc1369a57/Untitled%201.png

  • npmモジュールの追加

    npm install @react-native-firebase/app
    
  • iOSアプリ開発環境のセットアップ

    • CocoaPods経由でFirebaseをインストール。

    Finderで、「プロジェクト名」→「ios」→「podfile」を開く。podfile内にテキストで以下を記述する。

    target 'Project' do
      pod 'Firebase/Core'
      pod 'Firebase/Auth'
      pod 'Firebase/Firestore'
      pod 'Firebase/Functions'
    end
    

    iosアプリにFirebaseを作成するページで以下の流れに沿って進める。
    - pod install

    cd ios/ && pod install
    
  • Firebase SDKのセットアップ

    npm install @react-native-firebase/app
    npm install @react-native-firebase/auth
    npm install @react-native-firebase/firestore
    

■実装(データの操作)

  • CollectionとDocumentの取得、追加

    import firestore from '@react-native-firebase/firestore';
    
    // 'Users'というCollectionの、'KHarada'というDocumentを指定
    const userDocument = firestore()
      .collection('Users')
      .doc('kHarada');
        // 取得
        .get()
        // 追加
        .set()
    
  • AuthenticationアカウントとFirestoreドキュメントの作成

    新規登録画面で入力されたメールアドレス等をfirestoreドキュメントにぶちこむ。

    handleSubmit = async () => {
        try {
                // メールアドレス認証
          let res = await auth()
            .createUserWithEmailAndPassword(this.state.email, this.state.password)
            .catch((err) => {
              console.log(err);
            });
    
                    // ドキュメント内に保存するフィールド
            let userData = {
              uid: res.user.uid,
              email: this.state.email,
            };
    
                    // userDataを保存する
            await firestore()
              .collection('users')
              .doc(res.user.uid)
              .set(userData);
    
              console.log('email login success');
              this.props.navigation.navigate('App');
          }
    
        } catch (e) {
          console.log(e.message);
        }
      };
    
  • クエリの機能と種類

    クエリを使いこなすことで、firestore内に保存されているデータを制御し、自在に操作することが可能

    • 条件制限

      where

      Ex, 18歳以上のみ取得したいとき

      firestore()
        .collection('Users')
        // 18歳以上のみ
        .where('age', '>=', 18)
        .get()
      
    • 件数制限

      limit

      ドキュメントの数を制限したいとき

      firestore()
        .collection('Users')
        // 18歳以上のみ
        .where('age', '>=', 18)
        // ドキュメントを20こまで
        .limit(20)
        .get()
      
    • 並べ替え

      order By

      firestore()
        .collection('Users')
        // 降順で並べ替え
        .orderBy('age', 'desc')
        .get()
      
    • 特定のポイントから開始/終了

      start At / end At

      firestore()
        .collection('Users')
        .orderBy('age', 'desc')
          // 18歳から30歳まで
        .startAt(18)
        .endAt(30)
        .get()
      

      ↓実際に使う場面はこんなとき!

      startAtでリロード後の追加データをどのポイントから取得するのか指定する。

■Firestoreの課題

  • マイグレーション

    マイグレーション:ソフトウェアやシステム、データなどを別の環境に移転したり、新しい環境に切り替えたりすること。

    データのエクスポートが出来ない。バックアップする方法がない。

    (このデメリットはさほど気にならない気もする・・)

  • データベース設計

    データの形式が自由なだけあって、フィールドに書き込んだデータの型がわからなかったりする。

    チーム内でのドキュメント作成と共有が重要。

  • 検索

    ネイティブインデックスの作成やドキュメント内のテキストフィールドの検索をサポートしていないので、Algoliaという検索APIを使って、検索周りはそっちに任せる。

Firebaseでバックエンドエンジニア不在のアプリ開発 クックパッドが体感した、メリットと課題 #エンジニアHub - エンジニアHub|若手Webエンジニアのキャリアを考える!

全文検索 | Firebase

■Firestoreを今まで触ってみて思ったこと

  • プロジェクトの規模にもよるが、とにかく機能が充実していて、あまり不便さを感じていない。なので、RDBの必要性が見えてこない。
  • Authenticationは多くのSNSと連携することが可能なので、モダンなプロダクトにはもってこい
  • 多くの企業が利用しているせいか、日本語の記事が多い印象。初心者に優しい。

参考ページ

Cloud Firestore | React Native Firebase

実践Firestore (技術の泉シリーズ(NextPublishing))

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

【入門】iOS アプリ開発 #4【アーキテクチャの設計】

アーキテクチャの設計

今回はパックマンのゲームを構築するにあたり、全体のアーキテクチャを設計する。

仕様書から画面モードは4つあり、それぞれのモード内にはキャラクタがいて移動処理などがある。これらを1つ1つのオブジェクトとして管理し扱っていく。

Image2.png

設計方針

各画面モードをオブジェクトとして扱い、簡単に切り替え操作したい。

attractMode.startSequence()
// ・・・アトラクトモード実行中・・・

// アトラクトモードからクレジットモードへ切り替える
attractMode.stopSequence()
creditMode.startSequence()
// ・・・クレジットモード実行中・・・

こんな感じ。

またイベント・メッセージを画面モード内のオブジェクトにも簡単に通知したい。

attractMode.sendEvent(message: .Update, parameter: [16])
attractMode.sendEvent(message: .Touch, parameter: [x,y])

イベント・メッセージは表示更新タイミング(Update)やタッチイベントなどで、画面モードが無効のものには通知しないようにする。

まとめると、以下のようなオブジェクトの階層構造が作れるようにする。

Image4.png

これらの基本コンポーネントのクラスを作成する。

CbObject class(基底クラス)

/// Based object class
class CbObject {

    /// Kind of Message ID to handled events in object.
    enum EnMessage: Int {
        case None
        case Update
        case Timer
        case Touch
        case Swipe
        case Accel
    }

    /// When it is true, object can handle events.
    var enabled: Bool = true

    /// For accessing parent objects.
    private var parent: CbObject?

    /// Initialize self without parent object
    init() {
        parent = nil
    }

    /// Initialize self with parent object
    /// - Parameter object: Parent object
    init(binding object: CbObject) {
        parent = object
        parent?.bind(self)
    }

    /// Bind self to a specified object
    /// - Parameter object: Object to bind self.
    func bind( _ object: CbObject) {
        // TO DO: override
        // (This is pure virtual method.)
    }

    // Send event messages
    /// - Parameters:
    ///   - id: Message ID
    ///   - values: Parameters of message.
    func sendEvent(message id: EnMessage, parameter values: [Int]) {
        receiveEvent(sender: self, message: id, parameter: values)
    }

    /// Handler called by sendEvent method to receive events
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    func receiveEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        guard enabled else { return }
        if message == .Update {
            update(interval: values[0])
        } else {
            handleEvent(sender: sender, message: message, parameter: values)
        }
    }

    /// Event handler
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    func handleEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        // TO DO: override
        // (This is pure virtual method.)
    }

    /// Update handler
    /// - Parameter interval: Interval time(ms) to update.
    func update(interval: Int) {
        // TO DO: override
        // (This is pure virtual method.)
    }

}

基底クラスの CbObject は、init で上位のオブジェクトとの関連付けを行える。sendEventメソッドでメッセージを送信でき、メッセージが、.Update なら、updateハンドラが呼ばれ、それ以外は、handleEvent が呼ばれる。ハンドラは派生クラスでオーバーライドして扱う。プロパティの enabled を false にすると、ハンドラは呼ばれなくなる。

CbContainer class(派生コンテナ・クラス)

/// Container class that bind objects.
class CbContainer : CbObject {

    private var objects: [CbObject] = []

    /// Bind self to a specified object
    /// - Parameter object: Object to bind self.
    override func bind( _ object: CbObject) {
        objects.append(object)
    }

    /// Handler called by sendEvent method to receive events
    /// It sends messages to all contained object.
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    override func receiveEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        guard enabled else { return }

        super.receiveEvent(sender: sender, message: message, parameter: values)

        for t in objects {
            t.receiveEvent(sender: self, message: message, parameter: values)
        }
    }
}

派生コンテナ・クラスの CbContainer は、sendEventメソッドで受け取ったメッセージを、関連付けしている全てのオブジェクトに送信する。同様にプロパティの enabled を false にすると、イベント・ハンドラは呼ばれなくなる。

使用方法

class MyObject: CbObject {
    override func update(interval: Int) {
        print("Update!")
    }
}

func test() {
    root  = CbContainer()
    node1 = CbContainer(binding: root)
    node2 = MyObject(binding: node1)

    root.sendEvent(message: .Update, parameter: [16])
}

root に送ったメッセージが末端の node2 のオブジェクトに届くようになる。

まとめ

全体のオブジェクトの管理、イベント・メッセージの仕組みを設計した。

高々100行程度のコードだが、最初にこれらを決めておかないと、後々、苦労しそうな気がする。

次は、CbContainerクラスを継承して画面モード・クラスを作成していく。

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

【Swift】正規表現で含有判定

文字列を含んでいるのか

  • 正規表現パターン:patternを引数で渡し、含んでいるのか評価した結果をtrue/falseで返します。
  • エラーがthrowされた場合、正規表現が間違えています
extension String {
    func contain(pattern: String) -> Bool {
        guard let regex = try? NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options()) else {
            return false
        }
        return regex.firstMatch(in: self, options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, self.count)) != nil
    }
}

利用方法

実装

print("apple".contain(pattern: "a.*e"))
print("apple".contain(pattern: String()))
print("".contain(pattern: String()))

出力

true
false
false

参考

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