I want to change the color of a UIView based on the number of touches currently on it. I have managed to create this with a single touch:
class colorChangerViewClass: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setColor(color: .blue)
print("touchesBegan")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
setColor(color: .green)
print("touchesEnded")
}
func setColor(color: UIColor) {
self.backgroundColor = color
}
}
But I am struggling with implementing multitouch. I feel like I'm not understanding something here.
Does UIView have a property to check how many fingers are currently touching it?
I could check that each time a new touchesBegan or touchesEnded occurs.
First enable multi touch on your subclass
self.isMultipleTouchEnabled = true
Then inside your touch event functions you can get all touches with event from your UIView class.
let touchCount = event?.touches(for: self)?.count
Make sure the UIView has isMultipleTouchEnabled = true, the default is false.
Then change the touchesBegan method
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
setColor(color: .blue)
let touchCount = event?.touches(for: self)?.count
}
Related
I have the following hierarchy, the root view of the controller. Added two subviews, a standard UIView and a UIScrollView. The UIView is on top.
What I want to do is to receive touches on that UIView, meaning touchesBegan, touchesMoved... like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Began")
super.touchesBegan(touches, with: event)
next?.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Moved")
super.touchesMoved(touches, with: event)
next?.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Ended")
super.touchesEnded(touches, with: event)
next?.touchesEnded(touches, with: event)
}
But at the same time I want the scroll to work. The UIScrollView that is below, I want that to keep scrolling as usual.
This seems impossible to do. If my view handles touches, then I can't forward them to the UIScrollView. But I want both things: see the raw touches on the top view while the scrollview to work as usual.
How can I accomplish this?
The opposite would also work. Meaning, a scrollview on top that scrolls as usual but I also receive the raw touches (touchesBegan, touchesMoved...) on the view that is underneath.
Following this answer worked for me, though with some slight changes. Do note that for this solution, the UIScrollView is on top of the UIView. Firstly, you need to create a subclass of UIScrollView and override the touch methods.
protocol CustomScrollViewDelegate {
func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?)
}
class CustomScrollView: UIScrollView {
var customDelegate: CustomScrollViewDelegate?
override func awakeFromNib() {
super.awakeFromNib()
for gesture in self.gestureRecognizers ?? [] {
gesture.cancelsTouchesInView = false
gesture.delaysTouchesBegan = false
gesture.delaysTouchesEnded = false
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchBegan(touches, with: event)
super.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchMoved(touches, with: event)
super.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
customDelegate?.scrollViewTouchEnded(touches, with: event)
super.touchesEnded(touches, with: event)
}
}
Take note of the code in awakeFromNib(). UIScrollView has its own set of gestures. So for each gesture, delaysTouchesBegan and delaysTouchesEnded needs to be false to prevent delays for the touch events.
Finally, just assign the delegate to your ViewController and implement the methods like so.
class ViewController: UIViewController {
#IBOutlet weak var scrollView: CustomScrollView!
#IBOutlet weak var touchView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.customDelegate = self
}
}
extension ViewController: CustomScrollViewDelegate {
func scrollViewTouchBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Began")
}
}
}
func scrollViewTouchMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Moved")
}
}
}
func scrollViewTouchEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// Check if touch location is within the bounds of the UIView
if let touch = touches.first {
let position = touch.location(in: view)
if touchView.bounds.contains(position) {
print("Ended")
}
}
}
}
What's the best way to determine whether there is any active touch happening in a view hierarchy? Something like:
view.hasTouches
returns true if view or any of its descendant has active touches.
To those who would suggest using UIResponder's touch event handling API like touchesBegan, notice that view doesn't receive these calls if one of its descendant is handling the touch events.
You can use touchesBegan and touchesEnded methods in UIViewController as follows:
class ViewController: UIViewController {
var isTouching = false
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = true
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = false
}
}
I am making a "slide to confirm" button, the set up is a custom UIButton + a regular UIImageView, the image is passed to the button and the button listen to touches and move the image accordingly.
Everything is working great except for one detail after the button is released everything should go back to their initial state, yet the button stay changed as if its alpha was halved
i commented the alpha because the button was going to hide but i disabled that once i run into this problem, the problem happens regardless of setting the alpha or not.
This is the code for the custom button
class SlideButton: UIButton {
var arrowImage: UIImageView?
var initialLocation: CGPoint?
var initialArrowPosition: CGPoint?
func setArrow(arrow:UIImageView){
arrowImage = arrow
initialArrowPosition = arrow.frame.origin
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
let touch = touches.first!
let location = touch.location(in: self.superview)
initialLocation = location
UIView.animate(withDuration: 0.2) {
//self.alpha = 0.0
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
UIView.animate(withDuration: 0.3) {
self.arrowImage!.frame.origin = self.initialArrowPosition!
//self.alpha = 1.0
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
let currentLocation = touch.location(in: self.superview)
arrowImage!.frame.origin.x = initialArrowPosition!.x - initialLocation!.x + (currentLocation.x)
}
}
You are remembering to call super for one of your touches methods:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
But for the other touches methods you've forgotten to do that, so you've broken the core functionality of the button.
you might have to also add an override to touchesCancelled because you might not always hit the touchesEnded endpoint.
I have a stylized UIButton subclass that I want to present the standard AirPlay route selection interface on tap. I know AVRoutePickerView is available but it does not appear to offer any customizations except to configure its tint and active tint color on iOS.
Is there a way to present the route picker using your own button?
It appears this app was able to do just that:
Since the AVRoutePickerView is just a view you could add it to a custom button or add custom views on top of it. This is a very rough example
let frame = CGRect(x: 50, y: 50, width: 100, height: 50)
let routePickerView = AVRoutePickerView(frame: frame)
routePickerView.backgroundColor = UIColor.clear
view.addSubview(routePickerView)
let label = UILabel(frame: routePickerView.bounds)
label.backgroundColor = .black
label.textColor = .white
routePickerView.addSubview(label)
label.text = "Rough"
I spoke hastily with my examples of how you could make it work, button tap pass through is touchy as well as trying to use a gesture recognizer. However I came up with three alternative approaches that with some effort can be used. They all rely on having a view added to the picker as a subview as above. That view should have .isUserInteractionEnabled = true. I tested with a UILabel and just with a plain UIView, it probably needs to be something that doesn't have touch processing so avoid UIControls. Once you have this view in place you can use touchesBegan touchesCancelled and touchesEnded to do any visual adjustments or animation then pass along the touches. As always when fiddling with custom provided controls this may not always work but this is currently working for me and doesn't use private api just workarounds and a bit of luck.
Using a custom UILabel as the subview
class CustomLabel: UILabel {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .init(scaleX: 0.75, y: 0.75)
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesCancelled(touches, with: event)
}
}
Using a subclass of AVRoutePickerView
class CustomRoutePicker: AVRoutePickerView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .init(scaleX: 0.75, y: 0.75)
super.touchesBegan(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
transform = .identity
super.touchesCancelled(touches, with: event)
}
}
Using UIViewController with a reference to a pickerView
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
pickerView?.transform = .init(scaleX: 0.75, y: 0.75)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pickerView?.transform = .identity
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
pickerView?.transform = .identity
}
I have a custom class that is an SKNode, which in turn has several SKSpriteNodes in it. Is there a way I can detect touches on these child SKSpriteNodes from my game scene?
I'm working in swift
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
if([yourSprite containsPoint: touchLocation])
{
//sprite contains touch
}
}
Source: http://www.raywenderlich.com/84434/sprite-kit-swift-tutorial-beginners
Examine Apple's SceneKitVehicle demo. Someone kindly ported it to Swift.
The code you want is in the GameView.swift file. In the GameView you'll see the touchesBegan override. Here's my version of it for Swift 2.1:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let scene = self.overlaySKScene else {
return
}
let touch = touches.first!
let viewTouchLocation = touch.locationInView(self)
let sceneTouchPoint = scene .convertPointFromView(viewTouchLocation)
let touchedNode = scene.nodeAtPoint(sceneTouchPoint)
if (touchedNode.name == "Play") {
print("play")
}
}
If it's not clear; the GameView is set as the app's view class by way of the Storyboard.
You'll need to compare the location of your Touch to the location of your SKNode
You can get the location of your touch at one of the following methods, using locationInNode():
touchesBegan()
touchesMoved()
touchesEnded()
Swift 5+
If you wish to encapsulate touch logic in a node and deal with it locally, you can simply set interaction to true in the corresponding node.
isUserInteractionEnabled = true
And then, of course, override touchesBegan to your desire.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {}
If you still wish to receive touches in the scene when touches occur inside a child node, you can, for instance, define a protocol and property for the child node's delegate and set the scene to be it.
e.g:
final class GameScene: SKScene {
private let childNode = ChildNode()
override func didMove(to view: SKView) {
addChild(childNode)
childNode.delegate = self
}
}
extension GameScene: TouchDelegate {}
protocol TouchDelegate {
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
}
final class ChildNode: SKSpriteNode {
var delegate: TouchDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
delegate?.touchesBegan(touches, with: event)
}
}