Active clickable area for UIButton with rounded corners? - ios

So I have created a button with a border in my storyboard.
and then I rounded its corners and added a border color:
button.layer.cornerRadius = button.bounds.size.width / 2
button.layer.borderColor = greenColor
So the runtime result looks like this:
However the user can tap slightly outside the area of the button (where the corners used to be) and still call the button function. Is there a way to restrict the enabled area of the button to just be the circle?

With other answers you block the touch, I needed it to fall through.
And its even easier:
1) Setup your preferred path (for me circle)
private var touchPath: UIBezierPath {return UIBezierPath(ovalIn: self.bounds)}
2) Override point inside function
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return touchPath.contains(point)
}
All inside you UIButton subclass.

So I figured it out. Basically I have to detect the touch on the button, and then calculate the distance between the touch and the center of the button. If that distance is less than the radius of the circle (the width of the button / 2) then the tap was inside the circle.
Here's my code:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let radius:CGFloat = (self.frame.width / 2)
var point:CGPoint = CGPoint()
if let touch = touches.first {
point = touch.locationInView(self.superview)
}
let distance:CGFloat = sqrt(CGFloat(powf((Float(self.center.x - point.x)), 2) + powf((Float(self.center.y - point.y)), 2)))
if(distance < radius) {
super.touchesBegan(touches, withEvent: event)
}
}

(SWIFT 3) This solution works with all the buttons not just with round. Before we have to create path as a private property of the button class and after we can easily write this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
if path.contains(location) {
print("This print is shown only in case of button location tap")
}
}
}

Related

How do I move a SKSpriteNode to an area outside of it's constraints?

So I have these moving sprites with physics bodies that I initially have constrained to one area. Let's say call this area Box1. When the sprites bump the borders of Box1, they bump back and the SKConstraint is just an additional measure to make sure they stay within the box.
What I'm trying to do is make a button that causes all the sprites in Box1 move across the screen to a second box (Box2) that is basically identical to Box1 but right beside it. Box1 and Box2 share a border wall, so essentially the screen is divided in two sections.
My problem is when I call up my button function, the sprites get stuck at the wall and aren't able to go past it to Box2. I've tried setting both the physics bodies and the SKConstraints of the nodes to nil so I don't know what else is preventing them.
func moveSprites(){
if box1Sprites.isEmpty == false {
for sprite in box1Sprites{
sprite.constraints = nil
sprite.physicsBody = nil
let moveAction = SKAction.move(to: CGPoint(x: -300, y: 0), duration: 1) //This is a point within Box2
sprite.run(moveAction)
}
}
}
EDIT:
I also made it so that I could drag and drop the sprites from one box to the other and the code for that is pretty much the same and it works fine.
var currentMovingSprite: SKSpriteNode? = SKSpriteNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "sprite"{
node.constraints = nil
node.physicsBody = nil
currentMovingSprite = node
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pos = touch.location(in: self)
if let sprite = currentMovingSprite{
sprite.position = pos
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
spritePhysics(sprite: currentMovingSprite) // resets the sprite to how it was before
currentMovingSprite = nil
}

swift / spritekit move then touch element

I'm currently working on a game in swift using spritekit. I've got my joystick working using this library, and thats all good. I'm also moving the joystick to where the user taps using this code:
func updateJoystickPosition(pos: CGPoint) {
let posX = -(self.sceneSize.width/2) + pos.x
let posY = (self.sceneSize.height/2) - pos.y
let newJoystickPos = CGPoint(x: posX, y: posY);
self.joystick.position = newJoystickPos
}
which also works just fine, however, the joystick doesn't engage on that one tap. You have to tap on the actual joystick node for the joystick to engage, so obviously, i'd want the user to put their finger down on the screen and immediately be able to start moving it around to move the player.
the joystick it's self starts tracking with this function
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(touches)
if let touch = touches.first, stick == atPoint(touch.location(in: self)) {
tracking = true
startHandler?()
}
}
Question: Is there anyway i can modify the touchesBegan function or any of my other functions to achieve the desired results?
You have to override touchesMoved(_ touches: ,with event:) and in this method just processing all touches.
For example:
var startTouchingPoint = CGPoint.zero // property
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
startTouchingPoint = touch.location(in: self.camera!))
yourControllerPlace.position = startTouchingPoint
stickOnYourJoystick.position = CGPoint.zero
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let point = touch.location(in: self.camera!))
let convertedPoint = CGPoint(x: point.x - startTouchingPoint.x,
y: point.y - startTouchingPoint.y)
stickOnYourJoystick.position = convertedPoint
}
}

Is it possible to cancel a touch from inside touchesBegan or touchesMoved?

In my main view in a UIViewController I have a mapView and a another view (Let's say view A) that is above mapView. Both of them have frames equal to self.view.bounds. The view A is a rectangle that is resizable similar to those used to crop images. My goal here is to let the user specify an area on the map. So, I want the user to be able to zoom in an out of the map as well as change the rectangle width and height proportions since only letting the view A to be an unrealizable square would limit it too much.
I got this project from GitHub https://github.com/justwudi/WDImagePicker from which I am using the resizable rectangle functionality. In the second picture of the Github link, there's a rectangle with 8 dots and a shaded area outside. I want to be able to let the touch pass to the map which is behind the view A if the user touches on the shaded area. Only if the user clicks on the area inside the dots or on the dots (so that he wants to resize the rectangle) I want the view A to recognize the touch. So, I modified the code on touch on the view A and have this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if cropBorderView.frame.contains(touch.location(in: self)){
print("touch contains - touchesbegan")
//self.isUserInteractionEnabled = true
}
else{
print("Touch does not contain - touchesbegan")
self.touchesCancelled(touches, with: event)
//return
}
let touchPoint = touch.location(in: cropBorderView)
anchor = self.calculateAnchorBorder(touchPoint)
fillMultiplyer()
resizingEnabled = true
startPoint = touch.location(in: self.superview)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("inside touches moved")
if let touch = touches.first {
if cropBorderView.frame.contains(touch.location(in: self)){
print("touch contains - touchesmoved")
//self.isUserInteractionEnabled = true
}
else{
print("Touch does not contain - touchesmoved ")
self.touchesCancelled(touches, with: event)
//return
}
if resizingEnabled! {
self.resizeWithTouchPoint(touch.location(in: self.superview))
}
}
}
It is indeed recognizing the touch when I click inside and outside as I wanted, but it is not stopping the touch when I click outside. This means calling self.touchesCancelled(touches, with: event) is not working. Calling return gives a crash and does not work as well. Are there any solutions to this problem?
Thank you for your time and consideration.
touchesCancelled(_:with:) just a notification for UITouch, it will not work this way.
As far as I understand, you implemented touch handlers in your overlay UIView, if so, you can try to replace the call to self.touchesCancelled(touches, with: event) with cancelTracking(with:) function from UIControl class implementation:
else {
print("Touch does not contain - touchesmoved ")
self.cancelTracking(with event)
}
Update solution, based on hitTest:
I've checked possible solutions and it seems that you can use hitTest: to avoid unnecessary touch recognitions. The following example is Swift Playground, you can tap and drag touches and see what happens in the console:
import UIKit
import PlaygroundSupport
class JSGView : UIView {
var centerView = UIView()
override func didMoveToSuperview() {
frame = CGRect(x: 0, y: 0, width: 320, height: 480)
backgroundColor = UIColor.clear
centerView.frame = CGRect(x: 110, y: 190, width: 100, height: 100)
centerView.backgroundColor = UIColor.blue
addSubview(centerView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
dump(event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if (hitTest(touch.location(in: self), with: event) != nil) {
print("Touch passed hit test and seems valid")
super.touchesCancelled(touches, with: event)
return
}
}
print("Touch isn't passed hit test and will be ignored")
super.touchesMoved(touches, with: event)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if centerView.bounds.contains(centerView.convert(point, from: self)) {
return centerView
}
return nil
}
}
class JSGViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
let customView = JSGView()
view.addSubview(customView)
}
}
let controller = JSGViewController()
PlaygroundPage.current.liveView = controller.view

How do I make an if statement for my imageview rotation in touchesMoved?

I have this circle that the user can rotate 360 in either direction with their finger like a knob. What I would like to add is an if statement to the code so that I could handle other actions when the user turns the knob to the right and left. How would I do that? Here is the code I have and if you need more info let me know.
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
if touch!.view === knob {
let position = touch!.locationInView(self.view)
let target = leftTurnTable.center
let angle = atan2(target.y-position.y, target.x-position.x)
knob.transform = CGAffineTransformMakeRotation(angle)
}
}

let user to draw rectangle to select an area

I'm new in Swift and I'm trying to let the user draw a rectangle (touching and dragging) to select an area of an image just like when cropping but I don't want to crop I just want to know the CGRect the user created.
So far I have a .xib with a UIImage inside and its ViewController. I want to draw above the image but every tutorial I found about drawing is about subclassing UIView, override drawRect and put that as the xib class.
I figured it out. I just created a uiview and change its frame depending on the touches events
let overlay = UIView()
var lastPoint = CGPointZero
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
overlay.layer.borderColor = UIColor.blackColor().CGColor
overlay.backgroundColor = UIColor.clearColor().colorWithAlphaComponent(0.5)
overlay.hidden = true
self.view.addSubview(overlay)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Save original tap Point
if let touch = touches.first {
lastPoint = touch.locationInView(self.view)
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Get the current known point and redraw
if let touch = touches.first {
let currentPoint = touch.locationInView(view)
reDrawSelectionArea(lastPoint, toPoint: currentPoint)
}
}
func reDrawSelectionArea(fromPoint: CGPoint, toPoint: CGPoint) {
overlay.hidden = false
//Calculate rect from the original point and last known point
let rect = CGRectMake(min(fromPoint.x, toPoint.x),
min(fromPoint.y, toPoint.y),
fabs(fromPoint.x - toPoint.x),
fabs(fromPoint.y - toPoint.y));
overlay.frame = rect
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
overlay.hidden = true
//User has lift his finger, use the rect
applyFilterToSelectedArea(overlay.frame)
overlay.frame = CGRectZero //reset overlay for next tap
}

Resources