ちろる

理系大学生が自由気ままに

LaTeX関連のめちゃくちゃ便利な機能たち

はじめに

論文を書くようになって色々と機能を使わないと書くのが大変になってきたため、色々と導入してみました。それの備忘録かつこんなの使えると便利!っていうのがまとまってるといいなと思ったので記事の執筆に至りました。

ファイル分割

1つのtexファイルに全ての内容を書いてしまうと、編集しづらくなったりなどといった不便が生じます。したがって分量のある文書を作成する際は、SectionやChapterごとにファイルを分割するのが一般的です。

方法

\documentclass{hoge-thesis}
\usepackage{docmute}
\input{setting}

\begin{document}

\include{1-introduction}
%\include{2-hoge}

\end{document}

\documentclass{hoge-thesis}
  \input{setting}
  \begin{document}
    \section{introduction}
      hogehoge...
  \end{document}

% その他のパッケージなど
% \usepackage{...}

1. 分割するファイル(ここではsetting.texと1-introduction.tex)を作成
2. master.texでdocmuteパッケージを読み込む
3. master.texおよび1-introduction.texに\input{setting}によりsetting.texを読み込む
4. master.texに\include{1-introduction}で1-introduction.texを挿入

※\input{}および\include{}にはファイル名の.texを外したものを書いてください。

ちょっとだけ説明

docmute

それぞれ単体でもコンパイルできるようにしたいので、1-introduction.texにも\documentclass{hoge-thesis}を記述します。しかし、そうしてしまうとmaster.texコンパイルでは、\documentclassが2つ以上になってしまい失敗しまいます。こうした事態を防ぐために、docmuteを導入しています。公式では「他のTeXファイルのプリアンブルを無視して読み込み」と記載されています。

参考

qiita.com

bibtex

bibtexを用いることで100個など多数ある参考文献の記載をとても楽にできます。

方法

\begin{document}
〜らの研究[\cite{test}]によると〜
%...
\bibliography{reference}
\bibliographystyle{junsrt} 
%..
\end{document}

@article{test,
author="Mairittha, Nattaya and Mairittha, Tittaya and Inoue, Sozo",
title="On-Device Deep Learning Inference for Efficient Activity Data Collection",
journal="Sensors",
ISSN="1424-8220",
publisher="MDPI",
year="2019",
month="aug",
volume="19",
number="15",
pages="3434-1-3434-20",
URL="https://ci.nii.ac.jp/naid/120006719301/",
DOI="",
}

reference.bibの内容は大抵のサイトからアクセスでき、それをコピペしてくるだけでokです。

f:id:tsupiano:20191116090212p:plain

1. mastex.texコンパイル
2. 以下のどちらかのコマンドでデータベースの参考文献を反映
 ・和文bibtex {TeXファイル名}
 ・英文 → bibtex {TeXファイル名}
3. master.texを3回コンパイル

引用や参考文献の表示順などについては以下の記事を参照してください。
qiita.com

latexmk

latexmkはplatexやdvipdfmx、bibtexなど複数あって面倒な処理をコマンド一つで全てやってくれるやつでMakefileに近いですが、設定が遥かに楽なものになっているので、こちらの方が良いかなと思います。

設定

~/.latexmkrcを作成します。

#!/usr/bin/env perl
$pdf_mode         = 3;
$latex            = 'platex -halt-on-error';
$latex_silent     = 'platex -halt-on-error -interaction=batchmode';
$bibtex           = 'pbibtex';
$dvipdf           = 'dvipdfmx %O -o %D %S';
$makeindex        = 'mendex %O -o %D %S';

実行

$ latexmk master.tex

これだけで.texから.pdfを作成してくれます。また上記の設定では記述していませんが、この後pdfビューワーを開いてくれるなどといった設定も可能です。詳細は以下をどうぞ。
qiita.com
qnighy.hatenablog.com

ReactNative+Expoでジャイロセンサーと加速度センサーを使って姿勢を推定し、WebSocketでPCに送信して、OpenGLで可視化する

やりたいこと

WebSocketとPyOpenGLは姿勢の精度を視覚的に確認したかったのでやりました。姿勢推定はオイラー角、回転行列、クォータニオンなど色々ありますが、今回はクォータニオンを使います。また、姿勢の補正(ジャイロと加速度の合成??)はとりあえず一番簡単であろう相補フィルターを使います。OpenGLについてはあまり書かないのでご了承願います。。。

追記(2018/3/6): 姿勢の推定はExpoのDeviceMotionを使うことで簡単に実装することができるようです

f:id:tsupiano:20190304212709p:plain

Pythonによるデータ受信と可視化

ソースコードを貼っておきます。
Gyroscope Tester · GitHub

姿勢推定のクォータニオン周りは出来るだけ視覚的に確認しながら作りたいのでサーバーサイドからやります。

WebSocket

WebSocketは少ないデータ量でリアルタイムに通信するためのプロトコルです。今回はリアルタイムに通信を行いたいのでこれを使います。実装は今回のような一時的に使うような凝ったものでなければ、詳細を知らなくてもできると思いますが、一応WebSocketとはなんぞや的なものが書いてある記事を下に貼っておきます。OpenGLとWebSocketをメインスレッドで同時に行えない(?)ので別スレッドを立てて行いました。(これが正しいかはわかりませんが一応できたので良しとしています...)

WebSocketのインストール
sudo pip3 install websocket-server
サーバー(描画側)のコード
from websocket_server import WebsocketServer
import threading

def new_client(client, server):
   print("connected")

def received_quaternion(client, server, message):
  # クォータニオンから回転軸と回転角を計算し、その値を更新する処理
  # 送られてくるデータはmessageに格納されている


def buildServer():
  # サーバーのIPアドレスとポート番号を引数にしてWebsocketServerクラスを生成
  # IPは各自で調べて入力してください
  server = WebsocketServer(9999, host='192.168.1.6')
  # 新しい接続があったときの関数を設定
  server.set_fn_new_client(new_client) 
  # データを受け取ったときの関数を設定
  server.set_fn_message_received(received_quaternion) 
  # サーバーを立ち上げる
  server.run_forever()

if __name__ == "__main__":
  threadServer = threading.Thread(target=buildServer)
  threadServer.start()
動作確認

wscatを用いることでクライアントやサーバーがない状態でも動作確認を行うことができます。まずはインストールします。

sudo apt-get install npm
sudo npm install -g wscat

次に以下のコマンドで起動します。IPアドレスはプログラム内で使用したものを入力します。

wscat -c ws://192.168.1.6:9999

"connect"で出力されればコネクションの確立が完了しています。
qiita.com
www.raspberrypirulo.net

PyOpenGL

最近はglutではなくglfwを使うのが流行りらしいですが、スマホのセンサーのテスト用に作るプログラムで、学習コストを割きたくないので、glutを使います。

PyOpenGLとGLUTのインストール

次のリンクを頼りにインストールしてください。
tokoik.github.io

プログラム
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

rotationValue = 90
rotationVector = [1, 0, 0]

def display():
    glEnable(GL_DEPTH_TEST)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glPushMatrix()
    
    glRotatef(rotationValue*180/math.pi, rotationVector[0],rotationVector[1],rotationVector[2])

    glMaterialfv(GL_FRONT, GL_DIFFUSE, [1.0, 0.0, 0.0,1.0])
    glColor3f(1.0, 1.0, 1.0)
    glutSolidCube(0.5)
    glColor3f(0.0, 0.0, 0.0)
    glutWireCube(0.5)
    glDisable(GL_LIGHTING)

    # Axis
    glColor3f(0.0, 0.0, 1.0)
    glBegin(GL_LINES)
    glVertex3f( 999.0, 0.0, 0.0)
    glVertex3f(-999.0, 0.0, 0.0)
    glEnd()
    glColor3f(0.0, 1.0, 0.0)
    glBegin(GL_LINES)
    glVertex3f( 0.0,  999.0, 0.0)
    glVertex3f( 0.0, -999.0, 0.0)
    glEnd()
    glColor3f(1.0, 0.0, 0.0)
    glBegin(GL_LINES)
    glVertex3f( 0.0, 0.0,  999.0)
    glVertex3f( 0.0, 0.0, -999.0)
    glEnd()

    glPopMatrix()

    glFlush()

def timer(value):
  glutPostRedisplay()
  glutTimerFunc(10, timer, 0)

def pyMain():  
  glutInit(sys.argv)
  glutInitWindowSize(600, 600)
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH)
  glutCreateWindow(b"OpenGL")
  
  glMatrixMode(GL_PROJECTION)
  glLoadIdentity()
  glMatrixMode(GL_MODELVIEW)
  
  glutDisplayFunc(display)
  glutTimerFunc(10, timer, 0)
  glClearColor(0.0, 0.0, 0.0, 1.0)
  glutMainLoop()

if __name__ == "__main__":
  pyMain()

PyOpenGLに関しては何も書いてないのですが、OpenGLとほとんど同じ(セミコロンがなかったりするだけ)なので、OpenGLで検索して出てきた解説ページなどを見ればなんとなく理解できると思います。

OpenGLによる3次元CGプログラミング

OpenGLによる3次元CGプログラミング

tadaoyamaoka.hatenablog.com

ReactNativeでクライアント側を作る

加速度と角速度の取得方法

加速度にはAccelerometer、角速度にはGyroscopeというexpoでデフォルトで使えるAPIを使用します。
docs.expo.io
docs.expo.io
AccelerometerとGyroscopeの使い方はほとんど一緒です。

import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { Accelerometer, Gyroscope } from 'expo';

export default class App extends React.Component {

  componentDidMount() {
    this._toggle();
  }

  componentWillUnmount() {
    this._unsubscribe();
  }

  _toggle = () => {
    if (this._subscription) {
      this._unsubscribe();
    } else {
      this._subscribe();
    }
  };

  _subscribe = () => {
    // Accelerometerがデータを取得するインターバルと取得した際の処理を設定
    Accelerometer.setUpdateInterval(16); 
    this._accSubscription = Accelerometer.addListener((result) => {
        console.log("Acc:", result); 
  # 相補フィルターの処理を書く
    }); 
 // Gyroscopeがデータを取得するインターバルと取得した際の処理を設定
    Gyroscope.setUpdateInterval(16); 
    this._gyroSubscription = Gyroscope.addListener((result) => {
        console.log("Gyro:", result); 
        # クォータニオンによる姿勢推定をここで行う
    }); 
  };

  _unsubscribe = () => {
    this._accSubscription && this._accSubscription.remove(); 
    this._accSubscription = null;
    this._gyroSubscription && this._gyroSubscription.remove(); 
    this._gyroSubscription = null;
  };

  render() 
    return (
      <View style={styles.container}>
        <Text>Hello World</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

WebSocketでサーバーに送信する

WebSocketもAccelerometerなどと同様にexpoでデフォルトで使うことができます。
docs.expo.io

import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';

export default class App extends React.Component {

  componentDidMount() {
    // WebSocketクラスを生成
    this.ws = new WebSocket("ws://192.168.1.6:9999");
  }

  // コネクション確立時に実行される
  this.ws.onopen = () => {
    console.log("connected");
    // データを送信できる
    // 実際にはtimerなどを使ってクォータニオンを送信する
    this.ws.send("Javascript");
  }

  this.ws.onerror = (e) => {
    console.log(e.message);
  }

  render() 
    return (
      <View style={styles.container}>
        <Text>Hello World</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

ジャイロセンサーのデータから姿勢推定する

下の動画にあるように角速度を単純に積算するだけでは、姿勢は推定できません。そのため、クォータニオンを用います。
www.youtube.com

クォータニオンについては以下のサイトが参考になるかと思います。特にMSSさんの「クォータニオン計算便利ノート」は非常にまとまっていてわかりやすいです。
【Unity道場 博多スペシャル 2017】クォータニオン完全マスター - YouTube
http://www.mss.co.jp/technology/report/pdf/18-07.pdf
Python 3.x - ジャイロセンサーから測定したquaternionによる姿勢推定を用いた加速度の回転方法について|teratail
クォータニオンから回転角と回転軸の算出 ( プログラム ) - Color Model:色をプログラムするブログ - Yahoo!ブログ
quaternion - npm

初期クォータニオンを作って、角速度からクォータニオンの時間変化を作って足しこむことで、次ステップでのクォータニオンを求められます。計算を繰り返していると誤差が累積してしまうので、計算毎に正規化を行います。
 q(t+\Delta t) = q(t) + \frac{1}{2}\omega q(t)\Delta t


   \frac{d}{dt}\left(
     \begin{array}{c}
       q_0 \\ q_1 \\ q_2 \\ q_3
     \end{array}
   \right)

   = \left(
     \begin{array}{cccc}
       0 & -\omega_1 & -\omega_2 & -\omega_3 \\
       \omega_1 & 0 & -\omega_3 & \omega_2 \\
       \omega_2 & \omega_3 & 0 & -\omega_1 \\
       \omega_3 & -\omega_2 & \omega_1 & 0
     \end{array}
   \right)   
   \left(
     \begin{array}{c}
       q_0 \\ q_1 \\ q_2 \\ q_3
     \end{array}
   \right)

var quat = new Quaternion([1.0, 0.0, 0.0, 0.0]); // グローバル変数

var Quaternion = require('quaternion');


this._gyroSubscription = Gyroscope.addListener((result) => {
  var dt = 0.10

  var wx = -result.x 
  var wy = -result.y
  var wz = result.z

  var dq = new Quaternion([  0*quat.w -wx*quat.x -wy*quat.y -wz*quat.z,
                                              wx*quat.w + 0*quat.x -wz*quat.y +wy*quat.z,
                                              wy*quat.w +wz*quat.x + 0*quat.y -wx*quat.z,
                                              wz*quat.w -wy*quat.x +wx*quat.y + 0*quat.z])
  dq = dq.scale(0.5*dt)
  quat = quat.add(dq)
  quat = quat.normalize();

  this.ws.send("Gyroscope " + "Quaternion:" + " w:"+quat.w.toString() + " x:"+quat.x.toString() + " y:"+quat.y.toString() + " z:"+quat.z.toString()); 
});

加速度で相補フィルターをかける

相補フィルターはざっくりいえばジャイロセンサーと加速度センサーの加重平均のようなものです。以下の記事が非常にわかりやすいです。ベクトル演算をしたかったので、Three.jsをちょっとだけ使いました。以下のコードはまだ係数の設定をしていません。加速度とジャイロだけだとヨー軸を揃えることはできません。
qiita.com

import ExpoTHREE from 'expo-three'
import { THREE } from 'expo-three'

Accelerometer.setUpdateInterval(10); 
this._accSubscription = Accelerometer.addListener((result) => {
  var dt = 0.050
  var acc = new THREE.Vector3(result.x*dt, result.y*dt, result.z*dt)   
  var gravity = new THREE.Vector3(0, 0, 1)

  // Gが大きい時はそもそも加速度計の値を使用しないようにしています。
  if(Math.abs(acc.x)+Math.abs(acc.y)+Math.abs(acc.z) < 1.5){
    var accVectorCalc = quat.inverse().mul(acc).mul(quat).toVector().slice(1,4)
    var accVector = new THREE.Vector3(accVectorCalc[0],accVectorCalc[1],accVectorCalc[2])
    var correctionVector = accVector.cross(gravity)
    var correctionValue = Math.acos(accVector.dot(gravity))
   
    var accQuat = new Quaternion(Math.cos(0.5*correctionValue),
[correctionVector.x*Math.sin(0.5*correctionValue),correctionVector.y*Math.sin(0.5*correctionValue),correctionVector.z*Math.sin(0.5*correctionValue)])
    quat = quat.mul(accQuat)
  } 
});

React-Native-Chart-kitでグラフを書く

※ほとんどDocumentの翻訳です。Get Startedだけです。

Get Started

とりあえずDocumentに載っていたQuickExampleを動かしてみる。

yarn add react-native-chart-kit

でインストールしてApp.jsに次のコードを記述。

import React from 'react';
import { StyleSheet, Text, View, Dimensions } from 'react-native';
import {
  LineChart,
  BarChart,
  PieChart,
  ProgressChart,
  ContributionGraph
} from 'react-native-chart-kit'

const screenWidth = Dimensions.get('window').width;

export default class App extends React.Component {

  render() {
    return (
<View>
  <Text>
    Bezier Line Chart
  </Text>
  <LineChart
    data={{
      labels: ['January', 'February', 'March', 'April', 'May', 'June'],
      datasets: [{
        data: [
          Math.random() * 100,
          Math.random() * 100,
          Math.random() * 100,
          Math.random() * 100,
          Math.random() * 100
        ]
      }]
    }}
    width=screenWidth
    height={220}
    chartConfig={{
      backgroundColor: '#e26a00',
      backgroundGradientFrom: '#fb8c00',
      backgroundGradientTo: '#ffa726',
      decimalPlaces: 2, // optional, defaults to 2dp
      color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
      style: {
        borderRadius: 16
      }
    }}
    bezier
    style={{
      marginVertical: 8,
      borderRadius: 16
    }}
  />
</View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

チャートのスタイルはchartConfigで記述されている。指定できる値はチャートの種類によって違う。Documentにグラフの各種類におけるchartConfigで指定できる値が書いてある。グラフの種類は以下の通り。
・Line
・Bezier Line
・Progress Ring
・Bar
・Pie
・Contribution graph
github.com


レスポンシブな(様々なデバイスの大きさに対応した)グラフを書くにはDimensionsを使って画面サイズを取得して、その値を渡してあげる必要がある。

import { Dimensions } from 'react-native'
const screenWidth = Dimensions.get('window').width

// <LineCharts>タグ内
width=screenWidth

ImagePickerを使うときのデバイスのパーミッション関連

ImagePicker

Udemyの"実践的!作って学ぶReactNative入門!"でexpoを使っていてreact-native-image-crop-pickerの代わりにImagePickerを使ったのですが、その時にパーミッションでちょっと苦戦したのでメモを残しておきます。以下のサイトを参考にしました。JavaScript, ReactNative初心者なので書き方が適切であるかはわかりません。

Permissions - Expo Documentation
ImagePicker - Expo Documentation

ソースコード

import React from 'react';
import { StyleSheet, Text, View, Button, Image, TouchableOpacity } from 'react-native';
import { ImagePicker, Permissions, Camera, CameraRoll } from 'expo';

export default class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      image: null,
      permission: false,
    };
  }

  async componentWillMount() {
    let results = await Promise.all([
      await Permissions.askAsync(Permissions.CAMERA),
      await Permissions.askAsync(Permissions.CAMERA_ROLL)
    ]);

    this.setState({ permission: 
      results.every((result) => {
        const { status } = result;
        return status === 'granted';
      })
    });
  }

  render() {
    let { image } = this.state;
    const { permission } = this.state;

    if (permission === false) {
      return (
        <View style={styles.container}>
          <Text>カメラの使用が許可されていません。</Text>
        </View>
      );
    }

    return (
      <View style={styles.container}>
        <Button
          title="Pick an image from camera roll"
          onPress={this._pickImage}
        />
        {image &&
          <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />
        }
      </View>
 
    );
    
  }

  _pickImage = async () => {
    let result = await ImagePicker.launchImageLibraryAsync({
      allowsEditing: true,
      aspect: [4, 3],
    });

    //console.log(result);

    if (!result.canelled) {
      this.setState({ image: result.uri });
    }
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

ざっくり説明

パーミッションの取得はcomponentWillMountで行なっています。コールバック関数を用いて結果が全て"granted"であればtrue、そうでなければfalseを返しています。stateにパーミッションを記録して、それのtrue or falseでreturn文を切り替えています。

Google Map SDK for iOS (addSubviewを使う)

Google Map SDK for iOS

基本的なところは@koogawaさんの記事が参考になった。

qiita.com

今回やったところ

公式のドキュメントやQiitaにある記事を見ても下のコードのようにUIViewそのものをそっくり置き換え(?)ていたが、Mapと同一のUIViewにStoryboard上でボタンを配置したい。

view = mapView


とりあえず作成したプログラムとストーリーボード。

let BoundSize: CGSize = UIScreen.main.bounds.size
        
let camera = GMSCameraPosition.camera(withLatitude: 35.681167, longitude: 139.767052, zoom: 14.0)
let mapView = GMSMapView.map(withFrame: CGRect.init(x: 0, y: 0, width: BoundSize.width, height: BoundSize.height), camera: camera)
mapView.center = CGPoint(x:self.view.frame.width/2,y:self.view.frame.height/2)
view.addSubview(mapView)
self.view.sendSubviewToBack(mapView)

f:id:tsupiano:20181223160903p:plain
ストーリーボード
AddDataViewControllerのシーンのバックにマップを表示させて、マップ以外はStoryboardで作った。

  1. BoundSizeで画面の大きさを取得
  2. GMSMapView.mapの引数withFrameにCGRectでマップを表示する領域を指定して(?)Viewを作成
  3. addSubviewでAddDataViewControllerのビューに追加
  4. 最全面に配置されて他の部品が隠れてしまうので、sendSubviewToBackで最背面に移動

未解決の問題

上記の方法でやる前にContainer Viewを使って、それにembedした(?)MapViewContrallerという別クラスで作ろうとした。(それが設計上適切であるかはわからないけど、公式ドキュメントがview = mapViewとしていたのもあって)
でもマップとその他部品は正しく表示されたけど、指定した緯度経度の位置が画面の一番左上にきてしまった。
色々試行錯誤したけど解決できなかったので、原因等がわかるかたは良かったらコメントしてほしい。。。

iOSでFirebase入門 その2

今回やること

  1. データの読み込み
  2. データの書き込み
  3. 実装

メモ帳みたいな記事なので悪しからず...

読み込みと書き込みで共通のやるべきこと

まず、以下がインストールされていることを確認。

pod 'Firebase/Core'
pod 'Firebase/Database'
pod 'SwiftyJSON'

RealTime DatabaseとCloud Firestoreの2種類があるが、ここではRealTimeDatabaseを使う。コンソール(Webサイトのやつ)からRealtime Databaseのルールを開き、writeとreadの権限をtrueにする。

次はXcodeのプロジェクト側。データベースへの参照を定義する。

var ref: DatabaseReference!
ref = Database.database().reference()

データの読み込み

observeを使うことでデータベースにアクセスできる。observeメソッドはデータベースに変更がある度に自動的に呼び出される。引数によって、変更の種類を選択できる。observeの他にobserveSingleEventというメソッドがあり、こっちは一度だけ実行されるメソッド。結果はsnapShotに格納される。

var username: [String]

override func viewDidLoad() {
  super.viewDidLoad()
        
  ref = Database.database().reference()
  let postRef = ref.child("users")
        
  postRef.observe(.value) { (snapShot) in
    let json = JSON(snapShot.value as? [String : AnyObject] ?? [:])
  
    self.username = []
    for (key, val) in json.dictionaryValue {
      self.username.append(json[key]["username"].stringValue)
    }
  } 
}

データの書き込み

Firebase Realtime DatabaseではJsonオブジェクトで保存される。Json連想配列のようなもの。

{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}


データベースにデータを保存する為のメソッド一覧。(公式からの引用)

メソッド 一般的な使用例
setValue users// など、定義済みのパスへのデータの書き込みや、データの置換を行います。
childByAutoId データのリストに追加します。childByAutoId を呼び出すたびに、user-posts// のような一意の識別子としても使用できる一意のキーが生成されます。
updateChildValues データのすべてを置換することなく、定義済みのパスのキーの一部を更新します。
runTransactionBlock 同時更新によって破損する可能性がある複合データを更新します。

以下のようにすることで、データベースにデータが追加されます。

let data = ["username": "jon", "message": "hello"]
let uid = "001"
ref.child("users").child(uid).setValue(data)
let data = ["username": "Albert"]
let uid = "001"

とし、ユーザー名前だけを変更したい場合、setValueだと前回追加した際に記録されている"message"が消えてしまいます。これはsetValueはchildで指定した階層以下全てを書き換えるためです。他の値を残したいまま、更新したい場合はupdateChildvaluesを用います。


childByAutoIdは自動的にIDを割り振ってくれます。

let data = ["username": "bob","message":"good bye"]
ref.child("chat").childByAutoId().setValue(data)

実装

class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    var ref: DatabaseReference!
    var username: [String] = []
    

    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
        tableView.delegate = self
        
        ref = Database.database().reference()
        let postRef = ref.child("users")
        
        postRef.observe(.value) { (snapShot) in
            let json = JSON(snapShot.value as? [String : AnyObject] ?? [:])
            if json.count == 0 { return }

            self.username = []
            for (key, val) in json.dictionaryValue {
                self.username.append(json[key]["username"].stringValue)
            }
            self.tableView.reloadData()
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.username.count
    }
    
    func numberOfSections(in tableView: UITableView) -> Int { // sectionの数を決める
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "firebase_cell", for: indexPath)
        cell.textLabel?.text = self.username[indexPath.row]
        return cell
    }
}

class AddDataViewController: UIViewController, UITextFieldDelegate{
    
    @IBOutlet weak var username_textField: UITextField!
    @IBOutlet weak var message_textField: UITextField!
    
    var ref: DatabaseReference!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        ref = Database.database().reference()
        username_textField.delegate = self
        message_textField.delegate = self
    }
    
    @IBAction func sendData(_ sender: Any) {
        let data = ["username": username_textField.text!,"message":message_textField.text!]
        ref.child("users").childByAutoId().setValue(data)
        performSegue(withIdentifier: "toTableView",sender: nil)
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

参考
qiita.com
qiita.com
qiita.com
blog.all-in.xyz
qiita.com

iOSでFirebase入門 その1 サインアップとサインイン

Firebaseとは

Googleが提供してるサーバー処理を楽に行なってくれるmBaaS。
※ mBaaSは「Backend as a Service」の略で、スマホ向けのウェブアプリ機能を提供するサービスのこと。
(Firebaseについてわかってきたら追記予定。

プロジェクトのスタートアップ

Xcodeでプロジェクトを作成したら、Bundle Identifierの値をコピーしておく。Firebaseのページからプロジェクトの追加する。

先ほどコピーしたBundle Identifireをここに貼り付ける。
以降しばらくXcodeのプロジェクトとFirebaseのプロジェクトをリンクする手順が示されるのでそれに沿って進める。
f:id:tsupiano:20181125221917p:plain

ここまででとりあえず、XcodeのプロジェクトでFirebaseを使えるようになっているはず。

サインインとサインアップ

Firebaseではアカウント管理を簡単に行うことができる。また、メール、電話認証、GoogleFacebookTwitterなど様々なプロバイダ認証が行える。ここではもっとも単純(そうな)メールでの認証を行なってみる。

まず、Firebase/Authをpod installしておく。

pod ‘Firebase/Auth’

サインアップ部分。

@IBAction func signUpButtonTapped() {
        // "in"以下で完了時に実行される処理を記述。authResultかerrorに値が代入されている。
        Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!) { (authResult, error) in
            if let error = error {
                self.statusLabel.text = "Creating the user failed! \(error)"
            }
            // authResultからユーザー情報を取得できる。
            if let user = authResult?.user {
                // user.mailとすることでメールアドレスが取得できる。
                self.statusLabel.text = "user : \(String(describing: user.email)) has been created successfully."
            }
        }
    }

authResult.userではメール以外でも下記の情報が管理できるらしい。

user.getUid(); // UID(ユーザー一意のID)
user.getDisplayName(); // 表示名
user.getEmail(); // E-mail
user.getPhotoUrl(); // プロフ用画像URL
user.getProviders(); // プロバイダ情報
user.isAnonymous(); // true:匿名ユーザー

次にサインイン。

@IBAction func signInButtonTapped() {
        Auth.auth().signIn(withEmail: emailTextField.text!, password: passwordTextField.text!) { (authResult, error) in
            if let error = error {
                self.statusLabel.text = "Login failed. error: \(error)"
            }
            if let user = authResult?.user {
                self.statusLabel.text = "user: \(String(describing: user.email)) has been signed in successfully."
            }
        }
    }
}

なお、登録されているユーザー情報はFirebaseのコンソール(Webサイト側のやつ)の開発->Authentificationからみることができる。