Jetpack Compose をライブラリ化し動かす手順まとめ
Jetpack Composeとは
GoogleIOで発表された、平たくいうとReact/FlutterみたいなコードでAndroid-Viewを作れるやつです。(見た目はともかく構造としてはReactHooksが近いかもしれません)
しかし、まだリリースは遠く試すにはJetpackのソースからビルドするしか方法しかない模様。ちょっと試すだけならJetpack内のデモを編集でもいいのですが、いかんせん巨大なので時間がかかります。
そこで一度ライブラリ化し、独立したProjectから読み込めるようにするまでの手順をまとめました。
※記事は 2019/05/16 10:00 ごろのソースを用いました。前日はなぜかビルド不能だったので、日付により大きく変わる可能性があるので注意してください。また他のAndroidStudioがあるせいかわかりませんが、別マシンでは一切ビルドできませんでした。
すでにこの方法は不要です。普通にAndroidStudioPreviewをダウンロードするだけで大丈夫です。
続きを読むKotlin-AltJSの初心者向けTips&インプレ
AltJS-Kotlin
Android公式言語採用で一気にメジャーになったKotlinですが、実は JavaScriptに変換できます。いつからかは知りませんが、かなり当初からできたはず(1.0より前からあったはず)。
しかしマイナーすぎてあまり使っている人を見たことがありません。ネットの記事も少なめ。
軽くですが触った範囲でのTips&を記述していきます。
Tips
最初にコツ&ハマりポイントを。あまり出回ってないと思いますので
イベントハンドラ
DOMのクリックイベント等をWindowにイベントハンドラを追加したいことはよくあると思います。こんなやつです
<script> function onLinkClick(param) { } </script> <a href="javascript:onLinkClick('PARAMETER')">LINK</a>
で、このonLinkClick相当をKotlinにしたい。コイツが地味にkotlinだと面倒、というより最初やり方がわかりませんでした。結論からいうと asDynamic()
を使います
fun main(args: Array<String>) { window.asDynamic().onLinkClick = {param : String -> //do anything } }
しかもmain関数内。最初はグローバル関数として記述すると思ってました。
jsの感覚で window.onLinkClick =
と書くとビルド通らないので注意してください。
console.log
コイツも地味にはまりました。 print
ですね。
一番ハマったのが、 改行がくるまでログに出力されない ことです。
このため、 print
単体では出力されません。そのあとの println で一気に出力されます。
これ結構ハマったので注意してください。
もいっこ、Object詳細をログに出してくれません。以下のようなパラメータをjs/kotlinに渡してログ出力させると…?
<a href="javascript:onLinkClick({text: 'I love Kotlin!'})">LINK</a>
上がconsole.log、下がkotlinのprintlnです。見ての通り、オブジェクトの詳細がわかりません
なので kotlinでも console.log を使ったほうがいい です。printを使うメリットが分かりませんでした。記法が少し短い程度でしょうか? 基本はconsole.logがいいと思います。
js連携
jsのコードと連携する場合、external
で定義ファイルを記述します。Typescriptでいう、 .d.ts
ですね。
ためしに以下のようなjs用をKotlinで使おうとすると…?
//myMathをモジュール的に使ってるイメージ myMath = {} //モジュール内関数 myMath.sin = function(degree) { return Math.sin(degree * Math.PI / 180.0) } //クラス myMath.Point = function(x, y) { self = this; this.x = x; this.y = y; this.abs = function() { return Math.sqrt(self.x * self.x + self.y * self.y); } }
Kotlin用の定義ファイルはこうなります。
//モジュールはclassで包む。もっといい方法あるかも external class myMath { //myMath.sinはstatic関数相当なので、companion objectで記述 companion object { fun sin(degree: Double) : Double } //内部クラスはそのまま。ただしコンストラクタは constructor に限定される模様 class Point { constructor(x: Double, y: Double) fun abs() : Double } } //ためしに使う fun main() { println(myMath.sin(90.0)) //1 val p = myMath.Point(1.0, 2.0) println(p.abs()); //2.23... }
こんな感じ。正直自分も把握しきってないです(触り始めたばかりですし)
適当に書いたあと出力JSファイルをみて、それが意図した結果になってるか確認してることが多いです。.d.ts
があるならそれベースでコンバートするのが楽でしょう。
小数はDoubleで
個人的にですが、小数は全部Doubleで扱ったほうが楽でした。Floatだといちいち末尾に f
が必要でちょっと面倒。またKotlinはこの辺結構厳格なので、Double/Floatが入り乱れるとキャストが面倒です。
その上、jsにコンバートされたら全部number です。Java変換なら意味ありますがjs変換ではDouble/Floatを使い分けるメリットがありません。そんなわけで記法が楽な Doubleに統一がベスト と判断しました。
IntとLongについてもほぼ同等です…が、こっちは気分で分けています。
ユーザIDなど64bitなものは明示的にLongにして、それ以外はIntとしてます。 普段からLongは逆に面倒 なので。
またLongはID程度でしか使わないので加減算などをしないんですよね。よってLongだから扱いが面倒ということはないです(時刻はDoubleで扱う)
こちらもコンバートされた結果はIntだろうがLongだろうが一緒のはず。
ここは地味にKotlinToJSで使いづらい部分ですね。Typescriptと違って整数と小数を分けれるのはいいと思うのですが、それ以上は過剰機能という印象です。
一致しないオブジェクトを渡したとき
たとえば次のような定義ファイルを記述したとします。
external class User { var id : Long var name : String }
idとnameがあるユーザ、よくあるパターンですね。これに {name: "NAME"}
というオブジェクト、idが不足してるオブジェクトを渡した場合…?
//htmlが、href='javascript:onClick({name: "NAME"})' window.asDynamyc().onClick(user : User) { println(user.id) //undefined println(user.name) //NAME }
と、undefinedになります。当然といえば当然ですが。このへんは型に厳しいKotlinといえど注意する必要があるので気をつけましょう。
定義ファイルを全部をNullableとして定義するのも手ですが、コードがかなりめんどくさくなるのがネックです。 このへんはバランスをみて、になるかと思います。
インプレッション
まだ軽く触り始めたばかりですが、インプレッションを
Cons
- 型があるのはやはりいい。TypeScriptより強力!
- 言語仕様はTypeScriptより組みやすい気がする
- IntelliJが賢い。VSCodeより補完がきく印象
- IntelliJが一つのJSファイルとして出力してくれるのでwebpack等を記述する必要がない。楽。
- どうもmain関数がある部分をエントリーポイントとして出力する模様
Pros
- HTML/JSとの連携が難しい。ここはTypeScriptに軍配があがる
- 型定義ファイルがほとんどない。自作する覚悟が必要。
- 標準クラスの設計がJava基準で、JSの標準APIとは相性が悪い
- 規模が大きい場合フレームワークをどうするか。
- React/Angular/Vueのどれとも相性悪そう
- フルスクラッチでいく覚悟が必要かもしれない
個人的印象ですが、 Kotlin内で閉じれるならすごくいい。閉じれない(外部JSと連携必須)ならTypeScript ということろでしょうか。
もちろんKotlin用外部ライブラリも少ないです。Androidのものなんて当然使いまわせません。
それに加え規模が大きくなるとReactやAngularは使いたくなるでしょう…ここは鬼門ですね。 小規模なプロジェクト以外は導入が難しそうなのに、小規模なプロジェクトで厳しい型チェックをするメリットは薄いというジレンマ。主力SPA系フレームワークとの相性悪そうなのは痛い。かろうじてAngular?
ちょっと面白いので趣味開発はありですが、業務採用となるとかなりハードルたかそうです。
こんな記事を書いておいてなんですが、個人的にはKotlinToJSよりELMのほうが面白いと思いますw
WWDC Cocoa Development Tips まとめ
Cocoa Development Tips
「あなたが知らないCocoaの29のこと」という面白そうな副題がついてたので見てみました。
Internationalization
UserDefaults
- 4つの階層がある
- Argument Domain / Application Domain / Global Domain / Registration Domain
- 読み込み時は、先頭から順に問い合わせる
- 書き込み時はそれぞれの階層で手法が異なる
- Registration Domain > UserDefaults.standard.register([“key” : value])
- ↑これは永続化されない
- Global Domain > defaults write GlobalDomain key -bool value
- ↑コマンドラインから叩く。コードではない
- Application Domain > UserDefaults.standard.set(key, value)
- ↑よく使うもの
- Argument Domain > 起動時に設定。書き込みはない
- 4つの階層がある
役立つUserDefaults
-NSViewLayoutFeedbackLoopDebug YES
- 繰り返し発生しているレイアウトを追跡しやすくなる
-NSApplicationCrashOnException YES
- クラッシュ時の例外をキャッチして表示してくれる
UserDefaults + KVO
- UserDefaultsはKVOで監視できる
//新機能 extension UserDefaulst { @objc dynamic var showAtLaunch : Bool { return self.value(forKey: "showAtLaunch") as? Bool ?? false } } let observation = UserDefaults.standard.observe(\.showAtLaunch) { observed, change in //処理 }
Asset Catalogs
Unit Test
- XcodeのNewFileからUnitTestを選択することでテンプレートが生成される
NSBox
Restorable State
- こちらもMacOS専用
- NSResponderに状態を保持させ、再起動で復元させる?
- restorableStateKeyPathsプロパティを使えば楽になる?
CoreData
- ※もはやRealmの時代だとは思いますが
- NSPersistentContainerを使えば厄介なCoreData初期化で楽できる
- 配列を保存したいときは、RelationShip で ToMany を選択する
- 必要に応じorderedにもチェックをいれる
- Modelの新バージョンを作り、あとはCoreData側がマイグレーションしてくれる
- エラーは適切にハンドリングする
- 特に追加時
NSError
- NSErrorには各種メッセージがあるので、適切にユーザに表示する
- 自分でエラーを発生させるときも適切にメッセージを設定するように
- CocoaErrorを使えば楽できる(新規)
- 専用のドメインを作えばハンドルしやすくなる?
SharedKeySets
- 頻出するキーを渡しておけば高速化できる
- NSDictionary.sharedKeySet と NSMutableDicitonary.init(sharedKeySet:) をペアで使う
let set = NSDictionary.sharedKeySet(forKeys:["id", "name"]) let dict = NSMutableDictionary(sharedKeySet: set) dict["id"] = ... dict["name"] = ...
Accessibility
- VoiceOverはIBから簡単に設定できる
- Appを1024x640でテストしておく
- DeveloperToolのAccessibilityInspectorをうまく使う
Documents
- ユーザに保存させる文章にNSDocumentを使えばVersionControlなどいいようにやってくれる?
- CocoaAPIは使ったことがないのでイマイチつかめず
Reporting Exceptions
- NSApplication reportExceptionをオーバーライドすれば例外をロギングしやすくなる
Debugging
- XcodeのDebug領域をうまく使う
- View hierarchy, Debug memory graph, simulate location
Sound
- NSButtonにSoundを設定できる
※セッション一覧ページにはiOS/Mac/watchOS/tvOSとあったのですが、思ったよりMacでした。CocoaTipsなので当然かもしれませんが。
【書評】関数プログラミング実践入門
関数プログラミング実践入門
1年ちょっと前に買ったものですが、読み直しのついでにレビューをしてみます。
※残念ながら改訂版ではありません..もちろん個人の主観に基づくレビューですので、そこはご了承ください。
購入動機
- SwiftやScala/Java8等、関数型の側面が入った言語を触ることが多くなった
- それに伴い、関数型の知識が欲しかった。より関数型らしく組むには が欲しかった知識
- 特定の関数型言語の知識は求めていない
- 上記理由により、汎用的に関数型の知識を扱っていそうなこの本を選定
TL;DR
全体的に
長所
短所
- 他言語との実装比較が冗長に感じられた(1章)
- 各種サンプルが、関数型であるメリットをもっと感じる内容だとより良かった
レビュー
思ったよりHaskellの本だった、というのが正直なところです。タイトルから期待する内容と実際の内容にかなりのズレがあります(個人的印象ですけど)。タイトルが「Haskell実践入門」だったらまた違ったでしょう。(買わなかった可能性は大きいですが)
タイトルに沿った内容らしいのは、
- 「型付け」などプログラミング全般の話題に触れる0章
- 関数型らしい設計手法を提示した6章
ぐらいでしょうか?
とはいえ実はこの0章が最も関数型のメリットを解説しているのではないかと思います。
関数型言語の特徴、参照透過性と並行処理の相性の良さ、テストのしやすさなどなど関数型のメリットが分かりやすく提示されています。(関数型言語の、というよりHaskellの、というべきかも)
また、6章も「数独」という入門本にしてはかなり複雑な題材を取りあつかい、どのように設計していくかが述べられておりここも面白かったです。
この手の入門書としては相当高難度な題材といえるのではないでしょうか?しかももろに「アルゴリズム」の話で関数型とも相性がいいです。DBやHTTPが不要な分、完全にロジックに集中できます。
ここは本当に見ごたえがありました。ソースコードも豊富で当時は初Haskellでしたがなんとかなりました。
一方で 残りの章は普通のHaskell解説本です
特に1章が厳しい。いくつかのよくある実装パターンでHaskellとの比較を行っているのですが、「相手言語の苦手な分野と比較している」のは残念な印象でした。
たとえば
-
- 前のページで取り扱ってたJavaには型あるんだけど…
CだとTuple返せないから複数の値を返したい時不便でしょ?
- 次のページのJSならできるのでは…? いや、Cこそやりたい放題な気が。
というイメージ。Haskellの優位性をよく分からない理由付けで解説してるだけに感じてしまいました。
残りの章は主にHaskellの文法解説+αです。
Haskell入門書としては「FunctorやApplicativeなど厄介な部分を除き」良書という印象。
翻訳書籍でないので変な日本語に悩まされることもありませんし、各単語の説明もきっちりしてて読みやすいです。
問題はそのへんのつまづきやすい部分をさらっと流してること
この説明だけでIOモナドを扱える人はほぼいないのではなかろうか…? その辺は他の書籍で補完しないと厳しいです。
「数独のためのHaskell解説」と割り切ってる感はあります。確かに数独ではモナドを意識して使わないので数独部分を理解するには十分ではありました。(List/Maybeは他言語にもあり、モナドと意識せず扱えるから困らない)
ちょっと厳しい感想になってしまいましたが、読んでよかったと思ったのは間違いありません。これを読んでいなかったらelmは理解できなかった、触ろうとも思わなかったはずです。
ただしHaskell入門としては明らかに足りていません。特にモナド周り。ここは注意してください。
しかし6章の数独はそれをひっくり返すぐらい面白かった。先ほども述べたとおり、1-5章はこのための準備と割り切る必要はあると思います。
こういう内容が豊富でもっと具体的な設計手法を見れたら/試せたらより良かったと思います。
この数独を経験することでかなりオブジェクト指向での組み方が変わりました。具体的には「参照透過性のあるメソッド」「ないメソッド」を分離したくなる癖が付きました。(参照透過性のあるstaticメソッドがすごい増えた)
Androidでの簡単な例だと以下のようなイメージです。
//before if (state.isActive && !state.isLoading) { button.setText("送信"); } else { button.setText("通信中"); } //after static String getButtonText(State state) { return (state.isActive && !state.isLoading) ? "送信" : "通信中"; } String text = getButtonText(state); //なぜか一旦変数に格納したくなる症状も出た button.setText(text);
とにかくsetTextは1箇所に限定したくなる癖がつきました
コードとしては若干面倒になってますが、副作用を伴う操作は極力減らしたい思考に変わりました。
冗長な面やHaskell入門として足りない面はありますが、このような考え方をかえるきっかけとなった本なので良かったです。
特にelmにつながったのは大きい。(少なくともこの本で得た知識おかげである程度なんとかなったのは事実)
ターゲットとする読者層は狭い気はしますけど、上記のような考えかたに触れてみたいのでしたらオススメです。
逆に本格的にHaskell(含む他の関数型言語)に触れたい、商用投入したい/してるプロダクトに触れる必要がある、というのでしたらその言語に特化した他の本を購入されたほうがいいかと思います。
WWDC2017 Introducing ARKit まとめ
iOS11の大きなウリの一つ、ARKitのセッションです。
動画はこちら
Introducing ARKit
ARとは?
- Virtualな物体をPhysicalな世界に配置すること
- ポケモンGOを始めとするデモ
ARKit
概要
3つの機能がある
Unityに組み込まれている
API概要
ProcessingとRenderingに分かれる
- RenderingがMetalやSceneKit
- ProcessingがAR-Kit
- 内部でAVFoundationやCoreMotionを用いる
ARKit
ARSessionConfiguration
if ARWorldTrackingSessionConfiguration.isSupported { configuration = ARWorldTrackingSessionConfiguration() } else { configuration = ARSessionConfiguration() }
** ARSession
* ARプロセスの管理
* run
/ pause
など
* ARSessionDelegate
で更新された ARFrame を受け取る
** ARFrame * ARレンダリングに必要な情報がある * カメラ画像 * トラッキング情報 * Scene情報 * ARAnchor * 実世界のポジション * ARSessionに追加/削除する
Traking
ARKitは座標だけでなく、「実世界の距離」も計測する
- セッション開始時点からの位置になる
- カメラから複数の特徴点を抽出、その位置差分で位置を出す
- 加速度センサも活用している
サンプルコード
let session = ARSession() session.delegate = self let configuration = ARWorldTrackingSessionConfiguration() sesison.run(configuration)
- ARCamera
- 仮想的なカメラ情報を提供するオブジェクト
- Transform/TrackingState/Camera intrinsics
Demo
デモで気になったクラスをピックアップ
- ARSCNView : ARシーンレンダリングの便利クラス
- SceneKitをはめることができる
arScnView.scene = scnView
- UITapGestureでタップ判定可能
- ARSCNView : ARシーンレンダリングの便利クラス
タップでスクリーンキャプチャを画像としてSceneに追加するサンプル
func handleTap(...) { guard let currentFrame = sceneView.session.currentFrame else { return } //キャプチャした平面Object作成 let imagePlane = SCNPlane(width: sceneView.bounds.width / 6000, height: sceneView.bounds.height / 6000) imgPlane.firstMaterial?.diffuse.contents = sceneView.snapshot() imgPlane.firstMaterial?.lightingModel = .constant //シーンに追加 let planeNode = SCNNode(geometry: imagePlane) sceneView.scene.rootNode.addChildNode(planeNode) //カメラの10cm先に配置 var translation = matrix_identity_float4x4 translation.column.3.z = -0.1 planeNode.simdTransform = matrix_multiply(currentFrame.camera.transform, translation) }
Trackingの品質
カメラの環境に大きく左右される
Trackingの状態
func session(.., cameraDidChangeTrackingState camera: ARCamera) { if case .limited(let reason) = camera.trackingState { ... } }
func sessionWasInterrupted(...) { showOverlay() } func sessionInterruptionEnded(...) { hideOverlay() //必要に応じAR処理を再開 }
Scene Understanding
バーチャルな物体を配置するときに必要な情報のこと
- 平面検出
- ヒットテスト
- 光量推定
平面検出
- テーブルのような水平面を検出できる
- 複数のフレーム情報から計算する
- 広さを測定できる
- 面をマージできる
- ARWorldTrackingSessionConfigurationの
planeDetection
を使う- 現状、
.horizontal
しか値がない模様
- 現状、
ARPlaneAnchor
- 平面が検出されるとDelegateが呼び出される?
- 3軸回転、中央、広さを保持している
HitTest
- カメラからのみた特定の直線が実世界平面のどこに位置するかを調べる
- 4つの種類がある
- Existing Plane Using Extent: 平面の位置/広さを考慮したヒット位置
- Existing Plane : 平面の広さが無限とした場合のヒット位置
- Estimated Plane : まだ平面が検出されてないときに使う
- Feature Point : 面が小さいときに用いる(イレギュラーなケースらしい)
- 画面の座標ベースでテスト可能(0,0が左上、1,1が右下)
光量推定
- 画像ベースの光量推定
- 1000ルーメンがデフォルト
- Configurationで設定する
構成
SceneKit/SpriteKit/Metalのいずれか
SceneKit
- ARSCNView
- カメラ画像を表示する
- SCNCameraが人にあわせ移動する
- 自動でLightingされる
- SCNNode が ARAncher にマップされる
SpriteKit
- ARSKView
- SKNode が ARAnchor にマップされる
- SpriteKitは2D描画用
- 物理計算にテーブルを使うなどが出来る(?)
Metal
- カスタムレンダリングに用いる
SceneKitで配置するだけで動くのは良さそうですね。3Dはあまり経験がないので厳しいと思っていたのですが、結構簡単にできそうでちょっと興味わいてきました
WWDC Introducing Drag and Drop まとめ
今回はiOS11の目玉といえるドラッグ&ドロップ機能のセッションです。
他にもドラッグ&ドロップはセッションが多いので、Appleの気合のいれようが伝わってきますね。
とりあえずIntro部分のセッションを。
Introducing Drag and Drop
ビデオは こちら
Drag&Dropのコンセプト
Drag&dropとは?
- タッチによるデータの移動・コピー
Drag&Dropのゴール
- 素早い応答。非同期取得
- セキュリティ。アクセス制限など
- マルチタッチで直感的に操作できる
基本的にiPad向けの機能
- iPhoneでは同一アプリ内に制限される
API概要
- 4つのフェイズがある
- Lift : 長押しでドラッグ可能になった状態
- Drag : プレビューが移動してる状態。
- SetDown : キャンセル、もしくは転送先の決定
- DataTransfer : ターゲットへのデータ転送
Drag元のAPI
- DragInteraction 系APIが追加
- UIDragInteraction, UIDragInteractionDelegate
- UIDragItem
let view : UIView = ... let delegate : UIDragInteractionDelegate = ... let dragInteraction = UIDragInteraction(delegate : delegate) view.addInteraction(dragInteraction)
Delegateの役割
- Lift時に、
UIDragItem
を返す - データが無い場合はDragフェイズ終了。
- Lift時に、
UIDragItemとは?
- 以下の2つを持つ
- Dragプレビュー
- NSItemProvider
- 以下の2つを持つ
Drop先のAPI
- UIPasteConfiguration が追加
- 対応するデータ種別を指定する
- Drop対象のViewに追加する
let config = UIPasteConfiguration(typeIdentifiersForAcceptingClass : NSStrnig.self) view.pasteConfiguration = config //UIViewを継承して作る? UIResponderのメソッドだった override func paste(itemProiders: [NSItemProvider]) { }
- UIDropInteractinoDelegate で受け取りを行う
- 受け取り可能かどうかを指定するのもこのDelegate
フェーズ詳細
Lift
- ロングプレスで移動可能になった状態
- ドラッグ待ち
- Delegateで「移動するデータとプレビュー用View」を返す
//アイテムの決定 func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] { let itemProvider = NSItemProvider(object: "Hello World" as NSString) let dragItem = UIDragItem(itemProvider: itemProvider) return [ dragItem ] } //プレビュー func dragInteraction(_ interaction:UIDragInteraction, previewForLifting item:UIDragItem, session:UIDragSession) -> UITargetedDragPreview? { let imageView = UIImageView(image: UIImage(named: "MyDragImage")) let dragView = interaction.view! let dragPoint = session.location(in: dragView) let target = UIDragPreviewTarget(container: dragView, center: dragPoint) return UITargetedDragPreview(view: imageView, parameters:UIDragPreviewParameters(), target) } //リフトアニメーション func dragInteraction(_ interaction: UIDragInteraction, willAnimateLiftWith animator: UIDragAnimating, session: UIDragSession) { animator.addAnimations { self.view.backgroundColor = UIColor.gray } animator.addCompletion { position in if position == .end { //リフト完了 } else if position == .start { //リフトキャンセル。元に戻る } } }
Drag 送信
func dragInteraction(_ interaction: UIDragInteraction, previewForCancelling item: UIDragItem, withDefault defaultPreview: UITargetedDragPreview) -> UITargetedDragPreview? func dragInteraction(_ interaction: UIDragInteraction, item: UIDragItem, willAnimateCancelWith animator: UIDragAnimating) func dragInteraction(_ interaction: UIDragInteraction, session: UIDragSession, didEndWith operation: UIDropOperation)
Drag 受信
- 移動先が「データを受け取れるか」を返す
- DropInteractionDelegate
//sessionをHandleできるかどうか func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool { return session.canLoadObjects(ofClass: UIImage.self) } //handle可の後、Viewに入った時に処理可能かどうか func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal { return UIDropProposal(operation: UIDropOperation) } // func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession)
DropProposalは4種類ある
- cancel
- copy
- move
- これは同一アプリ内限定
- 移動するように作り手があわせる必要がある
- 送信元が「move可能」とする必要がある
- これは同一アプリ内限定
- forbidden
- Cancelににてるが、特殊なバッジがつく
- 利用例: ReadOnlyなフォルダに移動させようとした場合
SpringLoading(レインボーカーソルと訳すべきか?)
- UIButtonに追加
let button = UIButton() button.isSpringLoaded = true let springLoadedInteraction = UISpringLoadedInteraction { (interaction, context) in //長時間の処理 } view.addInteraction(springLoadedInteraction)
Drop
- drop後は
dropInteraction
が呼び出される- データを読み込む場所
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) { session.loadObjects(ofClass: UIImage.self) { objects in for image in objects as! [UIImage] { self.imageView.image = image } } }
デモ
動画29分からコードを交えながらのデモ
- コルクボードに写真をおくサンプル
- 気になった部分のみメモ
同一アプリ判定
- DropSessionで同一アプリ内か外部アプリかで処理分岐が必要になる
- アプリの仕様によりますけど
//同一アプリ内なら移動、別アプリからならコピー func dropInteraction(..., sessionDidUpdate ...) -> UIDropProposal { let operation: UIDropOperation if session.localDragSession == nil { operation = .copy } else { operation = .move } return UIDropProposal(operation: operation) } //Drop後 func dropInteraction(..., performDrop ...) { if session.localDragSession == nil { //コピー dropPoint = session.location(in: interaction.view) for dragAtime in session.items { loadImage(dragItem.itemProvider, center: dropPoint) } } else { //ボード内での移動、データコピーは不要 } } //Dropアニメーション func dropInteraction(_ ... previewForDropping: ...) { if session.localDragSession == nil { //外部ならなにもしない } else { //内部なら移動処理 return defaultPreview.retargetedPreview(...) } }