- 投稿日:2019-07-30T23:15:47+09:00
「ソースコード全部読まなきゃ病」と闘う方法
「OSS読んでパターンの勉強したくて、毎回挑戦してるんだけど、いっつも諦めるんだよね...。」とか「会社で新しいソースコード読む事になったんだけど量が多くて大変だよね...。」みたいな事をよく思っていた自分のような方を対象に、こうすればいいんじゃい?と自分が思ったことをまとめます。
最大の恐怖「全部読まないといけない」
自分のOSSリーディング活動を邪魔している感情や考えが、まずこの「全部読まないといけない病」にあることを自覚しました。「すごい人はOSSを毎日のように読んでる」「フレームワークの中身を全て知っている」こんな決めつけが根っこにありました。
それでGitHubでアルファベット順にならぶコードを上から順番に読もうとして途中で飽きたり挫けたり...。あるあるじゃないでしょうか?
ただよく考えるとそれって意味ある?という疑問が湧きました。
例えば小説や本などは最初のページから最後のページまでの時系列データなので、順番に読む事に意味はあります。ただ、ソースコードに関しては時系列データじゃないよね?と。なのでいくつか読み方を考えてみました。
対処法1 「Facadeを読む」
これは自分が上記の問題に対して一番初めに試してみたものなのですが、かなりの効果を感じています。
~Manager
や、OSS利用の窓口になっているAPIって必ずありますよね?まずはそこから読み始めます。一番使う回数の多いところ周辺から徐々に理解をしていく方式に切り替えると、繋がりが分かりやすく意味ある読み方ができそうです。対処法2 「必要なところだけ読む」
対処法1を理解していても「全部理解しなきゃ」と思っていると、どんどん読み込もうとしてしまって時間がかかったり嫌になったりします。なので、今知りたい知識が理解できたらそれより深くは読まないとか、利用していて分からないクラスや疑問が止まらない対象が出てきたら読む。という進め方が出来そうです。
対処法3 「時間を決めて読む」
「いつまでも読める」と考えると、どうしても必要のないコードを読んでしまいます。なので「このコードを読む時間は30分までと決める!この時間内で読むしかない」とすれば、コードを読む優先度を考えて、優先度の高いものから考えて読むでしょう。それを繰り返していくと良いと思います。
実際、自分の抱えているコードすべてをきちんと読もうとすると、読むだけで5年かかることもあります。業務でそればかりするわけにもいかないので、読みきれませんね。関連する部分(+アルファ)だけ集中的に読みます。
presented by @7of9 さん
対処法4 「古いバージョンから読む」
OSSだと古いバージョンほど機能が少なく、コアな機能しか実装されていないので、そのバージョンやコミットを読んでいくことで短い時間で効率良くコア部分に詳しくなる事ができます。
アンチパターンとして新しいバージョンで破壊的変更が行われている場合で、せっかく理解したのに...。という事が起こり得ます。
presented by @pCYSl5EDgo さん
終わりに
「全部読まなきゃ!」→諦めた。成果ゼロ。
よりも
「窓口だけ読んでみよう!」→3ファイル読めた。
の方が健全に進捗が出せていると思うので、「〜できないと〜する価値がない」みたいな考え方はやめて、まず読めるコードから読んでいって、いつのまにか全部読んでた。くらいで良いのではないかと思っています。PS
とりあえず公開して修正してくパターンを取っているので、今後色々加筆修正していきます。
- 投稿日:2019-07-30T18:38:10+09:00
[Swift]UIViewにaddGestureRecognizerしようとしてつまずいた話
問題
UIViewにUITapGestureRecognizerをaddGestureRecognizerしようとしてもできなかった
前提
・isUserInteractionEnabled = true
・UIViewもnilではなかった解決方法
addGestureRecognizer(.init(target: self, action: #selector(tapped)))initを直したら動きました
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapped)))最後に
今思うとaddGestureRecognizerなので、.initで書いたらサブクラスのUITapGestureRecognizerではなく,スーパークラスのUIGestureRecognizerが呼ばれるので、それはそうですよねというお話(引数が同じなのですっかりハマってしまいました。??
- 投稿日:2019-07-30T18:13:36+09:00
SwiftでMapタイプの切り替え方
何をやるのか?
簡易的なMapアプリのオプション機能として、mapタイプの切り替えを行う。
前提
MapKitにてMapが設置されていることを前提とする。
Mapの種類
画面 定義 内容 .mutedStandard 交通機関 .standard 標準の地図 .satellite 航空写真 .hybrid 航空写真
+
ラベル.satelliteFlyover 3D Flyover .hybridFlyover 3D Flyover
+
ラベル実装コード
@IBAction func changeMapButton(_ sender: UIButton) { if Map.mapType == .standard { Map.mapType = .satellite } else if Map.mapType == .satellite{ Map.mapType = .hybrid } else if Map.mapType == .hybrid{ Map.mapType = .satelliteFlyover } else if Map.mapType == .satelliteFlyover{ Map.mapType = .hybridFlyover } else if Map.mapType == .hybridFlyover{ Map.mapType = .mutedStandard } else { Map.mapType = .standard } }if文を使い現在のmapTypeと各mapTypeを順番に比較し、もし同じTypeだった場合次のTypeを代入するという簡単なコードになっています。
x-codeのフレームワークのButtonをStoryboardに貼り、それに対応する関数内に記述すれば問題ありません。最後に
非常に簡単な実装ですが、割と本格的なアプリのように仕上がるので、初学者の方やプログラミングやった事ないけど興味ある人も是非一度実装して、開発の楽しさを体験してみましょう。
- 投稿日:2019-07-30T16:28:17+09:00
UserDefaultsをテストする際にsuiteNameを利用する件
UserDefaultsをテストする際に
init(suiteName:)
でUserDefaultsをアプリとは別に作成し、それに対してデータを保存したり読み出したりしていたが、自分の意図している挙動と違うことがあったので動作確認コードを書いた。そうやってからようやくリファレンスが理解できるようになった。
だけどそのリファレンスを解説してみても間違いがあるかもしれないので、自分の至った結論と、その結論に至った理由と動作確認のコードを示す。最初に結論
- テストごと(Specごと)に別々の
init(suiteName:)
でUserDefaultsを作ったが、テストごとに分ける意味はないかもしれない
- 別々の
suiteName
で作っても相互にその値を 見ることができる
- 理由は後述
- テスト用のsuiteNameとして一つを作るのは意味がある
- 理由は後述
- 書き込みできているかのテストなら書き込む前にクリアすべきかも
- QuickならbeforeSuiteでクリアすると非同期に
it
が動作しているので他のテストに影響を与えてしまう
- 書き込む
it
があるならbeforeEachでクリアするほうが良さそう- アプリ側の挙動を変えないように
- テストが終わったら
init(suiteName:)
で作成したデータが参照されないように削除しておく理由
suiteNameで作った場合のテストとアプリという視点で見たデータ共有
suiteNameでテストごとにUserDefaultsを作る視点で挙動をまとめる
- テストターゲットで
init(suiteName:)
でUserDefaultsを作っても、それはアプリのstandard
なUserDefaultsから読み込める
- ただし、アプリ側で同様のkeyを使いすでに保存してたらそれが優先される(nilならテストコードで入れた値が使われる)
- アプリの
standard
で作成したUserDefaultsはテストターゲットのinit(suiteName:)
では読み込めないよくわからないので視点を変える
suiteNameで分ける場合の細かな挙動
suiteNameで分ける場合の細かな挙動をまとめる
init(suiteName:)
で作成して書き込んだデータはstandard
なデータ(.plist)とは別のデータ(.plist)が作成される
- 例
UserDefaults(suiteName: "A")
のsetメソッドでA.plistとBundleID.plistの2つができるstandard
なデータ(.plist)にないが別のデータ(.plist)にある場合、それはstandard
で別のデータの値で 取り出せる- 別のデータ(.plist)にないが
standard
なデータにある場合、それはinit(suiteName:)
でstandard
を 取り出せるinit(suiteName:)
は複数作れる
- 複数の.plistが作られる
- A.plistになくB.plistにある場合、
init(suiteName:A)
でBのデータが 取り出せてしまう動作確認コード
UserDefaults().removePersistentDomain(forName: "A") UserDefaults().removePersistentDomain(forName: "B") // udA let udA = UserDefaults(suiteName: "A")! udA.set(1, forKey: "foo") udA.set(2, forKey: "bar") udA.set(3, forKey: "baz") print("udA.foo:", udA.integer(forKey: "foo")) // -> 1 print("udA.bar:", udA.integer(forKey: "bar")) // -> 2 print("udA.baz:", udA.integer(forKey: "baz")) // -> 3 print("udA.ud :", udA.integer(forKey: "ud")) // -> 0 // udB let udB = UserDefaults(suiteName: "B")! udB.set(10, forKey: "foo") print("udB.foo:", udB.integer(forKey: "foo")) // -> 10 print("udB.bar:", udB.integer(forKey: "bar")) // -> 2 print("udB.baz:", udA.integer(forKey: "baz")) // -> 3 // standard UserDefaults.standard.set(100, forKey: "foo") UserDefaults.standard.set(200, forKey: "bar") UserDefaults.standard.set(999, forKey: "ud") print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100 print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200 print("stn.baz:", UserDefaults.standard.integer(forKey: "baz")) // -> 3 print("stn.ud:", UserDefaults.standard.integer(forKey: "ud")) // -> 999 // foo見たい print("udA.foo:", udA.integer(forKey: "foo")) // -> 1 print("udB.foo:", udB.integer(forKey: "foo")) // -> 10 print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100 // bar見たい print("udA.bar:", udA.integer(forKey: "bar")) // -> 2 print("udB.bar:", udB.integer(forKey: "bar")) // -> 2 print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200 // 削除したあとどうなるか UserDefaults().removePersistentDomain(forName: "A") print("udA.foo:", udA.integer(forKey: "foo")) // -> 0 print("udB.foo:", udB.integer(forKey: "foo")) // -> 10 print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100 print("udA.bar:", udA.integer(forKey: "bar")) // -> 0 print("udB.bar:", udB.integer(forKey: "bar")) // -> 0 print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200plistの確認法
~/Library/Developer/CoreSimulator/Devices/<シミュレータのID>/data/Containers/Data/Application/<アプリを区別するID>/Library/Preferences/
- 投稿日:2019-07-30T16:28:17+09:00
UserDefaultsをテストする際にuserSuiteを利用する件
UserDefaultsをテストする際に
init(suiteName:)
でUserDefaultsをアプリとは別に作成し、それに対してデータを保存したり読み出したりしていたが、自分の意図している挙動と違うことがあったので動作確認コードを書いた。そうやってからようやくリファレンスが理解できるようになった。
だけどそのリファレンスを解説してみても間違いがあるかもしれないので、自分の至った結論と、その結論に至った理由と動作確認のコードを示す。最初に結論
- テストごと(Specごと)に別々の
init(suiteName:)
でUserDefaultsを作ったが、テストごとに複数のinit(suiteName:)
を分ける意味はないかもしれない
- 理由
- 別々の
suiteName
で作っても相互にその値を 見ることができるstandard
なUserDefaultsでもsuiteName
で保存した値を 見ることができる- Quickで非同期にテストが実行される場合、DIしてないUserDefaultsをbeforeSuiteで決めるのは難しい
- テスト用のsuiteNameとして一つを作る意味は大きい
- 理由
suiteName
で作られたUserDefaultsはstandard
なものとは別なのでテスト終わったら削除すれば参照できなくなる- 書き込みできているかのテストなら書き込む前にクリアしておけばいい
- QuickならbeforeSuiteでクリアすると非同期に
it
が動作しているので他のテストに影響を与えてしまう
- 書き込む
it
があるならbeforeEachでクリアするほうが良さそうもしグローバルなUserDefaultsを保持しているなら
let myUserDefaults = MyUserDefaults.shared() class ASpec: QuickSpec { override func spec() { beforeEach { // 都度suiteNameで指定 myUserDefaults.ud = UserDefaults(suiteName: "A") // removePersistentDomain(forName:)で都度クリアし他のテストの影響を受けないようにする UserDefaults.removePersistentDomain(forName: "A") } afterEach { // removePersistentDomain(forName:)でクリアしアプリに影響を与えないようにする UserDefaults.removePersistentDomain(forName: "A") } it("...") { } it("...") { } } }理由
suiteNameで作った場合のテストとアプリという視点で見たデータ共有
suiteNameでテストごとにUserDefaultsを作る視点で挙動をまとめる
- テストターゲットで
init(suiteName:)
でUserDefaultsを作っても、それはアプリのstandard
なUserDefaultsから読み込める
- ただし、アプリ側で同様のkeyを使いすでに保存してたらそれが優先される(nilならテストコードで入れた値が使われる)
よくわからないので視点を変える
suiteNameで分ける場合の細かな挙動
suiteNameで分ける場合の細かな挙動をまとめる
init(suiteName:)
で作成して書き込んだデータはstandard
なデータ(.plist)とは別のデータ(.plist)が作成される
- 例
UserDefaults(suiteName: "A")
のsetメソッドでA.plistができ、もともとの.plistと2つになるstandard
なデータ(.plist)にないが別のデータ(.plist)にある場合、それはstandard
で別のデータの値で 取り出せる- 別のデータ(.plist)にないが
standard
なデータにある場合、それはinit(suiteName:)
でstandard
を 取り出せるinit(suiteName:)
は複数作れる
- 複数の.plistが作られる
- A.plistになくB.plistにある場合、
init(suiteName:A)
でBのデータが 取り出せてしまう動作確認コード
UserDefaults().removePersistentDomain(forName: "A") UserDefaults().removePersistentDomain(forName: "B") // udA let udA = UserDefaults(suiteName: "A")! udA.set(1, forKey: "foo") udA.set(2, forKey: "bar") udA.set(3, forKey: "baz") print("udA.foo:", udA.integer(forKey: "foo")) // -> 1 print("udA.bar:", udA.integer(forKey: "bar")) // -> 2 print("udA.baz:", udA.integer(forKey: "baz")) // -> 3 print("udA.ud :", udA.integer(forKey: "ud")) // -> 0 // udB let udB = UserDefaults(suiteName: "B")! udB.set(10, forKey: "foo") print("udB.foo:", udB.integer(forKey: "foo")) // -> 10 print("udB.bar:", udB.integer(forKey: "bar")) // -> 2 print("udB.baz:", udA.integer(forKey: "baz")) // -> 3 // standard UserDefaults.standard.set(100, forKey: "foo") UserDefaults.standard.set(200, forKey: "bar") UserDefaults.standard.set(999, forKey: "ud") print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100 print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200 print("stn.baz:", UserDefaults.standard.integer(forKey: "baz")) // -> 3 print("stn.ud:", UserDefaults.standard.integer(forKey: "ud")) // -> 999 // foo見たい print("udA.foo:", udA.integer(forKey: "foo")) // -> 1 print("udB.foo:", udB.integer(forKey: "foo")) // -> 10 print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100 // bar見たい print("udA.bar:", udA.integer(forKey: "bar")) // -> 2 print("udB.bar:", udB.integer(forKey: "bar")) // -> 2 print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200 // 削除したあとどうなるか UserDefaults().removePersistentDomain(forName: "A") print("udA.foo:", udA.integer(forKey: "foo")) // -> 0 print("udB.foo:", udB.integer(forKey: "foo")) // -> 10 print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100 print("udA.bar:", udA.integer(forKey: "bar")) // -> 0 print("udB.bar:", udB.integer(forKey: "bar")) // -> 0 print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200plistの確認法
~/Library/Developer/CoreSimulator/Devices/<シミュレータのID>/data/Containers/Data/Application/<アプリを区別するID>/Library/Preferences/おわりに
そもそも
suiteName
で.plistが分かれてというのは自分が作成した別アプリやウィジェットとのグループ間での共有のためだろうと思う。だから自分のアプリ単体であればsuiteName
で分かれてようが参照できても異常でない、みたいなことなんだろう。
- 投稿日:2019-07-30T13:00:17+09:00
UIDynamicsで物理演算を使った自然なアニメーション
UIKit Dynamics
Viewに物理演算を用いたアニメーションを設定できます。
ドキュメントはこちらUIDynamicAnimator
Viewに物理に基づいたアニメーションを提供する根幹となるもの
var dynamicAnimator: UIDynamicAnimator! override func viewDidLoad() { super.viewDidLoad() dynamicAnimator = UIDynamicAnimator(referenceView: self.view) } }重力を与える
storyboard上に配置した赤いViewに条件を追加します。
重力を与えるにはUIGravityBehaviorを使用します。let gravityBehavior = UIGravityBehavior(items: [redView]) dynamicAnimator.addBehavior(gravityBehavior)ここでデフォルトの重力の大きさは1.0で、大きさ1.0は1000 point/second^2の加速度を意味します。
デフォルトの重力の向きは(0.0, 1.0)です。パラメータ
パラメータとして次のようなものが設定できます。
プロパティ・メソッド名 型 説明 gravityDirection CGVector 重力ベクトル(大きさと方向) angle CGFloat 重力ベクトルの方向(ラジアン) magnitude CGFloat 重力ベクトルの大きさ setAngle(_:, magnitude:) (CGFloat, CGFloat) -> Void 重力ベクトルの方向(ラジアン)と大きさ 他にsetAngle(_ angle: CGFloat, magnitude: CGFloat)メソッドを使用しても設定できます。
例えばaddBehavior(_:)する前に以下のように設定すると、gravityBehavior.gravityDirection = CGVector(dx: 2.0, dy: 2.0) gravityBehavior.magnitude = 1.0一行目は右下に向かって重力の大きさ2.828..($2\sqrt{2} $)で動くような設定になります。
二行目でmagnitudeを1.0にすると、その方向を保ったままベクトルの大きさが1.0になるので最終的に重力ベクトルは(0.707..., 0.707...)となります。衝突境界
衝突境界を追加したい場合にはUICollisionBehaviorを使用します。
デバイスの枠を境界とする
translatesReferenceBoundsIntoBoundaryはreferenceViewを境界として扱うかどうかというBool値です。
referenceViewは最初にUIDynamicAnimatorを初期化した時に設定したViewです。
先ほどこれにself.viewを設定したので、この場合デバイスの画面の端が境界になると考えられます。let collisionBehavior = UICollisionBehavior(items: [redView]) collisionBehavior.translatesReferenceBoundsIntoBoundary = true dynamicAnimator.addBehavior(collisionBehavior)自由に境界を設定
CGPointでどこからどこまでを境界とみなすかを指定します。
第一引数で指定するIDは後でこの境界だけ削除する場合などに使います。let collisionBehavior = UICollisionBehavior(items: [redView]) collisionBehavior.addBoundary(withIdentifier: "floor" as NSCopying, from: CGPoint(x: self.view.bounds.width/2 - 20,y: self.view.bounds.height/2 + 200), to: CGPoint(x: self.view.bounds.width/2 + 20, y: self.view.bounds.height/2 + 200)) dynamicAnimator.addBehavior(collisionBehavior)この他にUIBezierPathを使用して境界を設定することもできます。
パラメータ
collisionModeで何を衝突対象とするのかを指定できる。
CollisionBehavior.Modeで指定できるのは以下。
CollisionBehavior.Mode 説明 動作 .items UICollisionBehaviorに紐付いているアイテム同士だけが衝突する .boundaries 設定した境界のみが衝突対象となり、アイテム同士は衝突しない .everything アイテム同士も境界も衝突する デフォルトは.everything。
弾性係数・抵抗・摩擦
ある物体に対する弾性係数や抵抗など初期条件になるようなものを設定するにはUIDynamicItemBehaviorを使用します。
let dynamicItemBehavior = UIDynamicItemBehavior(items: [redView]) dynamicItemBehavior.elasticity = 1.0 dynamicItemBehavior.resistance = 0 dynamicItemBehavior.friction = 0 dynamicAnimator.addBehavior(dynamicItemBehavior)パラメータ
設定できる項目には以下のようなものがあります。
プロパティ・メソッド名 型 説明 density CGFloat 相対的な質量密度(1.0の密度を持つ100 x 100ポイントのitemに、大きさ1.0の力を加えると、100 point/secondで加速する) elasticity CGFloat 弾性率の大きさ(1.0で完全弾性衝突) friction CGFloat スライドする時にかかる摩擦の大きさ(1.0で強い摩擦、それ以上の値を指定することも可) resistance CGFloat 抵抗の大きさ(CGFLOAT_MAXが最高値、1.0を設定した時は力が加えられなくなるとすぐに停止する) allowsRotation Bool 回転を許可するか(デフォルトでtrue) angularResistance CGFloat 角抵抗の大きさ isAnchored Bool アイテムの位置が固定されているか addLinearVelocity(_:, for:) (CGPoint, UIDynamicItem) -> Void 速度を与える(1秒あたりに動くpoint数を指定) addAngulerVelocity(_:, for:) (CGFloat, UIDynamicItem) -> Void 角速度を与える(1秒あたりに動くラジアンを指定) 外力
アイテムに外力を加えるにはUIPushBehaviorを使用します。
鉛直投げ上げをするとき、プログラムは以下のようになります。let pushBehavior = UIPushBehavior(items: [redView], mode: UIPushBehavior.Mode.instantaneous) pushBehavior.pushDirection = CGVector(dx: 0.0, dy: -5.0) dynamicAnimator.addBehavior(pushBehavior)ここで1.0の力とは、連続で1.0の力を与えたとき、密度値が1.0の100ポイントx 100ポイントのビューが100 point/second^2の加速度を持つような大きさをいいます。この値をUIKit Newtonと呼びます。
パラメータ
プロパティ名・メソッド名 型 説明 mode UIPushBehavior.Mode 力を加えるのが連続的(.continuous)か1度だけ(instantaneous)なのか pushDirection CGFloat 力のベクトル(大きさと方向) angle CGFloat 力のベクトルの方向(ラジアン) magnitude CGFloat 力のベクトルの大きさ setAngle(_:, magnitude:) (CGFloat, CGFloat) -> Void 力のベクトルの方向(ラジアン)と大きさ setTargetOffsetFromCenter(_:for:) (UIOffset, UIdynamicItem) -> Void 力が物体のどこにかかるか。指定しない場合は中心にかかる その他
他にもバネのような動きを実現するUISnapBehaviorや2物体の関係を扱うUIAttachimentBehavior、電場や磁場を設定できるUIFieldBehaviorがあります。
備考
先ほどの自由落下させて完全弾性衝突するプログラムのredViewの開始位置に印をつけました。
誤差があり線を超えたり、線まで届かなかったりします。
大まかな動きを再現するには良いですが、ぴったり数ポイント分の動きを実現したい場合この誤差は無視できないように思います。
- 投稿日:2019-07-30T13:00:17+09:00
UIKit Dynamicsで物理演算を使った自然なアニメーション
UIKit Dynamics
Viewに物理演算を用いたアニメーションを設定できます。
ドキュメントはこちらUIDynamicAnimator
Viewに物理に基づいたアニメーションを提供する根幹となるもの
var dynamicAnimator: UIDynamicAnimator! override func viewDidLoad() { super.viewDidLoad() dynamicAnimator = UIDynamicAnimator(referenceView: self.view) } }重力を与える
storyboard上に配置した赤いViewに条件を追加します。
重力を与えるにはUIGravityBehaviorを使用します。let gravityBehavior = UIGravityBehavior(items: [redView]) dynamicAnimator.addBehavior(gravityBehavior)ここでデフォルトの重力の大きさは1.0で、大きさ1.0は1000 point/second^2の加速度を意味します。
デフォルトの重力の向きは(0.0, 1.0)です。パラメータ
パラメータとして次のようなものが設定できます。
プロパティ・メソッド名 型 説明 gravityDirection CGVector 重力ベクトル(大きさと方向) angle CGFloat 重力ベクトルの方向(ラジアン) magnitude CGFloat 重力ベクトルの大きさ setAngle(_:, magnitude:) (CGFloat, CGFloat) -> Void 重力ベクトルの方向(ラジアン)と大きさ 他にsetAngle(_ angle: CGFloat, magnitude: CGFloat)メソッドを使用しても設定できます。
例えばaddBehavior(_:)する前に以下のように設定すると、gravityBehavior.gravityDirection = CGVector(dx: 2.0, dy: 2.0) gravityBehavior.magnitude = 1.0一行目は右下に向かって重力の大きさ2.828..($2\sqrt{2} $)で動くような設定になります。
二行目でmagnitudeを1.0にすると、その方向を保ったままベクトルの大きさが1.0になるので最終的に重力ベクトルは(0.707..., 0.707...)となります。衝突境界
衝突境界を追加したい場合にはUICollisionBehaviorを使用します。
デバイスの枠を境界とする
translatesReferenceBoundsIntoBoundaryはreferenceViewを境界として扱うかどうかというBool値です。
referenceViewは最初にUIDynamicAnimatorを初期化した時に設定したViewです。
先ほどこれにself.viewを設定したので、この場合デバイスの画面の端が境界になると考えられます。let collisionBehavior = UICollisionBehavior(items: [redView]) collisionBehavior.translatesReferenceBoundsIntoBoundary = true dynamicAnimator.addBehavior(collisionBehavior)自由に境界を設定
CGPointでどこからどこまでを境界とみなすかを指定します。
第一引数で指定するIDは後でこの境界だけ削除する場合などに使います。let collisionBehavior = UICollisionBehavior(items: [redView]) collisionBehavior.addBoundary(withIdentifier: "floor" as NSCopying, from: CGPoint(x: self.view.bounds.width/2 - 20,y: self.view.bounds.height/2 + 200), to: CGPoint(x: self.view.bounds.width/2 + 20, y: self.view.bounds.height/2 + 200)) dynamicAnimator.addBehavior(collisionBehavior)この他にUIBezierPathを使用して境界を設定することもできます。
パラメータ
collisionModeで何を衝突対象とするのかを指定できる。
CollisionBehavior.Modeで指定できるのは以下。
CollisionBehavior.Mode 説明 動作 .items UICollisionBehaviorに紐付いているアイテム同士だけが衝突する .boundaries 設定した境界のみが衝突対象となり、アイテム同士は衝突しない .everything アイテム同士も境界も衝突する デフォルトは.everything。
弾性係数・抵抗・摩擦
ある物体に対する弾性係数や抵抗など初期条件になるようなものを設定するにはUIDynamicItemBehaviorを使用します。
let dynamicItemBehavior = UIDynamicItemBehavior(items: [redView]) dynamicItemBehavior.elasticity = 1.0 dynamicItemBehavior.resistance = 0 dynamicItemBehavior.friction = 0 dynamicAnimator.addBehavior(dynamicItemBehavior)パラメータ
設定できる項目には以下のようなものがあります。
プロパティ・メソッド名 型 説明 density CGFloat 相対的な質量密度(1.0の密度を持つ100 x 100ポイントのitemに、大きさ1.0の力を加えると、100 point/secondで加速する) elasticity CGFloat 弾性率の大きさ(1.0で完全弾性衝突) friction CGFloat スライドする時にかかる摩擦の大きさ(1.0で強い摩擦、それ以上の値を指定することも可) resistance CGFloat 抵抗の大きさ(CGFLOAT_MAXが最高値、1.0を設定した時は力が加えられなくなるとすぐに停止する) allowsRotation Bool 回転を許可するか(デフォルトでtrue) angularResistance CGFloat 角抵抗の大きさ isAnchored Bool アイテムの位置が固定されているか addLinearVelocity(_:, for:) (CGPoint, UIDynamicItem) -> Void 速度を与える(1秒あたりに動くpoint数を指定) addAngulerVelocity(_:, for:) (CGFloat, UIDynamicItem) -> Void 角速度を与える(1秒あたりに動くラジアンを指定) 外力
アイテムに外力を加えるにはUIPushBehaviorを使用します。
鉛直投げ上げをするとき、プログラムは以下のようになります。let pushBehavior = UIPushBehavior(items: [redView], mode: UIPushBehavior.Mode.instantaneous) pushBehavior.pushDirection = CGVector(dx: 0.0, dy: -5.0) dynamicAnimator.addBehavior(pushBehavior)ここで1.0の力とは、連続で1.0の力を与えたとき、密度値が1.0の100ポイントx 100ポイントのビューが100 point/second^2の加速度を持つような大きさをいいます。この値をUIKit Newtonと呼びます。
パラメータ
プロパティ名・メソッド名 型 説明 mode UIPushBehavior.Mode 力を加えるのが連続的(.continuous)か1度だけ(instantaneous)なのか pushDirection CGFloat 力のベクトル(大きさと方向) angle CGFloat 力のベクトルの方向(ラジアン) magnitude CGFloat 力のベクトルの大きさ setAngle(_:, magnitude:) (CGFloat, CGFloat) -> Void 力のベクトルの方向(ラジアン)と大きさ setTargetOffsetFromCenter(_:for:) (UIOffset, UIdynamicItem) -> Void 力が物体のどこにかかるか。指定しない場合は中心にかかる その他
他にもバネのような動きを実現するUISnapBehaviorや2物体の関係を扱うUIAttachimentBehavior、電場や磁場を設定できるUIFieldBehaviorがあります。
備考
先ほどの自由落下させて完全弾性衝突するプログラムのredViewの開始位置に印をつけました。
誤差があり線を超えたり、線まで届かなかったりします。
大まかな動きを再現するには良いですが、ぴったり数ポイント分の動きを実現したい場合この誤差は無視できないように思います。
- 投稿日:2019-07-30T07:02:36+09:00
Swift Package Manager はじめの一歩
はじめに
Apple公式のパッケージマネージャーのSwift Package Manager(以下SwiftPM)を今回使用してみました。
パッケージマネージャーとはライブラリの依存関係を管理してくれるツールで、
CocoaPods
やCarthage
と同じようなものです。https://github.com/apple/swift-package-manager
はじめに
SwiftPMが動作するか確認しましょう。
XcodeをインストールされていればTerminalから下記のコマンドが実行できます。$ swift package --version Apple Swift Package Manager - Swift 5.0.0 (swiftpm-14490.62.2)また、ヘルプを確認するときは下記です。
swift package --help
プロジェクトを作成する
SwiftPMは作業ディレクトリを作成しないので、自分で作成する必要があります。
今回はHello-SwiftPM
という名前の作業ディレクトリを作成します。$ mkdir Hello-SwiftPM $ cd Hello-SwiftPM/今回はコマンドラインツールの作成方法です。
オプションに--type executable
を追加する必要があります。
これを追加するとmain.swift
も同時に作成されます。$ swift package init --type executable Creating executable package: Hello-SwiftPM Creating Package.swift Creating README.md Creating .gitignore Creating Sources/ Creating Sources/Hello-SwiftPM/main.swift Creating Tests/ Creating Tests/LinuxMain.swift Creating Tests/Hello-SwiftPMTests/ Creating Tests/Hello-SwiftPMTests/Hello_SwiftPMTests.swift Creating Tests/Hello-SwiftPMTests/XCTestManifests.swiftオプションはそのほかに
entity
library
system-module
があります
ビルド
$ swift package build
Xcodeプロジェクト作成
$ swift package generate-xcodeproj
リリースビルド
$ swift build -c release