potatotips #70 に参加しました (iOS/Androidブログまとめ) #potatotips
potatotips #70 に iOS ブログまとめ枠で参加させていただきました(Androidのもまとめました)。
今回はLINE Fukuokaさん主催で、Zoomのウェビナーを使ったオンラインでの開催となりました。
自分で見直すときのわかりやすさ重視でガッツリ元資料を引用していますが問題あればご連絡ください。
[iOS] Contributing to XcodeGen / @freddi
XcodeGen という YAMLでxcodeprojを管理できる OSS にコントリビュートした。
Swift Packageの依存が、Remoteにあるものしか解決できなかった。これをLocalでもできるようにした。
気をつけること、CONTRIBUTING.md というのがあるので読むでからPRする、後方互換性に気をつけるなど。
xcodegenの自動生成の機能もあるけどまだ実装中。
xcodegen 、一人プロジェクトだとあまり使いどころがないのです……。 #potatotips
— Kaoru (@TachibanaKaoru) 2020年7月10日
App Clips とか、iOS/watch とか、マルチターゲットになると xcodegenも必要性があがるかもですね。 #potatotips
— Kaoru (@TachibanaKaoru) 2020年7月10日
[Android] Jetpack Composeでテキストを装飾する話 / @uecchi
Jetpack Composeでテキストを装飾する方法を紹介。
テキスト全体を修飾
ふつうにTextの引数で設定する。
@Composable fun StyledGreeting(name: String){ Text( text = "Hello $name!", color = Color.Green, fontSize = 30.sp, fontFamily = FontFamily.Cursive, fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold )}
テキストの一部を修飾
従来のやつでは Spanned に対応するやつだが、 Annotated String というクラスを使う
@Composable fun StyledGreeting2(name: String){ val greetingText = annotatedString { pushStyle(SpanStyle(color = Color.Green)) append ("Hello ") // 前にpushしたstyleは引き継がれる pushStyle(SpanStyle( fontSize = 40.sp, // Snip fontFamily, fontStyle, fontWeight fontSynthesis = FontSynthesis.All, textDecoration = TextDecoration.Underline append("$name!!") pop()// 直近でpushしたstyleはpopできる Text(text = greetingText, fontSize = 30.sp)
こうなる
テキスト内にカスタム画像を表示
inline content というのがある。
@Composable fun StyledGreeting3(name: String){ val inlineContentId = "InlineContent" val greetingText = annotatedString { append("Hello $name!!") // inline contentを挿入 appendInlineContent(id = inlineContentId, alternateText = ":droid:") // snip
inlineContent: map<String, InlineTextContent> を Text に渡す
@Composable fun StyledGreeting3(name: String) { val inlineContentId = "InlineContent" val greetingText = // snip val inlineContent = map0f( inlineContentId to InlineTextContent( Placeholder( width = 30.sp, height = 30.sp, placeholderVerticalAlign = PlaceholderVerticalAlign.Center ){ alternateText -> // このlambdaはComposable関数 CustomEmojiImage(imageResId = R.drawable.ic_android_robot) }) Text(text = greetingText, fontSize = 30.sp, inlineContent = inlineContent) }
@Composable fun CustomEmojiImage(imageResId: Int){ Image( modifier = Modifier.fillMaxSize(), asset = vectorResource(id = imageResId), contentScale = ContentScale.FillWidth )}
こうなる
Jetpack Composeはこのようにプレビューが横に表示される。便利
サンプルコード
https://github.com/tsuyosh/JetpackComposeTextDemogithub.com
[iOS] ARKit 4.0 / @TachibanaKaoru
ARKitの歴史
ARKit4 新機能を紹介していく。
Geo Anchor
AppleがMap情報を取得するときに収集した実際の建物の3D情報と、カメラ画像から分析した3D情報と、端末のGPS情報を組み合わせた仕組みで、非常に正確な位置測定が可能。
セカイカメラと同じようなことが簡単に実装できる。
現時点ではサンフランシスコ、シカゴ、マイアミ、ニューヨーク、ロサンゼルスだけで使える。
使い方
Depth with LIDAR (Scene Geometry with LIDAR?)
新しいiPad Pro だけで使える、センサーから光を照射してその反射光を使って物体までの距離や物体の種類がわかるようになる。
判定可能な物体は天井、ドア、床、椅子、テーブル、壁、窓、その他、の8種類。
3.5でも距離と種類は取得できたのだが、APIが使いやすくなった。
「What'sOverThere」をApp Storeでhttps://t.co/4sayBYoHK2 @TachibanaKaoru さんの LIDARの SceneDetection を使って、物体までの距離と種類をしゃべってくれるアプリ #potatotips
— くまもʕ•͡וʔ (@kumamo_tone) 2020年7月10日
Face Tracking
TrueDepthカメラ搭載のみだったが、A12以上のプロセッサでも使えるようになった。(新しいiPhoneSEとか)
Reality Converter
Reality Converterというのが2020年1月にリリースされて、他のモデリングツールのファイルからUSDZファイルへ簡単に変換できるようになった。
次のiPhoneにLiDARが搭載されなかったらTachibanaさんのカシオミニがもらえます
[Android] license-list-pluginを使ってOSSライセンス画面を自動生成する / @syarihu
OSSライセンス画面を手軽に作るためのツール、いくつかある
- oss-licenses-plugin
- Google Play Services
- LicenseToolsPlugin
- license-list-plugin
- jmatsu <- new!
以下のような特徴がある。
- ライセンスリストを自動生成し、yamlに定義ファイルを出力人が読みやすい形なので、ライセンスの追加/変更/削除も簡単にできる
- oss-license-pluginと違いライセンスリストが隠蔽されていないため、差分も分かりやすい
- 定義ファイルを元に表示用のhtmlを自動生成したり、カスタマイズした画面を提供するために利用可能なjsonの出力も可能
- ローカルライブラリにも対応
- ライセンスリストから除外したいライブラリがある場合は.artifactignoreに記述することで簡単に除外可能
- cookpad/LicenseToolsPluginを使っている場合はcookpadのプラグインで利用するlicenses.ymlからの移行も可能
json だけでなく HTML の形式でも吐ける。従来のより見た目も良い。
Github Actions に組み込んで自動でライセンス定義を更新することもできる。
jmatsu/license-list-plugin: Gradle plugin to manage licenses of your Android app's dependencies.https://t.co/6TaBEfnfCm こんなのあったんだ よさそう Android で一昨年ぐらいにアプリ作ったときちょうどほしい感じのが見つからなかったんだよな #potatotips
— くまもʕ•͡וʔ (@kumamo_tone) 2020年7月10日
聞いた範囲たいへん良さそうだが This plugin is still under development. とのこと
[iOS] Speech framework tips / @tsuzuki817
note にセルフテキストまとめがあるので詳しくはこちら!
Apple純正の音声認識フレームワーク。ローカル(iOS13〜)とサーバー版がある。
サーバーのAPIの制限は1リクエスト60秒(音声の長さ)。1時間に1000回まで。
NSSpeechRecognitionUsageDescription と NSMicrophoneUsageDescription を Info.plist に入れて使う。
共有会って喋って認識させてみた結果。
通話中は録音ができないのでデバッグ時には注意。
[Flutter] Creating Flutter Animations with Rive
Rive は Flutterで使えるアニメーションツール。前まで flare という名前だった。ツール flr ファイルを読み込んで使うだけ。
フリー版でも機能が使えるが、作ったファイル public にしなければならない。 private にしたければ、 21ドル/月か、年間プランだと14ドル/月。
#potatotips RiveとLottie両方やったことあるデザイナーさんにどっちが作りやすいか聞きたい
— 所 友太 / Spinners Inc. (@tokorom) 2020年7月10日
iOSには対応しているけどAndroidには対応していない?(調べてない)
Androidはないのか…残念https://t.co/uWhYtoqwk2
— なべ (@NabeCott) 2020年7月10日
#potatotips
#potatotips おお、Riveの実行環境にSwiftも入ってるー https://t.co/UXER0btlLT
— 所 友太 / Spinners Inc. (@tokorom) 2020年7月10日
画面構成は必要そうなところが絞られててパッと見使いやすそうに見えた #potatotips
— くまもʕ•͡וʔ (@kumamo_tone) 2020年7月10日
[iOS] UICollectionView iOS14 / @shiz
iOS14 での UICollectionView の更新について。
SectionSnapShot
SectionSnapShot というのが追加された。セクションごとの折りたたみの状態を保存できたり、それのイベントハンドラがあったりする。
あと並び替えのサポートがされた。
Lists
UITableViewのような外見で、スワイプできる、デフォルトのレイアウトが用意されている、Compositional Layoutの上に作成できるなどの特徴がある。
Cell(View)の設定
struct で設定できるようになった。
いい感じに型を使ってセルの登録もできるようになった(これUITableViewにはないってことなのかな)
より深く理解したい人はAppleの公式サンプルコードがおすすめ。
Qiita にもまとめてくださっている。
[Android] getChangePayload in DiffUtil / @rmakiyama
音声版
本日の発表の音声版です!🚀#Radiotalk #ラジテクFM #potatotips https://t.co/E3zBkJrG5g
— まきやま (@_rmakiyama) 2020年7月10日
DiffUtil に getChangePayload というのがある。これはareItemsTheSameがtrueかつareContentsTheSameがfalseで呼ばれる。
変更に関するペイロードをオブジェクトで返せるので、細かい更新の制御が可能。画像の再読み込みが起こらずチラツキが起こらず、スマート。
Groupieにも同様の機能が提供されている。
サンプルコード
[iOS] Swift UIで勘違いした話 / @koher
Stepper に $counter.count を渡すように、 NumberDisplay にも @Binding の $counter.count を渡していたら、コンパイルエラーでうまくいかなかった
というのも、Stepperでは双方向バインディングなんだが、NumberDisplayは単方向バインディングなので、
Viewでは @Binding ではなくシンプルにプロパティを持っておいて、プロパティを渡せば 単方向バインディングは実現できるということがフォーラムに聞いたらわかった 質問するのは大事ですね
[Android] Material Components for Android 1.2.0 and 1.3.0 / @NabeCott
まだ開発中だが Material Components に入りそうな変更について。
SliderはSeekBarのMaterial Component版 目盛りの表示やTooltipの表示、範囲の選択などができる
ShapeableなImageViewなど
ほかにも複数行のタイトルが表示できるCollapsingToolbarやProgressBarのMaterial Component版、新しいTimePicker(In development)などが入りそう
[iOS] iOSアプリでWebRTC / @SatoHikaruDev
WebRTCのクライアント実装について理解を深めたい。
サンプルを参考に、どういう処理がされているのか見ていく。接続、SDPのやりとり、映像キャプチャなどについて実際のコードを交えて紹介。
WKWebViewはWebRTC未対応なんだ#potatotips
— ウホーイ (@the_uhooi) 2020年7月10日
[Android] Adaptive Cardsを使ってみた / @nakasho_dev
www.slideshare.net
Adaptive Cards は、JSONで作成された、プラットフォームに依存しないUIのスニペット。特定のアプリに配信されると、JSONは周囲に自動的に適応するネイティブUIに変換する。Microsoftが推進していて、 Android iOS JavaScript ASP.NET .NET WPF Windows ReactNative などで使えるもよう
Adaptive Card Designer というのがあって、ノンプログラマーでもいい感じに編集できる。
感想
前回に続きリモート開催だったが、運営さんの頑張りなどによって、なんだったらリモートのほうがいい面あるんではと思えるぐらい良い感じだった ありがとうございます
App Clips の短期間通知を有効にする
上記の続きになります。
App Clips の短期間通知を有効にする
App Clipの中には、価値を提供するためにスケジュールや通知を送る必要があるものもあります。
配達で食べ物を注文できるApp Clipを考えてみましょう。通知を送信することで、App Clipは予定されている配達についてユーザーに通知します。
通知がApp Clipの機能にとって重要な場合は、各起動後最大8時間まで通知をスケジュールまたは受信できるようにします。さらに、複数のビジネス向けにApp Clipを作成する場合は、通知のペイロードに変更を加えるようにしましょう。
一時的な通知の設定
App Clipが起動後、最大8時間まで通知をスケジュールしたり受信したりできるようにするには、まず、App ClipのInfo.plistファイルにDictionary型のNSAppClipキーを追加します。
次に、NSAppClipRequestEphemeralUserNotificationというキーを持つエントリをDictionaryに追加します。このキーの型はBooleanに、値をtrueに設定します。
App Clipの Info.plist に NSAppClipRequestEphemeralUserNotification のエントリを追加すると、App Clipの起動時に表示されるClip Cardには、App Clipが通知を受信したりスケジュールしたりすることができることをユーザーに知らせる追加のnoteが含まれています。
この権限はデフォルトで有効になっていますが、ユーザーはClip Cardのnoteをタップすることで無効にすることができます。
ユーザーはClip Cardで通知を無効にすることができるので、App Clipが通知をスケジュールして受信する権限を持っているかどうかをチェックするコードを追加します。
以下のコードは、ユーザーが短時間の通知送信の許可を与えているかどうかをチェックしています。
let center = UNUserNotificationCenter.current() center.getNotificationSettings { (settings) in { if settings.authorizationStatus == .ephemeral { // ユーザーがアプリのクリップカードで通知を無効にしていなかった場合 // ここにスケジュールや通知を受信するためのコードを追加します return } }
通知を送信するための明示的な許可を要求する
App Clipの機能が1日以上に及ぶ場合は、通知を送信するためのユーザーの許可を明示的に要求します。
例えば、レンタカー会社のApp Clipでは、レンタカーを返却する必要があるときにユーザーに通知を送信する許可を求めることができます。
しかし、この許可を求めるべきかどうかは慎重に検討してください。ユーザーはこの要求を拒否して、App Clipが各起動後最大8時間まで通知を受信してスケジュールする能力を上書きしてしまう可能性があります。
通知ペイロードを変更する
複数のビジネス向けのApp Clipを作成することができます。
例えば、飲食店向けのプラットフォームを提供している企業で、多くの異なる飲食店にサービスを提供するApp Clipを作成することができます。
ユーザーが短時間のうちに複数のビジネス向けのApp Clipを連続して起動した場合、App Clipのインスタンスがデバイス上に複数存在することがあります。
この場合、システムは、通知を受信すると、適切なApp Clipのインスタンスに通知をルーティングする必要があります。
その結果、システムは通知のペイロードに URL をtarget content identifierとして含めることを要求します。
次のコードは、複数のビジネスにサービスを提供するApp Clipの通知ペイロードを示しています。
{ "aps" : { "alert" : { "title" : "Order Status", "subtitle" : "Restaurant A", "body" : "Your order is ready." }, "category" : "order_status", "target-content-id" : "https://example.com/restaurants/restaurant_a/order/1234" } }
target-content-id の値は、対応するAdvanced App Clip Experienceに一致するURLである必要があります。
レストランの例では、これらのURLの両方をApp Store Connectに登録します。
呼び出しURLとtarget content identifiersは、次のようになります。
- example.com/restaurants/restaurant_a/order/1234
- https://example.com/restaurants/restaurant_b/order/5678
一般的に、可能な限り具体的なtarget content identifiersを使用します。
同様に、ローカル通知をスケジュールするためにApp Clipを有効にする場合は、通知のペイロードにtarget content identifiersを設定します。
詳細については、App Clipsの起動方法の設定、Generating a Remote Notification 、Scheduling a Notification Locally from Your App を参照してください。
出典
- Enabling Notifications in App Clips
Configure and link your app clips
WWDC 2020 のセッション、 Configure and link your app clips に関しては akatsuki174 さんの Qiita にわかりやすくまとまっていたので当ブログは下記の記事を推薦します。
以上
App Clipsの起動方法の設定とテストについて
上記の続きになります。
デフォルトの起動方法の設定
App Clip は単独でリリースできず、常に対応する本体アプリが必要で、アプリの一部としてApp Store Connectに提出します。App Store Connectでは、新しいアプリバージョンのページで、Default App Clip Experience を設定し、Clip Cardに次のメタデータを提供する必要があります。
- ヘッダー画像
- アプリのクリップの詳細情報を提供するサブタイトル
- アクションボタンをタップしたときに呼ばれるアクション
設計指針については、Human Interface Guidelines を参照してください。
Advanced App Clip Experience
Webサイトの Smart App Banner や Message アプリの共有リンクからの呼び出しは、前述の Default App Clip Experience が呼び出されます。
しかし、NFCタグなどをサポートしたい場合は、 Advanced App Clip Experience を設定する必要があります。呼び出しURLを設定し、App Store Connectで呼び出しと呼び出しURLの両方をAdvanced App Clip Experienceとして設定します。
次のような場合に、Advanced App Clip Experienceを設定します。
- NFC タグやビジュアルコードからの呼び出しを含む、すべての可能な呼び出しをサポートするようにしたい場合
- 物理的な場所に関連付ける必要がある場合
- 複数の事業者が利用する場合
Advanced App Clip Experience を設定するには、呼び出しURLを登録する必要があります。また、Advanced App Clip Experience ごとに異なる画像やメタデータを指定することもできます。
Advanced App Clip Experience に設定すべきURL
Advanced App Clip Experience に登録すべきURLの数は、ユースケースによって異なります。たとえば、App Clipが常に同じ画面を表示する場合、1つ登録すれば十分でしょう。
通常の呼び出し(WebのSmart App Banner、Messageアプリの共有リンク)以外の呼び出しをサポートしたくない場合は、Advanced App Clip Experienceを作成する必要すらありません。その他のユースケースでは、逆にAdvanced App Clip Experienceを設定することが最も重要な場合があります。
一般的には、登録するURLはできるだけ少なくして、できるだけ汎用的なURLを登録してください。
次に、最もふさわしい接頭辞を持つURLを考えます。
たとえば、https://example.com/menu を URL として設定すると、https://example.com/menu/lunch、https://example.com/menu/dinner などの同じURLで始まるすべてのURLをカバーすることができます。
ただし、ビジネスが複数の物理的な拠点を持つ場合は、1 つまたは複数の物理的な拠点用に それぞれ Advanced App Clip Experience を設定し、各拠点ごとに異なるヘッダー画像、メタデータ、および呼び出し URL を使用することができます。
(例: https://example.com/location1、 https://example.com/location2 など)
Advanced App Clip Experiences の作成
App Store Connect で Advanced App Clip Experienceを作成するには、呼び出し URL を登録する必要があります。異なるドメインの URL を使用している場合は、アプリとApp Clipの両方のターゲットの Associated Domains Entitlement に適切なドメインエントリを追加して、登録した URL を認証します(参考: Associated Domains Entitlement の追加)。
次に、App Clipと完全なアプリの両方のコードで、登録した URL に応答します。最後に、App Clipが物理的な場所に関連する機能を提供している場合は、Advanced App Clip Experience を物理的な場所に関連付けます(参考: ユーザーの位置情報の確認)。
Smart App Banner とリンクから呼び出せるようにする
WebサイトのSmart App Banner から、 App Clipを起動できるようにしましょう(注釈:Smart App Banner というのは、下記画面にもある、スマホサイトの上部に、アプリへの誘導を表示するアレ)。サイトにSmart App Bannerがすでに設置されている場合は、既存のmetaタグに app-clip-bundle-id=appClipBundleID 属性を追加し、その値にApp Clipのbundle identifierを使用します。
Smart App Bannerを表示したくない場合は、次のHTML metaタグを各ページに追加します。
<meta name="apple-itunes-app" content="app-id=myAppStoreID, app-clip-bundle-id=appClipBundleID, affiliate-data=myAffiliateData, app-argument=myAppArgument">
プレースホルダを適切な値に置き換えます。metaタグのcontent属性には、app-clip-bundle-idとapp-idの両方が含まれていることに注意してください。app-idを含めることで、iOS 13以前のデバイスでアプリを開いたり、プロモーションしたりできるようになります。
Smart App BannerをWebサイトに設置するには、Apple App Site Association ファイルを設定し、Webサイトのドメインを関連付けられたドメインのリストに追加する必要があります(参考: Associated Domains Entitlement の追加)。
App Clipでは、NSUserActivity オブジェクト経由で、呼び出し元の Smart App Bannerを設置したWebサイトのURLを取得できます。
Web サイトにSmart App Bannerを追加するには、Advanced App Clip Experience は必ずしも必要ありません。しかし、Smart App Bannerを表示できるWebサイトであれば、どのようなWebサイトにも設定することができます。
Advanced App Clip Experience の一部としてバナーの URL を登録したとします。このとき、バナーからApp Clipを起動すると、関連するClip Cardが表示されます。
一方、Default App Clip Experienceのみを設定した場合、バナーからApp Clipを起動すると、Default App Clip ExperienceのClip Cardが表示されます。
App Clip の呼び出しの設定
アプリとApp Clipのどちらもが、スムーズな起動を提供するために、呼び出しURL に応答して、UIを更新する必要があります。
たとえば、複数の物理的な場所を持つビジネスがあったとします。
https://example.com (何のパラメータもないURL)を設定した場合、起動後にユーザーにリストから物理的な場所を選択してもらうこともできますが、操作回数が増えてしまいます。呼び出し URL を適切に設定し、追加の URL パラメータを呼び出しに渡すことで、余計な操作を減らし、ユーザーの体験を良くすることができます。
アプリとApp Clipの両方について、
- Associated Domains capability を有効にし、App Clipを起動する各ドメインのエントリを追加します。上記の例では、example.comを追加します。
- ユーザーがある場所から別の場所に切り替えた場合に備えて、保存されていないデータを永続化します。
- 追加のパラメータを含む呼び出し URL を使用します(例:https://example.com/location1、https://example.com/location2 など)。
起動時には、URL に応答して、UIを場所に合わせて更新します。
マップアプリからの呼び出しや「Siriからの提案」からの位置情報ベースのサジェストでは、App Clip Experienceに登録したURLが呼び出しURLとして使用されます。呼び出しURLのプレフィックスとしてのみ使用する場合でも、App Clipとアプリの両方でこのURLを処理できるようにしておく必要があります。
たとえば、Advanced App Clip Experience の1つとして https://example.com/menu を登録したとします。
その場合、NFC タグからの呼び出しには、https://example.com/menu/dinner/item/1234 や https://example.com/menu/dinner/item/5678 のような URL を使用し、 https://example.com/menu は使用しないかもしれません。
そのような場合でも、App Clipとアプリは https://example.com/menu は扱えるようにする必要があります。
呼び出しURLへのアクセス
App Clip が起動されるタイミングで、NSUserActivityオブジェクトが起動時のライフサイクル関数に渡ってきます。
任意の状態とデータを永続化し、起動時にNSUserActivityオブジェクトにアクセスするために、以下のそれぞれの場合に対応する必要があります。
SwiftUIの場合
- SwiftUIのライフサイクル関数を実装します
- たとえば、onContinueUserActivity(_:perform:) を使用します。
UIKit + UISceneDelegate の場合
- UISceneDelegateで定義されている関数を実装します。
- たとえば、scene(:willConnectTo:options:)、scene(:willContinueUserActivityWithType:)、scene(_:continue:)などを実装します。
UIKit + UIApplicationDelegate の場合
- UIApplicationDelegateで定義されている関数を実装します。
- application(:didFinishLaunchingWithOptions:)ではNSUserActivityオブジェクトにアクセスできないので、必ずapplication(:continue:restorationHandler:)を実装してください。
起動時に、呼び出しの型が NSUsuserActivityTypeBrowsingWeb であることを確認してから、App Clip を起動した URL にアクセスします。
次のコードは、呼び出しURLから情報を抽出する関数です。
func respondTo(_ activity: NSUserActivity?) { // 異常なデータのための guard 文 guard activity != nil else { return } guard activity!.activityType != NSUserActivityTypeBrowsingWeb else { return } guard let incomingURL = activity?.webpageURL else { return } guard let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else { return } // App Clipに渡されたURLに基づいてUIを更新 }
ユーザーの位置情報の検証
物理的な場所で呼び出す App Clipを作成する場合、タスクを実行する前に、システムがユーザーの場所を検証する必要がある場合があります。
App Clipは、素早く起動するために、軽量なアルゴリズムでユーザーが特定の場所にいることを検証します。
- App Clip の Info.plist に NSAppClip という Dictionary 型のキーを追加します。
- NSAppClipRequestLocationConfirmation という Bool 型のキーを追加し、値をtrueにします。このエントリはアプリのInfo.plistには追加する必要はありません。 アプリをインストールしてApp Clipを置き換えたとき、App Clipがすでにユーザーの位置情報を確認できていた場合は、アプリもユーザーの位置情報を確認できるようになります。
- 位置情報をApp Clipに提供します。この情報を取得するには、App Clipを起動するURLにIDを含めて、そのIDを使用してデータベース内のビジネスの位置情報を検索することができます。または、App Clipを起動する URL に位置情報自体をエンコードして含めることもできます。
- 位置情報を使って、半径500mまでのCLCircularRegionオブジェクトを作成し、confirmAcquired(in:completionHandler:) に渡します。
Info.plist に NSAppClipRequestLocationConfirmation キーを追加すると、App Clipの呼び出し時に表示されるClip Cardには、App Clipが自分の位置情報を確認できることをユーザーに伝える追加のnoteが含まれています。この権限はデフォルトで有効になっていますが、ユーザーはClip Cardのnoteをタップすることで無効にすることができます。
次のコードは、App Clipを起動すると、ユーザーの位置情報を検証します。ユーザーがデバイス上の位置情報サービスの権限を拒否した場合も含めた、すべての可能性に合わせてUIを更新するようにしてください。
import UIKit import AppClip import CoreLocation class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? // 該当するすべてのライフサイクル関数で verifyUserLocation(_:) を呼ぶ func verifyUserLocation(_ activity: NSUserActivity?) { // 異常なデータのための guard 文 guard activity != nil else { return } guard activity!.activityType != NSUserActivityTypeBrowsingWeb else { return } guard let payload = activity!.appClipActivationPayload else { return } guard let incomingURL = activity?.webpageURL else { return } // CLRegion オブジェクトの作成 guard let region = location(from: incomingURL) else { // ここで解析エラーに対応してください。 return } // 呼び出しが期待した場所で発生したことを検証 payload.confirmAcquired(in: region) { (inRegion, error) in guard let confirmationError = error as? APActivationPayloadError else { if inRegion { // NFCタグの位置が、ユーザーの位置と一致した場合 } else { // NFCタグの位置が、ユーザーの位置と一致しなかった場合 // (例)だれかが NFC タグを移動したとき } return } if confirmationError.code == .doesNotMatch { // スキャンしたURLがApp Clipに登録されていなかった場合 } else { // ユーザーが位置情報へのアクセスを拒否したか、 // 呼び出し元がNFCタグやビジュアルコードではなかった場合 } } } func location(from url:URL) -> CLRegion? { let coordinates = CLLocationCoordinate2D(latitude: 37.334722, longitude: 122.008889) return CLCircularRegion(center: coordinates, radius: 100, identifier: "Apple Park") } }
App Clip の起動のテスト
環境変数を追加して App Clip の呼び出しをローカルでデバッグしたり、TestFlightでテストするための呼び出しを設定したりします。
App Clipを作成する際には、Advanced App Clip Experienceに登録したURLにApp Clipが応答できるように、可能性のあるすべての呼び出しをテストすることが重要です。
デバッグ時のローカルテスト
App Clip のデバッグ時に呼び出しをテストするには、以下の工程を行います。
- Xcodeで、[Product] > [Scheme] > [Edit Scheme] を選択し、App Clip の Scheme を選択します。
- [Run] を選択します。
- Argument タブで、新しい環境変数を追加し、名前を _XCAppClipURL とし、その値をテストするURLに設定し、変数を有効にします。プロジェクトにApp Clip Targetを追加すると、Xcodeはこの環境変数を追加します。
- テストしたいURLのドメインが、Associated Domains Entitlement に追加されていることを確認します(参考: Associated Domains Entitlement の追加)。
- NSUserActivity オブジェクトから構成したテスト URL にアクセスするために、App Clipをビルドして実行します。
TestFlight を使った呼び出しURLのテスト
新しいバージョンのApp Clipを App Store Connect に提出し、TestFlight でテスターが利用できるようにすると、テスト用に最大 3 つの異なる呼び出しを設定できます。
TestFlightアプリでは、設定された3つの呼び出しのいずれかからApp Clipを起動し、App Clipが期待通りに応答するかどうかを検証します。TestFlight アプリからApp Clipを起動した場合、Clip Cardは表示されないことに注意してください。
出典
- Configuring Your App Clip’s Launch Experience
- Responding to Invocations
- Testing Your App Clip’s Launch Experience
続き
iOS14 で追加された App Clips の概要と作り方
App Clip の概要
App Clipは、ユーザーが必要なときに必要な場所で、機能の一部を提供する機能です。
コーヒーショップのアプリを例に考えてみましょう。App Clipは、ドリンクを注文するだけの機能しか提供しません。
ユーザーがコーヒーショップの前を通ると、「Siri からの提案」で位置情報に基づいたサジェストがデバイスに表示されます。ユーザーはサジェストをタップすると、システムが提供するClip Card から、 すぐに App Clipを使ってドリンクを注文することができます。
「Siriからの提案」からの位置情報ベースのサジェストのほかにも、NFCタグをスキャンしたりするなどの起動経路があります。
App Clipは瞬時に起動し、ユーザーが日常的なタスクをできるだけすばやく実行できるようにし、必要な時間だけユーザーのデバイスに残ります。
ユーザーがApp Clip対応のアプリをインストールすると、完全なアプリがApp Clipに置き換わります。この時点から、すべての呼び出しはApp Clipの代わりに完全なアプリに切り替わります。
ユーザーがアプリをインストールせず、一定期間使われない場合は、システムは自動的にApp Clipを削除します。
またコーヒーショップの例で考えてみましょう。
App Clipは対応するアプリをユーザーにおすすめして、ユーザーはおすすめからそのアプリをダウンロードします。次に位置情報ベースの提案を見てそれをタップすると、App Clipの代わりに完全なアプリを起動します。アプリをインストールしていない場合は、App Clipが起動します。
App Clip の開発の概要
Xcodeプロジェクトにおいては、App Clipは、アプリのターゲットとコードとアセットを共有する追加のターゲットです。また、App Clipは、アプリで利用できるのと同じフレームワークにアクセスすることができます。
たとえば、以下のようなことを行うことができます。
ただし、App Clipには、ユーザーのプライバシー保護などの観点から、機能に制限がかけられています。アプリは1つの対応する App Clip を持ち、App Clipの機能はアプリで完全にサポートされている必要があります。
起動経路
App Clip は以下の起動経路があります。
- 物理的な場所でNFCタグまたはビジュアルコードをスキャンする
- 「Siriからの提案」の位置情報ベースのサジェストをタップする
- マップアプリのリンクをタップする
- ウェブサイト上でスマートアプリのバナーをタップする
- メッセージアプリで誰かが共有したリンクをタップする
起動時に、システムはメタデータとURLを検証します。次に、この URL を使用して、起動時に提示されるClip Cardと呼ばれるシートを生成します。
Clip Cardには、追加情報を提供し、ユーザーがApp Clipを起動するためにタップするボタンを表示されます。起動ボタンがタップされると、URLがApp Clipに渡され、URLの情報を使用してUIを更新します。
UXについて
App Clipはホーム画面に表示されず、ユーザーは通常のアプリを管理するような方法では管理しません。その代わり、システムは一定期間使用していないと、デバイスから削除されます。そのため、洗練されたUXを提供することが重要です。
タブバーや複雑なフォームのような複雑なUI要素を避け、直線的で焦点を絞ったUXを提供しましょう。
より複雑なタスクを実行する必要がある場合は、複雑さを軽減するテクノロジーを利用しましょう。例えば、サービスや商品の支払いにApple Payを提供したり、複雑な登録フォームを避けるためにSign in with Appleを利用したりすることができます。
デザインの指針については、Human Interface Guidelines を参照してください。
バイナリサイズの制限
アプリのクリップは、瞬時に起動できるようにするために、10 MB 以下の小さなサイズにする必要があります。可能であれば、App Clip はこの制限以下にしてください。
使用可能なフレームワーク
App Clip は SwiftUIとUIKit の両方を利用しており、アプリと同じフレームワークにアクセスすることができます。
ただし、以下のフレームワークはApp Clipでは利用できません。
- CallKit
- CareKit
- CloudKit
- HealthKit
- HomeKit
- ResearchKit
- SensorKit
- Speech
これらのフレームワークを使用してもコンパイルエラーは発生しませんが、これらのフレームワークの API は、実行時に使用不可、空のデータ、またはエラーコードを示す値を返します。たとえば、HealthKit の isHealthDataAvailable() は、App Clipから呼び出すと false を返します。
プライバシーの保護
App Clipでは、SKOverlayやSKStoreProductViewControllerを使用して広告を表示したり、他のアプリをおすすめしたりすることができます。しかし、ユーザーのプライバシーを保護し、アプリやApp Clip間でのユーザー追跡を防止するため、Limit App Trackingが常に有効になっています。
App Clipは、AppTrackingTransparency (iOS14.0+の機能)でユーザーを追跡する権限を要求することはできず、nameとidentifierForVendorはどちらも空の文字列を返します。さらに、URLSessionでバックグラウンドのネットワーク処理を行ったり、App Clipが使用されていないときにBluetooth接続を維持したりするなどのバックグラウンド処理を実行することができません。また、位置情報アクセスを要求することもできません(次の日の午前4時に自動的にリセットされる一時的な位置情報権限は要求することはできます)。
ユーザーデータを保護するため、App Clipは以下にアクセスできません。また、自身の本体アプリ以外の他のアプリとデータを共有することはできません。
- モーションデータとフィットネスデータ
- Apple Musicとメディア
- 連絡先、ファイル、メッセージ、リマインダー、写真などのアプリのデータ
本体アプリで行うべき機能
App Clipは、その場限りの体験を提供し、日常のタスクに対して可能な限り迅速な解決策を提供することに重点を置いています。以下のような機能は、本体アプリで行いましょう。
- Bonjourのような高度なネットワーク機能、またはローカルWi-Fiネットワークの設定
- App extensions
- カスタマイズと設定; たとえば、設定バンドルの作成
- データのHandOffとドキュメントのオープン
- アプリ内購入とペイメントカードのアプリ内提供
- 低レベルの Unix 機能; 例えば BSD 通知
- iPadでのマルチ画面対応
- カスタムURLスキームの登録
- StoreKitのrequestReview(in:)メソッドを使用したレビュー訴求
- ペアリングされたBluetoothデバイスの検索
App Clip を作成する
Xcodeを使用して、アプリのXcodeプロジェクトにApp Clip用のターゲットを追加し、アプリとの間でコードとアセットを共有することができます。プロジェクト内の他のターゲットと同様に、Xcode を使用して、シミュレータやデバイス上でApp Clipをビルド、実行、デバッグします。さらに、App Clip の妥当性(authenticity)をシステムが検証できるように、ウェブサーバを設定する必要があります。
App Clip Targetの追加
App Clipは、少なくともApp Clipと同じ機能を提供する対応するアプリが必要で、通常、アプリとApp Clipの両方に同じXcodeプロジェクトを使用します。
新しいアプリプロジェクトを作成する場合は、まずXcodeで新しいiOSプロジェクトを作成します。既存のアプリにApp Clipを追加したい場合は、そのXcodeプロジェクトを開きます。次に、XcodeプロジェクトにApp Clip Targetを追加します。
- App Clip テンプレートを使用して、新しいターゲットを追加します。
- Product Name を入力し、App Clipに適用可能なオプションを選択して、[Finish] をクリックします。
[Finish] がクリックされると、Xcodeは選択したオプションに必要なファイルと、以下を追加します。
- App Clipとそのテストをビルドして実行するためのScheme(本体アプリをビルドして実行するには、既存のSchemeを引き続き使用します)
- On Demand Install Capable という名前の新しい機能
- Parent Application Identifiers Entitlement
- アプリ識別子
- たとえば、完全なアプリのIdentifierが $(AppIdentifierPrefix)com.example.MyApp の場合、App Clip の Identifierは $(AppIdentifierPrefix)com.example.MyApp.Clip.Clip となります。
- App ClipのSchemeの一部としての環境変数
_XCAppClipURL
は、呼び出しをテストできるようにする - 本体アプリと同じデバイスのサポート(Universal なら iOS と iPadOS) (ただしmacOSを除く)
さらに、Xcodeでは、アプリにApp Clipを埋め込むための新しいBuild Phaseを作成します。
コードを追加する前に、まずシミュレータまたはデバイス上でApp Clipを実行しましょう。この時点では、まだコードやアセットを追加していないので、App Clipは空の白い画面を表示します。
Associated Domains Entitlement の追加
ユーザーは 呼び出しURL を使って App Clipを起動します。どの呼び出しをサポートする場合でも、アプリとApp Clipのターゲットに Associated Domains Entitlement を追加する必要があります。
Xcode でプロジェクトを開き、プロジェクト設定で Associated Domains を有効にして Associated Domains Entitlement を追加します。
App Clipまたはアプリを起動する各URLに対して、そのドメインを以下のパターンを使用して Associated Domainsに追加します。(例) appclips:example.com
appclips:<fully qualified domain>
Associated Domains Entitlement を追加することに加えて、起動前にシステムがApp Clipを検証できるようにサーバーを設定する必要があります。詳細については、Configuring Your App Clip’s Launch Experienceを参照してください。
コードとアセットの追加
App Clip はアプリと同じフレームワークを使用し、App Clipのターゲットにコードやアセットを追加すると、他のターゲットと同じように動作します。新しいソースファイルとアセットを作成したり、既存のソースファイルとアセットをApp Clipのターゲットのメンバーとして使用したりできます。プロジェクトの保守性を確保するために、アプリとApp Clipの両方で可能な限り多くのコードを共有する必要があります。
たとえば、
- 新しいアプリを作成する場合は、App Clipを作成することを念頭に置いて構築し、モジュール化のベストプラクティスに従います。
- たとえば、再利用可能なコンポーネントを作成したり、 Swift のライブラリとして切り出したりして、アプリとApp Clipの両方で同じコードを使用します。
- 既存のアプリにApp Clipを追加する場合、コードの重複を避けるために、コードベースをモジュール化し、App Clip とアプリの間でコードを共有するための時間を確保してください。
- 共有アセットを新しいAsset Catalogに追加し、アプリとApp Clipの両方で同じアセットを使用します。
Active Compilation Conditions 設定
App Clipとアプリの間でコードを共有する場合、アプリのコードの一部がApp Clipで使用できない場合があります。このような場合は、Active Compilation Conditions を使用して、コードを除外するための条件を指定してください。
まず、App ClipのTargetのビルド設定に移動し、 Build Settings の Active Compilation Conditions に新しい値を作成します。次に、必要に応じて共有コードにチェックを追加し、App Clipで使用したくないコードを除外します。
次のコードは、Active Compilation Conditionsに追加した APPCLIP
という値をチェックします。
#if !APPCLIP // Code you don't want to use in your app clip. #else // Code your app clip may access. #endif
注:開発中は、Testing Your App Clip’s Launch Experienceで説明されているように、URL をローカルまたは TestFlight でテストします。
サーバーとXcodeプロジェクトの変更
システムがClip Cardを提示したり、App Clipの呼び出しを許可したりする前に、App Clip の設定とURLを検証します。検証を実行できない場合は、Clip Cardは表示されず、App Clipは起動しません。システムがApp Clipを検証できるようにするには、WebサーバーとXcodeプロジェクトに変更を加える必要があります。
最初に、Apple App Site Association ファイルをサーバーに追加します(参照)。次に、appclipsキーに値を追加します。以前に Apple App Site Association ファイルをサーバーに追加した場合は、既存のファイルに appclips キーのエントリを追加します。
追加する内容は以下のようになります。apps キーの値が配列で、1 つのエントリ(App Clipのアプリ識別子)だけであることに注意してください。
{ "appclips": { "apps": ["ABCED12345.com.example.MyApp.Clip"] } ... }
最後に、Xcodeで、アプリと App Clip の両方のAssociated Domains Entitlementに、ウェブサイトのドメインを追加します。
呼び出しの設定
システムは、App Store Connectで設定したURLを使ってApp Clipを起動します。つまり、App Clipを起動するURLを特定するため、App Store Connectに登録することが必須になります。詳細については、Configuring Your App Clip’s Launch ExperienceとResponding to Invocationsを参照してください。
アプリのリリース
App ClipはApp Clip 単体ではリリースできず、本体アプリが必要です。App Clipを公開する準備ができたら、アプリのアーカイブの一部として送信します。App Clipを提出した後、App Storeの審査を通過する必要があることに注意してください。
出典
- https://developer.apple.com/documentation/app_clips
- https://developer.apple.com/documentation/app_clips/developing_a_great_app_clip
- https://developer.apple.com/documentation/app_clips/creating_an_app_clip
続き
potatotips #69 に参加しました (iOS/Androidブログまとめ) #potatotips
potatotips #69 に iOS ブログまとめ枠で参加させていただきました。
今回はTimersさん主催で、Zoomのウェビナーを使ったオンラインでの開催となりました。
[Android] 写真動画のフォルダ選択に対応した話 / akatsuki174 さん
写真フォルダを取得して表示する方法。
流れとしては
- ContentResolver を使ってデータを取得
- 加工しやすい形で格納
- データを表示する
取得するカラムなどを設定する。
ContentResolver のクエリとしてはこんな感じになる。
データの格納をして、
表示はよしなにする。
Android Q(10)〜では、Group by + Count で SQLiteException が起こったが、自前でハンドリングした。DATAはdeprecatedになっていたので、使わないようにした(パス自体は取る方法がある)
[iOS] For Developing URL Routing of SwiftUI App (SwiftUI時代のURLルーティングを考察する) / freddi さん
URL SchemeなどでURLが開かれて、アプリを特定の開くという状態を考える。
SceneDelegateでURLをパースして、特定の画面を開くとき、
以前は application(_:, open:, options:)
が呼ばれていた。
SceneDelegateではこっちが呼ばれる↑。
特典の画面を開く方法として UIApplication.keyWindow を使う方法があると思うが、これは Deprecated になっている。
SceneDelegate.window
を代わりに使う方法もあるが、scene(_ scene:, openURLContexts:)
を使って以下のように実装した。
ややこしくなってきたらCoordinatorのようなクラスを作るのがおすすめ。
[Android] ML Kit (Auto ML Vison Edge)で写真から家族を検出する / tsutou さん
犬、猫、子供、大人の画像を合計2万5千枚学習させて、入力写真がどれに属するかという分類をML Kit にやらせる。
ML Kit の学習のときは、 オプションに、下限レイテンシ、汎用、高精度を設定できる。
5時間ぐらいで割と精度が出た。
ML Kit はローカルのモデルを使用する運用、リモートのモデルを使用する運用、両方が選択できる。両方というのもあり。ローカルでやる場合はアプリサイズが増えてしまうが、それが許容できる場合はローカルに置いて、適宜リモートも使うという方法がよさそう。
実装方法はスライド参照。
[Q] アプリサイズはどのくらい大きくなりますか?
[A] 今回のだと 4MBぐらい
たぶん転移学習なのでそんなに容量食わない #potatotips
— Monkuma👾 (@kumamo_tone) 2020年4月27日
[macOS] Virtual Web Cameraを作ってみよう / kishikawa katsumi さん
Snap Camera 、どうなってるんだろうと思って中を見てみた。
インストーラーを見てみると、.pluginというファイルをインストールしているっぽい。
ここに置くとあたかもカメラが増えたようになる。
情報が少ないながらもにわかに盛り上がり始めている。
公式は、情報が32bit時代のやつでめちゃくちゃ古いが、PDFは2枚でよくまとまっている。
これを踏まえた上で、OBS用にやってくれてる人のコードとか、
同じ人が CoreMediaIO DAL plugin のサンプルを公開してくれたりしている。
さらにSwiftで書き換えてくれている人もいる。
これらを参考に、ウィンドウを重ねれるアプリを作ってみた。
やり方、つまづきそうなところだけ紹介する。まず、info.plistについて。Factory interface と右下のやつはなんでもいいんだけど値を合わせないといけない。
エントリーポイントの名前はここに対応している(C++の場合)。
サンプルバッファをCIImageにして位置を変えて合成。CIImageにしてオーバーレイ。AVCaptureSessionで画面をキャプチャ、ウィンドウのキャプチャもできる。データの受け渡しは、プロセス間通信が使えるはず。ただしモダンなやつが使えない。
[Android] Troubleshooting on Build & Release Flutter apps / Kenichi Kambara さん
Flutterアプリをリリースしようとしてみた。
Android側はRelease用の証明書をコンソールに登録しないと通らない。
Androidは透過画像がアイコンに設定できるのだが、iOSはアルファチャンネル入っているとArchive→Validate Appで弾かれる。
ソーシャルログインがある場合、Sign with Apple 対応しないとリジェクトされます。
Sign with Apple未対応でリジェクト、しょっぱい #potatotips
— Monkuma👾 (@kumamo_tone) 2020年4月27日
[iOS] Touch Bar on Catalyst / fromkk さん
以下書いたけど公式の書き起こしがあるのでそっちを見たほうが良い。
Catalyst から Touch Bar が使える。NSTouchBarとして表現される。限られているものの、 6種類ぐらい使える。
実装方法。
NSTouchBar.CustomizationIdentifierを定義。どのTouchBarかを識別する。
UIResponderはUIViewとかUIViewControllerとかが継承しちゃってるので複数が普通にあり得る...なので1つに絞る必要があるという感じ
— かっくん@社会復帰準備 (@fromkk) 2020年4月27日
NSTouchBarItem.Identiferで表示する項目の種類を識別。
CatalystではmakeTouchBarというメソッドを実装する。
NSTouchBarに生えているプロパティはこんな感じ。
カスタマイズできるかどうかはここで設定できる。
NSTouchBbarDelegateを実装する。メソッドは1個しかない。
NSTouchBarItemには、customizationLabelを設定しておくと、カスタマイズ時にラベルが表示されるようになる。
Touch Barを検索する場合順序があって、こんな感じになっている。
注意点。Swift UIでは表示できなかった。また、スライダーを表現するNSSliderTouchBarItemから取得するプロパティが無い。keyから取ってこれるけど心配。
サンプルコードは公開しています。
[Android] Tips on Databinding in RecyclerView / Shota Ara さん
RecycleView で LiveData を DataBinding する。
lifecycleOwner と model を binding に設定する。
今回話したいのは、lifecycleOwnerについて。
ここに何を渡すかというと、Activityなら、Activity自体だが、Fragmentの場合はviewLifecycleOwnerを渡す必要がある。
RecyclerViewの場合は? binding.root.contenxt as LifecycleOwner
を渡す。一見できているように見えるが、これだけだと挙動的にメモリリークしている。
解決方法。FragmentのLifecycleOwnerを渡せば良い。また、viewHolderをLifecycleOwnerにするという手もありそう。
[iOS] e-Conte board 開発話 / codelynx さん
https://speakerdeck.com/codelynx/e-conte-board-dev-story
絵コンテを作るためのアプリを作った。Apple Pencilで書き込める。
Core Graphics を使っている。
スクロールする方法はいくつかあるが、今回は3を採用(ペンと指で別の機能にする)。
タッチでdrawできなくしたり、pencilのときはZoomできなくしたりできる。
また、縦に広がるSegmentedToolControlというのを作った。
これについてはAdventCalendarに書いた。
また、コードはGithubで参照できる。
ちなみにアプリはダウンロードできるけど課金しないと使えない このアプリ使う可能性のある人って100人ぐらいしかいないので全員にダウンロードしてもらっても採算取れない せつない
[Android] ContextThemeWrapperでThemeをより賢く / rmakiyama さん
スライド
声
ContextThemeWrapperでThemeを動的に置き換えられる。
例として、ステータスが変わったらImageViewの背景色を変えるというのをやってみる。
既存のcontextを第1引数、置き換えたいやつを第2引数に指定する。
Viewの生成のときにも同じようにできる。
[iOS] SwiftUIでNeumorphism!!! / Ryo Tsuzukihashi さん
iOS6ぐらいまで使われていた、Neumorphismという考え方がある 現実の物質に似せて、直感的に操作を理解させるというやつ。
実装、ColorのExtensionでベースのカラーコードを受け取り、ベースの輝度のみを変えたもので影を作る。
shadow(color:radius:x:y)
を呼び出したら簡単に Neumorphism の影がつけられる。ライブラリにした↓
[Android] Wi-Fi Suggestion API / syarihu さん
[potatotips #69] Wi-Fi Suggestion APIhttps://t.co/4XzgnD7CHl
— syarihu🌙🎸syarihu.net (@syarihu) 2020年4月27日
本日の発表資料です!
発表聞いてくださったみなさまありがとうございました!#potatotips
おすすめの通知ダイアログとともにWiFi情報を自動で追加してあげる機能がある。
Q以上の場合は以前のAPIがdeprecatedになっているのもあり、Wi-Fi Suggestion API を使うと良い。
一回いいえで拒否されると、割と深いところで権限を取り直してもらわないといけなくなる。のでこれを出すときは割と丁寧なコミュニケーションをしないとユーザビリティ的に面倒なことになる。
このメッセージだが、2019年12月5日のアップデートで変わったので、同じAndroid 10 でもメッセージが違うという罠がある…
ブラックリストに入れられるとSSIDはどこからも確認できなくなってしまうので、解除について注意事項を書いておいたりすると良い
アンインストール時には削除されるのは、覚えておくと良さそう。
また、Android11以降は、大きな変更が入ってるっぽいので注意してみておくと良さそうとのこと。
[iOS] Custom Gesture Recognizer の世界 / TachibanaKaoru さん
Gesture Recognizerの話
ジェスチャが認識されている途中で音を鳴らすとかしたい
そういうときのために Custom Gesture Recognizer がある。
UITouchのpreviousLocationには直前にタッチした場所が入っている。
この位置が現在の位置と離れていれば離れているほど速く動いているってこと。
速さと同じようにgradient(方向)も取ることができる。
ジェスチャの各状態のときに、速さと方向が取れる。
P→P→P→Rなパターンのみ成立とみなす。
ストロークによっては、ユーザーの入力を意図通りに判断するのが難しい場合もあるため、状況に応じた最適化が必要。
コードはこちら
そのほか
- リモート参加だとディスプレイ広く使えるのでブログ書きやすくて良い
- カナダと福岡から発表している人もいてそれも良かった
- 個人的に内容も勉強になるのが多くてよかったです!
- QAがZOOM上でやれるのも良い
「2020/4/27 #potatotips (iOS/Android開発Tips共有会) 第69回」をトゥギャりました。 https://t.co/Tc45G36mPI
— Kosuke Ogawa🌒アルエンジニア🏝宮崎 (@koogawa) 2020年4月27日
AtCoder Beginner Contest 159 (ABC159) A,B,C 解説
自分は出なかったのだが、全体的に数学っぽい問題だったっぽい。実際(実際って何?)、AからCまでfor文ひとつ書かずに解くことができる。
自分はコンピュータサイエンスで修士取ったくせに数学苦手意識がすごく強いので、Twitterの感想見てて自分が参加してたらつらそうだったな〜と思った。
ついでに見たPDF版の解説が分かりにくかったのだが(すいません、個人の感想です)解説動画の内容がわかりやすいと思ったので、補足しつつ書き残しておく。
PDF: https://img.atcoder.jp/abc159/editorial.pdf
A - The Number of Even Pairs
問題概要
偶数が書いてあるN個のボールと奇数が書いてあるM 個のボールがある。ここから2つ選んで、書かれた数の和が偶数になるパターンの数を求めよ。
答え
偶数が書いてあるボールのグループ、奇数が書いてあるボールのグループから、それぞれ1個ずつ取る場合は絶対に奇数になる。
逆に、同じグループから選ぶ場合は絶対に偶数になる。
なぜなら、偶数、奇数の性質上、
奇数+奇数=偶数 // いいね‥
偶数+偶数=偶数 // いいね‥
奇数+偶数=奇数 // ダメです
となるので。
そのため答えは
N個のボールから2個選ぶ= =
パターン
M個のボールから2個選ぶ= =
パターン
を足したものになる。
ちなみに ってなんだっけとなったときは、ググると
=
ということがわかり、今回 r=2 なので
=
=
=
となる。
int main() { int N, M; cin >> N >> M; int ans = N*(N-1)/2 + M*(M-1)/2 ; cout << ans << endl; return 0; }
書きながら N*(N-1)/2
の結果って常に整数になるの?って一瞬思うが、これも偶数と奇数の性質上、
- Nが奇数だったらN-1は偶数
- Nが偶数だったらN-1は奇数
- 奇数×偶数は常に偶数
のため、絶対 N(N-1)
は偶数となり、2で割っても大丈夫。
B - String Palindrome
問題概要
akasaka のような、回文な上に、中心の文字の左右の文字列も回文になっている文字列かどうか判定せよ。
答え
問題文が厳密な書き方をしているので一見”"強い""回文って何?って思うが、上記の概要みたいなことを言ってるというのに気付ければ、そのまま実装すれば良いとなる。
まず回文かどうかは、以下の関数で求められる。
bool isPalindrome(string s) { string t = s; reverse(t.begin(), t.end()); // t をひっくり返したやつが return s == t; // 同じだったら同じ }
文字列をひっくり返す方法は最近の大体の言語で関数化されてるはずなのでC++以外の場合はそういうのを使うとよさそう。
あとは、全体を回文かどうか判定したあとに、真ん中の文字から左の文字が回文になっているかどうかだけ判定すれば実は右側の文字は判定しなくて良くて、以下のようになる。
int main() { string s; cin >> s; bool ans = true; if (!isPalindrome(s)) { ans = false; } string leftSub = s.substr(0, s.size()/2); if (!isPalindrome(leftSub)) { ans = false; } cout << (ans ? "Yes" : "No") << endl; return 0; }
C - Maximum Volume
問題概要
縦、横、高さの長さの合計が L の直方体としてありうる体積の最大値を出力せよ。
答え
直感的に(は?)、縦、横、高さが同じの場合に体積が一番でかそうだと気づいた場合、縦、横、高さがLの1/3のときの体積を求めればいいじゃんとなる。
なので、シンプルに
(L/3)3
を計算すれば良い。
int main() { double l; cin >> l; double ans = pow(l/3,3); cout << fixed << setprecision(12) << ans << endl; return 0; }
なんだその直感は
これだけで問題は解けるが、マジでそれで大丈夫か?となる。
これは相加相乗平均不等式というやつで証明できるっぽいが、もっと直感的に(?)確かめる方法がある。
まず、縦、横、長さが同じときの体積を a×a×a …(1)とする。
その状態から、ほんのちょっと ε だけ長さを変えたときの体積は、 a(a + ε)(a - ε) …(2) になる。
このとき、 (1) と (2) を比較すると、
a×a×a と a(a + ε)(a - ε)
a×a と (a+ε)(a-ε)
a2 と a2 - ε2 // 左のほうが絶対デカいね
となり、ε>0 (ほんのちょっとって言ってるのに0だったらズルじゃん?)から、 (2) は (1) より ε2 ぶん小さいので (1) のほうが確実にデカい、となる。
なので 、縦、横、長さが同じときの体積が一番デカい。よかったですね。
感想
ここまででもむずいなあとなり筆をおいてしまったのだけどCまで8,201人中7,134人解けてるので、たぶん標準的な参加者的にはそうでもないんだろうな…と思うとぐんにょりしてしまった