20190730のSwiftに関する記事は8件です。

Create MLとVisionで画像認識

できたもの

ezgif-4-4f9e671bca62 (1).gif
上記gif動画のように移したカードが何かを判定する簡単なアプリを作成しました。

.mlmodelファイルの作成

Create MLから.mlmodelファイルを作成します。
Xcodeのplaygroundから作成できます。次期バージョンからは独立したアプリになるみたいですがXcode10.3環境ではplaygroundから使用します。

playgroundをmacOSで開いたら

import CreateMLUI
let builder = MLImageClassifierBuilder()
builder.showInLiveView()

と記載して実行すると...
スクリーンショット 2019-07-30 23.31.31.png

上記画像のようになると思います。
Drop Images というところに画像ファイル(ディレクトリ)をドロップします。
パラメータも選択できますが、今回のアプリレベルなら調整は必要ありませんでした。精度に関してはこのあたりに書いてあります。
ドロップするとトレーニングが始まって完了するとmlmodelファイルがダウンロード可能です。
アップロードしたディレクトリは以下のようになっています。
スクリーンショット 2019-07-30 23.46.32.png
各ディレクトリには10枚程度画像が入っています。ドキュメントには最低10枚とあったのでまずは最低枚数を試しました(結果それでうまくいきました)。

iOS

コードはiOS11のVision.frameworkを使ってみるを参考にさせていただきました。

ViewController.swift
import UIKit
import AVFoundation
import Vision

class ViewController: UIViewController {
    @IBOutlet fileprivate weak var textView: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // カメラキャプチャの開始
        startCapture()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    // カメラキャプチャの開始
    private func startCapture() {
        let captureSession = AVCaptureSession()
        captureSession.sessionPreset = .photo

        // 入力の指定
        guard let captureDevice = AVCaptureDevice.default(for: .video),
            let input = try? AVCaptureDeviceInput(device: captureDevice),
            captureSession.canAddInput(input) else {
                assertionFailure("Error: 入力デバイスを追加できませんでした")
                return
        }

        captureSession.addInput(input)

        // 出力の指定
        let output = AVCaptureVideoDataOutput()
        output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "VideoQueue"))

        guard captureSession.canAddOutput(output) else {
            assertionFailure("Error: 出力デバイスを追加できませんでした")
            return
        }
        captureSession.addOutput(output)

        // プレビューの指定
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.videoGravity = .resizeAspectFill
        previewLayer.frame = view.bounds
        view.layer.insertSublayer(previewLayer, at: 0)

        // キャプチャ開始
        captureSession.startRunning()
    }
}

extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // CMSampleBufferをCVPixelBufferに変換
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            assertionFailure("Error: バッファの変換に失敗しました")
            return
        }

        // CoreMLのモデルクラスの初期化
        guard let model = try? VNCoreMLModel(for: ImageClassifier().model) else {
            assertionFailure("Error: CoreMLモデルの初期化に失敗しました")
            return
        }

        // 画像認識リクエストを作成(引数はモデルとハンドラ)
        let request = VNCoreMLRequest(model: model) { [weak self] (request: VNRequest, error: Error?) in
            guard let results = request.results as? [VNClassificationObservation] else { return }

            // 判別結果とその確信度を上位3件まで表示
            // identifierは類義語がカンマ区切りで複数書かれていることがあるので、最初の単語のみ取得する
            let displayText = results.prefix(3).compactMap { "\(Int($0.confidence * 100))% \($0.identifier.components(separatedBy: ", ")[0])" }.joined(separator: "\n")

            DispatchQueue.main.async {
                self?.textView.text = displayText
            }
        }

        // CVPixelBufferに対し、画像認識リクエストを実行
        try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
    }
}

プロジェクトに先程の.mlmodelファイルを加えたら完了です。

困った点

最初は読み込むカードのみの画像を入れたところ、カードが写ってなくてもどちらかに判定、それもどちらかに固定で判定されてしまう状態でした。さらにもう一方のカードを移してもあまりconfidenceが上がらない状態で困っていました。
カードが写っているかどうかも判定したかったため背景のみの写真も撮影してothersとしてアップロードしたところ精度が非常に上がりました。
今回は同じ部屋でしか試していないのですが、屋外などでは今回のままではうまく行かないかもしれません。
枚数やオプションを増やした場合、一気に学習時間が伸びたのでそのへんがやはり問題になってくるのだなと感じました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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が呼ばれるので、それはそうですよねというお話(引数が同じなのですっかりハマってしまいました。??

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftでMapタイプの切り替え方

何をやるのか?

簡易的なMapアプリのオプション機能として、mapタイプの切り替えを行う。

前提

MapKitにてMapが設置されていることを前提とする。

Mapの種類

画面 定義 内容        
スクリーンショット 2019-07-30 17.41.45.png .mutedStandard 交通機関
スクリーンショット 2019-07-30 17.42.29.png .standard 標準の地図
スクリーンショット 2019-07-30 17.43.53.png .satellite 航空写真
スクリーンショット 2019-07-30 17.44.14.png .hybrid 航空写真
+
ラベル
スクリーンショット 2019-07-30 17.44.40.png .satelliteFlyover 3D Flyover
スクリーンショット 2019-07-30 17.45.07.png .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に貼り、それに対応する関数内に記述すれば問題ありません。

最後に

非常に簡単な実装ですが、割と本格的なアプリのように仕上がるので、初学者の方やプログラミングやった事ないけど興味ある人も是非一度実装して、開発の楽しさを体験してみましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIAlertControllerにUIProgressViewを追加してKVO監視更新

データ変換をするクラスを作って、処理中にその中で変更される進捗度合いを、KVOで UIAlertController上のUIProgressViewに反映させます。

最初、進捗に応じた UIProgressView の表示更新ができなかったんですが、以下のように
表示側には DispatchQueue.main.async(flags: .barrier) {}
処理側には、 DispatchQueue.global(qos: .background).async {}を入れることで解決できました。

// progressView と _observer はメンバ変数

let progressAlert = UIAlertController(title: "Please wait", message: "Converting from old to new", preferredStyle: .alert)
self.progressView.progress = 0.0
progressAlert.view.addSubview(self.progressView)

// UIAlertControllerの高さ変更とUIProgressViewの配置
let height:NSLayoutConstraint = NSLayoutConstraint(item: progressAlert.view!,
                                                   attribute: NSLayoutConstraint.Attribute.height,
                                                   relatedBy: NSLayoutConstraint.Relation.equal,
                                                   toItem: nil,
                                                   attribute: NSLayoutConstraint.Attribute.notAnAttribute,
                                                   multiplier: 1, constant: 100)
progressAlert.view.addConstraint(height)
self.progressView.translatesAutoresizingMaskIntoConstraints = false
self.progressView.leadingAnchor.constraint(equalTo: progressAlert.view.leadingAnchor, constant: 10.0).isActive = true
self.progressView.trailingAnchor.constraint(equalTo: progressAlert.view.trailingAnchor, constant: -10.0).isActive = true
self.progressView.topAnchor.constraint(equalTo: progressAlert.view.topAnchor, constant: 65.0).isActive = true
self.progressView.heightAnchor.constraint(equalToConstant: 2.0)

// present と observe 設定と終了処理
present(progressAlert, animated: true, completion: {
    let dataMigration = DataMigration()
    self._observer = dataMigration.observe(\.dataProgress, options: .new) { object, change in
         DispatchQueue.main.async(flags: .barrier) {
            self.progressView.setProgress(object.dataProgress, animated: true)
        }
    }
    dataMigration.convert(何かオプション, completionHandler: {(sucess)->Void in
        self._observer = nil
        if sucess {
            print("Convert Complete")
        } else {
            print("Error")
        }
        progressAlert.dismiss(animated: true, completion: {
            ... 終了時の処理 ...
        })
    })
})

observeをするためには、DataMigrationは NSObjectのサブクラスでなければなりません。

class DataMigration: NSObject {
    @objc dynamic var dataProgress: Float = 0.0

    func convert(options: [Any], compleationHandler:@escaping ((_ success: Bool) -> Void) ) {
        var numberOfData: Float = ...データの個数...
        var countData: Float = 0.0
        DispatchQueue.global(qos: .background).async {
            for ...numberOfData分の繰り返し... {
                ...何か処理...
                countData += 1
                self.dataProgress = countData / numberOfData
            }
            completionHandler(true)
        }
    }
}

参考にしたのは下記です。
StackOverflow: Add Progress bar to UIAlertController with showing update

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CoreData: warning: Multiple NSEntityDescriptions claim および CoreData: error: +[XXXX entity] Failed to find a unique match for an NSEntityDescription

CoreDataで、デフォルトのオートマイグレーションを使うのではなく、自前で2つの NSPersistentContainerloadPersistentStores() して、一方から読み込み、もう一方に書き込むということをしました。

このとき書き込み側で、
CoreData: warning: Multiple NSEntityDescriptions claim ....
あるいは
CoreData: error: +[XXXX entity] Failed to find a unique match for an NSEntityDescription ...
のような警告もしくはエラーが発生しました。

やっていたこと。

let object = Xxxx(context: moc)
...attrbuteに値を代入...
do {
    try moc.save()
} catch {
    print("Failed to save new note")
}

これを下記のようにすることで回避できました。

let discription = NSEntityDescription.entity(forEntityName: "Xxxx", in: moc)!
let object = Xxxx(entity: discription, insertInto: moc)
moc.insert(object)
...attrbuteに値を代入...
do {
    try moc.save()
} catch {
    print("Failed to save new note")
}

mocNSPersistentContainerviewContext: NSManagedObjectContext です。

参考記事は以下です。
stackoverflow: Multiple NSEntityDescriptions Claim NSManagedObject Subclass

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の開始位置に印をつけました。
誤差があり線を超えたり、線まで届かなかったりします。
大まかな動きを再現するには良いですが、ぴったり数ポイント分の動きを実現したい場合この誤差は無視できないように思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の開始位置に印をつけました。
誤差があり線を超えたり、線まで届かなかったりします。
大まかな動きを再現するには良いですが、ぴったり数ポイント分の動きを実現したい場合この誤差は無視できないように思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift Package Manager はじめの一歩

はじめに

Apple公式のパッケージマネージャーのSwift Package Manager(以下SwiftPM)を今回使用してみました。

パッケージマネージャーとはライブラリの依存関係を管理してくれるツールで、
CocoaPodsCarthageと同じようなものです。

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む