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

React Native(Expo)でiOSアプリ「寝過ごし・乗り過ごし防止」をApp Storeに公開した件

はじめに

どんなアプリを作ったの?

「寝過ごし・乗り過ごし防止」アプリ

降車する駅を登録しておくと、
電車などの乗物で近づいたタイミングで教えてくれるアプリ
サラリーマンや電車通勤の学生向け

いらすとや_3.png

つまり?⇒
* 降りる駅を気にしなくて済む!
* 寝ててもOK!漫画やスマホに熱中しててもOK!
* 音楽でアナウンスが聞こえなくてもOK!

いらすとや_2.png

アプリの使い方

  1. 自宅や会社・学校の最寄り駅を検索して駅を登録
  2. 登録した駅に一定距離に近づくと通知してくれる
  3. 必要に応じて通知時間を設定

   

本記事の目的

宣伝目的だよ。 備忘録やExpoを使用してみた感想など。
これから使ってみたいって人に興味を持っていただけたら嬉しい。
ある程度ダウンロードされたらどれくらい需要があるのかも書きたいと思います。

話すこと

  • 開発当初から申請が通るまでの手順
  • Expo(React-native)でどんなライブラリを使ったか
  • どんな機能を使ったか
  • 連携した外部機能
  • 苦労したこと
  • したいけどできなかったこと
  • これからしたいこと
  • 収入・ダウンロード数 ※いつかは掲載します

話さないこと

  • Expoなどの具体的な使い方
  • App Storeの申請方法

開発当初から申請が通るまでの手順

  • 持ち物
    • imac
    • iphone6S (Expo Clientをインストール)
  • 環境・ツール
    • Expo Snack
      • 最初のプログラム開発。簡単な実装はこれで確認したほうが楽。
    • vscode
      • 「Expo Snack」から途中で交代。中盤~終盤はこちらで実装。
    • Github(smartGitで管理)
      • ソースコード管理といったらコレでしょ。
    • xcode(Application loader)
      • これがないとApp Storeにビルドファイルをアップロードできないのです。
    • iphone simulator
      • App Store公開時にipadサイズ、iphoneXRサイズのスクリーンショットが必要。
    • (iphone:MandalArtアプリ)
      • アプリのアイデアだし、メモなどに。
  1. Expoに登録してExpo Snackで画面周りや簡易機能などをゴリゴリに実装(2019/03~04)
  2. 簡単に実機・Emu接続ができるので検査 ※バグがあったら「1」に戻る(2019/03~04)
  3. ある程度できたらvscodeでビルド(2019/04~06)
  4. 「3」でビルドしたipaファイルをxcodeでアップロード(2019/05~06)
  5. App Storeに申請 ※リジェクト(申請却下)されたら「3」に戻る(2019/05~06)
  6. 目出度く公開完了(2019/06/20)

開発時にどんなライブラリを使ったか

ええい、package.jsonを見たほうがはやい!

package.json
"axios": "0.19.0",
"ex-react-native-i18n": "0.0.5",
"expo": "^33.0.0",
"expo-ads-admob": "^5.0.1",
"expo-localization": "^5.0.1",
"expo-location": "^5.0.1",
"expo-permissions": "^5.0.1",
"expo-task-manager": "^5.0.1",
"react": "16.8.3",
"react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
"react-native-app-link": "1.0.0",
"react-native-elements": "1.1.0",
"react-native-maps": "^0.24.2",
"react-native-modal-datetime-picker": "7.4.2",
"react-native-swipeout": "2.3.6",
"react-native-vector-icons": "6.5.0",
"react-navigation": "3.11.0",
"react-redux": "7.0.3",
"redux": "^4.0.0-0",
"sentry-expo": "^1.13.0"

どんな機能を使ったか

  • axios
    • API実行
  • ex-react-native-i18n
    • メッセージ、名称などを簡易に多言語化
  • expo
    • Expoのメイン機能
  • expo-ads-admob
    • google-admobの広告
  • expo-location
  • expo-task-manager
    • 位置情報取得・geolocation機能
  • expo-permissions
    • iphoneの通知・位置情報取得権限
  • react-native-app-link
    • 無料版アプリから有料版アプリへのリンク飛ばし
  • react-native-elements
    • ヘッダーなどExpoの素材でないもの系
  • react-native-maps
    • 地図、本アプリではGoogle Mapを使用
  • react-native-modal-datetime-picker
    • 時刻picker
  • react-native-swipeout
    • リストを横スワイプ(削除)できる
  • react-native-vector-icons
    • アイコン系、豊富で見てみると面白い
  • react-navigation
    • 画面遷移
  • react-redux
  • redux
    • reduxによるステータスの保存。ただしアプリ履歴を消したら、ステータスも消えてしまうので、AsyncStorageにも設定していた。
  • sentry-expo
    • バグ検出用

連携した外部機能

  • Google API ※一定以上で有料
    • Geolocationや位置情報を取得時に使用
    • 駅名検索で使用(place API)
  • google-Admob
    • 広告。Expoだとgoogle-Admobの広告機能が簡単に実装できます。
  • sentry-expo
    • sentryというExpoで管理できない発生したエラーもこちらで出力、感知してくれます。終盤のAppleリジェクト時にお世話になりました。

苦労したこと

  • Apple様によるリジェクト地獄。合計20回ほど。
    • Expoでは動く、自分の実機でも動く。でもApple様は動かない。
    • Emulatorで確認⇒実機で確認⇒sentryで確認しました。
      • こっちでは動くのに、こっちでは動かないってことが多々あった。
    • App Store ConnectよりdSYMでcrash-reportを確認してみる。
      • Google Mapの地図を使う場合は『app.json』に"config":{"googleMapsApiKey":"XXXXXXXXXXXXX"}を記載する必要がある。
    • その他の指摘
      • 通知(notofication)を使用する場合は「Permissions.getAsync( Permissions.NOTIFICATIONS」を使ってね
      • 位置情報取得を使用する場合は「Permissions.askAsync(Permissions.LOCATION)」を使ってね
      • 位置情報取得を使用する場合は『app.json』に"infoPlist": {"NSLocationWhenInUseUsageDescription":"AAA","UIBackgroundModes":["location"]}を記載する必要がある
    • テンプレのような指摘内容、そして意味不明なクラッシュレポート
    • 英語
      • Google翻訳
  • Expo Clietで途中からgeolocationがテストできなくなった。(実機では動くから心配しないで)
  • iMacのバージョンが古くて、xcodeが最新じゃない
    • ビルドしたIPAファイルを実機に入れることができなかった。 それでも申請は通った。
  • デザイン
    • デザインは色々な人にみてもらった。最初は明るめな色にしていたが、暗めがいい、類似色に統一したほうがいいと意見をたくさんもらった。
    • 奥が深い...(ちなみに、アプリアイコンはデザイナーに作ってもらいました)

いらすとや_4.png

したいけどできなかったこと

  • 無料版と有料版は分けたくなかった(アプリ内課金を使いたかった)
    • デタッチを使えばできるんだけど、xcodeを使っての管理になるのが嫌だったので止めた
  • バックグラウンドでの通知時にバイブレーションや音声機能を使用
    • 無理だった。バックグラウンドではアプリの機能の一部が使えなくなった。

これからしたいこと

  • Android対応
    • Expoってマルチプラットフォーム開発が強みなんです。だからAndoridもすぐビルドはできたのですが、iosとは違ってできること、できないことがあります。
  • 海外の言語対応
    • App Storeの申請では国ごとに説明を変えることができる。まずはアメリカなどの英語圏を網羅して分母を増やしていきたい。
  • デザイン、要望の対応
    • 画面の機能以外に構想はしていたが、蛇足な気がしたので排除した。しかし、今後も色々と要望は出てくると思うので多数の意見は組んでいきたい。
  • マーケティング
    • まずは無料版が売れたらいいなぁと感じています。そのために、SNSやアプリのレビューサイトなどを色々駆使して世に知らしめていきたいと思っています。

終わり

Expoはクラウドの開発環境なのですが、PCのスペックが低かったこともありとても使いやすかったです。実機と簡単に接続ができるところもGoo!
会社の業務でreactを触っていたので、開発しやすかったのはありますが、初めてのアプリ開発はけっこう楽しかったです。

いらすとや_6.png

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

iOS13ではStoryboardでもDIができる件について

はじめに

これまでのiOS13未満のUIKitのAPIでは、Storyboardで定義したUIViewControllerではinitializerでのDI(Dependency Injection|依存性の注入)ができないという課題がありました。

※initializerでのDIはコンストラクタインジェクションとも呼ばれますが、Swiftでは慣習的にコンストラクタという用語はあまり使わないので、本記事では「initializerでのDI」とします。

UIViewControllerの実装方法とinitializerでのDIの可否をまとめると以下のようになります。
※黒魔術的なライブラリなどを利用しない前提

レイアウト実装方法 initializer DI可否(〜iOS12) initializer DI可否(iOS13〜)
コードベース
XIB
Storyboard ×

iOS13未満での実装方法の詳細については以下の記事で説明しています。
Qiita - iOSとコードベースレイアウト

勿論、プロパティインジェクションならばiOS12以下でもStoryboardで定義したUIViewControllerで可能ですが、initializerでのDIをする設計にすることで以下の恩恵が得られます。

  • DIする値をプロパティで保持する場合に、varでなくletで定義できるのでより安全
  • DIする値をプロパティで保持する場合に、Optional型でなく非Optional型で定義できるので無駄なUnwrapが不要
  • UIViewControllerの生成を行うために、必ずDIが必要になるので、プロパティインジェクションのように設定忘れが起こり得ない

iOS13でStoryboardでイニシャライザでDIする

iOS13では地味にこんなAPIが加わっています。

instantiateInitialViewController(creator:)

func instantiateInitialViewController<ViewController>(creator: ((NSCoder) -> ViewController?)? = nil) 
-> ViewController? where ViewController : UIViewController

このAPIでは、createrというクロージャーを引数として渡すことで、これまで隠蔽されていたStoryboardからのUIViewControllerの生成過程に介入することができます。

以下のようにUIViewControllerの継承クラスで以下のようにNSCoderを引数にとるinitializerを実装します。

class ViewController: UIViewController {
    @IBOutlet fileprivate weak var textView: UITextView!

    private let dependency: Int

    // DI用のinitializer
    init?(coder: NSCoder, dependency: Int) {
        self.dependency = dependency
        super.init(coder: coder)
    }

    // 独自のinitializerを実装するときにrequiedとして実装が要求されるinitializer
    // 利用しないので、fatalError()として実装を省略する 
    required init?(coder: NSCoder) {
        fatalError()
    }
}

上記のように定義したinitializerをcreaterのクロージャーで利用します。

let storyboard = UIStoryboard(name: "ViewController", bundle: nil)
let viewController = storyboard.instantiateInitialViewController { coder in
    ViewController(coder: coder, dependency: 10)
}

以上のようにすることで、Storyboardで定義したUIViewControllerでもinitializerでのDIが可能です。

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

【iOS】 Cloud Functions for Firebaseを使ってアプリにサーバ日時取得処理を追加する

はじめに

ライセンスの期限チェックやゲームの時間チート対策など、端末の日時ではなくサーバの日時が欲しいケースがあります。
Google Firebaseの1機能であるCloud Functions for Firebaseを使うことで、容易にサーバ日時取得処理をアプリに追加できます。

Cloud Functions for Firebaseとは

アプリなどの環境から、Googleクラウド上にデプロイしているバックエンドコードを実行することができる機能です。
毎月200万回、処理時間100万秒、トラフィック容量5GBまでであれば無料で利用できます。2019/7/10現在

前提条件

この記事を利用するにあたり、次の状態を前提とします。
- 処理を追加するXcodeプロジェクトが存在する
- Google Firebase上にプロジェクトを作成し、Xcodeプロジェクトに導入している
- XcodeプロジェクトにCocoaPodsからFirebaseを導入している
- Xcodeプロジェクトで、Firebaseの初期化処理を実装している

CocoaPodsを使っていない場合など環境が異なる場合は、記事の該当部分について適宜対応してください。

環境の構築

上の項目から順番にインストールしていきます。
すでにインストール済みの場合、スキップして次の項目に移ってください。

Node.jsとnpmのインストール

Node.js公式サイトにアクセスし、インストーラーをダウンロードしインストールします。
インストール後ターミナルで次のコマンドを入力し、バージョン情報を得られたらインストール完了です。

$ node --version
$ npm --version

Firebase CLIのインストール

ターミナルで次のコマンドを入力し、Firebase CLIをインストールします。

$ npm install -g firebase-tools

バックエンド側の実装

プロジェクトの作成

管理しやすい場所(Xcodeプロジェクトフォルダの直下など)に適当なフォルダを作成します。(以降バックエンドフォルダ)

$ mkdir FirebaseFunctions

ターミナルで作成したフォルダに移動し、次のコマンドを入力します。

$ firebase init

実行するといくつか選択項目が表示されるので、次の値を選択します。
- 利用する機能:Functions: Configure and deploy Cloud Functions
- デフォルトのFirebaseプロジェクト:作成したFirebaseプロジェクトを選択します
- 言語:JavaScript
- ESLintを利用するか:好みで選択します。単純な機能を実装するため、Nで問題ありません
- 今すぐ依存関係をインストールするか:Y
以上の手順でバックエンドフォルダ直下にファイル一式が作成されます。

コードの記述

サーバ日時を取得するためのコードを記述します。
バックエンドフォルダ/functions/index.js に次のコードを追加します。

バックエンドフォルダ/functions/index.js
exports.ServerTime = functions.https.onRequest((request, response) => {
    var dt = new Date();
    var delta = dt.getTime();
    response.send({
        data: String(delta)
    });
});

コードのデプロイ

ターミナルで次のコマンドを入力し、ログインします。

$ firebase login

ログインしている状態で次のコマンドを入力し、デプロイを実行します。

$ firebase deploy

Deploy complete!と表示されたらデプロイ完了です。
ターミナルに出力されたProject ConsoleのURLを開き、Functionsからデプロイした関数を確認できます。

XCodeプロジェクト側の実装

SDKのインストール

CocoaPodsからFirebaseFunctionsをインストールします。
podfileに次の行を追記します。

pod 'Firebase/Functions'

追記後ターミナルで次のコマンドを入力し、インストールを実行します。

$ pod install

サーバ日時取得処理の実装

この記事ではサンプルとして、アプリ起動時にサーバ日時を取得しログ出力するよう実装します。
ViewController.swiftに次のコードを記述します。

ViewController.swift
import FirebaseFunctions

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let functions = Functions.functions()
        functions.httpsCallable("ServerTime").call { result, error in
            if let error = error {
                debugPrint(error.localizedDescription)
            } else {
                if let data = result?.data as? String {
                    let ms = UInt64(data) ?? 0
                    let timeInterval = TimeInterval(ms) * 0.001
                    let date = Date(timeIntervalSince1970: timeInterval)
                    debugPrint("サーバ日時: \(date.description(with: Locale.current))")
                } else {
                    debugPrint("受信データなし")
                }
            }
        }
    }
}

実行し、次のようなログが出力されれば成功です。

"サーバ日時: Wednesday, July 10, 2019 21:02:43 Japan Standard Time"

メモ

サーバ日時はミリ秒で得られる為、Date型に変換する際は0.001を掛けて秒に変換する必要があります。

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

iOS 13 の Core NFC で運転免許証を読み取ろう【TRETJapanNFCReader】

WWDC19 にて発表された iOS 13 の Core NFC にて、これまでの NDEF タグの読み書きの他に FeliCa を含む NFC の読み書きができるようになりました。

そこで私は日本の NFC、FeliCa カードの読み取りを行うためのライブラリとして、treastrain/TRETJapanNFCReader を開発しています。

開発途中のため、まだまだできることは少ないですが、少しでも iOS 13 における NFC の利用方法について知ってもらいたいと思い、この記事では運転免許証を読み取るサンプルを紹介させていただきたいと思います。

なお、記事中で紹介するコードは treastrain/TRETJapanNFCReader 0.0.5 の Examples にある DriversLicenseReader にすべて含まれています。

TRETJapanNFCReader

treastrain/TRETJapanNFCReader: 日本の NFC、FeliCa カード向けリーダーライブラリ(iOS 13.0 以降) - GitHub

  • Suica、PASMOなどの交通系ICカード、運転免許証の読み取り
  • 日本語・英語に対応

様々な方から いいね、Star をいただいており、とてもうれしく思っています!

環境

  • Xcode 11 beta 3
  • NFC の読み取りが可能な iOS 13.0 端末(iOS 13.0 beta 3 の iPhone X で動作確認しています)
  • 運転免許証(いわゆる ICカード免許証、NFC-B (Type-B))
  • treastrain/TRETJapanNFCReader 0.0.5

残念ながら SwiftUI ではありません……。

GitHub からダウンロード

まずは GitHub からコード一式を持ってきてください。
Releases から Tag 0.0.5 をダウンロードすることをおすすめします。

GitHub の Examples にあるサンプルは Capabilities や Info.plist の設定が既に済んでいます。README をご確認ください。

ライブラリのインポート

以降、DriversLicenseReaderディレクトリ内を操作します。
すでにサンプルのための ViewController が作成されていますが、ここでは新しく ViewController.swift を作成するものとして記事を進めます。
まずはライブラリのインポートから。

Examples/DriversLicenseReader/ViewController.swift
import UIKit
import CoreNFC
import TRETJapanNFCReader

初期化とデリゲートの継承

運転免許証の読み取りを司る DriversLicenseReader を初期化します。
DriversLicenseReader は初期化の際に DriversLicenseReaderSessionDelegateDriversLicenseReaderSessionDelegate を継承した UIViewControllerDriversLicenseReaderViewController を要求します。

TRETJapanNFCReader/DriversLicense/DriversLicenseReader.swift
public typealias DriversLicenseReaderViewController = UIViewController & DriversLicenseReaderSessionDelegate

DriversLicenseReader(viewController: ) で初期化すると、エラーが発生した場合にライブラリ内で UIAlertController を作って viewController.present します。エラーのときの処理をすべて自前で行う場合は DriversLicenseReader(delegate: ) で初期化してください。

Examples/DriversLicenseReader/ViewController.swift
import UIKit
import CoreNFC
import TRETJapanNFCReader

class ViewController: UIViewController, DriversLicenseReaderSessionDelegate {

    var reader: DriversLicenseReader!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.reader = DriversLicenseReader(viewController: self)
    }
}

DriversLicenseReader.get(items: ) を呼ぶ

DriversLicenseReader の初期化が済んだら、あとは DriversLicenseReader.get(items: ) を呼ぶだけです。引数の items[DriversLicenseCardItem] を要求します。DriversLicenseCardItem は、記事執筆時点で以下のとおりです。

TRETJapanNFCReader/DriversLicense/DriversLicenseCard.swift
/// 日本の運転免許証から読み取ることができるデータの種別
public enum DriversLicenseCardItem: CaseIterable {
    /// MF/EF01 共通データ要素
    case commonData
    /// MF/EF02 暗証番号(PIN)設定
    case pinSetting
    /// DF1/EF01 記載事項(本籍除く)
    case matters
}

ここで気をつけなければいけないのは、ICカード免許証の仕様で 共通データ要素暗証番号(PIN)設定 以外のデータの読み取りには、ICカード免許証の発行時に各自で設定した暗証番号が必要となることです。
ICカード運転免許証には暗証番号1と暗証番号2の2つがあり、本籍 を含むデータを読み取る場合は暗証番号1と暗証番号2の両方、それ以外のデータの場合は暗証番号1が必要となっています。
また、この暗証番号は3回間違えるとロックされ、以降は警察署などでロックを解除してもらわなければ、読み取りの際に暗証番号が必要なデータを読み取ることができなくなってしまいます。

よって、itemscommonDatapinSetting を指定した場合は問題ありませんが、matters が含まれている場合(DriversLicenseCardItem.allCasesとした場合なども同様)は暗証番号1も渡す必要があります。

self.reader.get(items: [.commonData, .pinSetting])

self.reader.get(items: [.matters], pin1: "暗証番号1")

一度 self.reader.get(items: [.pinSetting]) で ICカード免許証を読み取り、暗証番号が設定されているかどうかを確認することもできます。

読み取った結果を確認する

DriversLicenseReader.get(items: ) を呼んだ後は iOS 端末が NFC の読み取りモードに入ります。iPhone の上部(受話部)を ICカード免許証の下半分を隠すように置いて、読み取ります。
読み取りが正しく成功すると、DriversLicenseReaderSessionDelegate により driversLicenseReaderSession(didRead driversLicenseCard: DriversLicenseCard) が呼ばれます。

Examples/DriversLicenseReader/ViewController.swift
    func driversLicenseReaderSession(didRead driversLicenseCard: DriversLicenseCard) {
        // driversLicenseCard に読み取った運転免許証の情報が格納されている
    }
}

driversLicenseCard に読み取った情報が入っているので、確認してみましょう。DriversLicenseCardTRETJapanNFCReader/DriversLicense/DriversLicenseCard.swift で定義されています。

終わりに

以上の手順で簡単に運転免許証のデータを読み取ることができたかと思います。
例えば、いわゆる IDセルフィー と運転免許証の NFC 読み取りを組み合わせれば本人確認の手段として有効に活用できるのではないかなと思います。

もしよろしければ GitHub のほうに Star していただけるとありがたいです!また、指摘や意見等もお待ちしています。

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

[WWDC19] Xcode11新機能まとめ

はじめに

WWDC19 報告会 at Yahoo! JAPANで発表したXcode11の新機能について文章でまとめた記事です。
NDAや著作権に配慮してスクリーンショットなどは載せておりません。

エディタ

エディタUIの刷新

  • コミット履歴が一番右のインスペクタに統合
    • 真ん中の時計アイコン(Source Controll History表示)
  • AssistantとAuthorsエディタをStandardエディタに統合
    • Editor Optionから表示モードが選択できるように
      • Editor Only(エディタのみ)
      • Editor and Canvas(SwiftUIのプレビュー用)
      • Editor and Assistant(従来のアシスタントエディタ)
  • Authorの表示
    • Editor Option > Show Authors
  • エディタを縦横に追加可能
    • 縦に追加する場合はoptionを押しながら
  • 個々のエディタで個別の表示設定が可能

    • エディタを縦横に分割可能
  • 縦に追加する場合はoptionを押しながら

  • フォーカスモードで一つのエディタを一時的に拡大

SwiftUIのサポート

  • プロジェクト作成時にUse SwiftUIにチェック
  • プレビュー表示
    • Editor Option > Editor and Canvas

Mini Map | ミニマップ

  • ミニマップの表示
    • Editor Option > Show Minimap
  • すばやく任意のセクションにジャンプ
  • ホバーで関数名などを表示
  • find箇所のハイライト

inline code diffs | インラインコード差分

  • change barをクリックしてShow Changeをクリック
  • マウスホバーで差分を強調表示

ドキュメンテーション機能の強化

  • パラメータを追加した時に差分だけのコメントを追加

    • command + クリック > Add Documentation
  • typoの修正もドキュメンテーションと同期

    • command + クリック > Edit All in Scope

Spell Checking | スペルチェック

  • スペルチェック機能がサポートされた
  • 現在開いているファイルのチェック
    • Edit > Format > Spelling and Grammer > Check Document Now
    • command + ;
  • タイピング時に自動でチェック
    • Edit > Format > Spelling and Grammer > Check Spelling While Typing
  • 赤線部分を右クリック
    • スペル候補が表示される
    • Ignore Spelling(スペルを無視)
    • Learn Spelling(スペルを学習)

cherry-pick

  • git cherry-pickコマンドがGUIで操作可能に
  • 別ブランチでのコミットを反映できる機能

stash

  • git stashコマンドがGUIで操作可能に
  • commitしていない変更を一時退避できる機能
  • メッセージも任意で追加可能

source control file inspector

  • インスペクタから現在のブランチでの履歴を見られる
  • StoryboardやXIBも含め、全ての種類のファイルで閲覧できる

パッケージ管理

Swift Package Manager

  • Swift Package Managerの機能がXcodeに統合
  • GitHub、Bitbucket、GitLab、または自分のホストで公開されているSwiftパッケージを利用できる
  • 依存関係分析に基づいてパッケージを自動的に取得
  • Swiftパッケージの作成方法(参考:Creating Swift Packages
  • Swiftパッケージの導入方法(参考:Adopting Swift Packages in Xcode

XCFramework

  • ライブラリ、フレームワークの新しいバイナリ配布形式(.xcframework)
  • SwiftとC-basedコードをサポート
  • 1つのフレームワークで複数のプラットフォームで動かせる
    • 複数のフレームワークがアーキテクチャ毎のディレクトリに含まれる構成
  • xcframeworkを作成
    • ビルド設定のBuild Options > Build Libraries for Destiribution をYesにする
xcodebuild archive -sheme XXX -destination "xxx" -destination "xxx" SKIP_INSTALL=NO
xcodebuild -create-xcframework -framework [path] -framework [path] -output XXX.xcframework

デバッグ・テスト

Device Conditions

  • デバイスの発熱状態、 通信状態をシミュレートできる機能
  • Window > Device and Simurators > DEVICE CONDITIONS
  • Thermal State (発熱状態のシミュレート)
    • 選択候補
      • Fair
      • Serious
      • Critical
    • 本当に熱くなるわけではない
  • Network Link(通信状態)
    • 選択候補
      • 100% packet loss
      • Very poor network
      • Edge Network - poor
      • Edge Network - average
      • Edge Network - good
      • Edge Network - best
      • 2G Network - poor
      • 2G Network - better
      • 3G Network - average
      • 3G Network - good
      • 3G Network - best
      • LTE Network
      • WiFi Network
      • WiFi Network(802.11ac)
      • DSL Network
      • High Latency DNS

Environment Overrides

  • デバイスの表示設定を実行時にリアルタイムで変更できる機能
  • Interface Style(ダークモードかどうか)
  • Dynamic Type(ダイナミックタイプ)
  • Accesibility Options(アクセシビリティでの設定)
    • Increse Contrast(コントラストを上げる)
    • Bold Text(文字を太くする)
    • Reduce Transparency(透明度を下げる)
    • Reduce Motion(示唆効果を減らす)
    • On/Off Labels(オン/オフラベル)
    • Button Shapes(ボタンの形)
    • Grayscale(グレイスケール)
    • Smart Invert(色を反転)
    • Differentiate Without Color(色なしで区別)

XCTest frameworkの新しいAPI

XCTUnwrap

  • nilが入ったらassert(=XCTAssertNotNill)
  • nilでない場合はUnwrapして値が取り出せる
  • nilの場合throwされるので、テストケースの関数をthrowsにする
func testFirstNameNotEmpty() throws {
    let forenames: [String] = customer.forenames

    let firstName =  try XCTUnwrap(forenames.first)
    XCTAssertFalse(firstName.isEmpty)
}

measure(metrics:options:block:)

func testExample() {
    // Measures the CPU and memory impact of sorting the input list.
    measure(metrics: [XCTCPUMetric(), XCTMemoryMetric()]) {
        sortedList = qsort(list: self.fiftyNumbersFrom0to100)
    }
}

Test Plan

  • 異なる設定毎にでテストを複数回実行できる機能
    • ローカリゼーション(言語、地域、位置情報)
    • アルファベット順、ランダム順
    • サニタイザー(Address、Thread、Undefined Behavior)
    • メインスレッドチェッカー
    • 引数もしくは環境変数
    • Memory diagnostics(MallcStackLogging、NSZombies)
    • スクリーンショットの撮影
  • 一箇所にテスト変数を定義し、複数のスキーム間でシェア可能
    • Test Planファイル(拡張子は.xctestplan)を作成(中身はJSON形式)
      • Testsタブ
        • テストケースの有効/無効のチェック
        • 並列実行するか
        • 新しいテストケースを自動で追加するか
      • Configurationsタブ
        • Shared Settings(すべてのテストで共通するデフォルト設定、個々のConfigurationで上書きも可能)
        • Configuration(Configurationを作成した分だけテストが実行される)
      • Option + クリックで選択したConfigurationのテストを1回だけ実行することも可能
        • テストナビゲーターではcontroll + クリックで同様のことが可能
    • 既存のTestスキームをTest Planをコンバートすることも可能
# -testPlanオプションを複数つけることで、Configurationを複数指定
xcodebuild test -project ... -scheme ... -testPlan 'Configuration1' -testPlan 'Configuration2'

Result Bundle

  • ビルドとテストの結果を含む新しいファイル形式(拡張子は.xcresult)
    • ビルドログ
    • テストレポート
    • コードカバレッジレポート
    • テストアタッチメント
  • 特徴
    • 従来のフォーマットに比べて容量が小さい(4倍小さい)
    • Xcodeで内容を閲覧可能
    • プログラムでアクセス可能なコンテンツ
    • xcresulttoolでアクセスする
# Result Bundleの書き出しパス指定
xcodebuild test ... -resultBundlePath /path/ResultBundle.xcresult

# 結果をJSONで取得
xcrun xcresulttool get --path ResultBundle.xcresult --format json

# マークダウン形式
xcrun xcresulttool formatDescription get

# コードカバレッジ
xcrun xccov view --report ResultBundle.xcresult

# コードカバレッジレポートの比較
xcrun xccov diff --json Before.xcresult After.xcresult

その他の改善

Signing and Distribution | 署名と配布

  • Signing & Capabilitiesタブにまとめられた
  • Configuration毎の設定が可能
  • 新しい証明書タイプ
    • Apple Development、Apple Distribution
    • すべてのプラットフォームをサポートする
    • Xcode11以降で利用可能
    • 既存の証明書も利用可能だが、旧バージョンのXcodeでは新しい証明書を利用できない

Asset Catalogの新機能

  • ローカライズのサポート
  • ダークモードのサポート
    • Darkアピアランスの設定(Any、Light、Dark)
  • SF Symbols
    • Symbol Configurationからサイズと太さのバリエーションを設定
  • 切り取り、コピー、貼り付けが可能に

Metrics Organizer

  • Window > Organizer > Metrics
  • リリース済みアプリのメトリクスデータを収集する機能
    • Battery(バッテリー消費)
    • Launch Time(起動時間)
    • Hang Rate(ハング率|レスポンス)
    • Memory(メモリ消費)
    • Disk Writes(ディスク書き込み)
  • ユーザーの許可設定が必要(設定 > プライバシー > 解析 > Appデベロッパと共有)

シミュレータ

  • Metalサポート
  • 60FPSで滑らかに描画
  • CPUの利用は90%減
  • Warm bootが二倍早く
  • シミュレータでもWatch単体で起動できるように

参考資料

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

macOS 10.14でiTunes 12.6.5を使う

macOS 10.14 Mojaveでは、Appの閲覧ができるiTunes12.6系の使用ができなくなりました。

これはWWDC2019でもあったような、iTunesアプリ解体のための方針のためですが、macOSでもAppStoreが閲覧したかったり、端末のアプリ管理がしたいケースはある気がします。
というか自分がしたい派なので入れてみました。

SIPを無効化

まずSIPを無効化します。

Mac OS X El CapiptanでSIPを無効化する - Qiita

  1. Macを再起動
  2. リンゴマーク前にcommand+Rを押しっぱなし
  3. リカバリモードでユーティリティ→ターミナルでターミナル起動
  4. ターミナルにcsrutil disableを入力
  5. reboot入力などで再起動.

これでSIPはオフになりました。
作業後に必要に応じてcsrutil enableします。

iTunes12.6.5をダウンロード

まだ、公式サイトにダウンロードリンクが存在するため、こちらからダウンロードしましょう。

企業環境で iTunes を使って App を配布する - Apple サポート

中身を出してinstall script書き換えてインストール

ここら辺はPacifist(CharlesSoft)を使う方法と普通にターミナルで書き換える方法があります。

Pacifist

こちらがpacifist使用例。
- macOS MojaveでApp Storeが使えるiTunes 12.6.5.3にダウングレードして使用する方法 | Will feel Tips
バージョンチェックが追加されたのか、pacifistだとうまくいかなかったのでもう一つの方法をやりました。

スクリプト

こちらで変更方法が共有されていました。
Apple's Special Version of iTunes That Still Has an App Store Currently Incompatible With macOS Mojave | Page 8 | MacRumors Forums
この回答内容だと、AppleScriptを作成して、それを実行することで書き換えられますが、sedで書き換えてるだけなので、普通にterminalで実行もできます。

# 元のiTunes削除
$ sudo rm -rf /Applications/iTunes.app

# ダウンロードしたdmgをマウントしてpkg展開
$ hdiutil mount ~/Downloads/iTunes12.6.5.dmg
$ pkgutil --expand /Volumes/iTunes/Install\ iTunes.pkg ~/tmp

# インストールスクリプトを書き換え
$ sed -i '' 's/18A1/14F2511/g' ~/tmp/Distribution
$ sed -i '' 's/gt/lt/g' ~/tmp/Distribution

# リパッケージしてアンマウント、tmp削除
$ pkgutil --flatten ~/tmp ~/Desktop/iTunes.pkg
$ hdiutil unmount /Volumes/iTunes/
$ rm -rf ~/tmp

# リパッケージしたpkgファイルを実行
$ open ~/Desktop/iTunes.pkg

# インストール後にiTunes.app/Contents/Info.plistのバージョンをごまかします。
$ sudo sed -i '' 's/12.6.5/12.9.4/g' /Applications/iTunes.app/Contents/Info.plist

# 必要に応じてローカルライブラリを削除します。
$ rm ~/Music/iTunes/iTunes\ Library.itl

これで普通にiTunes12.6.5がインストールできました。
Mac AppStoreでもアップデートのお知らせ等は出ないはずです。

必要に応じて:SIPを有効化

SIPはセキュリティ面で有効なので、逆の手順で有効化しておきます。
リカバリモードで、csrutil enableを実行します。

まとめ

最近では、iTunesでiOSのアプリとかを同期管理することも少なくなりました。
なのでこの記事は最後の悪あがきですね。

macOS 10.15ではFinderで管理できるらしいですが、アプリの同期はできないような気もします。。

上の記事のスクリーンショットとかでもアプリは表示されてないですし。。
iOS 13では古いiTunesでの同期も禁止されそうですね。

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

iPhoneでYouTubeAPI(iframe_api)の背景動画が自動再生されないとき

省電力モードになってない?

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

PINRemoteImageのキャッシュに保存された画像をロードする方法

動機

PINRemoteImageManagerでキャッシュした画像をUIActivityViewControllerを使ってシェアしたかった。
そのときにどうやってキャッシュしたUIImageを取得するかわからなかったため。

方法

PINRemoteImageManagerのdownloadImageメソッドを使う
用意されたメソッドを使うだけだが意外と調べても出てこなかったのでメモ。

guard let url = url else { return }
_ = PINRemoteImageManager.shared().downloadImage(with: url) { (result) in
  // resultはPINRemoteImageManagerResult型でimageプロパティから画像が取得できる
  var activityItems: [Any] = [result.image]
}

こそっとリファレンスを見てみる

Summary

Download or retrieve from cache the image found at the url.
All completions are called on an arbitrary callback queue unless called on the main thread and the result is in the memory cache (this is an optimization to allow synchronous results for the UI when an object is cached in memory).

日本語訳

URLで見つかったイメージをダウンロードするか、キャッシュから取得します。
すべての補完は、メイン・スレッドで呼び出され、その結果がmemory cache(これは、オブジェクトがメモリにキャッシュされたときのUIの同期結果を可能にする最適化です。)にない限り、任意のcallbackで呼び出されます。

キャッシュがあればキャッシュから取得、無ければダウンロードしてくれる便利メソッドでした。
Pinterest様様ですね。

Declaration

downloadImageWithURL:(nonnull NSURL *)url
          completion:(nullable PINRemoteImageManagerImageCompletion)completion;```
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む