LoginSignup
8
11

More than 5 years have passed since last update.

【Swift3】スワイプの当たり判定48手

Last updated at Posted at 2017-06-26

特定のビューの外側から内側へスワイプした時に当たり判定を実装する方法

タイトルに48手とありますが、実際はそんなにありません。すいません。
「嘘ついてんじゃねーよ、期待させやがって。この下手くそ!」
はい、すいません。。。

UIKit

まずは画面の真ん中に四角いUIViewを1つ置きます。青くしてもいいです。

ViewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.


    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //ビューを生成
        let v:UIView = UIView(frame: CGRect(x:0, y:0, width:self.view.frame.size.width/3, height:self.view.frame.size.width/3))
        //画面の中心に
        v.center = CGPoint(x:self.view.frame.size.width/2, y:self.view.frame.size.height/2)
        //青色に
        v.backgroundColor = UIColor.blue
        //識別用にタグを1に
        v.tag = 1
        //出現
        self.view.addSubview(v)

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

Simulator Screen Shot 2017.06.22 17.53.54.png
こういう事ですね。

当たり判定を得るとポッと赤くなるようにします。
Simulator Screen Shot 2017.06.22 17.59.39.png

こうなるんです。かわいいヤツめ。

◆1手

touchesMoveメソッドでヤル

ViewController.swift
 override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        //タッチ座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)

        //ターゲットのビューを取得
        let v:UIView = self.view.viewWithTag(1)!

        //タッチ座標がターゲットの内側にあったら赤く染まる
        if v.frame.contains(point) {
            v.backgroundColor = UIColor.red
        }

    }

ちなみに、touchesMovedはスワイプ中ものすごく呼ばれるので、ちゃんとやるなら”初めて触れる時だけ”や、”ターゲットをクラス変数に置いとく”とかした方が良いかと思ってます。

◆2手

touchesMovedとhitTestでヤル

ViewController.swift

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        //タッチ座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)

        //タッチ座標にあるサブビューを取得
        let v:UIView? = self.view.hitTest(point, with: event)

        //ビューが取得できてタグが1なら運命の出会い
        if v != nil && v?.tag == 1 {
            v!.backgroundColor = UIColor.red
        }
    }

方法1とあまり変わらないですかね。。。

◆3手

touchesBeganとtouchesMoveメソッドでヤル

ViewController.swift
  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        //タッチ開始座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)

        //タッチ開始座標に小さいビューを置く
        let finger:UIView = UIView(frame: CGRect(x:0, y:0, width:5, height:5))
        finger.center = point
        self.view.addSubview(finger)
        //識別用にタグを2に
        finger.tag = 2

    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        //タッチ座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)

        //ターゲットのビューを取得
        let v:UIView = self.view.viewWithTag(1)!

        //タップ開始時に置いたビューを取得
        let finger:UIView = self.view.viewWithTag(2)!
        //今の指の位置に持ってくる
        finger.center = point

        //2人が重なりあったら赤く染まる
        if v.frame.intersects(finger.frame) {
            v.backgroundColor = UIColor.red
        }

    }

手間ですね。この方法のメリットとしては、fingerを”おっきくできる”という事でしょうか。
ゲームなどでは使い道ありそうです。
あ、fingerは忘れずにtouchesEndedで消しましょう。

ViewController.swift
 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)

        //タップ開始時に置いたビューを取得
        let finger:UIView = self.view.viewWithTag(2)!

        //消す
        finger.removeFromSuperview()
    }

◆4手

UIDynamicsでヤル

ちょっと長いんですが。。。

ViewController.swift
import UIKit

class ViewController: UIViewController,UICollisionBehaviorDelegate {

    var animator:UIDynamicAnimator!
    var behavior:UICollisionBehavior!
    var snap:UISnapBehavior!


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        //物理世界へダイブ
        animator = UIDynamicAnimator(referenceView: self.view)

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //ビューを生成
        let v:UIView = UIView(frame: CGRect(x:0, y:0, width:self.view.frame.size.width/3, height:self.view.frame.size.width/3))
        //画面の中心に
        v.center = CGPoint(x:self.view.frame.size.width/2, y:self.view.frame.size.height/2)
        //青色に
        v.backgroundColor = UIColor.blue
        //識別用にタグを1に
        v.tag = 1
        //出現
        self.view.addSubview(v)

        //ビューの存在を主張
        behavior = UICollisionBehavior(items: [v])
        behavior.translatesReferenceBoundsIntoBoundary = true

        //当たっても微動だにしない
        behavior.addBoundary(withIdentifier: "v" as NSCopying, for: UIBezierPath(rect:v.frame))

        //デリゲートを設定
        behavior.collisionDelegate = self

    }


    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        //タッチ開始座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)

        //タッチ開始座標に小さいビューを置く
        let finger:UIView = UIView(frame: CGRect(x:0, y:0, width:5, height:5))
        finger.center = point
        finger.backgroundColor = UIColor.yellow
        self.view.addSubview(finger)
        //識別用にタグを2に
        finger.tag = 2

        //fingerも自らを主張
        behavior.addItem(finger)

        //2人の存在を物理世界に知らせる
        animator.addBehavior(behavior)

        //昔の事は水に流す
        if snap != nil {
            animator.removeBehavior(snap)
        }

        //新たなfingerに生まれ変わる
        snap = UISnapBehavior(item: finger, snapTo: point)

        //重力世界に申請
        animator.addBehavior(snap)

    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        //タッチ座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)


        //タップ開始時に置いたビューを取得
        let finger:UIView = self.view.viewWithTag(2)!
        //今の指の位置に持ってくる
        finger.center = point

        //昔の事は水に流す
        if snap != nil {
            animator.removeBehavior(snap)
        }

        //新たなfingerに生まれ変わる
        snap = UISnapBehavior(item: finger, snapTo: point)

        //重力世界に申請
        animator.addBehavior(snap)

    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)

        //タップ開始時に置いたビューを取得
        let finger:UIView = self.view.viewWithTag(2)!

        //消す
        behavior.removeItem(finger)
        finger.removeFromSuperview()


    }

    //当たった時に呼ばれる
    func collisionBehavior(_ behavior: UICollisionBehavior, endedContactFor item1: UIDynamicItem, with item2: UIDynamicItem) {
        //ビューを取得して赤く染める
        let v:UIView = self.view.viewWithTag(1)!
        v.backgroundColor = UIColor.red
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

はい、コメントでふざけすぎてゴメンナサイ。
あんまり自信ないんです。だって難しから。。。
こちらのブログを参考にさせていただきました。 ->Swiftで遊ぼう! - 417 - UIDynamicAnimator再び 1
このブログ全部読めば、初心者でもswiftバリバリ書けるようになりそう。す、すごい。。。

わざわざこんなやり方しなくていいです。と思います。
メリットとしては、同時に物理法則を適用できる事ですかねぇ。
例えば上のコードから、

ViewControllwe.swift
 //当たっても微動だにしない
        behavior.addBoundary(withIdentifier: "v" as NSCopying, for: UIBezierPath(rect:v.frame))

ここを消すと、赤く染まったビューが無重力空間を漂います。
背景を白から黒にしたら宇宙空間みたいになりますから。
ちなみに、今回は使っていないのですが、UIGravityBehaviorを一緒に使うとちゃんと重力空間が作れます。

◆5手

カスタムビューでヤル

少しヤリ方を変えまして、File -> New からUIViewを継承したファイルを一つ作ります。
6d567d438ebe04940d87d9d392728d26.png
TargetView.swiftという名にしました。
このクラスに動いてもらい、自身はマグロを決め込む魂胆ですね。わかります。

ViewController.swift
 override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //ビューを生成
        let v:TargetView = TargetView(frame: CGRect(x:0, y:0, width:self.view.frame.size.width/3, height:self.view.frame.size.width/3))
        //画面の中心に
        v.center = CGPoint(x:self.view.frame.size.width/2, y:self.view.frame.size.height/2)
        //出現
        self.view.addSubview(v)

    }

まさにマグロ。

TargetView.swift
import UIKit

class TargetView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        //青なんです
        self.backgroundColor = UIColor.blue

    }

    //実装必須
    required init?(coder aDecoder: NSCoder) {
        //frameで初期化して欲しいの
        fatalError("init(coder:) has not been implemented")
    }


    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        let touch = touches.first
        let point:CGPoint = touch!.location(in: self)

        //タッチ座標が中に入ったら染まる
        if self.bounds.contains(point) {
            self.backgroundColor = UIColor.red
        }
    }

    //全てのタッチイベントを我が手に
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return true
    }
}

ViewControllerがすっきりしますね。勝手にやってくれてます。
しかし、共存不可です。共存不可どころか、全てのタッチイベントを持ってかれてしまうので、スワイプ判定以外のアクションは受け付けませんw
こんな実装しちゃダメ、絶対。

◆6手

監視してヤル

タイトルからはイケナイ雰囲気がビンビン伝わってきますが。。。
5手に対して、自分が少し動く事でお互いに幸せになろうよっていうアプローチのつもりで監視してます。

ViewController.swift
 override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //ビューを生成
        let v:TargetView = TargetView(frame: CGRect(x:self.view.frame.size.width/3, y:0, width:self.view.frame.size.width/3, height:self.view.frame.size.width/3))
        v.center = self.view.center

        //タグ付け
        v.tag = 1;

        //出現
        self.view.addSubview(v)

    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        //タッチ座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)

        //タッチ座標を通知
        let v = self.view.viewWithTag(1) as! TargetView
        v.point = point
    }

タッチ座標を知らせる動作が増えています。

TargetView.swift
import UIKit

class TargetView: UIView {


    //ずっと見てるんだからね
    var point:CGPoint? {
        didSet{
            //中に入ったら染まる
            if self.frame.contains(point!) {
                self.backgroundColor = UIColor.red
            }
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        //青なんです
        self.backgroundColor = UIColor.blue

    }

    //実装必須
    required init?(coder aDecoder: NSCoder) {
        //frameで初期化して欲しいの
        fatalError("init(coder:) has not been implemented")
    }


}

”ずっと見てるんだからね”の部分でプロパティの変更を監視してます。
ViewController.swiftからpointに値を入れられる毎に、中なの?外なの?と確認されてるわけですね。恐ろしや。。。

◆7手

動いてるやつをヤル

アニメーション中のビューに対して判定をかける方法です。
まずはビューをセットして動いてもらいます。

ViewController.swift
  override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //ビューを生成
        let v:UIView = UIView(frame: CGRect(x:self.view.frame.size.width/3, y:0, width:self.view.frame.size.width/3, height:self.view.frame.size.width/3))

        //青色に
        v.backgroundColor = UIColor.blue
        //識別用にタグを1に
        v.tag = 1
        //出現
        self.view.addSubview(v)

        //上下運動
        UIView.animate(withDuration: 1, delay: 0, options: [.autoreverse, .repeat], animations: {() -> Void in
            v.transform = CGAffineTransform.init(translationX: 0, y: self.view.frame.size.height - v.frame.size.height)
        }, completion: nil)
    }

触ります。

ViewController.swift
 override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        //タッチ座標を取得
        let touch = touches.first
        let point:CGPoint = touch!.location(in: self.view)

        //ビューを取得
        let v:UIView = self.view.viewWithTag(1)!

        //上手く触れたら赤く染まる
        if (v.layer.presentation()?.frame.contains(point))! {
            v.backgroundColor = UIColor.red
        }
    }

基本的には方法1と一緒なんですけどね。
レイヤー姫を触りに行くんですよって事が伝えたかったわけです。

ViewController.swift
 //上手く触れたら赤く染まる
        if (v.layer.presentation()?.frame.contains(point))! {
            v.backgroundColor = UIColor.red
        }

この部分です。
これは意外と使いますし、よもやハマりかねない部分なので個人的に覚えておくようにします。

SpriteKit

まずは画面の真ん中に四角いノードを1つ置きます。青くしてもいいです。

まずは新規プロジェクトをgameで作成。
7a1678355d1201214bdca43aa42c23e7.png

デフォルトを以下に書き換え。

GameViewController.swift
 override func viewDidLoad() {
        super.viewDidLoad()

        if let view = self.view as! SKView? {

            let scene = GameScene(size: view.frame.size)

            view.presentScene(scene)
            view.ignoresSiblingOrder = true

            view.showsFPS = true
            view.showsNodeCount = true
        }
    }
GameScene.swift
 //SKSpriteNodeを定義
    private var targetNode : SKSpriteNode?

    override func didMove(to view: SKView) {

        //背景白に
        self.backgroundColor = UIColor.white

        //青い娘を配置
        self.targetNode = SKSpriteNode.init(color: UIColor.blue, size: CGSize(width: self.size.width/3, height: self.size.width/3))
        self.targetNode?.position = CGPoint(x: self.size.width/2, y: self.size.height/2)

        self.addChild(self.targetNode!)
    }


    func touchDown(atPoint pos : CGPoint) {
       //中身を空に

    }

    func touchMoved(toPoint pos : CGPoint) {
        //中身を空に

    }

    func touchUp(atPoint pos : CGPoint) {
        //中身を空に

    } 

これで例の画面になりますね。
上記、プロジェクト作成時より変更なしの部分は載せていないのでご注意ください。

Simulator Screen Shot 2017.06.26 11.58.00.png
こういう事ですね。

◆8手

nodesでヤル

GameScene.swift
  func touchMoved(toPoint pos : CGPoint) {
        //今触れている相手を全て取得
        let array = self.nodes(at: pos)

        //意中の相手だったら赤く染まる
        for target in array{
            if target == self.targetNode {
                self.targetNode?.color = UIColor.red
            }
        }
    }

SpriteKitなので、主な用途はゲームでしょうか。
ターゲットに.nameで名前をつけて、名前で判定するのもありです。

◆9手

物理世界でヤル

GameScene.swift

//プロトコル継承
class GameScene: SKScene,SKPhysicsContactDelegate {

    //SKSpriteNodeを定義
    private var targetNode : SKSpriteNode?
    private var fingerNode : SKSpriteNode?

    //カテゴリビットマスクを定義
    let targetCategory:__uint32_t = 0x1 << 0
    let fingerCategory:__uint32_t = 0x1 << 1


    override func didMove(to view: SKView) {

        //デリゲート設定
        self.physicsWorld.contactDelegate = self
        self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)

        //背景白に
        self.backgroundColor = UIColor.white

        //青い娘を配置
        self.targetNode = SKSpriteNode.init(color: UIColor.blue, size: CGSize(width: self.size.width/3, height: self.size.width/3))
        self.targetNode?.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
        self.addChild(self.targetNode!)
        //カチカチに
        self.targetNode!.physicsBody = SKPhysicsBody.init(rectangleOf: self.targetNode!.size)
        self.targetNode!.physicsBody!.categoryBitMask = self.targetCategory
        self.targetNode!.physicsBody!.contactTestBitMask = self.fingerCategory

    }


    func touchDown(atPoint pos : CGPoint) {
        //タッチ座標に小さい僕を配置
        self.fingerNode = SKSpriteNode.init(color: UIColor.clear, size: CGSize(width: 2, height: 2))
        self.fingerNode!.position = pos

        //カチカチに
        self.fingerNode!.physicsBody = SKPhysicsBody.init(rectangleOf: self.fingerNode!.size)
        self.fingerNode!.physicsBody!.categoryBitMask = self.fingerCategory
        self.fingerNode!.physicsBody!.contactTestBitMask = self.targetCategory

        self.addChild(self.fingerNode!)

    }

    func touchMoved(toPoint pos : CGPoint) {
        //僕が指に追従
        self.fingerNode?.position = pos
    }

    func touchUp(atPoint pos : CGPoint) {
        //さよなら僕
        self.fingerNode?.removeFromParent()
    }

    //触れ合ったら赤く染まる
    func didBegin(_ contact: SKPhysicsContact) {
        self.targetNode?.color = UIColor.red
    }

//  〜〜〜 以下、略 〜〜〜 
//  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) とかがあります

}

SpriteKitを使ったゲームの衝突判定の王道ですね。
ただのスワイプ判定にこれを使うのかは疑問ですが。。。

おわりに

という事で、今回は48手の内、9手を紹介させて頂きました(強がり)。

こんなスワイプ判定をいくつか使ったゲームアプリをリリースしています。
よかったら触ってみてください。

icon.png ありくえ

こんなクソゲー作ってゴメンナサイ・・・

いくつかレビューサイトでレビュー頂いてます。
個人開発者の皆様、こんな僕のアプリでも掲載頂けましたので、こちらにレビュー依頼を送ると掲載の可能性高いかもですよ!

->ゲームアプリCH様
->Appliv様
->アプリゲット様
->ゲームキャスト様

それでは皆様、よいスワイプを!
8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11