Button changing after image moves over it? - ios

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.

Related

How to handle touches by an UIView on top of a UIScrollView and the UIScrollView both at the same time

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")
}
}
}
}

Present AirPlay route picker from a custom UIButton

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
}

How to get touch location when clicking a UIImageView?

I want to be able to determine the x,y location where the user clicked a UIImageView in Swift.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: imageView)
print(position.x)
print(position.y)
}
}

How to check if a child node has been touched Swift 3

I have sprites moving across the screen, and if they are clicked then they disappear (i.e deleted).
I have overridden the touchesBegan func as follows:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let location = touch.location(in: self)
for child in self.children {
if child.position == location {
child.removeFromParent()
}
}
}
This doesn't seem to have any effect, can someone tell me where I am going wrong?
In which class did you implement this method?
If it was in SKNode itself, you simply do:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.removeFromParent()
}
However, if this method is in SKScene, this way that was implemented would probably not work. Because child.position returns a point (x, y) where the touch was made. And you're trying to compare the touch point and position of the SKNode (center point), it's unlikely to work.
Instead of using this way, try using .nodeAtPoint, a method of SKScene.
For this you will need to put a value in the 'name' property of your SKNode:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch")
let touch = touches.first!
let positionInScene = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(positionInScene)
if let name = touchedNode.name
{
if name == "your-node-name"
{
touchedNode.removeFromParent()
}
}
}
Font: How do I detect if an SKSpriteNode has been touched

Getting the number of fingers touching a UIView

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
}

Resources