- 投稿日:2019-12-14T22:12:51+09:00
iOS用OpenVPNの設定ファイルの作り方
iOS版のOpenVPNでもOpenVPNの設定ファイルと秘密鍵などをインポートすると使うことができますが、設定ファイルと秘密鍵など複数のファイルに分かれているとなかなか扱いが面倒だと思います。(iTunesのファイル共有を使ってファイルを転送する必要があるかもしれません。)
OpenVPNの設定ファイルには鍵情報を含めることができますが、PCとiOSによって形式が違うため気をつけないとハマってしまいます。(私のことです…)
備忘録も兼ねて残しておこうと思います。設定のファイル内に鍵情報が混在することになるため扱いにはご注意ください。
TL;DR
OpenVPN.ovpn#通常通り設定する client dev tun #... #鍵ファイルの指定はコメントアウトするか消す #ca ca.crt #cert client.crt #key client.key #tls-auth ta.key 1 #tls-auth使っている場合は下記を追記 key-direction 1 <ca> -----BEGIN CERTIFICATE----- CAの証明書 -----END CERTIFICATE----- </ca> <key> -----BEGIN RSA PRIVATE KEY----- クライアントの秘密鍵 -----END RSA PRIVATE KEY----- </key> <cert> -----BEGIN CERTIFICATE----- クライアントの証明書 -----END CERTIFICATE----- </cert> <tls-auth> # # 2048 bit OpenVPN static key # -----BEGIN OpenVPN Static key V1----- TLSAuth用の鍵 -----END OpenVPN Static key V1----- </tls-auth>設定にファイルを組み込む
ファイルの指定を削除する
通常
ca ca.crt
やkey client.key
などと鍵や証明書のファイルを指定しますがその箇所を削除します。
ただしtls-auth ta.key 1
と指定している場合はkey-direction 1
に置き換えてください。設定ファイルにファイルを追記する
ファイルの指定を削除した代わりに各ファイルを組み込む必要があります。
XMLのようにタグを使い中にファイルの内容を記載します。
ca ca.crt
なら<ca>~</ca>
、
tls-auth ta.key 1
なら<tls-auth>~</tls-auth>
のようなタグを使います。生成スクリプト
事前に組み込み前の設定ファイルと鍵ファイル等を用意しているのであれば下記のスクリプトで変換することができます。
#! /bin/sh if [ ! "$#" = "1" ];then echo Usage: $0 openvpn.conf fi # Original config file confname=$1 # CA cert file caname=`grep -e "^ca" ${confname} |sed 's/.* //g'` # Client cert file certname=`grep -e "^cert" ${confname} |sed 's/.* //g'` # Key file keyname=`grep -e "^key" ${confname} |sed 's/.* //g'` # TLS-Auth key file tlsauthname=`grep -e "^tls-auth" ${confname} |sed 's/tls-auth \(.*\) 1/\1/g'` # New config file ovpnname="${confname%%.*}.ovpn" if [ -f "${ovpnname}" ]; then printf "Overwrite(${ovpnname})?:" read ans if [ ! "${ans}" = "Y" -a ! "${ans}" = "y" ]; then exit fi fi # Load original config file cat ${confname} |grep -v -e "^ca" -e "^cert" -e "^key" | sed 's/^tls-auth .*$/key-direction 1/g' > ${ovpnname} # Append CA cert if [ ! "${caname}" = "" -a -f ${caname} ]; then echo "<ca>" >> ${ovpnname} cat ${caname} >> ${ovpnname} echo "</ca>" >> ${ovpnname} fi # Append client private key if [ ! "${keyname}" = "" -a -f ${keyname} ]; then echo "<key>" >> ${ovpnname} cat ${keyname} >> ${ovpnname} echo "</key>" >> ${ovpnname} fi # Append client cert if [ ! "${certname}" = "" -a -f ${certname} ]; then echo "<cert>" >> ${ovpnname} cat ${certname} >> ${ovpnname} echo "</cert>" >> ${ovpnname} fi # Append TLS Auth key if [ ! "${tlsauthname}" = "" -a -f ${tlsauthname} ]; then echo "<tls-auth>" >> ${ovpnname} cat ${tlsauthname} >> ${ovpnname} echo "</tls-auth>" >> ${ovpnname} fiあとがき
iOSでOpenVPNを設定する際に苦戦したため記事に残しておきました。
この記事で沼から抜け出せる人がいることを願っています…
もし間違いなどがあればご指摘いただければ幸いです。Reference
- 投稿日:2019-12-14T20:49:01+09:00
[Swift] UITextViewに画像を添付する方法
はじめに
GameWith AdventCalendar 2019 の14日目の記事になります。
普段はiOSエンジニアをやっていて、最近はWebの業務を行なっているのでその辺りについても来週のアドベントカレンダーで話したいと思います。
今回の記事では
UITextView
にNSTextAttachment
クラスを用いて画像を添付する方法をまとめています。
入力中の文字のpositionなどを取得して、その上下にUIImageView
を配置するなどといった事をしなくても簡単に実装できるので具体例をまじえて紹介したいと思います。具体例
SNSサービスなどでカメラロールから画像を選択し、テキストと一緒に投稿などを行う場合のUIイメージを想定しています。
カメラロールから画像を取得
シンプルに
UIImagePickerController
を使うケースです。let picker = UIImagePickerController() picker.delegate = self picker.sourceType = .photoLibrary present(picker, animated: true, completion: nil)UIImagePickerControllerDelegateを設定する
extension SampleViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let image = info[.originalImage] as? UIImage { let fullString = NSMutableAttributedString(string: textView.text) let imageWidth = image.size.width // 画像の幅を調整したい場合paddingなどをframeから引く let padding: CGFloat = 16 let scaleFactor = imageWidth / (textView.frame.size.width - padding) let imageAttachment = NSTextAttachment() imageAttachment.image = UIImage(cgImage: image.cgImage!, scale: scaleFactor, orientation: .up) let imageString = NSAttributedString(attachment: imageAttachment) fullString.append(imageString) // TextViewに画像を含んだテキストをセット textView.attributedText = fullString } dismiss(animated: true, completion: nil) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { dismiss(animated: true, completion: nil) } }終わりに
今回利用している
NSTextAttachment
ですが、こちらを使う事でUILabel
にも画像を適用する事ができるので非常に便利なクラスになっています。
今後文字と画像を結合するUIを実装する際には是非使ってみてください!
- 投稿日:2019-12-14T20:03:01+09:00
Flutter でアプリ開発してみる
はじめに
個人的にスマホアプリをいくつか作りリリースしているのですが、作ったアプリを友達などに見せると、必ず言われるのが、
「iPhoneで動かないの?じゃいいや」
でした。
自分の周りでは、iPhoneを使っている率が高い気がしますが、実際のところ Android と iOSの日本でのシェア率は、ほぼ半々ぐらいのようです。そのような状況なので、モバイルアプリをリリースする場合は、Android, iOS の両対応が必須です。
ただ、Android と iOS はまったく別物なので、Native なアプリを作るとなると、工数は倍近く掛かってしまいます。
ということで、クロスプラットフォーム対応のフレームワークがいくつかあります。
たとえば、 Xamarin や PhoneGap、うちの会社なら Qt もその一つだと思います。その中で、最近注目され、実際に使われ始めているものに Flutter があります。
この記事では、その Flutter について紹介致します。Flutter って?
Flutter は、Google が、これまで蓄積してきたモバイルアプリ作成の知見を基に、新たに作ったクラスプラットフォーム対応のフレームワークです。
使用する言語は Dart です。Java に比較的近い言語なので、Java を理解していれば、容易に使えるようになるかと思います。
パフォーマンスは AndroidでもiOSのそれぞれ、Native code にコンパイルして実行するらしく、Nativeアプリに比べても遜色は無いパフォーマンスだと思います。
UI周りでは、マテリアルデザインの UI Widget が揃っているので、マテリアルデザインに沿った アプリを、Android Native で作成するよりも比較的簡単に実現できるかと思います。
Flutter の導入事例はすでに多くあり、Google だと、Google Assistant で使用されています。
https://flutter.dev/showcaseまた、個人的雑感ですが、Android アプリをJavaで開発するのは、そろそろ限界に来ていると思います。これまで、スマホの使われ方の進歩に併せて機能拡張をして来た結果の構造的欠陥があちこちに出てきていると思います。
よって、Android だけにリリースするアプリでも、Flutter で作るのが良いと感じています。開発環境
iOS アプリとして動作させる場合は、最終的に Mac が必要になりますが、開発自体は、Windows, Mac, Linux のどれでも大丈夫です。
開発環境としては、Android Studio か、VS Code を使用します。
https://flutter.dev/docs/get-started/install
に従って進めてゆけば、開発環境の構築は完了します。
もちろん全て無料で開発できます。Flutter の特徴として、HotReload というものがあります。
エミュレータや実機などで開発中のアプリを動作させておくと、ソースコードを書いて保存した瞬間に、その挙動が反映されます。
ビルドやアプリの再インストールが不要なため、結果を見ながらのコーディングが可能です。
特にUI周りの挙動などのコーディング時に役立ちます。入門
公式にサンプルやチュートリアルが豊富に用意されているので、それから始めるのがお勧めです。
またcodelabs を順に実施すると、基本的なところから学習できます。
https://flutter.dev/docs/codelabsFlutter のデメリット?
英語が必須
正式版のリリースから1年経った今でも頻繁に update されるので、公式ドキュメントを参照する場面がよく出てきます、ただ、公式ドキュメントはすべて英語なので、そこが多少の障壁になってしまうかと思います。
Dart のエラーメッセージが不親切
エラーメッセージがちょっと分かりにくい場合があります。
例えば、以下のようなソースだと、void main() { double x; double y = 0.2; double z = x + y; print ("answer = $z"); }実行時に
The method '+' was called on null. Receiver: nullのようなエラーメッセージが表示されます。
これは、x を初期化していないことが原因ですが、ちょっと分かりにくいかな?と思いました。UI Widget の挙動を変えるのが大変
実際のアプリを作成する際、用意された UI Widget を使って実装するわけですが、UI の挙動を変えたい場合など、苦労することがあります。
以前、Tar bar を使ったアプリを作ったのですが、TabView の追加、削除はサポートされていなかったので、それを実現するために、結構泥臭い実装が必要になりました。で、作ってみたアプリ
お試しでアプリを Flutter で作成し、リリースしてみました。
消費税計算機です。
https://play.google.com/store/apps/details?id=com.gmail.kazutoto.works.flutter_consumption_tax
ちなみに、Flutterで作ったので iOSでも動くのですが、Apple税を払うの辞めてしまったので、公式にリリースはしていません。
- 投稿日:2019-12-14T19:33:48+09:00
SwiftUI×HealthKitでiOSデータ入門(後編)
HealthKitデータ取得 & Firebase 編
ここまで SwiftUI のキャチアップをしつつ前編で環境を構築し,中編で HealthKit を利用可能にしました.今回は実際に HealthKit で歩数データを取得します.
HealthKitで歩数データを取得
前回の記事で権限は得られたのでデータの読み出しが可能となっているはずです.次にデータを取得していきます.少しややこしいです.細かいところは順次付け足していくとして,とりいそぎで書き加えたコードだけを載せます.
DataCollection.swift を編集していきます.body より前に以下の変数を定義していきます.
let type = HKObjectType.quantityType(forIdentifier: .stepCount)! // allTypeとは別にtypeを定義 var calendar = Calendar.current // カレンダーを取得このように書きます(追加したのは下2行).
DataCollection.swiftstruct DataCollection: View { // @Stateを使ってUIの状態と同期をとる @State var labelText = "Tap Here" @State var flag = false let healthStore = HKHealthStore() // HealthKitのデータを格納するHealthStoreを定義 // アクセス許可が欲しいデータタイプを指定 let allTypes = Set([HKObjectType.quantityType(forIdentifier: .stepCount)!]) //今回は歩数のみ // 以下を追加 let type = HKObjectType.quantityType(forIdentifier: .stepCount)! // allTypeとは別にtypeを定義 var calendar = Calendar.current // カレンダーを取得さらにボタンがタップされた時の処理を以下のように書き換えます.ここでは「昨日から今日まで」に歩いた歩数を取得します.
DataCollection.swift// ボタンがタップされた時 // もしHealthKitが利用可能なら if HKHealthStore.isHealthDataAvailable() { self.labelText = "HealthKit Available" self.healthStore.requestAuthorization(toShare: nil, read: self.allTypes) { (success, error) in} // 以下を追加 let date = Date() // 今日の日付を取得 let yesterday = self.calendar.date(byAdding: .day, value: -1, to: date) // "昨日"は上で定義したカレンダーを使って計算する // 取得するデータの開始(きのう)と終わり(きょう)を入れる let predicate = HKQuery.predicateForSamples(withStart: yesterday, end: date) var step = 0.0 // 結果を格納するための変数 // クエリを作る let query = HKStatisticsQuery(quantityType: self.type, quantitySamplePredicate: predicate, options:.cumulativeSum){query, result, error in let query_result = result?.sumQuantity() as Any step = (query_result as AnyObject).doubleValue(for: HKUnit.count()) // HKQuantity型をdoubleに変換 print(step) // コンソールに出力 self.labelText = String(step) + "歩" // 文字列に変換してデバイスに表示これで間違いがなければビルドが通り,コンソールに歩数データが表示されるはず.
ここまでで,iPhone からデータを取得することに成功しました!
Firebaseへデータを格納
ここからは,先ほど取得したデータをデータベースへ格納します.他のアプリと連携できるように Firebase の Firestore にデータを格納します.当初 Realm を使う予定でしたがハッカソンの戦略上,Firebase へ変更しました.
Firebase (Firestore)
Firebase の環境構築は公式のインストラクションがわかりやすいのでここでは省略します.「Firebase を iOS プロジェクトに追加する」方法をみながら設定をしてください.この設定が済んでいる前提でデータを送るところをやっていきます.
まずは以下の3つのコード
import Firebase var window: UIWindow? FirebaseApp.configure()をAppDelegate.swift に追記します.以下のように書き変えていきます.
AppDelegate.swiftimport UIKit import Firebase // 追加した行 @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? // 追加した行 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. // Firebase FirebaseApp.configure() // 追加した行 return true }そして DataCollection.swift のボタンがタップされた時の処理を書いた部分に以下の処理を追加します.
DataCollection.swift<略> // クエリを作る let query = HKStatisticsQuery(quantityType: self.type, quantitySamplePredicate: predicate, options:.cumulativeSum){ query, result, error in let query_result = result?.sumQuantity() as Any step = (query_result as AnyObject).doubleValue(for: HKUnit.count()) // HKQuantity型をdoubleに変換 print(step) // コンソールに出力 self.labelText = String(step) + "歩" // デバイスに表示 // ここからが追加したFirebaseの処理 let db = Firestore.firestore() var ref: DocumentReference? = nil ref = db.collection("Health").addDocument(data: [ "歩数": step ]) { err in if let err = err { print("Error adding document: \(err)") } else { print("Document added with ID: \(ref!.documentID)") } } } self.healthStore.execute(query) }else{ <略>これで問題なければ,ボタンを押すたびにデータが FireStore にも格納されているはずです.
実験で作った余分なコレクションも見えていますが,こんな感じでうまくいきました!
まとめ
ここまでSwift×HealthKitでiOSデータ入門の 前編,中編,後編 を通してざっくり
- Swift (SwiftUI) のキャッチアップとして画面遷移とボタンの実装
- HealthKit からのデータの取得
- Firebase へのデータの格納
の3つを行いました.iOS開発どころか Xcode や Swift, HealthKit を触るのも初めてだったので説明不足や理解が不十分な点がまだまだあります.このあたりは今後も引き続きキャッチアップとアップデートを行っていこうと考えています.もし気づいた点などあればコメントなどいただけると大変助かります.
以上,SwiftUI×HealthKitでiOSデータ入門 でした.
- 投稿日:2019-12-14T18:01:34+09:00
Codemagicの設定手順&ハマりそうなところをまとめてみた
はじめに
- ネイティブアプリを開発したことがない方
- Flutterを触っている、もしくは触る予定の方
へ向けた記事になります。
私自身、ネイティブアプリの開発がなく、Flutterに入門しており、リリースの時にあたふたしたため、同じような境遇の方のためになればと思い執筆しました。
Codemagic とは
Flutter専用のマネージド CI / CD サービスです。
登録から最初のビルドまで簡単に出来てしまう大変便利なサービス。Codemagic に登録
任意のリポジトリサービスを選択して簡単にSignUpすることができます。
任意のリポジトリを選択し、即ビルド
ビルドが終わると APK ファイルが生成されています。
めっちゃ簡単。。
実際にアプリをリリースビルドして自動でアップロードするには Android / iOS 共に設定が必要になります。
それぞれ分けて説明します。Android に関する設定
Keystore ファイルの作成
Keystore ファイルは Google Play に公開するアプリの署名の際に必要になります。
Keystore ファイルは紛失するとアプリのアップグレードが出来なくなりますし流出すると成りすまされてアプリを公開されてしまう可能性があるので取り扱いには注意しましょう。Ksystore生成コマンドkeytool -genkey -v -keystore keystorename.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000Keystore のパスワードと Key のパスワードの2種類入力しなければならないので覚えておいてください。のちに使用します。
Codemagic 上での Code Signing
Android Code Signing
基本的に公式のサイトにまとまっています。Keystore ファイルのアップロード
Workflow 内の Publish の項目の
Android code signing
に、作成した keystore をアップロードします。
その際に、
- keystore password
- Key alias
- Key password
も合わせて入力しておきます。
環境変数の設定
公式にもある通り、Keystore に関する情報を環境変数で与えなければいけません。
- FCI_KEYSTORE_PASSWORD = myKeystorePassword
- FCI_KEY_ALIAS = MyReleaseKey
- FCI_KEY_PASSWORD = myKeypassword
- FCI_KEYSTORE_FILE = myKeystoreEncodedByBase64
最後の
FCI_KEYSTORE_FILE
は最初何を設定するのか分かりませんでしたがどうやら Base64 に encode した Keystore 文字列を突っ込む必要があるらしい。先程の項目で Keystore アップロードしていたので必要ないと勝手に思い込んでました。ドキュメントちゃんと読まないとですね。Base64_encode$ base64 -i /path/to/keystore -o output.txtPost-clone script の追加
Post-clone script
に以下のコードを追加します。#!/usr/bin/env sh set -e # exit on first failed commandset echo $FCI_KEYSTORE_FILE | base64 --decode > $FCI_BUILD_DIR/keystore.jksPlay Console でのアプリケーション作成
Google Play の Developer 登録が済んでいない人は公式サイトから登録しましょう。
SignInしたらアプリの作成を押して新規アプリを作成します。
Service Acount の作成
Codemagic から APK ファイルを自動アップロードするために Service Account を作成する必要があります。
手順に関してはこちらのサイトが参考になるかと思います。Codemagic 上での Google Play の設定
Workflow の
Publish
の項目のGoogle Play
を開きます。Service Account の作成の項目で生成した Service Account の APIKey ( JSON ) を
Credentials
にアップロードし、APK ファイルをどの Track にアップロードするか選択します。※ 注意
App を作成した後に、一度手動で APK ファイルをアップロードする必要があります。
Android に関しては以上になります。
iOSに関する設定
iOS Code Signing
公式で手順が解説されています。
順を追って解説していきます。Apple Developer Portal と連携
User Settings
もしくはTeam Integration
にある Developer Portal の connect ボタンを押して連携します。
連携できると以下のように緑色に変わります。Apple Developer Portal で App ID ( Bundle ID ) を生成
Apple Developer Portal へアクセスし、
Account
=>Certificates, Identifiers & Profiles
の順にクリックします。
+
ボタンを押してこちらを参考に App ID を生成しますApp Store Connect でのアプリ作成
Apple Developer Portal へアクセスし、
Account
=>Certificates, Identifiers & Profiles
=>App Store Connect
の順にクリックします。左上の
+
ボタンから新規 App
を押すと以下の画面になります。
- 名前 ( アプリ名 )
- プライマリ言語 ( 第一言語選択 )
- バンドルID ( 先程作った Bundle ID )
- SKU ( アプリを識別するための ID )
を入力して作成を押します。 SKU に関しては変更ができないのでよく考えて入力する必要があります。
Codemagic 上での Code Signing
iOS は
Autmatic
という項目があり、自動で設定することができます。めっちゃ便利ですね!※ Pre-build scriptの追加 ( Option )
Build flavor を設定している場合、
Pre-build
script に xcconfig ファイルを上書きする処理を記述しなければいけません。詳しくは monoさんの記事をご覧ください。Codemagic 上での Apple Store Connect の設定
Workflow
のPublish
内にある App Store Connect を開きます。
- Apple ID ( アカウントの Email Address )
- App-spcific password (App 用パスワード )
- App ID ( App Store Connect で作成した App の ID )
App-specific password を作成するには、
Apple ID Service にアクセスします。アカウントの管理のセキュリティ
の項目があるのを確認します。
パスワードを生成...
をクリックし、任意のラベルをつけて作成します。その際に表示されるパスワードをメモしておきます。パスワードはこれ以降確認できないので注意が必要です。
また、2ファクタ認証
が有効になっていない場合は App 用パスワードを生成することができないので済ませておきましょう。App ID は App Store Connect へアクセスし、作成したアプリをクリックして確認することができます。
全般の設定
Build triggers の設定
- Trigger on push
- Trigger on pull request update
- Trigger on tag creation
の3つの Trigger を設定できます。
ワイルドカードを使って特定の Branch を対象にすることも可能です。
下の画像では、feature
かfix
で始まる Branch から PullRequest が出された時に実行するパターンです。連携の設定
Slack
Publish もしくは Team integrationに画像のような項目があるので connect を選択
Slack の integration の設定ページに飛ぶので許可する
その後、Codemagic でビルドを開始して終了すると Slack に Artifact のリンクを含んだメッセージが post されます。終わりに
Flutter 自体もとっつき易く、新しく始めるのに敷居が低いので嬉しい限りです。
Flutterを触り始めてから Swift や Kotlin でも開発してみたいなと思うようになったので挑戦しようかなと目論んでいます。
ありがとうございました!
- 投稿日:2019-12-14T07:58:37+09:00
Xcode 11 の Test Plan を、試しながらゆるく解説
この記事では WWDC 2019 の 「Testing in Xcode」 をベースにして、Test Plan
について調べたことや試してみたことを書こうと思います。すぐに Test Plan を試せるプロジェクトは↓こちら。
https://github.com/akatsuki174/TestPlanSampleTest Plan の存在意義
例えばローカライズ対応をしているアプリで、開発時のデフォルト言語ではテストがパスしても、別の言語で実行した時に UI 崩れが発生するかもしれない。複数の条件(言語、テストの実行順、サニタイザー、引数や環境変数)を跨いで常にテストを実行することは不具合の発見率を高めることができる。
今までの Xcode でもスキームエディタを使うことによって様々な設定をすることが可能だったが、実行は1回しかできない。一方 Test Plan では設定を変えてテストを何回も実行できるところがメリットになる。
Test Plan でできること
- 設定を変えてテストを複数回実行
- 全てのパターンを1箇所に定義
- 設定を複数のスキームで共有
- CI サーバや Xcode Server で使う(xcodebuild にも対応している)
- 既存プロジェクトに導入
xctestplan ファイルの作り方
Test Plan を使うために xctestplan ファイルというテスト設定ファイルを作成する。
②Test タブを選択し、下の
Convert to use Test Plans
をクリック
③自分に合った変換方法を選択
既存の設定を引き継いで Test Plan を作る、空の Test Plan を作る、すでにある Test Plan を使うの3種類の方法を取ることができる。
ちなみにここからでも Test Plan ファイルの新規作成や編集ができるっぽい。
xctestplan ファイルの中身 - Tests タブ
Test タブはこの通り。存在しているテストターゲットが一覧になっている。Option をクリックすると、並列でテストを走らせるか否かなどの設定を行うことできる。
特定のターゲットのテストを無効にしたい場合は、Enabled のチェックを外す。
xctestplan ファイルの中身 - Configurations タブ
実行の方法を定義している部分。設定できる項目の大区分は以下の通り。
- 引数
- ローカライズ
- UI Test
- アタッチメント
- テスト実行
- コードカバレッジ
- ランタイムサニタイザー
- ランタイムAPIチェック
- メモリ管理
Share Settings では毎回のテストに共通するオプションを設定する。太字になっているところは、カスタム値を設定しているという意味。
条件を変えてテストを実行したい場合は新しく Config ファイルを作成してカスタマイズする。詳細は次の例の中にて。
使ってみる
2ヶ国語の翻訳表示テスト
日本語と英語での翻訳表示がちゃんとできているか確認する UI テストの例。ただ Test Plan を試したいだけなのでテストの内容がアレなのはご容赦。
Label に表示されている文字列が正しいかをチェックする。class ViewController: UIViewController { @IBOutlet weak var greetingLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() greetingLabel.text = NSLocalizedString("Top_title_label", comment: "") } }class TestPlanSampleUITests: XCTestCase { var app: XCUIApplication! override func setUp() { continueAfterFailure = false app = XCUIApplication() app.launch() } func testLabelLocalization() { XCTAssertEqual(app.staticTexts["greetingLabel"].label, localizedString(key: "Top_title_label")) } private var currentLanguage: (langCode: String, localeCode: String)? { let currentLocale = Locale(identifier: Locale.preferredLanguages.first!) guard let langCode = currentLocale.languageCode else { return nil } var localeCode = langCode if let scriptCode = currentLocale.scriptCode { localeCode = "\(langCode)-\(scriptCode)" } else if let regionCode = currentLocale.regionCode { localeCode = "\(langCode)-\(regionCode)" } return (langCode, localeCode) } private func localizedString(key:String) -> String { let localizationBundle = Bundle(path: Bundle(for: TestPlanSampleUITests.self).path(forResource: currentLanguage?.langCode, ofType: "lproj") ?? "") let result = NSLocalizedString(key, bundle:localizationBundle!, comment: "") return result } }準備は各言語用の Config ファイルを作るだけ。Config の追加は見慣れたこの+ボタンをクリックすればOK。
設定を変えるのは Localization の部分。
もし1言語だけテストを回したいなら、テストファイルで Option を押しながらひし形をクリックする。すると使用する Config の選択肢が出てくるので、使いたい方を選ぶ。
ではここでわざと日本語のテストが失敗するようにしてみる。
override func viewDidLoad() { super.viewDidLoad() var text = NSLocalizedString("Top_title_label", comment: "") let lang = Locale.preferredLanguages.first if lang == "ja" { text += "hoge" } greetingLabel.text = text }どのメソッドで、どの Config の時に失敗したのかは、レポートナビゲータからチェックできる。
Allタブでは全ての結果が、Passed タブではテストが通ったメソッドが、Failed タブではテストが通らなかったメソッドが、Mixed では Config によって結果が異なるメソッドが表示される。
特定の Config の結果だけ見たいときはこのボタンをクリックして選択する。
Test Plan の活用例
異なるサニタイザーを組み合わせて使う
サニタイザーは Xcode に内蔵されているツールで、再現しにくいバグを特定してくれる。例えば ASan と UBSan を組み合わせて使うといったことができる。
Language, Location, Locale 別に Config を用意する
各国の言語で UI テストを実行しつつ、Shared Settings でスクショを撮る設定をする。Xcode 11 では成功したテストでも全てのスクショを保存できる。
異なる種類の項目を組み合わせる
- メモリの安全性を重視:Address Sanitizer + Zombie Objects
- 並列処理を重視:Thread Sanitizer + Undefined Behavior Sanitizer + Random Order
- 追加診断:ENABLE_LOGGING=1(カスタム環境変数) + Keep Attachments(テストが成功してもAttachmentsを保持)
- 投稿日:2019-12-14T00:59:53+09:00
【iOS】簡単にKeyboardを表示したときにTextFieldが隠れないようにする
この記事は ZOZOテクノロジーズ #1 Advent Calendar 2019 14日目の記事になります。
昨日の記事は @hkame さんによる「Aurora->CloudSQLへMySQLレプリケーションはできるのか」でした。はじめに
本記事はKeyboardを表示したときにTextFieldが隠れてしまわないようにするための実装についてです。
TextFieldが隠れないようにする実装のやり方は色々あると思いますが、本記事ではFrame計算などのない簡単な方法を紹介したいと思います。結果
このような動きになります。
日本語キーボードにしてキーボードの高さが変わってもTextFieldの位置が変わっているのがいいですね。実装について
実装は単純で、キーボードが表示されたらScrollViewのcontentInset.bottomの値を調整します。
ポイントとしては、contentOffsetの調整ではなく、contentInsetの値を調整することです。
contentInsetの値を調整するメリットは、TextFieldの表示位置を気にしなくてもいいことです。
contentOffsetの値を変更する際に単純にキーボードの高さを足してしまうと、TextFieldの位置の制御をするためにキーボードがどの位置に表示されているかにより設定するcontentOffsetを求める計算が必要になります。
contentOffset.yにキーボードの高さを足した場合 contentInset.bottomにキーボードの高さを足した場合 import UIKit class ViewController: UIViewController { @IBOutlet private weak var scrollView: UIScrollView! @IBOutlet private weak var nicknameTextField: UITextField! @IBOutlet private weak var birthdayTextField: UITextField! @IBOutlet private weak var submitButton: UIButton! deinit { NotificationCenter.default.removeObserver(self) } override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidAppear(_:)), name: UIResponder.keyboardDidShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) } @objc private func keyboardDidAppear(_ notification: Notification) { guard let keyboardFrame = (notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? NSValue)?.cgRectValue else { return } scrollView.contentInset.bottom = keyboardFrame.height } @objc private func keyboardWillDisappear(_ notification: Notification) { scrollView.contentInset.bottom = 0 } }キーボードの高さの監視タイミングについて
キーボードの表示、非表示のタイミングをそれぞれまとめてみました。
キーボードの表示
keyboardWillAppear keyboardDidShowNotification キーボードの非表示
keyboardWillAppear keyboardDidShowNotification まとめ
キーボードを表示した際にTextFieldを隠れないようにするには、scrollViewの
contentInset.bottom
の値を変更する実装がとても簡単です。
キーボードの非表示時監視タイミングをkeyboardDidShowNotification
にするとカクつくのでおすすめしません。明日の記事は @r-tezuka さんによる公開予定です。どうぞお楽しみに。
- 投稿日:2019-12-14T00:52:23+09:00
UIColor から CIColor に変換するときの注意
前提
UIColor から CIColor にする方法は2種類あります
# 変数 color は UIColor のインスタンスとする # initializer CIColor(color: color) # property color.ciColor注意
color.ciColor
で取得する時、color
が Core Image でイニシャライズされたものでない (=CIColor
で作成されてない) 場合は例外を返されます。
公式ドキュメントにはかいてありますが、抜け穴だと思います。This property throws an exception if the color object was not initialized with a Core Image color.
結論
基本的に
CIColor(color:)
の方を使っておけば問題ない。環境
- Swift 5
参考
- 投稿日:2019-12-14T00:19:51+09:00
Flutter Interact 2019のまとめ
はじめに
この記事はFlutter #2 Advent Calendar 2019の11日目分です。
今年もすぐに枠が埋まってしまっていたのでFlutter Advent Calendarに参加する予定はなかったのですが、ふとカレンダーを覗いていみると11日枠が投稿されずに空いていたのでちょうどFlutter Interactが開催されたので急遽この記事を書くことにしました。
Flutterの最新情報に興味がある方はFlutterウィークリーもよかったらご覧ください
https://qiita.com/tags/flutterweeklyFlutter Interact 2019とは
https://developers.google.com/events/flutter-interact
Flutter Interact is a day dedicated to creation and collaboration with the world, inspired by the makers of brilliant experiences.
一言でいうとGoogle公式のFlutter年次イベントです。12月11日にニューヨークのブルックリンで開催されました。昨年はFlutter Live 2018が同じ時期に開催されました。来年はまた名称が変わっているかもしれませんね。
セッション一覧
Vision Keynote (Flutter Interact '19)
https://youtu.be/NfNdXgJZfFo
ビジョンキーノートを努めたのはMatias Duarte、以前はPalmのVPで、2010にGoogleに入社しAndroid User Experienceのディレクターをやっていて最初にリリースしたのはHoneycombと呼ばれるAndroid 3.0のデザインになります。次にMaterial Designを手掛けたという輝かしい経歴の彼が、現在はFlutterに取り組んでいます。GoogleがFlutterにかける意気込みもこの辺りから伝わってきますね。マテリアルデザインを世界中のスクリーンに広めるためにFlutterに取り組んでいると言っても良さそうです。キーノートでも言及されていますが、マテリアルデザインコンポーネントは専門のエンジニアがFlutterチームにいて新しいコンポーネントのドキュメントが出来るよりも先にFlutterで実装されていることも多いそうです。Google FontsもFlutterから簡単に使えるようになりました。
https://pub.dev/packages/google_fonts
Gogole Fontsを使えば.ttf
ファイルをアセットに組み込むことなく自由に1000ものフォントを利用することが出来ます。使い方は
Text( 'This is Google Fonts', style: GoogleFonts.lato(), ),や
Text( 'This is Google Fonts', style: GoogleFonts.lato( textStyle: TextStyle(color: Colors.blue, letterSpacing: .5), ), ),のように指定するだけでGoogle Fontsを利用可能です。まだバージョン0.2.0のベータ版ですが利用することが出来ます。
Very Early Experimental
アダプティブUIもシングルコードベースで実現できるようになりました。Flutter Webやマルチサイズのスクリーンに簡単に対応できるようにするには必要な機能ですね
開発中のレイアウトコードエディタ。レイアウトの細かな調整もプレビューで確認しながらデザイナーが調整しそのままコードを出力できるようになります。
Product Keynote (Flutter Interact '19)
a portable UI framework for an ambient computing worldFlutterはモバイルアプリのクロスプラットフォームとして知られていますが、様々なスクリーンが増え続ける中すべてのスクリーン向けのアプリを開発できるようにすることを目指してFlutterの開発が続いています。
Flutter 1.12リリース
ダークモードのiOS対応
新しいCupertinoウィジェット
Android Xがデフォルト
既存アプリへの追加
Googleフォント
Flutterギャラリー
StadiaをFlutterで開発
eBayがFlutterでアプリを作り直した
https://play.google.com/store/apps/details?id=com.ebay.mobile
Flutter on Desktop
Flutter on the Web
Dart
Dartpad
https://dartpad.dev/
Web上のPlaygroundでFlutterも動くようになりましたCodelabs
https://flutter.dev/docs/codelabs
コードラボもそのままFlutterの動作を確認できるようになりましたFlutter outline Editor
レイアウトエクスプローラー
Adobe XD
キーノート以外のセッション
Welcome to the Sparkle Party (Flutter Interact '19)
https://youtu.be/1AxXF038-lYMaterial Theming with Flutter (Flutter Interact '19)
https://youtu.be/stoJpMeS5aYDesigning for the Web with Flutter (Flutter Interact '19)
https://youtu.be/tot-R_q5-0oBuilding in Accessibility with Flutter (Flutter Interact '19)
https://youtu.be/bWbBgbmAdQsDesign and Build Clock Displays with Flutter (Flutter Interact '19)
https://youtu.be/i60HG1TtKJoUse Rive and Flutter for dynamic, interactive, & animated experiences (Flutter Interact '19)
https://youtu.be/6QZy5sYozVI