UIButton continue tracking even when out of bounds - swift

I'm trying to track the touches on the button. But whenever the touch leaves the button frame, it stops tracking the touch all together.
I've tried using:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let relativeFrame = bounds
let hitTestInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
return UIEdgeInsetsInsetRect(relativeFrame, hitTestInsets).contains(point)
}
and
override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let minimalWidthAndHeight: CGFloat = 60
let buttonSize = frame.size
let widthToAdd = (minimalWidthAndHeight - buttonSize.width > 0) ? minimalWidthAndHeight - buttonSize.width : 0
let heightToAdd = (minimalWidthAndHeight - buttonSize.height > 0) ? minimalWidthAndHeight - buttonSize.height : 0
let largerFrame = CGRect(x: 0-(widthToAdd / 2), y: 0-(heightToAdd / 2), width: buttonSize.width + widthToAdd, height: buttonSize.height + heightToAdd)
return largerFrame.contains(point) ? self : nil
}
and I also tried using continueTracking(with:) but in all cases, whenever the touch moves away from the button frame, cancelTracking(with:) and touchesCancelled(with:) is called.
What do I have to do to make it keep tracking even when the touch leaves the frame of the button?

Related

SpriteKit: How to create a region that's the same size as the device screen?

I'm working in SpriteKit and I need to create a 'playable area', an area that is same size as the device screen so that I can stop my player from moving off-screen.
I'm using the following line of code:
var playableRect: CGRect = UIScreen.main.bounds
But the resulting rectangle is about a quarter of the device screen, with a corner of that rectangle at what looks to be the center of the screen. And the device orientation doesn't change that.
I've tried everything I can think of. Nothing is working.
How do I create a rectangle that's the same size as the device screen?
Here's my full coding, after changing to include Gene's suggestion. I incorporated Gene's suggestion by revising playableRect inside the didMove method. But the result is unchanged with that coding.
import SpriteKit
import GameplayKit
import CoreMotion
#objcMembers
class GameSceneUsingTilt: SKScene {
let player = SKSpriteNode(imageNamed: "player-motorbike")
let motionManager = CMMotionManager()
var playableRect = UIScreen.main.bounds
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "road")
background.zPosition = -1
addChild(background)
if let particles = SKEmitterNode(fileNamed: "Mud") {
let farRightPt = frame.maxX // start the emitter at the far right x-point of the view
particles.advanceSimulationTime(10)
particles.position.x = farRightPt
addChild(particles)
}
let nearLeftPt = frame.minX * 3 / 4 // start the player at a quarter of the way to the far left x-point of the view
player.position.x = nearLeftPt
player.zPosition = 1
addChild(player)
motionManager.startAccelerometerUpdates()
// coding below shows outline of playableRect
let bounds = UIScreen.main.bounds
let scale = UIScreen.main.scale
// let size = CGSize(width: bounds.size.width * scale, height: bounds.size.height * scale)
playableRect = CGRect(x: 0, y: 0, width: bounds.size.width * scale, height: bounds.size.height * scale)
drawPlayableRect(rect: playableRect)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
func boundsCheck() {
let playableRect = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: frame.height)
let bottomLeft = CGPoint(x: playableRect.minX, y: playableRect.minY)
let topRight = CGPoint(x: playableRect.maxX, y: playableRect.maxY)
if player.position.x <= bottomLeft.x {
player.position.x = bottomLeft.x
}
if player.position.x >= topRight.x {
player.position.x = topRight.x
}
if player.position.y <= bottomLeft.y {
player.position.y = bottomLeft.y
}
if player.position.y >= topRight.y {
player.position.y = topRight.y
}
}
override func update(_ currentTime: TimeInterval) {
if let accelerometerData = motionManager.accelerometerData {
// note that since the game will be running in landscape mode, up and down movement is controlled by the x axis and right to left movement is controlled by the y-axis...so this is the inverse of what you'd normally think.
let changeX = CGFloat(accelerometerData.acceleration.y) * 4
let changeY = CGFloat(accelerometerData.acceleration.x) * 4
// you have to subtract from x position and add to the y position because device is rotated so the axises aren't what you would normally expect.
player.position.x -= changeX
player.position.y += changeY
// check to make sure position isn't outside payable area:
boundsCheck()
}
}
// function below shows outline of playableRect
func drawPlayableRect(rect: CGRect) {
let shape = SKShapeNode()
let path = CGMutablePath()
path.addRect(rect)
shape.path = path
shape.strokeColor = .red
shape.lineWidth = 4.0
addChild(shape)
}
}
Use: UIScreen.main.nativeBounds

How to move these objects with touch?

Having some troubles figuring out how to move these objects with touch. Simply touching them and moving them is what i'm focusing on figuring out right now. Below I will add most of the code from GameScene.swift, it should be enough to help.. I think. However, I do have another class called Tetromino.swift and if you need that to help answer my question, let me know and I will add it in!
I've added touchesBegan, touchesMoved, and touchesEnded and i've been trying some different things but cannot get the objects to move - so the code isn't correct in them. The objects are squares that make up different Tetromino pieces. Thanks in advance!
class GameScene: SKScene {
var activeTetromino1 = Tetromino()
var activeTetromino2 = Tetromino()
var activeTetromino3 = Tetromino()
override func didMove(to view: SKView) {
// Setup scene
self.anchorPoint = CGPoint(x: 0, y: 0)
activeTetromino1 = Tetromino(drawTetrominoAtPoint(location: CGPoint(x: frame.width / 4, y: frame.height / 4)))
activeTetromino2 = Tetromino(drawTetrominoAtPoint(location: CGPoint(x: frame.width / 2, y: frame.height / 4)))
activeTetromino3 = Tetromino(drawTetrominoAtPoint(location: CGPoint(x: frame.width / 4 * 3, y: frame.height / 4)))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in (touches) {
//let location = touch.location(in: self)
drawTetrominoAtPoint(location: touch.location(in: self))
/*
if activeTetromino1.contains(location) {
activeTetromino1.position = location
}
*/
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in (touches) {
drawTetrominoAtPoint(location: touch.location(in: self))
/*
let location = touch.location(in: self)
if activeTetromino1.contains(location) {
activeTetromino1.position = location
}
*/
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
//activeTetromino1 = Tetromino(position = CGPoint(x: frame.width / 4, y: frame.height / 4))
}
override func update(_ currentTime: CFTimeInterval) {
// Called before each frame is rendered
}
func drawTetrominoAtPoint(location: CGPoint) {
let t = Tetromino()
for row in 0..<t.bitmap.count {
for col in 0..<t.bitmap[row].count {
if t.bitmap[row][col] > 0 {
let block = t.bitmap[row][col]
let square = SKSpriteNode(color: colors[block], size: CGSize(width: blockSize, height: blockSize))
square.anchorPoint = CGPoint(x: 1.0, y: 0)
square.position = CGPoint(x: col * Int(blockSize) + col, y: -row * Int(blockSize) + -row)
square.position.x += location.x
square.position.y += location.y
self.addChild(square)
}
}
}
}
}

Making two UIViews touch-detectable when one or the other is tapped

My issue is that I have two UIViews, and I need it to create a dot that's a certain color depending on which UIView I tap in. The problem is that when I tap, it only creates a blue dot, and even though I tap in the yellow dots view (They don't overlap) it still creates a blue dot. I don't think something is right.
My code:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
let locationTop = (touch as! UITouch).locationInView(self.topCourt)
var dotTop = UIView(frame: CGRect(x: locationTop.x, y: locationTop.y, width: 10, height: 10))
dotTop.layer.cornerRadius = 5
dotTop.backgroundColor = UIColor.blueColor()
self.topCourt.addSubview(dotTop)
let locationBot = (touch as! UITouch).locationInView(self.bottomCourt)
var dotBot = UIView(frame: CGRect(x: locationBot.x, y: locationBot.y, width: 10, height: 10))
dotBot.layer.cornerRadius = 5
dotBot.backgroundColor = UIColor.yellowColor()
self.bottomCourt.addSubview(dotBot)
}
I need some way to separate this so it actually detects which view I tap in and it creates the color dot it's supposed to. (I already have the creation of the dot down, the problem is detecting which view or what color to put it in)(Because no matter which view I tap in, it's always blue)
UPDATED ANSWER:
New code:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
var touch = touches.first as! UITouch!
let locationT = (touch as! UITouch).locationInView(self.topCourt)
let locationB = (touch as! UITouch).locationInView(self.bottomCourt)
if touch.view == topCourt{
var dot = UIView(frame: CGRect(x: locationT.x, y: locationT.y, width: 10, height: 10))
dot.layer.cornerRadius = 5
dot.backgroundColor = UIColor.blueColor()
self.topCourt.addSubview(dot)
}
if touch.view == bottomCourt{
var dot = UIView(frame: CGRect(x: locationB.x, y: locationB.y, width: 10, height: 10))
dot.layer.cornerRadius = 5
dot.backgroundColor = UIColor.yellowColor()
self.bottomCourt.addSubview(dot)
}
}
}

Detect two touches separately and handle them at the same time

I have two SKSpriteKitNodes, one in the left and one in the right. Then I detect where is the location of the touch and make the action according to its location (the left side shoots, and the right side speeds up). But the thing is, if the left is being touched, how can I handle the right-side touch at the same time?
The code I have so far:
let left = SKSpriteNode()
let right = SKSpriteNode()
override func didMoveToView(view: SKView) {
left.size = CGSize(width: frame.width / 2, height: frame.height)
right.size = CGSize(width: frame.width / 2, height: frame.height)
left.position = CGPoint(x: frame.width / 4, y: frame.height / 2)
right.position = CGPoint(x: 3 * frame.width / 4, y: frame.height / 2)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches{
let location = touch.locationInNode(self)
if right.containsPoint(location){
//HANDLE THE SPEED
if speed == 0{
speed = 5
}
shouldBreak = false
}
if left.containsPoint(location){
//HANDLE THE SHOOTING
shoot.position = CGPoint(x: frame.midX, y: frame.height * 0.2)
isShooting = true
}
}

How to enlarge hit area of UIGestureRecognizer?

I'm using few gesture recognizers on some views, but sometimes views are too small and it's hard to hit it. Using recognizers is necessary, so how can I enlarge hit area?
If you are doing this for a custom UIView, you should be able to override the hitTest:withEvent: method:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGRect frame = CGRectInset(self.bounds, -20, -20);
return CGRectContainsPoint(frame, point) ? self : nil;
}
The above code will add a 20 point border around the view. Tapping anywhere in that area (or on the view itself) will indicate a hit.
Swift version of #rmaddy answer:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let frame = self.bounds.insetBy(dx: -20, dy: -20);
return frame.contains(point) ? self : nil;
}
If you're using a UIImageView as a button, you can use the following extension (Swift 3.0):
extension UIImageView {
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.isHidden || !self.isUserInteractionEnabled || self.alpha < 0.01 { return nil }
let minimumHitArea = CGSize(width: 50, height: 50)
let buttonSize = self.bounds.size
let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
let largerFrame = self.bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)
// perform hit test on larger frame
return (largerFrame.contains(point)) ? self : nil
}
}
Similar to the UIButton extension here