#特定のビューの外側から内側へスワイプした時に当たり判定を実装する方法
タイトルに48手とありますが、実際はそんなにありません。すいません。
「嘘ついてんじゃねーよ、期待させやがって。この下手くそ!」
はい、すいません。。。
#UIKit
まずは画面の真ん中に四角いUIViewを1つ置きます。青くしてもいいです。
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.
}
}
こうなるんです。かわいいヤツめ。
###◆1手
touchesMoveメソッドでヤル
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でヤル
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メソッドでヤル
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で消しましょう。
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でヤル
ちょっと長いんですが。。。
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バリバリ書けるようになりそう。す、すごい。。。
わざわざこんなやり方しなくていいです。と思います。
メリットとしては、同時に物理法則を適用できる事ですかねぇ。
例えば上のコードから、
//当たっても微動だにしない
behavior.addBoundary(withIdentifier: "v" as NSCopying, for: UIBezierPath(rect:v.frame))
ここを消すと、赤く染まったビューが無重力空間を漂います。
背景を白から黒にしたら宇宙空間みたいになりますから。
ちなみに、今回は使っていないのですが、UIGravityBehaviorを一緒に使うとちゃんと重力空間が作れます。
###◆5手
カスタムビューでヤル
少しヤリ方を変えまして、File -> New からUIViewを継承したファイルを一つ作ります。
TargetView.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)
}
まさにマグロ。
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手に対して、自分が少し動く事でお互いに幸せになろうよっていうアプローチのつもりで監視してます。
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
}
タッチ座標を知らせる動作が増えています。
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手
動いてるやつをヤル
アニメーション中のビューに対して判定をかける方法です。
まずはビューをセットして動いてもらいます。
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)
}
触ります。
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と一緒なんですけどね。
レイヤー姫を触りに行くんですよって事が伝えたかったわけです。
//上手く触れたら赤く染まる
if (v.layer.presentation()?.frame.contains(point))! {
v.backgroundColor = UIColor.red
}
この部分です。
これは意外と使いますし、よもやハマりかねない部分なので個人的に覚えておくようにします。
#SpriteKit
まずは画面の真ん中に四角いノードを1つ置きます。青くしてもいいです。
デフォルトを以下に書き換え。
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
}
}
//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) {
//中身を空に
}
これで例の画面になりますね。
上記、プロジェクト作成時より変更なしの部分は載せていないのでご注意ください。
###◆8手
nodesでヤル
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手
物理世界でヤル
//プロトコル継承
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手を紹介させて頂きました(強がり)。
こんなスワイプ判定をいくつか使ったゲームアプリをリリースしています。
よかったら触ってみてください。
こんなクソゲー作ってゴメンナサイ・・・
いくつかレビューサイトでレビュー頂いてます。
個人開発者の皆様、こんな僕のアプリでも掲載頂けましたので、こちらにレビュー依頼を送ると掲載の可能性高いかもですよ!
->ゲームアプリCH様
->Appliv様
->アプリゲット様
->ゲームキャスト様
#####それでは皆様、よいスワイプを!