20200107のiOSに関する記事は8件です。

IFTTTのリッチ通知から任意のiOSショートカットを起動する

はじめに

iOSのショートカットは、ショートカットのURLスキームを使って外部から任意のショートカットを起動することができます。
ここでは、リッチ通知から任意のショートカットを起動する方法について説明します。

前提

  • iOSショートカットアプリが必要です。
    • また、通知から起動する任意のショートカットを事前に作成する必要があります。
  • IFTTTから通知を受信するために、IFTTTアプリのインストールが必要です。

ショートカットのURLスキームの仕様

URLスキームを使用してショートカットを実行するのドキュメントに記載されていますが、以下のURLスキームを開くことで任意のショートカットを起動することができます。

shortcuts://run-shortcut?name=Make%20PDF&input=text&text=soup

nameにショートカット名を指定するのですが、URLエンコードが必要です。
また、inputパラメータの値にtextを指定することで、textパラメータの値をショートカットに渡すことができます。
その他、inputパラメータの値にclipboardを指定することもできますが、詳しくは上記のドキュメントをご参照ください。
ただし、ここでは、inputパラメータやtextパラメータの指定はなしで説明します。

IFTTT側のリッチ通知の設定

ここでは、IFTTTのアクションの作り方については詳しく説明しません。他の記事をご参照ください。

まずは、IFTTTのThisのアクションは適当にWebhooksなどを指定します。
次に、Thatですが、Notificationsを選択後、Send a rich notification from the IFTTT appを選択します。

選択後、通知で表示するタイトルやメッセージ内容を適当に設定します。
ここで重要な項目はLink URLです。
この項目に起動したいショートカットのURLスキームを指定します。

例えば、天気という名称のショートカットを起動する場合は、

shortcuts://run-shortcut?name=%E5%A4%A9%E6%B0%97

Link URLに指定します。

image.png

上記を設定後、Saveボタンを押下したら完了です。

リッチ通知からのショートカットの起動

通知を受信したら自動でショートカットが起動されてほしいとも思うかもしれませんが、残念ながら(セキュリティ上)自動でショートカットは起動されません。
ショートカットを起動するには、受信した通知をタップする必要があります。
通知をタップ後に、URLスキームで指定した名称のショートカットが起動します。

おわりに

ショートカット自体は、通知を実際にタップしないと起動されないため、若干煩わしいですね。
また、リッチ通知からショートカット起動して何がうれしいのかはまだよく分かっておりませんが、アイデア次第で色々活用方法はあるのではないでしょうか。
例えば、技術本の新刊情報を通知で知らせて、通知をタップしたらAmazonの購入サイトに遷移すると良いかなとも思います。
今後良い活用方法を模索したいと思います。

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

急にiOSアプリ開発(未経験)の仕事が来たのでパッケージマネージャ何がいいねんに立ち向かう

背景

タイトルの通りですが、業務でやったことのないiOSアプリ開発の仕事が急に来た。
直近の課題の1つにパッケージマネージャ何使えばいいんだ問題があったのでわからないなりに調べてみた結果と所感をまとめた個人の備忘録です。
間違ってたら優しく指摘ください!(個人ブログも参考にしたので情報古いところあるかも)

候補

  • CocoaPods
  • Carthage
  • Swift Package Manager

CocoaPods

特徴

  • 中央集権的なパッケージマネージャ
  • Specsと呼ばれる巨大なリポジトリの中に全てのパッケージ情報を詰め込んでいる(ので中央集権的)
  • podコマンドによりパッケージの登録・更新が可能
  • OSSで開発されている。Ruby製(なのでgemでMacへインストールする)
  • 公式ページの検索が非常に優秀でライブラリを探しやすいとのこと

メリット

  • Webサイトでライブラリを探せる
  • Dynamic FrameworksとStatic Librariesの両方に対応(Dynamic Frameworkはアプリ起動時に結合されるもの、Static Frameworkはアプリビルド時に結合されるものらしい)
  • 依存するライブラリがさらに依存しているライブラリも解決してくれる
  • 依存関係の定義ファイルを見ればどのライブラリにアプリが依存しているか一目でわかる
  • 依存ライブラリのアップデートをコマンド1つで検知できる
  • pod tryでライブラリのリポジトリ中のサンプルプログラムをすぐに試せる
  • 公式で簡単に依存関係を管理できるアプリを出している
  • ほぼすべてのライブラリがCocoaPodsをサポートしている

デメリット

  • CocoaPodsを入れてから初めて依存関係をインストールとき、長時間待つ必要がある。それは前述の巨大なリポジトリをすべてMacに入れるため。また、pod updateするときも同じだけ待つ必要がある
  • 依存ライブラリが使えるようにするために、CocoaPodsは勝手にプロジェクトをいじる。それによって問題が起きてしまう場合が多い
  • プロジェクトをビルドするとき全ての依存ライブラリをビルドするのでビルド時間が長い

Carthage

特徴

  • 非中央集権的パッケージマネージャ
  • CocoaPodsとは違いパッケージが1リポジトリで管理されていなく、別々のリポジトリで個別に管理
  • 依存に追加したいgithubのリポジトリを指定してパッケージをインストールする
  • Swift製のOSS

メリット

  • Dynamic FrameworksとStatic Librariesの両方に対応
  • 依存するライブラリがさらに依存しているライブラリも解決してくれる
  • 依存関係の定義ファイルを見ればどのライブラリにアプリが依存しているか一目でわかる
  • 依存ライブラリのアップデートをコマンド1つで検知できる
  • 1度しかライブラリのビルドをしないのでCocoaPodsに比べてビルド時間が速い
  • CocoaPodsのように勝手にプロジェクトがいじられることがないので安心

デメリット

  • 依存関係を追加するためのステップが多いのでミスしがち
  • すべてのライブラリがCarthageをサポートしているわけではない

Swift Package Manager

特徴

  • Apple公式で出したパッケージマネージャ
  • 対応しているライブラリが少なく、バックエンド用途のライブラリが多いらしい

メリット

  • 今後スタンダードになっていくであろうパッケージマネージャらしい
  • 依存するライブラリがさらに依存しているライブラリも解決してくれる
  • 依存関係の定義ファイルを見ればどのライブラリにアプリが依存しているか一目でわかる

デメリット

  • watchOS、tvOSなどサポートしていないOSがある
  • 決まったフォルダ構成に従うことを強制される
  • 対応するライブラリがまだ少ない

所感

対応しているライブラリがまだまだ少なそうな「Swift Package Manager」は現時点で避けたい。
依存関係の追加が楽だが勝手にプロジェクトをいじられて大変な思いをしそうなCocoaPodsをとるか、やや追加が面倒かつ対応してないライブラリもあるがプロジェクトをいじられない心理的安全性を保てるCarthageをとるかでいうと、個人的にはCarthageに軍配。
ただでさえ初めてのネイティブ開発で依存関係追加の手間を惜しんで、CocoaPodsが何かを壊してXcodeとにらめっこ...は避けたいため。(ある程度形ができたら引き継ぐ予定のメンバーもネイティブ知らない人ばかりなので...)

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

ExpoのPermissions周りを実機検証してみる

はじめに

Expoでの開発に限らず、iOSアプリでは一部のプライバシーに関わる機能を利用する際、その目的を説明する文言を設定する必要があります。設定した説明文は、ユーザーに権限を確認するためのダイアログに表示されます。

設定が必要な機能については下記のような記事が参考になりました。

iOS のユーザデータにアクセスするための Info.plist への許可設定まとめ
http://neos21.hatenablog.com/entry/2018/06/18/080000

[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです
https://dev.classmethod.jp/smartphone/iphone/ios10-privacy-data-purpose-description/

Expoにおいてこの説明文(UsageDescription)はapp.jsonexpo.ios.infoPlistオブジェクトに追加することでカスタマイズできます。
デフォルトだと説明文が存在しない訳ではなく、英語、かつ目的を説明していない(Give ~ permissionという感じの)ため、App Storeに申請する際、下記の記事に書かれているようにリジェクトされてしまいます。

【React Native】【Expo】iOSのパーミッション要求ダイアログで審査リジェクトされた話とその対応
https://tech.maricuru.com/entry/2018/07/27/195921

私が過去リジェクトされた際には目的がどうのというより、言語を統一しなさいという感じだったと思うので、目的をちゃんと説明しつつアプリが対象としている言語で設定する必要があるようです。

フォトライブラリ(カメラロール)、カメラ、通知、位置情報のパーミッション設定をiOS/Androidの実機で確認してみたので、その流れを説明します。

画面を作成

このようにPermissionを確認するボタンだけを表示するような画面を作ります。
PNGイメージ 15.png

追加するモジュールはこのあたりです

  • expo-image-picker
  • expo-permissions
  • expo-location
  • expo-haptics これは全然関係ないけど使ってみたかったので。許可が得られた時に振動をつけてみます。

Permission取得の処理

PermissionButton.js
import React, { useState } from "react";
import {
  TouchableOpacity,
  Text,
  View,
  ActivityIndicator,
  Alert
} from "react-native";
import * as Haptics from "expo-haptics";
import { Linking } from "expo";
import * as Permissions from "expo-permissions";

function PermissionButton(props) {
  const { type, title } = props;
  const [isLoading, setLoading] = useState(false);
  const [isGranted, setGranted] = useState(false);

  return (
    <TouchableOpacity
      style={{
        width: "100%",
        height: 50,
        marginVertical: 12
      }}
      disabled={isLoading}
      onPress={async () => {
        if (isGranted) {
          props.onPress && props.onPress();
          return false;
        }
        setLoading(true);
        const { status } = await Permissions.askAsync(Permissions[type]);
        setLoading(false);
        if (status === "granted") {
          setGranted(true);
          await Haptics.notificationAsync(
            Haptics.NotificationFeedbackType.Success
          );
          setTimeout(props.onPress, 500);
        } else {
          // ユーザーが意図的にPermissionを不許可にしている場合
          // アラートを表示して設定画面に移動する
          Alert.alert(
            `${title}が無効になっています`,
            "設定画面へ移動しますか?",
            [
              {
                text: "キャンセル",
                style: "cancel"
              },
              {
                text: "設定する",
                onPress: () => {
                  Linking.openURL("app-settings:");
                }
              }
            ]
          );
        }
        console.log(type, status);
      }}
    >
      <View
        style={[
          props.style,
          {
            position: "relative",
            width: "100%",
            height: "100%",
            justifyContent: "center",
            alignItems: "center",
            backgroundColor: isGranted ? "#3cbe8d" : "#3cb3ff",
            borderRadius: 25
          }
        ]}
      >
        {isLoading ? (
          <ActivityIndicator
            color="#ffffff"
            style={{
              position: "absolute",
              width: "100%",
              height: "100%",
              top: 0,
              left: 15,
              justifyContent: "center",
              alignItems: "flex-start"
            }}
          />
        ) : null}
        {isGranted ? (
          <Text
            style={{
              position: "absolute",
              width: "100%",
              height: "100%",
              lineHeight: 50,
              top: -2,
              left: 15,
              justifyContent: "center",
              alignItems: "flex-start"
            }}
          >
            &#x1f44d;
          </Text>
        ) : null}
        <Text
          style={{
            color: "#ffffff",
            fontWeight: "bold"
          }}
        >
          {title}
        </Text>
      </View>
    </TouchableOpacity>
  );
}

export default PermissionButton;

このように、指定したPermissionをaskするボタンをコンポーネント化しておきました。

画面を作成し、各機能を確認する処理を追加

App.js
import React from "react";
import { Text, View, Alert } from "react-native";
import PermissionButton from "./PermissionButton";
import * as ImagePicker from "expo-image-picker";
import * as Location from "expo-location";
import { Notifications } from "expo";

export default function App() {
  return (
    <View
      style={{
        flex: 1,
        padding: 32,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center"
      }}
    >
      <Text
        style={{
          fontSize: 24,
          marginBottom: 16,
          fontWeight: "bold"
        }}
      >
        パーミッション確認
      </Text>
      <PermissionButton
        type="CAMERA_ROLL"
        title="フォトライブラリ(カメラロール)"
        onPress={async () => {
          try {
            const result = await ImagePicker.launchImageLibraryAsync();
            console.log(result);
          } catch (error) {
            Alert.alert(error.message);
          }
        }}
      />
      <PermissionButton
        type="CAMERA"
        title="カメラ"
        onPress={async () => {
          try {
            const result = await ImagePicker.launchCameraAsync();
            console.log(result);
          } catch (error) {
            Alert.alert(error.message);
          }
        }}
      />
      <PermissionButton
        type="NOTIFICATIONS"
        title="PUSH通知"
        onPress={async () => {
          try {
            const token = await Notifications.getExpoPushTokenAsync();
            console.log("token", token);
            const res = await fetch("https://expo.io/--/api/v2/push/send", {
              method: "POST",
              headers: {
                Accept: "application/json",
                "Content-type": "application/json"
              },
              body: JSON.stringify({
                to: token,
                title: "通知テスト",
                body: "ここにbodyが入ります",
                _displayInForeground: true
              })
            });
            console.log(res);
          } catch (error) {
            Alert.alert(error.message);
          }
        }}
      />
      <PermissionButton
        type="LOCATION"
        title="位置情報"
        onPress={async () => {
          try {
            const { coords } = await Location.getCurrentPositionAsync();
            Alert.alert(
              "位置情報の取得に成功しました",
              `緯度:${coords.latitude}\n経度:${coords.longitude}`
            );
          } catch (error) {
            Alert.alert(error.message);
          }
        }}
      />
    </View>
  );
}

このように画面にボタンを配置して、各機能を確認するだけの処理を実装します。
通知に関しては送信が確認できればいいので、ExpoのAPIを直接叩いています。その際、iOSでもアプリ起動時に通知が表示されるように_displayInForeground: trueをオプションに指定しておきます。

app.jsonの編集

app.json
{
  "expo": {
    "name": "許可テスト",
    "slug": "permission-test",
    "privacy": "public",
    "sdkVersion": "36.0.0",
    "platforms": [
      "ios",
      "android",
      "web"
    ],
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#3cb3ff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "バンドル名",
      "infoPlist": {
        "NSCameraUsageDescription": "[テスト文言]写真をアップロードするためにカメラを使用します",
        "NSPhotoLibraryUsageDescription": "[テスト文言]アカウント画像をアップロードするためにフォトライブラリを使用します",
        "NSLocationAlwaysUsageDescription": "[テスト文言]位置情報をアップロードします",
        "NSLocationUsageDescription": "[テスト文言]位置情報をアップロードします",
        "NSLocationWhenInUseUsageDescription": "[テスト文言]位置情報をアップロードします"
      }
    },
    "android": {
      "package": "パッケージ名",
      "permissions": [
        "ACCESS_COARSE_LOCATION",
        "ACCESS_FINE_LOCATION",
        "CAMERA",
        "READ_EXTERNAL_STORAGE",
        "WRITE_EXTERNAL_STORAGE",
        "VIBRATE",
      ]
    },
    "notification": {
      "icon": "./assets/notificationicon.png",
      "color": "#3cb3ff"
    }
  }
}

app.jsonはこんな感じで設定しました。
expo.ios.infoPlistに各UsageDescriptionを、expo.android.permissionsには必要なだけの機能を記載します。
一応expo.notificationでAndroid用の通知アイコンも設定しておきました。(詳しくはこちらの記事
expo.android.permissionsVIBRATEは、expo-hapticsを使うので入れています。

検証

Android

実機での検証はAndroidの方が圧倒的に楽なので先にやってみます。
Androidの場合も6.0から一部機能でダイアログが表示されますが、この文言は設定できないので、
適切なexpo.android.permissionsがあれば申請の際に気にする必要はありません。
Expo Clientでの確認で十分な気もしつつ、念の為スタンドアロンアプリとしてビルドし実機で確認します。

ビルド
$ expo build:android

ビルド後に表示されたURLからダウンロードし、
端末を繋げてからAndroid SDKのadb installコマンドでAPKファイルのパスを指定してインストールします。

インストール
$ adb install permission-test-xxxxxx.apk

各Permissionを確認してみます。

カメラ
android

フォトライブラリ
android

通知
通知の許可に関してはダイアログが出ず通りました。
問題なく送信され、通知アイコンの設定も上手くいきました。色とアイコン(鍵)の部分が独自デザインです。
android

位置情報
android

以上、Androidはこんな感じです。

iOS

iOSの場合、ios.infoPlistの内容はExpo Client上では反映されないため、スタンドアロンアプリとしてビルドしてみないと検証できません。
しかし、スタンドアロンアプリを実機で確認する場合、リリースビルドをApp StoreやTestFlight経由でインストールするしか方法がありません(少なくとも公式には)。

今回はTestFlightの内部テストで確認してみたいと思います。

途中までは通常のアプリ申請のフローと同じなので、
App Storeへの申請についての記事
iOS用にビルド(Apple Developer Programにメンバーシップ登録後)
セクションから

アプリ(ipaファイル)をApp Store Connectにアップロード
セクションまでと同じ手順でビルドからアップロードまでを行います。

Transporterでアップロードする場合はこんな感じに。
空白_Skitch_キャンバス.jpg

そしてアップロードが完了した後、App Store ConnectのTestFlightタブを見るとこんな感じです。
App_Store_Connect_2.jpg

何らかの処理をしているようなので、しばらく待ちます。数十分から一時間ほどかかります。。。
しばらくしてAppleからメールが届くかと思います。もう一度App Store Connectを見ると、

App_Store_Connect3.jpg

米国の輸出コンプライアンスへの確認が求められます。「!」アイコンから進み質問に答えてから、

App_Store_Connect4.jpg
App_Store_Connect5.jpg

テスターに自分を招待して、iOS端末にTestFlightをインストールして確認します。

PNGイメージ 14.png

各Permissionを確認してみます。

フォトライブラリ
PNGイメージ 24.png

"アプリ名"が〜を求めています」という見出しは端末の言語設定によってiOSが固定で入れている文言です。
その下の部分に、app.jsonで設定した説明文が表示されています。

カメラ
PNGイメージ 19.png
こちらもOK。

通知
PNGイメージ 21.png
PNGイメージ_22.jpg
通知の場合は、もともとOSで決められたものが表示されます。
PUSH通知も問題なく届きました。

位置情報
PNGイメージ 23.png
設定した説明文が表示されました。

再確認の方法

iOSの許可ダイアログは一回しか表示されず、「設定」から各アプリ・各機能へのアクセスを不許可にしても再度表示することはできません。
再度ダイアログを表示させるには、「設定」→「一般」→「リセット」→「位置情報とプライバシーをリセット」で全てのアプリのプライバシー設定ごとリセットするか、アプリを再インストールするしか方法は無いようです。

TestFlightではアプリをアンインストールしてからこのようにすぐ再インストールすることが可能ですので、App Store経由の場合よりは比較的簡単にリセットできるかと思います。
PNGイメージ 25.png

Androidの場合は「設定」→「アプリと通知」→アプリを選択し、「権限」から各機能の許可を外すことができます。
その場合はiOSと違って、リクエストした際には再び許可ダイアログが表示されます。
android

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

Swiftで連絡先アプリに連絡先を追加する

はじめに

連絡先アプリから読み込みを行う記事はそこそこありましたが、連絡先を追加する記事はそんなになかったのでメモとして残しておきます。

環境

以下の環境で動作確認を行いました。

  • Xcodeのバージョン: Xcode11.3
  • Swiftのバージョン: Swift5

下準備

連絡先にアクセスするためにinfo.plistに以下の項目を追加します。

Key Value
Privacy - Contacts Usage Description 連絡先にアクセスする理由等を記述

これがないとエラーになるので忘れずに追加します。

連絡先へのアクセス許可を確認

アプリが連絡先にアクセスすることを許可してもらう必要があるので、現在の認証状態を確認してそれに応じた対応を行います。

初回の場合はいつものアクセス許可を求めるアラートを表示させたりするのが一般的だと思います。
その際にアラートを出したり、後の連絡先を保存する際に必要になるCNContactStoreをインスタンス化させておきます。

import Contacts

let contactStore = CNContactStore()

switch CNContactStore.authorizationStatus(for: .contacts) {
case .notDetermined, .restricted:
    contactStore.requestAccess(for: .contacts) { granted, error in
        if let error = error {
            print(error)
        }
        if granted {
            // アクセスを許可してもらえた時
        } else {
            // アラートからアクセスの許可をしてもらえなかった時
        }
    }
case .denied:
    // 拒否されている場合
case .authorized:
    // すでにアクセスが許可されている場合
default:
    // それ以外の場合
}

連絡先を追加

それでは本題です。
まずは連絡先のデータベースに保存を行うためのCNSaveRequestと保存する連絡先の情報となるCNMutableContact用意します。

import Contacts

let request = CNSaveRequest()
let contact = CNMutableContact()

名前や電話番号、メールアドレスなどの情報をCNMutableContactに入れていって、最後にCNSaveRequestで保存を行うような流れになります。

名前

名字、名前、それぞれのふりがなを指定することができます。

contact.givenName = "名前"
contact.familyName = "名字"
contact.phoneticGivenName = "なまえ"
contact.phoneticFamilyName = "みょうじ"

組織

組織名とふりがなを指定できます。

contact.organizationName = "組織名"
contact.phoneticOrganizationName = "そしきめい"

電話番号

電話番号は複数指定可能です。
CNLabeledValue<CNPhoneNumber>の配列となっていて、初期化に若干苦戦しました。

引数labelCNLabelPhoneNumberMainのようなCNLabelPhoneNumber...となっている定数を指定すると、デフォルトで用意されている電話番号の項目用のタイトルが上につきます。String?なので任意の文字列を入れることも可能です。

contact.phoneNumbers = [
    CNLabeledValue<CNPhoneNumber>(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: "123-4567-8910")),
    CNLabeledValue<CNPhoneNumber>(label: "カスタムのラベル", value: CNPhoneNumber(stringValue: "123-4567-8910"))
]

メールアドレス

メールアドレスも電話番号と同じく複数指定可能です。

CNLabelEmailAddress...みたいなのはなかったので、CNLabelHomeCNLabelWorkを指定しています。この辺はなんでもいいのかもしれません。

contact.emailAddresses = [
    CNLabeledValue<NSString>(label: CNLabelHome, value: NSString(string: "sample@sample.com")),
    CNLabeledValue<NSString>(label: CNLabelWork, value: NSString(string: "sample@sample.com"))
]

URL

これも同じく複数指定可能です。

contact.urlAddresses = [CNLabeledValue<NSString>(label: CNLabelURLAddressHomePage, value: NSString(string: "https://www.apple.com/jp/"))]

住所

同じく複数指定可能です。
CNMutablePostalAddressが引数に必要になるのでインスタンス化して住所の情報を代入します。

let address = CNMutablePostalAddress()
address.country = "日本"
address.postalCode = "460-8508"
address.state = "愛知県"
address.city = "名古屋市"
address.street = "中区三の丸3丁目1−1"

contact.postalAddresses = [CNLabeledValue<CNPostalAddress>(label: CNLabelHome, value: address)]

保存

最後に保存を行います。
CNSaveRequestに保存するCNMutableContactを追加してから保存を行います。
例外を投げるのでtry catchが必要です。

request.add(contact, toContainerWithIdentifier: contactStore.defaultContainerIdentifier())

do {
    try contactStore.execute(request)
} catch {
    print(error)
}

追加された連絡先はこんな感じに表示されます。

スクリーンショット 2020-01-07 17.39.18.png

さいごに

他にもbirthdayrelationsocialProfilesなどを指定することができますが、とりあえずよく使いそうな項目だけ抜粋しました。

何か間違っている点などがありましたら指摘してください?‍♂️

参考にさせていただいた記事

swift(iOS10)で連絡先アプリから連絡先データを取得する

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

【解決済み】SWIFT_VERSION '5.0' is unsupported, supported versions are: 3.0, 4.0, 4.2の対処法

解決方法

まず最初に書いておきます。

podを選択
スクリーンショット 2020-01-07 17.56.16.png

ライブラリを選択して、Swift Language Versionを4.2に選択
スクリーンショット 2020-01-07 17.56.34.png

完了
スクリーンショット 2020-01-07 17.56.47.png

説明

pod init
target "aaa" do

  use_frameworks!

  pod "Alamofire"
  pod "SwiftyJSON"
  pod "NVActivityIndicatorView"

end
pod install

ここでインストールしたライブラリの数だけエラーが出ます。エラー内容が

SWIFT_VERSION '5.0' is unsupported, supported versions are: 3.0, 4.0, 4.2

こんな感じ。

書いてあることは5.0はサポートされてないよー。3.0, 4.0, 4.2のどれかにしてねーって言ってます。

swift --version

でswiftのバージョンを調べてみても

swift --version
Apple Swift version 4.2.1 (swiftlang-1000.11.42 clang-1000.11.45.1)
Target: x86_64-apple-darwin17.7.0

と出てきます。

XcodeとSwiftのバージョンが悪いのかなと考えていたのですが、答え見つけました。

https://stackoverflow.com/questions/55585062/xcode-keeps-showing-1-swift-version-5-0-is-unsupported-supported-versions-a

英語読めてよかったーーー!

まとめ

初心者はエラーに弱いですが、頑張ってググれば出てきます。頑張りましょう!

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

【swift5】現在地を取得した天気アプリ

ソースはこちら

https://github.com/sventouz/swift_weatherApp

全体像

スクリーンショット 2020-01-07 17.32.25.png

①は固定。。。
これは位置情報を取得して今いる場所の地名を取得したかったができなかった。
Google Map APIの逆ジオコーディングを使用することで実現可能なので近日中に仕上げたい。

②こちらは今日の日付を取得して表示するだけなのだが、時間がなく割愛。ここもあとで実装する。 

③ここは変わる。笑
現在地の天気によって画像が変わる仕様になっている。参考のYOUTUBEの動画のかたが親切にもまとめてくださっているので使うと簡単。

④どんな天気かが表示される。

⑤現在地の気温。

アプリの詳細

このアプリは

  • Alamofire
  • SwiftyJSON
  • NVActivityIndicatorView

の3つのライブラリを使用している。

現在地から天気や気温などを取得しているのはOpenWeatherAPIである。

https://openweathermap.org/

ソース

ソースは以下の通りです。説明が長くなってしまうのでページ下部にあるYoutubeをみていただきたい。

import UIKit
import Alamofire
import SwiftyJSON
import NVActivityIndicatorView
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {


    @IBOutlet weak var locationLabel: UILabel!
    @IBOutlet weak var dayLabel: UILabel!
    @IBOutlet weak var conditionImageView: UIImageView!
    @IBOutlet weak var conditionLabel: UILabel!
    @IBOutlet weak var temperatureLabel: UILabel!


    let apiKey = "ここはあなたのAPIkey"
    var lat = 26.8205
    var lon = 30.8024
    // loading
    var activityIndicator: NVActivityIndicatorView!
    // user location
    let locationManager = CLLocationManager()


    override func viewDidLoad() {
        super.viewDidLoad()

        let indicatorSize: CGFloat = 70
        let indicatorFrame = CGRect(x: (view.frame.width-indicatorSize)/2, y: (view.frame.height-indicatorSize)/2, width: indicatorSize, height: indicatorSize)
        activityIndicator = NVActivityIndicatorView(frame: indicatorFrame, type: .lineScale, color: UIColor.white, padding: 20.0)
        activityIndicator.backgroundColor = UIColor.black
        view.addSubview(activityIndicator)

        // use popup to check and get location
        locationManager.requestWhenInUseAuthorization()
        if (CLLocationManager.locationServicesEnabled()) {
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
            locationManager.startUpdatingLocation()
        }
    }


    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location = locations[0]
        print(location)
        lat = location.coordinate.latitude
        lon = location.coordinate.longitude

        Alamofire.request("http://api.openweathermap.org/data/2.5/weather?lat=\(lat)&lon=\(lon)&appid=\(apiKey)&units=metric").responseJSON {

            response in

            self.activityIndicator.stopAnimating()
            if let responseStr = response.result.value {
                let jsonResponse = JSON(responseStr)
                let jsonWeather = jsonResponse["weather"].array![0]
                let jsonTemp = jsonResponse["main"]
                let iconName = jsonWeather["icon"].stringValue

                self.locationLabel.text = jsonResponse["name"].stringValue
                self.conditionImageView.image = UIImage(named: iconName)
                self.conditionLabel.text = jsonWeather["main"].stringValue
                self.temperatureLabel.text = "\(Int(round(jsonTemp["temp"].doubleValue)))"
            }
        }
    }
}

まとめ

今回初めてきちんとAPIから天気を取得して表示することができた。

これで自分好みの天気アプリを作りたい。

天気アプリを作った感じだが、実際には

  • APIの使い方
  • 位置情報の使い方
  • JSONの扱い方

などとその周りも扱えたのがよかった。

参考

今回はこちらの方のものを参考にさせていただきました。

https://www.youtube.com/watch?v=WHRntPeAOo4

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

スプレッドシートを使って、iOS開発の効率を上げる

はじめに

アプリ開発の中でデバッグは時間がかかる作業である。
動的に変わる処理のデバッグはさらに時間がかかる。
AndroidとiOS両プラットフォームで展開しているサービスであるとさらに時間がかかる。
その解決手段としてスプレッドシートを使った方法を紹介します

なぜスプレッドシートを使うのか

  • 誰でも容易に変更、閲覧ができる
  • 簡単なAPIとして扱える
  • プラットホーム関係なしに扱える

実践してみる

準備

  • シートを作成
  • ファイル → ウェブに公開を選択

Screenshot 2020-01-08 at 12.15.17.png

  • 公開範囲を指定する Screenshot 2020-01-08 at 12.19.27.png

文言の管理をまとめる xvrh/localize-with-spreadsheet

  • スプレッドシートで定義した文言をStringファイルに変換するツール
  • Android、iOSの文言の共通管理できる

スプレッドシートの値をアプリで呼び出す t-osawa-009/SpreadsheetClient

  • スプレッドシートの値をアプリで呼び出すためのライブラリを自作した物を活用します。

カスタムスキームの定義とデバッグ

  • スプレッドシートをカスタムスキームのドキュメントとして使える
  • 定義したURLをアプリで取得し、UniversalLink, カスタムスキームを処理するメソッドを呼び出すことでデバッグを簡単に行える
  • シートを更新することでUniversalLink, カスタムスキームのパラメータも動的に変更可能

Screenshot 2020-01-07 at 10.51.52.png

デバッグ用のアカウント管理

  • アカウント情報のAPIとして活用できる

Screenshot 2020-01-07 at 11.16.28.png

注意点

  • スプレッドシートをURLを知っていると誰でも情報取得可能。公開されても問題ないもののみ扱う方がいいと思われる

まとめ

  • スプレッドシートを使うことで簡単に開発効率を上げられる
  • ドキュメントのデータとアプリで実行するデータを一致した値で扱える

環境

Xcode 11.3
Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
Target: x86_64-apple-darwin19.0.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS&Firebase 初めの一歩:iOSアプリのプロジェクトでFirebaseを使ってみる

対象読者

iOSアプリの作成経験はあるが、Firebaseはまだ使った事がない。
ざっくりとした使用感を掴みたい人。
実プロジェクトで使いそうな要素のみを抽出したサンプルアプリを作成してみる。

準備

まずはFirebaseにプロジェクトを登録し設定する。

プロジェクト作成

  1. 開発用のGoogleアカウントがあれば(無ければ個人のものでも良い)それでFirebase Consoleにログインする。
    Firebaseコンソール

  2. プロジェクトを作成(プラスマークのタイル)から新規にプロジェクトを作成する。
    ちなみにプロジェクトは異なるクライアント環境ごとに作る必要はない
    モバイルアプリで言えばサーバーサイドの環境に当たるので、大抵はAndroidでもiOSでも共通だろう。

  3. 以下を入力

    • プロジェクト名:任意のもの。
    • 地域/ロケーション:デフォルトではアメリカ合衆国になっているが、日本でサービスするならばJapanに変更した方
    • アナリティクスの地域:日本(リスト最後の方にある)
    • Cloud Firestoreのロケーション:asia-northeast1 →これは異なるリージョン間通信が発生すると課金対象となるので、予め最もユーザーが多い地域にしておくのが有利。 →またCloud Firestoreを利用しない場合も予め上記に設定しておいた方が良い。この値は後から変更できないからだ。
    • Firebase 向け Google アナリティクスのデータ共有にデフォルトの設定を使用する →基本的にアナリティクスのデータをgoogleに共有するかどうか、という内容。 →特に個人情報を渡すわけではない(あくまでアナリティクスデータのみ)ので、 理由がない限りはチェックしておく。
    • 測定管理者間のデータ保護条例に同意。 →上記アナリティクスの共有に同意した場合、こちらにもチェックを付けないとプロジェクトが作成できないようになっている。
  4. 少しするとプロジェクトが作成され、コンソールが表示される。

アプリの追加

このプロジェクトを利用するクライアント(=アプリ)を追加する。
用意されているのは、iOS・Android・Web・Unity
iOSを選択する。

  1. コンソール上部の「アプリにFirebaseを追加しましよう」の下にある「iOS」アイコンをクリック
  2. iOSアプリにFirebaseを追加ダイアログを入力していく

    1. Xcodeでプロジェクトを作成する。
    2. アプリの登録
    3. バンドルID:Xcodeからコピぺ
    4. ニックネーム:Firebaseコンソール上で識別しやすい名前が良い。アプリ名やiOSが入っていると良いのかな。
    5. AppStore ID:App Store Connectにある。入れておく。
    6. 設定ファイルのダウンロード Firebase接続に必要な情報が書かれた設定ファイルを自動生成してくれるので、ダウンロードしてそのままプロジェクトに追加する。 GoogleService-Info
    7. Firebase SDKの追加 cocoa podsでSDKをインストールする。
      1. Cocoa podsをインストール(既にあればこの手順は不要)
$ cd プロジェクトディレクトリ
$ pod init
  1. Podファイルを編集して保存
$ vi Podfile

target 'FirebaseSample2' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for FirebaseSample2

  pod 'Firebase/Core'       //←この1行を追加
end
  1. インストール実行
    $ pod install

    これでxcworkspaceファイルができるはずなので、そこからプロジェクトを起動し、ビルドしてみる。

  2. 初期化コードの追加
    AppDelegateに以下のコードを追加する。

import Firebase

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // 追記する        
        FirebaseApp.configure()

        return true
    }
  • Firebaseをimport
  • application(application,didfinishLaunchingWithOptions)メソッドに FirebaseApp.configure()(Firebaseの初期化処理)を追記
  1. アプリを実行して確認 初回起動時にSDKからの通信が正しく行われているかをコンソールから確認できるようになっている。 コンソール側でアプリを認識できたらOK

認証機能の追加

ユーザーを識別しないアプリの方が少ないと思うので、大抵は何はともあれ認証しないと始まらない。
Firebaseの認証(Authentication)の方式としては以下が用意されている。
* メール/パスワード
* 電話番号
* Google
* Playゲーム
* Game Center
* Facebook
* Twitter
* GitHub
* 匿名

この中でも多く使われそうなのは、メール/パスワード、SNS、匿名あたりだろうか。
アプリによっては電話番号も良いソリューションになるだろう。
また、自前システムでの認証などにも対応できるカスタム認証という手法も取れる(ここでは扱わない)

準備

準備といっても先ほどの認証方式を設定するだけ。
1. FirebaseコンソールのAuthenticationをクリック
2. ログイン方法タブをクリック
3. 認証方式(ログインプロバイダという)のうち、使うものを有効にする。

なお、FacebookやTiwtterを利用する場合、それぞれで開発者登録したうえでアプリの登録が必要となる。
この辺のことは情報も多いので各自探し、以下ドキュメントも参照してほしい。
https://firebase.google.com/docs/auth/?authuser=0

また、より詳細な情報を得るにはガイドの参照もお勧めする。
https://firebase.google.com/docs/ios/setup

SDKの導入

Firebaseはサービスドメインごとにポッドが別れている。
既に導入しているCoreは必須ライブラリだがそれ以外は個別に指定する必要がある。

認証に関して言えば基本はAuhtなので、以下を追記することになる。

pod 'Firebase/Auth'

また、今回は扱わないがSNSなど複数のフェデレーションを利用する場合などに便利なUI(ViewControllerサブクラス)を自動生成してくれる仕組みも用意されている。その場合には、さらにpod 'FirebaseUIを追記する。

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