- 投稿日:2019-06-25T23:15:59+09:00
iOS Appバンドル公開時のメモ
- 単体アプリと同様の審査が必要。
- 審査通過後は“販売可能“のOnでリリースとなるので、審査提出時は”販売可能”をOffにしておいた方が良さそう。
- 審査通過後もバンドル名、説明、URLなどは変更可能。
- スクショは単体アプリのを全部取ってくるが、バンドル内では変更ができない。
- バンドルの価格は、一番高いアプリの価格から全バンドルアプリの合計価格の範囲内で設定可能。
- バンドル内のアプリを所有している場合は、差額がバンドル価格となる。既に支払額がバンドル価格を超えている場合は 価格表示が”complete” となり、無料でバンドルを購入可能。
- 単体アプリページで購入してしまうと、バンドル価格は適応されないので注意。その際はユーザーが Apple に申し出れば返金はしてくれるとのこと。
- 投稿日:2019-06-25T23:00:42+09:00
bind(バインディング)の概念について(Rxswift)
bind(バインディングってそもそも何)
Rxswiftの.bindなどで用いられるバインディングですがプログラムに置き換えると結局どういうことなの?
と感じたのでまとめました
・バインディングは.bindしなくてもそもそもできるもの
・Timer
と似たようなもんで監視対象が時間ではなくViewなどになった
だけ// 1秒'たったら'getInfoを実行する let timerGetInfo = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(getInfo), userInfo: nil, repeats: true) timerGetInfo.fire()// hugaを満たしたらhogeにバインディング .map { huga } .bind(to: hoge)・まぁ用は
紐付ける
ぐらいに考えとけばいい
→深く掘り下げすぎなくても分からなければならなくなってからやってったらいい、キャリア積んだ人でもこの辺のことは探り探りで進めていくことも多い結論
あんま気にすんな
- 投稿日:2019-06-25T23:00:42+09:00
bind(バインディング)の概念について(Rxswift)(kboy流)
bind(バインディングってそもそも何)
Rxswiftの.bindなどで用いられるバインディングですがプログラムに置き換えると結局どういうことなの?
という疑問がググってもなかなか消化できずkboyさんにお聞きしたところ
・バインディングは.bindしなくてもそもそもできるもの
・Timer
と似たようなもんで監視対象が時間ではなくViewなどになった
だけ// 1秒'たったら'getInfoを実行する let timerGetInfo = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(getInfo), userInfo: nil, repeats: true) timerGetInfo.fire()// hugaを満たしたらhogeにバインディング .map { huga } .bind(to: hoge)・まぁ用は
紐付ける
ぐらいに考えとけばいい
→深く掘り下げすぎなくても分からなければならなくなってからやってったらいい、キャリア積んだ人でもこの辺のことは探り探りで進めていくことも多い結論
あんま気にすんな
kboyさん
ARKit開発は国内トップの凄腕iOSエンジニア
MENTAというサービス内でiOSアプリ開発のメンターをされています(1万円/月)
- 投稿日:2019-06-25T18:03:17+09:00
iPhoneでテザリング時にUIの表示崩れを見かけたので原因を探ってみる
iPhoneでのテザリング
iPhoneでのテザリング時には画面上部にOS側で使用する領域が増える。
インターネット共有: X台接続中
の分、画面が縦に狭くなる。
通常時 テザリング時 これが影響し、以下のような挙動のアプリを割と見かける。
- スプラッシュ画面のロゴの中段が潰れる
- スクロールが最上部/最下部までスクロールできない
- 画面上部が潰れる
- フッタボタン群が一部しか表示されない
- 下スクロール時にフッタボタン群が隠れるような動作をする場合、上スクロールでフッタボタン群が一部しか表示されない
通常時 テザリング時 原因を推測してみる
表示だけの問題で、UI操作自体には問題ないようなので、
高さ取得に問題があると推測する。検証
コードで通常時/テザリング時の画面の高さを取得する。
検証時の端末:iPhone 8 Plus(iOS12.3.2)
高さ取得 コード 通常時 テザリング時 画面の高さ UIScreen.main.bounds.size.height 568 568 ディスプレイの高さ UIScreen.main.nativeBounds.size.height 1481 1481 基底viewの高さ ViewController#view.frame.height 568 548 違いがあるのは、基底viewの高さのみ。
iPhone Xシリーズでの検証をしていませんが、
見た目では、テザリング時にOSが使用する領域は変わらないため、
表示上の問題はなさそうです。
(テザリング時はSafe Areaの表示領域が変わらず見た目が変わるだけです)対策案
(実際にコードを書いて対策案を検証した訳ではないため、現段階では案のみです)
画面の大きさを取得し、それを元に表示領域を判定する
と
テザリング時に表示領域が狭くなった場合に、
画面自体の大きさは変わっていないため、
表示やUI操作に問題が発生する可能性があります。また、アプリ画面表示中に別のiPhoneからの操作により、
突然テザリング時の表示に切り替わるため、
画面初期表示時の基底viewの高さもあてになりません。これらに柔軟に対応するためには、
AutoLayout
とNSLayoutConstraint
で画面を定義し、
自動的にサイズや位置を調整したり、
NSLayoutConstraint
で上限/下限などを定義などの対策になると思われます。追記:対策案2
- UIApplication.didChangeStatusBarFrameNotification
OSが使用するステータスバーの表示領域変更を検知できるので、
アプリ画面表示中の突然のテザリング表示への切り替わり
に対応できます。
- 投稿日:2019-06-25T12:47:13+09:00
TwitterのiOSアプリにおけるEmpty StateのUIまとめ
これは?
Empty StateのViewを作るにあたってTwitterを参考にしたのでその備忘録。
そもそもEmpty Stateとは?
表示するコンテンツが無い状態のことで、UIの設計において見落とされがちだが新規ユーザーに対してはかなり重要な役割を担う部分です。
参考: https://material.io/design/communication/empty-states.html
TwitterにおけるEmptyState
何もオプショナルな情報を入力しなかったテストユーザーを作成して色々見ていきます。
ホームと通知
ホーム 通知(すべて) 通知(@ツイート) アイコンとかあっても良さそうだが、意外とシンプル 他のアプリだと運営などからの「ようこそ」的なメッセージがデフォルトで入ってたりもしますよね 通知(すべて) と同じ メッセージとフォロー/フォロワー
メッセージ フォロー中 フォロワー 悲しい一文とともに友達を探すための導線が表示される アカウントを探す導線が表示される 特に導線はない ユーザー
ツイート
プロフィール入力が終わるとツイートを促すボタンに変わる
フラグとかがあるんでしょうね、丁寧
ツイート ツイートと返信 メディア いいね ツイートへの導線が表示される 同じく 少し文言は違うがツイートへの導線が同様に表示される 特に導線はない 他人から見た時のユーザー
フォロー/フォロワー
フォロー中 フォロワー 普通 普通 ツイート
ツイート ツイートと返信 メディア いいね おすすめのユーザーが表示されるだけ(他のユーザーでも表示される)で特に何も無い 同じく 普通 普通 参考
Pinterestで「Empty State」で検索をかけると色々出てきますのでそちらも見てみると良さそうです。
https://www.pinterest.jp/search/pins/?q=empty%20state&rs=typed&term_meta[]=empty%7Ctyped&term_meta[]=state%7Ctypedまとめ
Empty Stateの表現として、
「アイコン + 文言 (+ あれば導線)」
というのが一般的な気がしました。
Twitterは意外とシンプルな表現でしたがしっかりと新規を導く導線が張られていて参考になりそうです。
- 投稿日:2019-06-25T09:27:19+09:00
2019/06/20 CICDTestNight #4 行ってきた
=====
2019/06/20(木)
19:00 〜 22:00
@渋谷ヒカリエ21F DeNAhttps://testnight.connpass.com/event/130465/
ハッシュタグ #cicd_test_night
地味なテーマ(司会者談)ながら、回を重ねるごとに参加者数が伸びている、らしい。私は初参加。
先に所感
- 『パイプラインファースト』広めたい
- 『至高のCI/CDパイプラインを実現する5つの約束』は皆様読んで欲しい
- モバイルアプリ界隈の話題が多かった、勉強にはなった
- Bitrise とかMacOSビルドとか
- SETやSREの人ばかりが登壇
- 総人口は多くないにせよ、アピールしてくるなぁ(いいなぁ、羨ましいなぁ)
- TEKTON ちょっと気になる
hisa9chi:「MacStadium使ってみた」
DeNA SWETグループ所属の井口恒志さん
CircleCI Japan Usesr Group コミュニティリーダーhttps://speakerdeck.com/hisa9chi/macstadiumshi-tutemita
- 課題
- 物理リソース
- 物理マシンの設置スペース
- 電源容量問題
- 調達速度
- 開発者の手元に届くまで約1ヶ月待つこともあり
- ハイスペック・カスタム時
- 管理コスト
- OS/SWのアップデート
- 定期的な停電対応
- そこで
- オンプレの物理マシンではなく、クラウドサービスの活用
- MacStadium 使えるのでは?
- MacStadium
- macOS物理マシンのホスティングサービス
- 物理マシンお払い出し
- 特徴
- 他の類似サービスに比べ、ハイスペックマシンもOK!
- マシン管理はWebUIのみ
- サポートは24x7
- 料金体系
- 1マシンあたり1ヶ月定額(前払い)
- 1台単位での月定額制
- 追加したマシンを返却→追加すると2台分かかる
- データセンター
- 場所による料金の変動なし
- Add-Ons
- 追加IPやファイアウォールも可能
- サポート
- 電話
- Live Chat
- 問い合わせチケット
- 基本操作
- マシンの払い出し
- ログイン:パスワードは払い出し時に生成されるチケットに記載される
- WebUI上から、Macの起動停止ができる
- 利用料金の確認
- ユーザの追加・ロールの設定
- Jenkins Slave環境構築CI
- SWETでは、社内的にJenkinsのつらみを軽減する施策を実施中
- MacStudioでJenkinsスレーブ用環境構築用のマシンを調達
- アクセス制御
- plistを使った(ファイアウォールのお金を今回はケチった)
- アクセスはVNC or SSH
- OpenVPNでMacStudioからGHEにアクセス
- AWS上にVPNサーバを用意、ふむふむ
- 動作速度は(ハイスペックなので)快適だった
- まとめ
- 試してみただけ、なので実運用に耐えるかは引き続き評価
Macのホスティングサービスというのも出てきているんですね。今回はMacOS系のビルドサーバ用途ということでしたが、弊社でもニーズがあるのでしょうか?
社外からのGHEアクセスは事例は興味深いものがありました。セキュアに、そういった利便性を確保していきたいですね。manabusakai:「CI/CD パイプラインを最速で組み立てるための 4 つのポイント」
https://speakerdeck.com/manabusakai/four-points-to-assemble-the-ci-cd-pipeline-fastest
- freee のSREの人
- 資料
- TBD
- 昨年のリリース数 278件(ほぼ毎営業日)
- 「パイプラインファースト」をやっているか?
- まずはアプリを作り始める前に、パイプラインを組み立てる
- Web TBD
- 今回はCircleCI前提
- ポイント1:いきなりCIサービス上で試そうとしない
- ローカル環境の circleci コマンドで動作確認/デバッグできる
- 詳細はQiitaに記事がある
- ポイント2:依存関係のインストールは事前に済ませる
- パイプラインの中で依存関係のインストールをしない
- キャッシュなど、設定ファイルが複雑化する
- ビルドの依存関係をすべてインストール済みのDockerイメージを用意して pull するだけ
- ポイント3:車輪の再発明やコピペを避ける
- 既存の設定ファイルのコピペが散乱
- CircleCI Orbsを活用する(共通機能がパッケージとして提供される)
- ポイント4:CIとCDを適切に分離する
- CIの延長線上でCDパイプラインを構築しがち、複雑化
- 迅速にRevertするのが難しい
- CIサービスに強力な権限を付与しないといけない
- CIからのWebhookをトリガーにCDをキックする
- Kubernetes環境なら GitOps
- まとめ
- (ポイントの列挙)
『パイプラインファースト』という言葉があるんですね。言わんとしているところは激しく同意です。クリーンビルド&デプロイができる環境が整ってからアプリ開発を始めようぜ、と。
プレゼン内で紹介されている 至高のCI/CDパイプラインを実現する5つの約束 も、ぜひ社内に広めていきたい考え。TaiheiMishima:「Voicy iOSのCI環境をゼロから整えた話」
https://speakerdeck.com/tihimsm/iosfalsecihuan-jing-wozerokaragou-zhu
- Voicy「音声xテクノロジー」の会社
- CI環境を整備した経緯
- iOSアプリの開発をしていたが、CI環境がなかった...
- 壮絶な手作業によるリリース
- 新規開発が迫ってくるヤバイ
- CI環境を作るぞ
- やったこと
- 順番めちゃくちゃ、1.5ヶ月くらいかかった(CI素人だったので)
- まずはここから整えよう
- テスト配信/プロダクション配信の自動化
- PM/QAに対して、開発者ローカルで動作確認をする必要があったので優先
- 簡単に使える
Bitrise
を使った- ビルドコンフィグ系
- 証明書まわり
- iOSの設定まわりは面倒くさい
- 証明書の更新とかとか
- 静的解析
- SwiftLint / Danger
- まとめ
- ↓の順番でやるべき
- ミスをしちゃだめなところ
- 工数が大きいところ
- コミュニケーションコストがかかるところ
この後のプレゼンでも度々出てくる Bitrise - Mobile Continuous Integration and Delivery は全然知りませんでしたが、モバイルアプリ向けのCI/CDツールとして定評があるみたい。
モバイルアプリ界隈の知見はほとんど持っていないので、ふむふむ、な内容が大半。ikesyo:「Travis CIのBuild Matrixを活用して、Swift製ライブラリをLinux対応させる」
- はてな の人
- Swiftコミッター
- Swift のライブラリ作成
- Swift Package Manager(SwiftPM)
- Server-Side Swift
- Swift on Linux
- Swiftは実はLinuxで動く
- 普通のSwiftプログラマーは macOS + Xcodeで開発してる
- ローカル開発はDocker
- swift-docker
- 複数のSwiftバージョンでテスト
- 手動ではやってられないのでCI
- Travis CI
- Build Matrix
- 2x2x2 の8通りのOSバージョン/Swiftバージョン/環境でテストする、みたいな
- ビルド環境にLinuxだけじゃなくmacOSも使える
- バージョンマネージャ(swiftenv)
- Travis CIのジョブでDockerを使うのは結構面倒だから使ってる
- Bitrise
- CircleCI
- Azule Pipeline
- でも使えるよ
SwiftはLinux上でも動くんだ、ふ〜ん。Travis CIのBuild Matrix的な仕組みは、昨今どのCIサービスでも似た機能が実装されているので、多様な環境でのビルドテストが必要な場合に重宝しそう。
ShuheiKitagawa:「5分でわかった気になるTEKTON」
- freeeの人、SRE
- CD.FOUNDATION
- にホストされることになったプロジェクト TEKTON
- TEKTON
- Knativeのサブプロジェクト、Knative Build pipeline
- Tekton Pipeline
- k8sのネイティブリソースを用いたCI/CDコンポーネント
- Pipelineを既存のPipelineの実行エンジンとして使える
- パイプライン実行部分の標準化(CIサービスごとの際を無くしたい)
- 現在
- v0.4
- 2019年中には v1.0
- 今はまだ機能が不足しており、実務で使うには時期尚早
- 今なら開発者として参加するのがおすすめ
資料が公開されていませんが、気になったセッション。まだ実用化に足る段階ではないものの、CI/CDパイプラインのエンジン部分を標準化しちゃおうぜ、というもの。これが実現できると、どのCI/CDサービスを使っていても同じ定義で実現できるようになるので、利用者としては嬉しい一方で、サービスの差別化も難しくなってきそうだな、と。
Yuki Tamazawa:「ソフトウェア品質を支えるE2Eテストのビルドパイプライン作り」
https://speakerdeck.com/srea/sohutoueapin-zhi-wozhi-eru-e2etesutofalsepaipurainzuo-ri
- JapanTaxi の人, SET
- SETは3名体制(社員)
- JapanTaxiの紹介
- やっていること
- QA体制の構築
- テストプロセスの確率・運用・改善サイクル
- 組織横断・複数プロダクト
- シフトレフト
- E2Eテストの自動化
- メインシナリオが正しく動くことを常にチェック
- 実現したいこと
- ビルドは bitrise
- E2Eテストの実行環境は、社内のMac
- テストしたら結果をSlack通知
- URLからテスト結果の詳細を確認
- 行灯ラボ(Blog)をよろしく
ここでも出てきた Bitrise。そして TestRail、これはテスト管理ツールだそう。以前、弊社内でCATを試して花咲かなかったことがあったようですが、Excelでテスト仕様書を書くのはよいとしても、テスト管理はWeb化リアルタイム自動化したいところなので、これも検討してみてもいいかもしれない。
ikuma_hayasaka:「老後必要資金2000万円時代に送る、Azure Pipelinesによる継続的テスト」
https://speakerdeck.com/ihysk/continuous-appium-e2e-testing-on-azure-pipelines
- SET @ Sony Interactive Entertainment
- こんな時代にCI/CDにお金を払っている場合なのか
- Privateリポジトリ運用はCIビルドにお金がかかる
- そこでAzule Pipelines
- Microsoftの優しさ
- 個人開発者が無料枠で試してみた
- 試験対象は個人開発のiOSアプリ
- pipeline は yml ファイルで書く
- 実際はGUIエディタでもできる
- テストは失敗しがち、デバッグしづらい
- テスト実行中の録画ができるAPIがある
- Azule Pipelineなら無料で結構できる!
やはりモバイルアプリ開発なみなさまはmacosでビルドしたくてBitriseなんだなーとしみじみ。Azule Pipelineを推している人は、他の登壇者でもいたので、今後ぐぐっと勢力を広げてくるかも、ですね。
mats:「expoアプリ開発におけるCI/CD」
(残念ながら登壇者が欠席)
- 投稿日:2019-06-25T09:12:25+09:00
Asset Catalogで同じ名前の別画像を利用する
はじめに
- Xcodeのアセットカタログ(デフォルトではAssets.xcassets)は、画像などのリソースを管理します
- アセットカタログ内で、フォルダ分けも可能です
- その際、別のフォルダに同じ名前の画像を置きたくなることもあります
- その場合の扱い方です
検証環境
- Xcode 10.2.1
- iOS 12.2
- Swift 5
フォルダにNamespaceを付与する
デフォルトの状態(Namespaceなし)
- この画像の例では、
bird
という画像がforest
フォルダとsea
フォルダの両方に配置されています- ですが、デフォルトの状態ではフォルダ名は無視されるので、これらの画像には
bird
という名前でアクセスすることになり、区別ができませんフォルダ名付きでのアクセス(Namespaceあり)
- アセットカタログ内でフォルダを選択し、Attributes Inspectorから
Provides Namespace
にチェックを入れると、そのフォルダ名がNamespaceとして利用されます
- フォルダの色も黄色から水色に変わっていますね
- この画像の例では、それぞれ
forest/bird
とsea/bird
という名前で区別されますこんな感じで、コード内でもInterface Builderでも参照できます
imageView.image = index == 0 ? UIImage(named: "forest/bird") : UIImage(named: "sea/bird")まとめ
- Namespaceがあると、格段に管理がしやすくなりますね
- 今回作成したサンプルコードは、GitHubに置きました
- なお、この記事は私のブログ記事からの転載です
参考
- 投稿日:2019-06-25T08:59:25+09:00
Flutter製iOSアプリのFABで、スペース区切りのlabelが改行される
事象
FABのlabelに渡したTextが、スペースで改行される。
(ちなみにスペースを2つ入れると改行されません。)FloatingActionButton.extended( icon: Icon(Icons.gamepad), label: const Text("START GAME"), shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(16)), );暫定対応
Textに
TextStyle(letterSpacing: 1)
を設定することで、事象が改善されました。FloatingActionButton.extended( icon: Icon(Icons.gamepad), label: const Text("START GAME", style: TextStyle(letterSpacing: 1)), shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(16)), );根本原因
内部実装など追えていないため、何故改行されるのか、何故letterSpacingを入れると解消されるのかはまだ判明していません。
分かり次第追記します。
- 投稿日:2019-06-25T08:34:41+09:00
FlutterのAdMobプラグインを入れたiOSアプリが起動時にクラッシュする
事象
アプリ起動時に画面が真っ暗になり、アプリがクラッシュする。
クラッシュログ
*** First throw call stack: ( 0 CoreFoundation 0x000000010c2956fb __exceptionPreprocess + 331 1 libobjc.A.dylib 0x000000010b839ac5 objc_exception_throw + 48 2 CoreFoundation 0x000000010c295269 -[NSException raise] + 9 3 Runner 0x000000010248bd91 GADVerifyApplicationID + 148 4 Runner 0x000000010248c9c0 GADVerifyApplicationID + 3267 5 libdispatch.dylib <…> Lost connection to device.原因
GADApplicationIdentifier(AdMobアプリID)
をInfo.plist
に追記していなかった。AndroidでAdMobプラグインを導入する際には、
AndroidManifest.xml
にAdMobアプリIDを記載する必要がありますが、iOSでも同様の処理が必要だったようです。
こちらの公式ドキュメントに記載がありました。
https://firebase.google.com/docs/admob/ios/quick-start?hl=en#update_your_infoplist解決策
Info.plist
に下記の<key>, <string>
を追記します。Info.plist<dict> <key>GADApplicationIdentifier</key> <string>[MY-ADMOB-APP-ID]</string> </dict>
- 投稿日:2019-06-25T06:20:12+09:00
[Swift] 初心者向け 失敗しないScrollViewの設定のしかた
はじめに
初めてScrollviewの設定をする際に、少々手こずったので自分のためにまとめておきました。
自分なりに一番納得した手順を説明します。何か指摘や質問があれば気軽にコメントください。手順
1.ScrollViewの追加
元からあるViewにScrollViewを追加します。
2.AutoLayoutの設定
ScrollViewに対し、top,bootom,left,rightそれぞれを以下のように0に設定する。
以下のように対象がSafeAreaになっていることを確認します。
3.ScrollViewの上にViewを追加
1と同じようにScrollViewの上にViewを追加します。4.追加したViewに対しAutoLayoutを設定
まず、2と同じように追加したviewのtop,bottom,left,rightを0に設定します。
次に、commandキーを押しながら追加したviewとScrollViewを選択し、以下のようにequal Widthに設定します。
(注意)equal Heightには設定しないようにしてください。
5.スクロールできるviewの高さを決める。
追加したviewの高さを好きな値に設定して完成。
(今回は1000にしました。)
StoryBoardの設定
storyboardでUIの作成を行う時には以下の設定をして、StoryBoardに表示されるviewの大きさを変更します。
1. main.storyboardでviewControllerを選択
2. 右のウィンドウからsimulatedSizeをFreedomに設定する。
以下のようにsimulatedSizeをFreedomに設定し、Heightの値をview全体が表示される値に設定します。
3.以上
さいごに
ScrollViewの設定についてまとめました。
次は、僕がお気に入りのライブラリについて紹介していきます。
気軽にコメントや質問ください。
- 投稿日:2019-06-25T01:44:47+09:00
iOS12 以下をサポートするプロダクトで SwiftUI の恩恵を得る方法
はじめに
SwiftUI に注目が集まる一方、iOS13 未満をサポートバージョンから切り捨てるまで導入が困難なのも事実です。今回はこの問題点に着目し、 iOS12 以下も今までどおりサポートしつつ SwiftUI と Xcode11 を利用したプレビュー機能を実装する方法をご紹介します。
参考に
AkkeyLab/StoryboardPreviewsBySwiftUI
この記事は、サンプルからの抜粋となります。詳細はこちらをご覧ください。Storyboard Preview by SwiftUI
WWDC 報告会にて同様の内容で発表も行っております。ぜひご覧ください。環境
Xcode 11 β
macOS Catalina β実現すること
- iOS12 以下をサポートしつつ Xcode Previews を利用する
- 既存の Storybord や xib ファイルをプレビューさせる
実現方法
- Xcode Previews 専用のターゲットを作る
- プレビューさせる画面に関連するソースコードとプレビュー用の swift ファイルが含まれており、 iOS13 以降をターゲットバージョンとする
- 対象のクラスを
UIViewRepresentable
に準拠させる
updateUIView
メソッド内で施した変更がプレビューに即時反映されるPreviewProvider
に準拠させた struct を定義
static var previews: some View
でプレビューに必要な処理を施す※ UIViewController をプレビューさせる場合はそれぞれ
UIViewControllerRepresentable
,updateUIViewController
となります。import SwiftUI import UIKit #if DEBUG // Create new for preview struct UserDetailBasicInfoCellPreviews: PreviewProvider { static var previews: some View { Group { // Sets the format of the preview UserDetailBasicInfoCell() .previewLayout(.fixed(width: 320, height: 100)) .previewDevice(PreviewDevice(rawValue: "iPhone SE")) UserDetailBasicInfoCell() .previewLayout(.fixed(width: 414, height: 100)) .previewDevice(PreviewDevice(rawValue: "iPhone XS Max")) } } static var platform: PreviewPlatform? = .iOS } // UserDetailBasicInfoCell.swift // UserDetailBasicInfoCell.xib extension UserDetailBasicInfoCell: UIViewRepresentable { typealias UIViewType = UserDetailBasicInfoCell func makeUIView(context: Context) -> UserDetailBasicInfoCell { return Self.instantiate() } func updateUIView(_ uiView: UserDetailBasicInfoCell, context: Context) { // Make parameter change for preview } } #endif利点
- 表示・非表示が切り替わるパーツが多い場合のデバッグ強化
- 複数端末(画面サイズ)でのレイアウト確認が一度に行える
※サンプルの README を見ればもっとイメージしやすいかもしれません。
欠点
- 前準備がある程度必要になる
最後に
今年の秋が楽しみですね!
- 投稿日:2019-06-25T00:58:37+09:00
iOSアプリからOutlookの予定表を取得する方法(Swift)
はじめに
Microsoft Graph APIを使って、iOSアプリからOutlookの予定表を取得する方法を紹介します。
「Microsoft Graph API」とは?
Microsoft 365のデータにアクセスするためのAPIです。
詳細は公式ドキュメントをご参照ください。
https://docs.microsoft.com/ja-jp/graph/overview前提条件
- Microsoftアカウントを取得している
- アプリ登録の権限を持つAzure AD(Active Directory)が存在する
Azure ADの追加方法は以下の通り
https://blogs.technet.microsoft.com/jpazureid/2018/01/16/azuread-operation/手順
公式ドキュメントに沿って実装します。
https://docs.microsoft.com/ja-jp/azure/active-directory/develop/quickstart-v2-ios#option-2-register-and-manually-configure-your-application-and-code-sample選択肢1(自動)はやり方がわからなかったので、今回は選択肢2(手動)で行います。
アプリケーションの登録
まず、Azureにアプリを登録します。
以下のページにアクセスし、[+新規登録]をクリックします。
https://aka.ms/MobileAppReg
[サポートされているアカウントの種類]では、「この組織のディレクトリ内のアカウントのみ」を選択するとシングルテナントアプリ、他の2つを選択するとマルチテナントアプリとなります。
これでアプリの登録は完了です。
Bundle IDの更新
次に、登録したアプリにBundle IDを追加します。
※公式ドキュメントの手順がわからなかったので、異なる方法で行っています。[Step 1: Configure your application]にある[Make this change for me]をクリックします。
[バンドル ID]にiOSアプリのBundle Identifierを入力し、[更新する]をクリックします。
後述するサンプルコードのBundle IDは
com.microsoft.identitysample.MSALiOS
です。
※一度Bundle IDを変更すると、[Make this change for me]が非表示になるため、再度Bundle IDを変更できなくなります。
変更する方法を知っている方がいらっしゃったら教えてくださいこちらのページの
kClientID
とkAuthority
は後で使うので、ページは開いたままにしてください。実装
サンプルコードのダウンロード
以下からサンプルコードをダウンロードします。
https://github.com/Azure-Samples/active-directory-ios-swift-native-v2/archive/master.zipクライアントIDと認証URLの更新
MSALiOS.xcworkspaceをXcodeで開きます。
クライアントIDと認証URLを、先ほど開いたページに記述されている値に変更します。
ViewController.swift- let kClientID = "{デフォルトのクライアントID}" + let kClientID = "{自アプリのクライアントID}" … let kAuthority = "https://login.microsoftonline.com/common" // マルチテナントアプリ let kAuthority = "https://login.microsoftonline.com/{自アプリのディレクトリ(テナント)ID}" // シングルテナントアプリ
kAuthority
はマルチテナントアプリとシングルテナントアプリで指定する値が異なります。マルチテナントやシングルテナントについては、公式ドキュメントをご参照ください。
https://docs.microsoft.com/ja-jp/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant権限の追加
必要に応じて権限を追加します。
私は予定表を取得したいので、Calendars.Read
を追加しました。予定表の取得に必要な権限は、以下に記載されています。
https://docs.microsoft.com/ja-jp/graph/api/user-list-calendarview?view=graph-rest-1.0&tabs=csViewController.swiftlet kScopes: [String] = ["https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Calendars.Read"]URLスキームの変更
Info.plistに記述されているURLスキームを以下のように書き換えます。
サンプルアプリだとこのままでも動作しますが、書き換えておくのがいいです。Info.plist<array> <dict> <key>CFBundleURLSchemes</key> <array> - <string>msauth.com.microsoft.identitysample.MSALiOS</string> + <string>msauth.$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array> </dict> </array>デバッグ
ここまでできたらデバッグして動作確認します。
[Call Microsoft Graph API]をタップします。
Microsoftアカウントを入力し、[Next]をタップします。
[Result from Graph:]にJSONデータが表示されたらログイン成功です!
予定表の取得
自分の1週間の予定表を取得する処理を実装します。
GraphエクスプローラーにサインインしてAPIを試し、クエリやレスポンスボディを把握すると実装しやすいです。
https://developer.microsoft.com/ja-jp/graph/graph-explorerサンプルコードの
getContentWithToken()
メソッドをコピぺし、メソッド名とURLのみ変更すればOKです。ViewController.swiftprivate func getCalendarWithToken() { let formatter = DateFormatter.fullISO8601 let startDateTime = formatter.string(from: Date()) let endDateTime = formatter.string(from: Date(timeIntervalSinceNow: 60*60*24*7)) let urlString = kGraphURI + "calendarview?startdatetime=" + startDateTime + "&enddatetime=" + endDateTime // Specify the Graph API endpoint let url = URL(string: urlString) var request = URLRequest(url: url!) // Set the Authorization header for the request. We use Bearer tokens, so we specify Bearer + the token we got from the result request.setValue("Bearer \(self.accessToken)", forHTTPHeaderField: "Authorization") URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { self.updateLogging(text: "Couldn't get graph result: \(error)") return } guard let result = try? JSONSerialization.jsonObject(with: data!, options: []) else { self.updateLogging(text: "Couldn't deserialize result JSON") return } self.updateLogging(text: "Result from Graph: \(result))") }.resume() } }DateFormatter+ISO8601.swiftimport Foundation extension DateFormatter { static let fullISO8601: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" formatter.calendar = Calendar(identifier: .iso8601) formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() }
getContentWithToken()
メソッドを差し替え、デバッグします。(2箇所)ViewController.swift- self.getContentWithToken() + self.getCalendarWithToken()おわりに
あとはこれらのコードを参考に製品アプリを実装すればOKです。
iOSアプリからMicrosoftのデータを取得するのはなかなか大変でした
参考リンク
- 【Swift】型を使うという意味を考える (Codable再入門) - Qiita
ISO 8601フォーマットの日付を文字列に変換する方法を参考にさせていただきました。
- 投稿日:2019-06-25T00:43:32+09:00
ダークモード適用を回避する方法
はじめに
iOS13から遂にダークモードが導入されました。
iOS13 プレビュー記事今後のiOSアプリ作成にはダークモードを適用が前提となっています。
しかし、アプリ独自のレイアウトを設定しているために、
すぐにはダークモードを適用できない場合があると思います。そこで今回は、ダークモードを回避する方法を共有します。
UIViewControllerごとに設定
UIViewControllerに追加された
overrideUserInterfaceStyle
の値を指定します。
デフォルトはunspecified
(指定なし)です。ユーザーの設定によって変化します。
値 説明 unspecified 指定なし(デフォルト) light ライトモード(明るい外観) dark ダークモード(暗い外観) UIUserInterfaceStyle - UIKit | Apple Developer Documentation
サンプルコード
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 常にライトモード(明るい外観)を指定することでダークモード適用を回避 self.overrideUserInterfaceStyle = .light } }アプリ全体に適用する
アプリ全体に適用するには
Info.plist
のUIUserInterfaceStyle
パラメータをLight
にします。
これで常にライトモードになります。
なおAutomatic
(自動)Dark
(ダークモード)を指定することもできます。
なお設定しない場合や不正な値が入力された場合は常にLight
が採用されます。まとめ
これらの方法でダークモードの適用を回避することができました。
しかし、Appleはダークモードの適用を推奨しているので、
いずれかのタイミングでアプリにダークモードを適用させる対策が必要です。この対応はあくまで一時的な対応か外観を変化させたくない特段の事情がある場合に止めるべきでしょう。
参照
Choosing a Specific Interface Style for Your iOS App | Apple Developer Documentation
外観モードの変更方法についてImplementing Dark Mode on iOS - WWDC 2019 - Videos - Apple Developer
WWDC2019のダークモードに関するセッション。動画やサンプルコードがあります。
- 投稿日:2019-06-25T00:14:55+09:00
【Flutter】firebase_admobでdisposeしても広告が消えない
概要
広告が表示される前に画面遷移をすると、disposeしても広告が消えない。
再現例
mypage.dartimport 'package:firebase_admob/firebase_admob.dart'; import 'package:flutter/material.dart'; class MyPage extends StatefullWidget { @override _MyPageState createState() => new _MyPageState(); } class _MyPageState extends State<MyPage> { final String appId = 'ca-app-pub-0000000000000000~0000000000'; final String adUnitId = 'ca-app-pub-0000000000000000/0000000000'; BannerAd _bannerAd; @override void initState() { super.initState(); FirebaseAdMob.instance.initialize(appId: appId); _bannerAd = _createBannerAd()..load()..show(); } @override void dispose() { _bannerAd?.dispose(); _bannerAd = null; super.dispose(); } BannerAd _createBannerAd() { return BannerAd( adUnitId: BannerAd.testAdUnitId, size: AdSize.smartBanner, listener: (MobileAdEvent event) { print("BannerAd event is $event"); }, ); } @override Widget build(BuildContext context) { return Scaffold( body: Text('MyPage'), ); } }対処法
initStateではloadだけ行い、
BannerAdのload完了時にshowするようにする。mypage.dart@override void initState() { super.initState(); FirebaseAdMob.instance.initialize(appId: appId); _bannerAd = _createBannerAd()..load(); } BannerAd _createBannerAd() { return BannerAd( adUnitId: BannerAd.testAdUnitId, size: AdSize.smartBanner, listener: (MobileAdEvent event) { print("BannerAd event is $event"); if (event == MobileAdEvent.loaded) { if (mounted) { _bannerAd..show(); } else { _bannerAd = null; } } }, ); }