- 投稿日:2020-08-14T22:19:08+09:00
新しい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) }参考
- 投稿日:2020-08-14T21:43:15+09:00
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
説明
対応方法のみ知りたい方は、読み飛ばしてください
![]()
警告メッセージの日本語訳
「
MyApp [Debug/ Release]
(最終的なアプリバンドル )のターゲットが、Pods/Target Support Files/Pods-MyApp/Pods-MyApp.xxx.xcconfig
で定義された、ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES
(Swift標準ライブラリ を常に埋め込む)というビルド設定を、オーバーライドしています。
これにより、CocoaPodsのインストールで問題が発生する可能性があります。」つまり、CocoaPodsで管理しているビルド設定を、アプリのターゲットのビルド設定が上書きしちゃってるけどいいの?という注意喚起をしてくれています。
ビルド設定「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 したあとに、確実にこの対応をするようにしなければならない。
方法
- 設定に移動
- ターゲットを選択
- 「Build Setting」を選択
- 「All」と「Combined」を選択
- "Always Embed Swift Standard Libraries" を検索
- 「$(inherited)」を値に入力、もしくは項目自体を削除。
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
- 投稿日:2020-08-14T21:11:15+09:00
iOSのネットワーク接続状態の検出にはReachabilityよりシンプルなNWPathMonitorを使いましょう。
iOSでネットワーク接続状態の取得といえば
Reachability
を使うといった印象が強くありませんか。
Reachability
を使うには、まず
https://developer.apple.com/library/archive/samplecode/Reachability/Introduction/Intro.html
からApple の提供しているサンプルコードをダウンロードして・・・なんて手間がありましたが、もっと手軽に利用できるApple標準のクラスがありました。簡単にネットワーク接続のモニタリングが可能
ライブラリいらずで、iOSのネットワーク接続状態監視にはNWPathMonitor使うとよいことを知った。30行くらいで書ける。 pic.twitter.com/X6e8C0YAq8
— Natsui@?iOSエンジニア (@MeSummery) August 14, 2020NWPathMonitor
ネットワークの接続状態やネットワークインターフェースなどの情報をモニタリングできる
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
!」が定着すれば良いなと思います。以上です
ありがとうございました。
- 投稿日:2020-08-14T17:43:28+09:00
【iOS】Pythonista3でQiitaのトレンドを表示してくれるWidgetを作った。【Python】
はじめに
iPhone, iPadユーザーの皆さん、iOS端末上でPythonでの開発ができるPythonista3というアプリはご存知でしょうか?
Pythonを実行するだけであれば個人的にはCarnets - Jupyterがおすすめですが、
Pythonista3では各種ショートカットやウィジェットなどといったアブリ外から使用可能な機能の開発や、ゲームを作ることも出来ます。今回はそんなPythonista3を使って
Qiitaのトレンドを表示してくれて、クリックするとSafariでそのページを開いてくれるウィジェット
を作成したので紹介したいと思います。
やったこと
今回のウィジェットの場合、
Example/Widget
下にあるLauncher.py
の内容がほとんどそのまま使えるので、それをベースに、・QiitaのトップページからトレンドのタイトルとURLを取得してくる機能
・取得した情報を一時的に保存しておく機能
・前回の情報の取得から一定時間上経過していたら再度Qiitaにアクセスし情報を更新する機能を追加しました。
コード
列数、折り畳んだ状態での行数、フォントサイズの変数をインポート文の後にまとめておいたので、お好みでレイアウトを調節してください。
窮屈&押しにくくなりますが、行数を5以上にすれば全デイリートレンド(30件)を表示できます。qiita_trends_widget.pyimport 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はいいぞ(使えるパッケージ増えるといいなあ…)
- 投稿日:2020-08-14T17:43:28+09:00
【iOS】Pythonista3でQiitaのトレンドを表示してくれるウィジェットを作った。【Python】
はじめに
iPhone, iPadユーザーの皆さん、iOS端末上でPythonでの開発ができるPythonista3というアプリはご存知でしょうか?
Pythonを実行するだけであれば個人的にはCarnets - Jupyterがおすすめですが、
Pythonista3では各種ショートカットやウィジェットなどといったアブリ外から使用可能な機能の開発や、ゲームを作ることも出来ます。今回はそんなPythonista3を使って
Qiitaのトレンドを表示してくれて、クリックするとSafariでそのページを開いてくれるウィジェット
を作成したので紹介したいと思います。
やったこと
今回のウィジェットの場合、
Example/Widget
下にあるLauncher.py
の内容がほとんどそのまま使えるので、それをベースに、・QiitaのトップページからトレンドのタイトルとURLを取得してくる機能
・取得した情報を一時的に保存しておく機能
・前回の情報の取得から一定時間上経過していたら再度Qiitaにアクセスし情報を更新する機能を追加しました。
コード
列数、折り畳んだ状態での行数、フォントサイズの変数をインポート文の後にまとめておいたので、お好みでレイアウトを調節してください。
窮屈&押しにくくなりますが、行数を5以上にすれば全デイリートレンド(30件)を表示できます。qiita_trends_widget.pyimport 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はいいぞ(使えるパッケージ増えるといいなあ…)
- 投稿日:2020-08-14T15:29:32+09:00
【Flutter】超簡単!要素を画面いっぱいに表示する方法【Expanded】
この記事を読んで習得できること
Flutterの画面作成で「画面いっぱいに要素を表示したい」っていう問題を解決する力(多分)
結論
・
Expanded
を使って要素を隙間いっぱいに大きくするこんな感じ
とりあえず、書いてみる
main.dartclass _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), ), ], ), ); ), ); } }?
要素が吹っ飛んでしまった。
これは、高さが何も指定されてないためであり、仕方がないこと。高さを指定してみる
なので、heightをContainerにつけてみる。
main.dartclass _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, ), ], )); } }Containerが表示されるようになったが、高さ指定をしてしまっているので、
これだと不都合がことが起きる。青いContainerを画面いっぱいに引き伸ばしてみる
上での問題を解消するために、Containerを
Expanded
で囲ってみる。main.dartclass _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, ), ], ), ); } }すると、いい感じに画面いっぱいに要素が表示されるようになる。
とりあえずやりたいことは終わり。Expandedを複数入れると均等に要素を広げてくれる
一番上の画像のような要素表示は、以下のソースで行ってくれた。
main.dartclass _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.dartclass _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-14T07:57:01+09:00
TFCoreMLで変換したモデルの出力がおかしい時の対処法
WWDC2020あたりを境に、tfcoremlで変換したGANの出力が全部真っ白になりました。
対処法
Core ML Tools 4.0をインストールして変換し直す。
pip install coremltools==4.0b2tfcoremlに比べて、変換メソッドのオプションがシンプルに変わっています。
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")これで出力されました。
Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-08-14T06:59:11+09:00
ReactNative × FirestoreでiOSアプリを作成する手順とまとめ
■概要
CloudFirestoreとは
Firebaseが提供している、NoSQLベースのデータベースのうちの一つ(MBaasの一つ)
RealtimeDatabase → CloudFirestoreに進化!
Q、何が改善されてる?
A、データモデルの改善、クエリ強化されている。Firebaseの機能とデータベース
■準備
Firebaseプロジェクトのセットアップ
Firebase公式ページからコンソール画面に移動して、新規アプリ作成。
プロジェクトIDとロケーションは後で変更が出来ないので、注意!
※国内向けサービスの場合、asia-northeast1(東京)or asia-northeast2(大阪)
バックエンド側のセットアップ
- Firebase CLIをインストールする。これを使うと、Firebaseを使った開発がより効率的になる。
npm install firebase-tools
- デプロイを可能にするためにセットアップする。
npm firebase loginFirestoreをセットアップ
最初はテストモードで開始。
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' endiosアプリにFirebaseを作成するページで以下の流れに沿って進める。
- pod installcd ios/ && pod installFirebase 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エンジニアのキャリアを考える!
■Firestoreを今まで触ってみて思ったこと
- プロジェクトの規模にもよるが、とにかく機能が充実していて、あまり不便さを感じていない。なので、RDBの必要性が見えてこない。
- Authenticationは多くのSNSと連携することが可能なので、モダンなプロダクトにはもってこい
- 多くの企業が利用しているせいか、日本語の記事が多い印象。初心者に優しい。
参考ページ
- 投稿日:2020-08-14T02:10:13+09:00
【入門】iOS アプリ開発 #4【アーキテクチャの設計】
アーキテクチャの設計
今回はパックマンのゲームを構築するにあたり、全体のアーキテクチャを設計する。
仕様書から画面モードは4つあり、それぞれのモード内にはキャラクタがいて移動処理などがある。これらを1つ1つのオブジェクトとして管理し扱っていく。
設計方針
各画面モードをオブジェクトとして扱い、簡単に切り替え操作したい。
attractMode.startSequence() // ・・・アトラクトモード実行中・・・ // アトラクトモードからクレジットモードへ切り替える attractMode.stopSequence() creditMode.startSequence() // ・・・クレジットモード実行中・・・こんな感じ。
またイベント・メッセージを画面モード内のオブジェクトにも簡単に通知したい。
attractMode.sendEvent(message: .Update, parameter: [16]) attractMode.sendEvent(message: .Touch, parameter: [x,y])イベント・メッセージは表示更新タイミング(Update)やタッチイベントなどで、画面モードが無効のものには通知しないようにする。
まとめると、以下のようなオブジェクトの階層構造が作れるようにする。
これらの基本コンポーネントのクラスを作成する。
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クラスを継承して画面モード・クラスを作成していく。
- 投稿日:2020-08-14T00:37:56+09:00
【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
参考