20200331のiOSに関する記事は4件です。

今更ながらReact Nativeに入門してみた①

はじめに

実は最近、React Nativeなるものに入門してしまいました...。

React Nativeとはなんなのか?といったところの説明から、実際に簡易的なTODOアプリを作成するところまでご紹介できればと思います。

では、早速行ってみましょう〜!

React Nativeとは何か

React Nativeとは、

Reactを使って、iOSやAndroidなどのネイティブアプリを開発することができるクロスプラットフォームな開発ができるフレームワークです。

公式サイトはこちら

なぜReact Nativeで作るか

個人的にメリットだと感じる点はいくつかあります。

  1. React(JavaScript)で書ける
  2. プラットフォームを意識せず開発できる

普段JavaScriptでバックエンドやフロントエンドを書いている自分にとって、同じような感覚でネイティブアプリも作れてしまうのは非常に魅力的です!

また、本来であればiOSならSwift、AndroidならKotlinなど、プラットフォームごとに言語を使い分けるか、どちらかのプラットフォームに絞って開発していたものが、多少UIを変更するだけで良いのはユーザー獲得という観点でもメリットが大きいように思います。

また、ネイティブアプリだけでなく、WEBアプリ版も作成する時、WEBでもReactを使えば、アプリケーション自体はそのまま使い回し、UIのみ変更すれば良いので、そういう意味でもクロスプラットフォームな開発ができます。

Expoとは何か

React Nativeによるネイティブアプリの開発は、Reactを使えることによってかなり開発しやすくなったとはいえ、普段WEBを開発しているエンジニアにとっては少しハードルが高くなります。

そんなネックを解消してくれるのが、Expoです。

Expoは、React Nativeによる開発をより簡単に、わかりやすくしてくれる開発用ツールです。

どう簡単にしてくれるかというと、

  • ほぼReactでWEBアプリを開発している感覚でコードが書ける
  • iOSアプリの場合、アプリのアップデートが再審査なしで行える

などがあります。

ただ、デメリットとして、

  • 実装できる機能に制限がある

ことは念頭に置いておいた方が良さそうです。

公式サイトはこちら

簡易的なTODOアプリの作成

それでは、早速React Nativeを使ってTODO管理ができるアプリを作ります。

今回はアプリの配信はせず、ローカル環境で動作させるだけに留めます。

また、今回は、

  • 開発のしやすさ
  • 実装したい機能を全て実現可能

ということで、Expoも使っていきます。

まずは公式ドキュメントに沿ってReact NativeとExpoの環境構築を済ませておいてください。

プロジェクトを作成したいディレクトリで、Expoプロジェクトを作成します。

expo init todo_app_expo

プロジェクトを作成したら、プロジェクトのルートディレクトリでExpoを起動します。

expo start

コンポーネントに切り分ける

まずは、作りたいものをなるべく最小単位で、コンポーネント化していきます。

今回はシンプルなTODOアプリなので、必要なコンポーネントは下記の通りです。

  1. ユーザーの入力を受け付けるフォーム&ボタン
  2. 登録されたTODOを表示する場所

もっと細かくコンポーネント化できるかもしれません。

その辺りは追々身につけていきます笑

UIを作成する

次に、コンポーネントごとにまずは画面上に表示させてみましょう。

ExpoでiOSシミュレーターを起動させたら、自動でコードの変更を検知して更新されるような設定になっています。

コンポーネントは、ルートディレクトリ直下にcomponentsディレクトリを作成して、その下に配置していきます。

各コンポーネントの実際のコードは下記の通りです。

./components/Form.tsx

フォームの作成は、React NativeのTextInputを使用します。

また、ボタンはReact NativeのButtonを使用します。

ドキュメントはこちら

コンポーネントのソースは下記の通りです。

./components/Form.tsx
import React from 'react';
import { View, Button, TextInput, StyleSheet } from 'react-native';

const Form = () => {

  const [value, onChangeText] = React.useState('');
  const placeholder = 'テキストを入力してください'

  return (
    <View style={styles.form}>
      <TextInput
        style={styles.form}
        onChangeText={text => onChangeText(text)}
        value={value}
        placeholder={placeholder}
      />
      <Button
        onPress={() => {alert('pressed!')}}
        title="Submit"
        color="blue"
      />
    </View>
  )
}

const styles = StyleSheet.create({
  form: {
    height: 40,
    width: '100%',
    borderColor: 'gray',
    borderWidth: 1,
    margin: 'auto',
  }
})

export default Form;

ボタンを押した時のalertは仮実装なので、後ほどきちんと作ります。

./components/TodoList.tsx

TODO一覧のコンポーネントは、現状ダミーのデータを用意しています。

ロジックとしては、TODOが配列で格納されているので、そのデータをReact NativeのFlatListでループ処理して表示しています。

削除ボタンは現状alert出すだけの仮実装です。

ソースは下記の通りです。

./components/TodoList.tsx
import React from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';

const TodoList = () => {

  const todoList = ['TODO1', 'TODO2', 'TODO3'];

  return (
    <View style={styles.list}>
      <FlatList
        data={todoList}
        renderItem={({item}) => (
          <View>
            <Text style={styles.item}>{item}<Text onPress={() => {alert('delete!')}}> ×</Text></Text>
          </View>
        )}
        keyExtractor={(item, index) => index.toString()}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  list: {
    alignItems: 'center',
    marginBottom: 50
  },
  item: {
    fontSize: 22
  }
})

export default TodoList;

あとは軽く配置を整えてとりあえずUIは完成とします。

現状はこんな感じです。

stateでコンポーネントの状態を保持する

さて、UIが完成したら、実際にTODOアプリとして動作するようにしていきます。

TODOアプリにするためには、

  1. 入力したTODOを登録できる
  2. 入力したTODOが一覧表示される
  3. 完了したTODOを削除できる

上記の要件を満たす必要があります。

また、上記の要件を満たすためには、TODOを一時的なデータとしてクライアント側で保持しておく必要があります。

そういったことを実現するために、React Nativeにはstateという物が用意されています。

stateの公式ドキュメントはこちら

stateとは、各コンポーネントの状態を保持するための機能で、

コンポーネントの状態とは、ここでいうTODOが登録されたり削除されたり、といったことを指します。

では早速、stateを用いてTODOの登録、一覧表示、削除を実装していきましょう。

stateの定義・初期化

まずは、現状のTodoデータは下記のコードで定義しています。

const todoList = ['TODO1', 'TODO2', 'TODO3'];

これをstateを使用するように記述すると、下記のようになります。

const [list, setList] = useState(['TODO1', 'TODO2', 'TODO3']);

TODOリストなので、現状は空の配列でstateの値を初期化しておきましょう。

const [list, setList] = useState([]);

これでコンポーネント内で値を保持しておくことができるようになりました!

stateの値を表示・更新する

次は実際にstateで保持している値を表示して、更新するところまでです。

stateの値を使用するときは、下記のように使用できます。

const [list, setList] = useState([]);

const FuncComponent = () => {
    return(
        <Text>{list}</Text>
    )
}

そして、値の更新は下記のように行います。

const [list, setList] = useState([]);

const FuncComponent = () => {
    setList(['TODO']);
    return(
        <Text>{list}</Text>
    )
}

上記のコードだと、更新された値が表示されています。

少し解説すると、stateを作成するのに使用している「useState」ですが、引数には初期値を渡します。

今回のTODOアプリで言うと、空の配列を渡していますよね。

そして、useStateは実際のstateの値と、stateの値を更新する関数を返します。

上記のコードでは、それを変数に格納しているというわけです。

以上を踏まえて、実際に実装したTODO登録の関数とTODO削除の関数を作成しました。

(TODO登録の関数)
javascript
const addList = (item: string) => {
const newList = [...list, item];
setList(newList);
}

(TODO削除の関数)
javascript
const deleteList = (item: string) => {
const newList = list.filter(li => { return li !== item });
setList(newList);
}

一点注意点としては、stateの値の操作で配列を扱う際、

破壊的なメソッドを使用するとリアクティブにレンダリングされなくなります

ですので、値の追加にはpushなどは使わず、今回のようなスプレッド演算子を使うか、concatなどで追加します。

値の削除も同じように、popやshiftではなく、filterなどを使用すると良いでしょう。

propsで親コンポーネントから子コンポーネントに値を渡す

最後に、切り分けたコンポーネント間で値を受け渡せるようにしましょう。

コンポーネントの階層としては、Appコンポーネントが親コンポーネントとなり、それぞれFormコンポーネントとTodoListコンポーネントを子コンポーネントとしています。

親コンポーネントから子コンポーネントに値を渡す際に使えるのが、propsです。

例えば、親コンポーネントで保持している数字を、子コンポーネントで表示する際は下記のようなコードになります。

(親コンポーネント)

const ParentsFunc = () => {
    const [number, setNumber] = useState(0);

    return (
        <ChildFunc num={number} />
    )
}

(子コンポーネント)

const ChildFunc = (props) => {
    return (
        <Test>{props.num}</Test>
    )
}

上記のように記述することで、親コンポーネントから渡ってきた値を子コンポーネントで使用することができます。

また、

const ChildFunc = ({num}) => {
    return (
        <Test>{num}</Test>
    )
}

propsは受け取る時点で展開しておけば、記述量が少なくなります。

このpropsを利用して、同じ容量で先ほど作成した登録用の関数と削除用の関数を親コンポーネント内で定義し、子コンポーネント内で発火させるように実装していきます。

そしてついに完成しました!

完成品のコードはこちら

終わりに

今回初めてReactNativeを使ってみましたが、ドキュメントも充実していて学びやすかったです。

WEBアプリと比べて、ネイティブアプリの方が一般の方に使ってもらいやすいサービスが作れそうでワクワクします!

次はReduxをある程度使えるようになったら、また記事にしてみようかなあ。

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

Swift4以降のKVOについて

はじめに

KVOとはKey-Value Observingの略で、対象のObjectの値を監視し、変更のタイミングで通知を行ってくれるものです。
とても簡単に特定の値の監視を行うことができますので、今回はその使い方についてまとめてみました。

KVOの使い方

監視の登録方法について

Objectの値の監視を登録するにはobserve(_:options:changeHandler:)を使用します。
各引数の説明を簡単に行います。

func observe<Value>(_ keyPath: KeyPath<T, Value>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (T, NSKeyValueObservedChange<Value>) -> Void) -> NSKeyValueObservation
  • keyPath : KeyPath形式で監視したいObjectの値を指定
  • options : 変更前後、どの値を通知してもらうか指定するオプション
  • changeHandler : Value変更時に呼ばれるHandler
    • T : 対象のObject
    • NSKeyValueObservedChange<Value> : 監視対象の値の変更時の値を格納

実例

では実例を見ていきましょう、使い方は簡単です。
下の例ではviewframeの変更時に通知をするよう処理を記述しています。

/// インスタンスを保持
var observations = [NSKeyValueObservation]()

/// Objectの値の監視を追加
func addKeyValueObserver() {
    let observation = view.observe(\.frame, options: [.new, .old], changeHandler: { object, change in
        // value変更時の処理を記載
        print("変更前の値: \(change.newValue), 変更後の値: \(change.oldValue)")
    })
    observations.append(observation)
}

/// 監視を停止し、インスタンスを削除
func removeKeyValueObserver() {
    // 監視を停止
    observations.forEach({ $0.invalidate() })
    observations.removeAll()
}

optionsで設定している値はnewで変更後の値を通知するよう指定、oldで変更前の値を通知するよう指定できます。
ちなみに変更が通知された際に、変更された値の中身を確認する必要がない場合はoptions:は省略することができます。
その場合は、newValueおよびoldValuenilになります。

KVOの使い所

プロパティを外から監視できる

既存のframeworkのプロパティなど、変化を検知したいが検知するためのdelegateが存在しない場合などは、KVOを使用して該当のプロパティ対して外から監視を行い、プロパティの変更を検知をすることができます。

おわりに

KVOを使用することで簡単にプロパティの監視を行うことが出来ますので、是非使い所を見つけて使ってみてください。

参考文献

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

Xcodeで古いOSバージョンのSimulatorを起動する

XcodeはそのXcodeがリリースされたときの最新のOSバージョンのSimulatorが用意されているが、そのXcodeを使いつつ古いOSバージョンのSimulatorを起動するためには一手間必要なので、その手順をメモしておく

環境

Xcode 11.4

手順

Xcodeのメニューから Window > Devices and Simulators を選択する

「Simulators」タブを選択することでシミュレータ一覧画面が表示されるので、左下の「+」ボタンをクリックする
01_デバイスとシミュレータ画面.png

以下のようなシミュレータ追加画面が表示されるので、「OS Version」をクリックして選択肢を表示する
02_シミュレータ追加画面.png

「Download more simulator runtimes」を選択する
03_シミュレータランタイム追加画面.png

以下のようなコンポーネント一覧画面に遷移するので、起動したいOSバージョンの左側に表示されているダウンロードボタンをクリックし、ダウンロードが完了するまで待つ
04_iOSコンポーネント追加画面.png

シミュレータ追加画面に戻り、追加されたOSバージョンと端末を選択し、シミュレータを作成する
この際、端末の種類によっては追加したOSバージョンが古すぎて作成できない場合がある
05_古いOSのシミュレータ追加画面.png

Xcodeで適当なiOSプロジェクトを開き、画面左上のターゲットデバイスから先ほど作成したSimulatorを選択し、Runボタンをクリックすれば、起動する
06_古いOSのシミュレータ.png

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

[Tips]BitriseでIssue with input: no IpaPath nor PkgPath parameter specifiedが出た時は、export methodを疑った方が良い

「あれ?これなんで発生してるエラーなんだ?」と思った時用のTips書き残しです。

何が発生する?

「Issue with input: no IpaPath nor PkgPath parameter specified」というエラーが発生します。

いつどこで発生する

AppStoreConnectへのアップロードステップで発生します。

なぜ発生する

Bitriseで、iOSApp/MacAppのプロジェクトを作った時にデフォルトのenv_varでBITRISE_EXPORT_METHODがdevelopmentになっているのですが、そのまま変更されていない場合はipa/pkgがそもそもアーカイブステップで出力されないので、パスがないと言われてしまいます。

どう解決する

環境変数でワークフローごとの変数を設定できるのでBITRISE_EXPORT_METHODapp-storeを設定するか、各種ステップのexport methodを指定する箇所で直接app-storeすると解決できます。

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