- 投稿日:2020-04-30T22:47:05+09:00
HKHealthStoreのデータ読み取り権限があるかチェックする
結論から言うと読み取り権限があるかチェックすることはできません。
書き込み権限の有無はauthorizationStatus(for:)でチェックすることができます。このメソッドのDiscussionに以下の説明がありました。
This method checks the authorization status for saving data. To help prevent possible leaks of sensitive health information, your app cannot determine whether or not a user has granted permission to read data. If you are not given permission, it simply appears as if there is no data of the requested type in the HealthKit store. If your app is given share permission but not read permission, you see only the data that your app has written to the store. Data from other sources remains hidden.権限がない場合はデータが取得できないだけだそうです。
- 投稿日:2020-04-30T22:43:31+09:00
iOSウィジェットにCO2濃度を表示する 【Raspberry Pi × CO2-mini × co2meter】
WFH、捗ってますでしょうか。
我が家の場合、机やモニターなど家で仕事をするのに十分な設備は元々整っていたのですが、たったひとつだけオフィスにはあって自宅にないものがありました。
CO2モニターです。
成果物
パッと目につくところに表示させたかったので、iOSのウィジェットに置くことにしました。
最終的にこんな感じでCO2濃度、ついでに室温が見えるようになりました。iPhone
iPad
構成と概要解説
ポイントは以下です。
- CO2-miniでCO2濃度、室温を計測する
- CO2-miniとRaspberry Piを接続して定期的な計測を行い、Webサーバーとしてアクセス可能にする
- iOSアプリからWebサーバーへアクセスし、ウィジェットとして表示する
- 換気したくなる
CO2-mini
CO2濃度の計測は既製品であるこちらを使います。
CO 2モニター CO2-mini | 自然環境測定器 - 製品情報 - 計測器のカスタムMH-Z19などのモジュールも検討したのですが、WFHによる需要の高まりのためか高価になってる or 配送に時間がかかるため、比較的手に入りやすいこちらを選択しました。
電源供給がmicro USB Type-Bなのでこれを利用してPaspberry Piと接続します。
公式でAPIが公開されているわけではありませんが、有志の解析によってOSSなどを経由してアクセスできるようになっています。なお、今回はCO2-miniに予め備わっている表示部を見て換気したくなる行為は反則負けとします。
サーバーサイド
主となるCO2濃度のロギング、Webサーバー化は
co2meterを使います。
https://github.com/vfilimonov/co2meterこのOSSを使うことで、Pythonを使ったCO2-miniへのアクセスが可能になります。さらに定期的な計測、JSON/CSVへの書き出し、FlaskによるWebサーバー化、折れ線グラフによる可視化まで担ってくれます。
クライアントサイド
iOSアプリ部分については自作して、簡単なものですが公開しました。
https://github.com/akeome/RoomConditionラズパイで構築したサーバーにアクセスして、レスポンスのJSONの最新値を表示するだけのものです。
CO2濃度を見える化する
この環境を構築するための手順を記します。
僕は今回初めてRaspberry Piをいじったので、備忘も兼ねてセットアップの記載から始めます。準備
必要なハード類です。
- Mac
- Raspberry Pi
- 今回はRaspberry Pi Zero WHを使いました。Wi-Fiに接続できればどのモデルでも大丈夫なはず
- CO2-mini
- microSDカード
- アダプタ、ケーブル等
- MacからmicroSDに書き込むためのハブなどが必要です
- Raspberry PiとCO2-miniの接続は、micro USB Type-Bとmicro USB Type-Bです
- Raspberry Pi Zero WHの場合、電源供給はmicro USB Type-Bです
Raspberry Pi のセットアップ
今回使用したRaspberry Pi Zero WHには有線LANポートがないので、Wi-Fi経由でMacから操作を行うための手順を書きます。
MacからmicroSDに書き込む
Paspberry PiにmicroSDを挿す前に、Macで諸々書き込んでいきます。
OSを書き込む
公式サイトからOSをダウンロードします。
https://www.raspberrypi.org/downloads/raspbian/
- GUIは使わないので軽量な
Raspbian Buster Liteにしました- めっちゃ時間かかります(1時間ぐらいかかりました)
diskutill listコマンドでMacに接続されているmicroSDのパスを取得します。$ diskutil list # (中略) /dev/disk4 (external, physical): #: TYPE NAME SIZE IDENTIFIER 0: FDisk_partition_scheme *32.0 GB disk4 1: Windows_FAT_32 NO NAME 32.0 GB disk4s1自分の環境の場合、32.0 GBの表示から
/dev/disk4が該当のmicroSDを指すとわかりました。
diskutil unmountDiskでアンマウントと、ddでOSの書き込みを行います。$ diskutil unmountDisk /dev/disk4 Unmount of all volumes on disk4 was successful $ sudo dd bs=1m if=/?パス/2020-02-13-raspbian-buster-lite.img of=/dev/rdisk4 conv=sync Password: 1764+0 records in 1764+0 records out 1849688064 bytes transferred in 55.800569 secs (33148194 bytes/sec)
/dev/disk4としている箇所は環境に合わせて変えてください?パスとしている箇所はOSをダウンロードしたパスです- .imgファイルのファイル名も時期によって変わるはずです
Macに
bootという名前でディスクが接続されていればOKです。$ ls /Volumes/ Macintosh HD bootssh接続を有効にする
MacからsshでRaspberry Piに(一時的に)接続するために空ファイルが必要です。持続的に接続する方法については後述します。
$ touch /Volumes/boot/sshWi-Fi接続を有効にする
Wi-Fi接続に必要なファイルを作成します。
$ nano /Volumes/boot/wpa_supplicant.conf以下の内容を書き込みます。
wpa_supplicant.confcountry=JP ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 network={ key_mgmt=WPA-PSK ssid="?SSID名" psk="?パスワード" }
- SSID、パスワード(場合によってはkey_mgmtも)は環境に合わせて変えてください
- パスワードを直接入力するとセキュリティ上のリスクがあります。ここでは触れませんが
wpa_passphraseコマンドで暗号化できますもちろん
nanoコマンドを使わなくても、.confを作成できればおっけーです。ここまででmicroSDへの書き込みは完了です。
Paspberry Piを起動する
諸々書き込みが終わったmicroSDカードをRaspberry Piに差し込みます。
電源に接続します。Raspberry Pi Zero WHの場合、電源供給はPWRと書かれた方の差込口を使います。
電源が入ると緑のLEDが点灯します。
sshコマンドでMacからラズパイに接続します。
接続先はpi@raspberrypi.local、パスワードはraspberryです。$ ssh pi@raspberrypi.local The authenticity of host 'raspberrypi.local (xxxx)' can't be established. ECDSA key fingerprint is SHA256:xxxx. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added 'raspberrypi.local,xxxx' (ECDSA) to the list of known hosts. pi@raspberrypi.local's password:pi@raspberrypi:~ $こんな感じでプロンプトがラズパイになっていれば成功です?
続いて諸々の設定をやっていきます。
タイムゾーンを設定する
ラズパイの設定画面に入るコマンドを使います。
pi@raspberrypi:~ $ sudo raspi-config
4 Localisation Options > I2 Change Timezone > Asia > Tokyo
でタイムゾーンを日本にしておきます。ssh接続を継続して利用可能にする
上記設定画面から、
5 Interfacing Options > P2 SSH > Yes
でssh接続が持続的に利用可能になります。IPアドレスを固定する
ifconfigで現在のIPアドレスを、route -nでデフォルトゲートウェイを確認します。pi@raspberrypi:~ $ ifconfig lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (Local Loopback) RX packets 4032 bytes 203013 (198.2 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 4032 bytes 203013 (198.2 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.100.50 netmask 255.255.255.0 broadcast 192.168.100.255 pi@raspberrypi:~ $ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.100.254 0.0.0.0 UG 302 0 0 wlan0無線LANはwlan0なので、上記の場合
192.168.100.50が現在のIPです。
また、Gatewayに表示された192.168.100.254が現在のデフォルトゲートウェイです。確認できたら
sudo nano /etc/dhcpcd.confで以下の内容を追記します。dhcpcd.confinterface wlan0 static ip_address=192.168.100.50/24 static routers=192.168.100.254 static domain_name_servers=192.168.100.254
- ip_addressに固定するIPアドレスを入力します。今回は
ifconfigで確認した192.168.100.50を使っています。サブネットマスクは基本的にはそのまま/24でいいはずです- routersとdomain_name_serversには
route -nで確認したデフォルトゲートウェイを入力します再起動後、設定したIPアドレスになっていればおっけーです。
# 再起動 pi@raspberrypi:~ $ sudo reboot # 再接続 $ ssh pi@raspberrypi.local # IP確認 pi@raspberrypi:~ $ ifconfig wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.100.50 netmask 255.255.255.0 broadcast 192.168.100.255各種ライブラリをアップデートする
ラズパイのセットアップでのお決まりのようです。
pi@raspberrypi:~ $ sudo apt update pi@raspberrypi:~ $ sudo apt upgradeデフォルトのPythonのバージョンを3系にする
これは必須ではありませんが、
pythonコマンドのデフォルトで Python2系が起動したので3系にしておきます。pi@raspberrypi:~ $ python --version Python 2.7.16 pi@raspberrypi:~ $ sudo unlink /user/bin/python pi@raspberrypi:~ $ sudo ln -s /user/bin/python3 python pi@raspberrypi:~ $ python --version Python 3.7.3Gitをインストールする
Gitをインストールします。ライブラリの使用に必要です。
pi@raspberrypi:~ $ sudo apt install git pi@raspberrypi:~ $ git --version git version 2.20.1Raspberry Pi と CO2-mini の接続
ラズパイとCO2-miniを接続します。
接続端子はお互いmicro USB Type-Bです。
接続できるとCO2-miniはしばらくの待機後、CO2濃度と室温を表示してくれます。
co2meterの事前準備Python製のライブラリ
co2meterを使って、ラズパイからCO2-miniへアクセスします。
vfilimonov/co2meter: A Python library for USB CO2 meterこのco2meterを動かすにあたって必要なライブラリをインストールしていきます。
依存ライブラリのインストール
USB接続機器にアクセスしたりするために必要になるみたいです。
pi@raspberrypi:~ $ sudo apt install libusb-1.0-0-dev libudev-devrulesファイルの作成
/etc/udev/rules.d/98-co2mon.rulesを作成します。root@raspberrypi:/home/pi# nano /etc/udev/rules.d/98-co2mon.rules以下を書き込みます。
98-co2mon.rulesKERNEL=="hidraw*", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="a052", GROUP="plugdev", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="a052", GROUP="plugdev", MODE="0666"
udevadmコマンドで設定を反映させます。
が、この操作はrootユーザーとしての実行が必要らしく、まずはrootユーザーになるためにパスワードを設定します。pi@raspberrypi:~ $ sudo passwd root New password: Retype new password: passwd: password updated successfully
- パスワードを2度聞かれるので2度入力します
suコマンドでrootユーザーに変身します。pi@raspberrypi:~ $ su Password: root@raspberrypi:/home/pi#プロンプトが
root@raspberrypi:になっていればおっけーです。
この状態でudevadmコマンドを実行します。root@raspberrypi:/home/pi/# sudo udevadm control --reload-rules && udevadm triggerrootユーザーから一般ユーザーに戻るには
exitです。root@raspberrypi:/home/pi# exit exit pi@raspberrypi:~ $ここまでで事前準備は完了です。
ライブラリ
co2meterのインストールこれによっていよいよCO2-miniへのアクセスが可能になります。
pi@raspberrypi:~ $ pip3 install hidapi co2meterREADME記載の基本的な使い方を試してみます。
pi@raspberrypi:~ $ python>>> import co2meter as co2 >>> mon = co2.CO2monitor() >>> mon.read_data() (datetime.datetime(2020, 4, 25, 11, 10, 21), 1005, 24.100000000000023)取得できました??
内容は(タイムスタンプ, CO2濃度, 室温)のタプルです。Raspberry Pi をWebサーバーとして動かす
CO2濃度、室温が取得できることがわかったところで、続いてWebサーバーとして動かす方法を記載します。Webサーバーとして動かすことで、外部からブラウザ経由で計測結果を確認することができます。
co2meterには、予めサーバーとして動かす機能が実装されているので利用します。
- 定期的な計測
- JSON/CSVへのロギング
- 折れ線グラフによる可視化
- Webサーバー立ち上げ
といった機能が簡単に使えます。
関連パッケージのインストール
flask、pandasのインストール
co2meterのWebサーバー化はflaskで実装されています。pi@raspberrypi:~ $ pip3 install -U flask pandasWebサーバー起動
co2meterをサーバーとして実行するにはco2meter_serverコマンドだけでOKです。
これと組み合わせて、バックグラウンドでプロセスを継続させるためにnohupコマンドと&オプションをつけて実行します。pi@raspberrypi:~ $ nohup co2meter_server -H 0.0.0.0 -P 1201 &
-H 0.0.0.0オプションをつけることで外部のブラウザから(例えば同じLAN内のMacから)接続できます-P 1201オプションはポート番号です。デフォルトで使われていた1201に指定していますが数字に意味はないですこれで、ブラウザから
http://ラズパイのIPアドレス:ポート番号(この手順通りに進めていればhttp://192.168.100.50:1201/)へアクセスしてCO2濃度、室温が見れるようになりました???サーバーとして動かすことでlogsディレクトリが作成され、CSVでログが蓄積されていきます。
計測間隔はデフォルトで約35秒のようです。pi@raspberrypi:~ $ cat logs/co2.csv timestamp,co2,temp 2020-04-26 15:01:59,772,23.9 2020-04-26 15:02:34,768,23.9 2020-04-26 15:03:09,766,23.9 2020-04-26 15:03:44,766,23.9 2020-04-26 15:04:20,760,24.0Webサーバーの停止
停止させるには、
psコマンドでPIDを確認してkillコマンドです。-9オプションは強制終了です。pi@raspberrypi:~ $ ps PID TTY TIME CMD 599 pts/0 00:00:00 bash 626 pts/0 00:23:44 co2meter_server 6201 pts/0 00:00:00 ps pi@raspberrypi:~ $ kill -9 626プロセスが表示されない場合は
xオプションをつけてps xで試してみてください。iOSウィジェットに表示する
ここまでの手順で念願のCO2濃度可視化は実現できました。
あとはどこにどんな感じで表示するかですが、今回はWebサーバーから返されるJSONを使ってiOSウィジェットに表示することにしました。できたソースはこちら
https://github.com/akeome/RoomConditionXcodeからこのプロジェクトをビルドすれば動くはずです。
- Targetはまず本体アプリ
RoomCondition、次にウィジェットRoomConditionTodayExtensionの順番でビルドします- 接続先のIPアドレス、ポート番号は
http://192.168.100.50:1201/にしています。環境に合わせて適宜変更してください。この記事通りに進めていればそのままで問題ないですちょっとコード解説
ウィジェット(Today Extension)は今回初めて実装してみたので知見を書いておきます。
API通信部分のコード共通化
本体アプリ、ウィジェットで共通して使う処理はFrameworkとして実装することで共通化できます。
Frameworkの作成はメニューの
File > New > Target > Frameworkです。
今回はAPIRequestという名前で作成しました。RoomCondition.swiftpublic struct RoomCondition: Codable { public let co2: String public let temp: String public let timestamp: String }APIRequest.swiftpublic struct API { public static func request(completion: @escaping (RoomCondition?) -> Void) { let url = "http://192.168.100.50:1201/log.json" let session = URLSession.shared let task = session.dataTask(with: URL(string: url)!) { data, urlResponse, error in let currentCondition = try! JSONDecoder().decode([RoomCondition].self, from: data!) completion(currentCondition.last) } task.resume() } }呼び出し側では
import APIRequestして使います。呼び出し側.swiftAPI.request(completion: { roomCondition in guard let roomCondition = roomCondition else { return } print(roomCondition) // RoomCondition(co2: "1015", temp: "21.4", timestamp: "2020-04-28 00:35:32") })今回は結局Today Extension側でしか使ってないのでFrameworkにしなくてもよかったかもしれません?
Today Extensionの更新契機について
ウィジェットが表示される度に
func widgetPerformUpdate(completionHandler: @escaping (NCUpdateResult) -> Void)が呼び出されるようです。
この中に通信処理を書いているのですがちゃんと都度最新の値を取ってきてくれます。
completionHandlerの引数にはNCUpdateResultを渡します。public enum NCUpdateResult : UInt { case newData case noData case failed }上から順に、新規データあり、新規データなし(更新不要)、失敗を表します。
これによっていい感じにウィジェットの再描画が行われるようです。色分けのロジック
CO2-miniの表示部に着想を得て、ppmによって色分けしました。
TodayViewController.swiftfunc co2Color(co2Value: String) -> UIColor { switch Int(co2Value) { case (..<1000)?: return .systemGreen case (..<1200)?: return .systemYellow default: return .systemRed } }1000とか1200の基準は環境にもよると思うので適宜変更してください。
また室温の色分けは季節によって変えるべきかもしれません。おまけ、本体アプリ
本体アプリを起動しても真っ白なのは寂しかったのでとりあえず
SFSafariViewControllerでダッシュボートを表示させておきました。ViewController.swiftoverride func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let url = URL(string: "http://192.168.100.50:1201/dashboard") if let url = url { let safari = SFSafariViewController(url: url) present(safari, animated: false) } }おわりに 〜二酸化炭素濃度が人体に与える影響について〜
一般的に空気中の二酸化炭素濃度が概ね1000ppmを超えると眠気や倦怠感を誘発すると言われています。
厚生労働省が規定する建築物環境衛生管理基準においても、建築物の管理者は1000ppmになるように空調設備をちゃんとしてね〜との記載があります。
建築物環境衛生管理基準について|厚生労働省この基準値を検証する資料もありました。
1968年のWHO報告書を根拠としていると考えられる、との考察があったりなかなか興味深いです。
建築物環境衛生管理基準の設定根拠の検証について
(※pdfです)上記資料から一部抜粋
18ページ
二酸化炭素自体は、少量であれば人体に有害ではな
いが、1000ppmを超えると倦怠感、頭痛、耳鳴り、息苦しさ等の症
状を訴えるものが多くなり、フリッカー値(フリッカー値が小さいほ
ど疲労度が高い)の低下も著しい19ページ
1000ppmのCO2の吸入実験(Eliseeva 1964)で呼吸、循環器系、大
脳の電気活動に変化がみられたと報告している。街の噂で聞く1000ppmで眠くなる説はこのあたりがソースになっているのかもしれませんね。
ということで我が家のCO2濃度可視化の記録でした。
適度に換気して、快適なWFHを〜?参考記事
参考にさせていただきました。先人の皆様ありがとうございます。
- Raspberry Pi のセットアップ!モニター、キーボードなしでMacからSSH
- Raspberry Pi Zero W 環境構築ことはじめ MacOS編 - Qiita
- Raspberry Pi Zero(W, WH)のセットアップ - Qiita
- Raspberry Pi Zero WHの環境構築(Mac PC) - 日々の学びのアウトプットするブログ
- Raspberry Pi に固定IPアドレスを割り当てる方法(Raspbian Jessie) - Qiita
- RaspberryPiでPythonのデフォルトをPython2.7からPython3に変更する | そう備忘録
- 「apt-get」はもう古い?新しい「apt」コマンドを使ったUbuntuのパッケージ管理 | LFI
- How to Install Git on Raspberry Pi | Linuxize
- Python - Raspberry piでnumpyがimportできない。|teratail
- nohupを使ってsshログアウト後もシェルスクリプトを動かす - Qiita
- networking - Opening port not working - Unix & Linux Stack Exchange
- Today Extension Tutorial: Getting Started | raywenderlich.com
- 投稿日:2020-04-30T14:06:06+09:00
【Swift5】iOSアプリを公開するまでの勉強法について
はじめに
この記事では、プログラミング初心者な僕がAppStoreにアプリを公開し、月137円稼ぐまでの勉強法を書きたいと思う。
Swiftの勉強法は2択
Swifrの勉強法は、以下の2択です。
- プログラミングスクール(オンライン)に通って瞬殺でSwiftマスターになるか
- 参考書をたくさん買ってじっくり時間をかけてSwiftマスターになるかプログラミングスクール
現在のプログラミングスクールでは、自分のオリジナルアプリをAppStoreに公開するまで付き添ってくれるらしい。
iOS開発オススメのプログラミングスクール参考書
僕は、参考書でじっくり時間をかけてSwiftマスターになる方法を選んだ。
1.ひたすら手を動かす(2019年10月~)
まずは、超初心者のための参考書をたくさん買ってひたすら手を動かした。
ここで気をつけなければならないのは、購入する参考書のバージョンだ。
Swift、Xcodeのバージョン変更は、割と頻繁に行われており、現在のバージョンにあった参考書がとても少ない。
バージョンが違うと、手順通り行ってもエラーが出てしまうため、挫折してしまう。
買う時は、現在のバージョンを調べてから買うこと。ここで学んだこと
- 基本的なstoryboardの使い方
- 基本的なViewControllerの書き方
2.AppStoreに超簡単なアプリをAppStoreに公開してみる(2019年12月~)
ショートカットキークイズアプリを公開してみた。
『クイズアプリを作ってみよう』的な参考書があったので、その通りに作成し、
そのアプリを自分でショートカットキークイズアプリに変えた。ここで学んだこと
- ボタンなどのオブジェクトの使い方
- Viewの表示の仕方
- AppStoreに公開する方法
3.ちょっと本格的なアプリをAppStoreに公開してみる(2019年1月~)
人間関係を管理するアプリが欲しかったのに、AppStoreに微妙なものしかなかったので、自分で作ってみた。
ヒトメモというアプリを作成した。
このアプリで実装した主な機能は
- 誕生日通知機能
- プロフィールメモ機能
- パスワード機能
- 検索機能
- 並び替え機能
- バリデーションチェックアラート機能
と、Swiftでの基本的な機能だったので、とても学べた。
これらは全てネットの情報を頼りに進めた。ここで学べたこと
- ライブラリの使い方
- 主なSwiftの基本
4.Swift関連の記事をひたすら書く(2019年3月~)
勉強は結局アウトプットらしいので、勉強しながら、勉強したことを記事にしてアウトプットしていく。
- おしゃれな画面遷移ライブラリ『BubbleTransition』の使い方
- モーダルからdismissで戻ったときに処理をする方法
- Xcodeショートカットキーのチートシート
- UIStackViewで簡単にトルツメを実装する方法(表示非表示切り替え)
- 文字列操作(String型)チートシート〜置換,結合,削除,比較,取得〜
- 2020年版AppStoreに公開するまでの全手順まとめ【前編】
- セルの右側に、「>」右矢印マークをつける方法。つける理由
- XcodeとSwiftのバージョン確認方法とバージョン早見表
- Segmented Controllerの設定、プロパティまとめ
- Segmented Controllerの使い方〜超初心者にもわかるようにたくさん画像を使っています。〜
- Xcodeのシミュレーターの画面撮影(画面録画)する方法
- 超初心者向け!CocoaPodsのインストールの仕方と使い方
- Swiftでカラーを指定する方法をまとめてみました。
- 画面遷移のチートシート。Segueを画面遷移とSegueを使わない画面遷移を徹底解説
- 縦画面固定、ダークモード無効化、iPadでiPhone表示。毎回忘れるので、記事に残しておきます。
- 複数のstoryboardを分割してスマートに管理する方法
- TextFieldに値が入っていないときにボタンを非活性(無効)にする方法
- 戻るボタンの実装で初心者がやってしまうダサい間違い。戻るボタンの正しい画面遷移方法
と、記事を書きまくった。
Swiftマスターになるために勉強する、記事を書くために勉強すると、勉強する目的を2つに増やしたおかげで、やる気が二倍になった。
これからもこの勉強方法は続けていきたい。5.今後の勉強予定
- テストコード
- 読みやすく、洗練されたコード
- UIデザイン、UXデザインを超意識した記憶アプリ作成











