I have a subclass of UIControl
what I want to achieve is a UISlider with 2 UIButton-like thumbs
but I'm coufused if should I use
touchesBegan(::)
or
beginTrackingWithTouch(::)
and how could i force my button follow touch events
What I've only achieved is highlighting a button with overriding its #highlighted property like this :
class RangeSliderThumbLayer: UIButton {
override var highlighted: Bool {
didSet {
//print("did set \(highlighted)")
setNeedsDisplay()
if highlighted {
self.backgroundColor = UIColor.redColor()
} else {
self.backgroundColor = UIColor.greenColor()
}
}
}
here how I've tried to move my button :
let lowerThumbButton = RangeSliderThumbLayer(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
let upperThumbButton = RangeSliderThumbLayer(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
previousLocation = touch.locationInView(self)
print(previousLocation)
lowerThumbButton.frame = CGRectMake(previousLocation.x, 0, 20, 20)
// Hit test the thumb layers
if lowerThumbButton.pointInside(previousLocation, withEvent: event) {
print("point indide")
lowerThumbButton.highlighted = true
} else if upperThumbButton.frame.contains(previousLocation) {
upperThumbButton.highlighted = true
}
return lowerThumbButton.highlighted || upperThumbButton.highlighted
}
private func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) -> Double {
return min(max(value, lowerValue), upperValue)
}
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
let location = touch.locationInView(self)
let value = round(location.x / step)
let validValue = minimumValue + (Double(value) * increase)
let deltaLocation = Double(location.x - previousLocation.x)
print(value)
lowerThumbButton.frame = CGRectMake(value, 0, 20, 20)
previousLocation = location
if lowerThumbButton.highlighted {
lowerValue = boundValue(validValue, toLowerValue: minimumValue, upperValue: upperValue)
} else if upperThumbButton.highlighted {
upperValue = boundValue(validValue, toLowerValue: lowerValue, upperValue: maximumValue)
}
return true
}
Related
I'm trying to detect when subView is tap using hitTest. Here is my implementation:
class MyViews:UIView {
override init (frame : CGRect) {
super.init(frame : frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subview in self.subviews.reversed() {
let subPoint = subview.convert(point, from:self);
if let result = subview.hitTest(subPoint, with:event) {
return result;
}
}
return nil
}
}
Here is how I'm adding the subViews:
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UITapGestureRecognizer(target: self, action: #selector(doSomethingHere(_:)))
let blueView = MyViews(frame: CGRect(origin: CGPoint(x: 38.0, y: 231.0), size: CGSize(width: 335.0, height: 444.0)))
blueView.tag = 100
blueView.isUserInteractionEnabled = true
blueView.backgroundColor = .blue
blueView.addGestureRecognizer(gesture)
view.addSubview(blueView)
let grey = MyViews(frame: CGRect(origin: CGPoint(x: 38.0, y: 20.0 ), size: CGSize(width: 279, height: 203.0)))
grey.tag = 200
grey.backgroundColor = .gray
grey.isUserInteractionEnabled = true
grey.addGestureRecognizer(gesture)
blueView.addSubview(grey)
let white = MyViews(frame: CGRect(origin: CGPoint(x: 56.0 , y: 17.0 ), size: CGSize(width: 182.0, height: 92)))
white.tag = 300
white.isUserInteractionEnabled = true
white.addGestureRecognizer(gesture)
grey.addSubview(white)
let result = blueView.hitTest(CGPoint(x: 157.5, y: 149.0), with: nil)
print(result ?? "no result")
}
I'm simulating a touch on this line let result = blueView.hitTest(CGPoint(x: 157.5, y: 149.0), with: nil)
But the response from this function:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subview in self.subviews.reversed() {
let subPoint = subview.convert(point, from:self);
if let result = subview.hitTest(subPoint, with:event) {
return result;
}
}
return nil
}
It always is nil. I don't understand why it should be returning the white view.
Any of you knows why this function is not returning the correct view?
I'll really appreciate your help.
It looks like you hitTest is never checking to see if the point is in the view. Give this a whirl:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// if there are subviews, step through them
// if the subview contains the point, call hitTest on the subview to see if the subview's subviews contain the point
// if they do, return the result,
// if they don't, return the subview that does contain the point.
for subview in self.subviews.reversed() {
let subPoint = subview.convert(point, from:self)
if subview.point(inside: subPoint, with: event) == true {
if let result = subview.hitTest(subPoint, with: event) {
return result
} else {
return subview
}
}
}
// none of the subviews contain the point,(or there are no subviews) does the current view contain the point?
// if yes, return self. otherwise, return nil
if self.point(inside: point, with: event) == true {
return self
} else {
return nil
}
}
I'm trying to make a UIView that recognizes tap gestures, however taps are not ever correctly registered by the UIView. Here's the code for the UIView subclass itself:
import UIKit
class ActionCell: SignalTableCell, UIGestureRecognizerDelegate {
var icon: UIImageView!
var actionType: UILabel!
var actionTitle: UILabel!
var a:Action?
var tap:UITapGestureRecognizer?
required init(frame: CGRect) {
//
super.init(frame:frame)
tap = UITapGestureRecognizer(target: self, action: #selector(self.touchTapped(_:)))
tap?.delegate = self
addGestureRecognizer(tap!)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
tap = UITapGestureRecognizer(target: self, action: #selector(self.touchTapped(_:)))
tap?.delegate = self
addGestureRecognizer(tap!)
}
#objc func touchTapped(_ sender: UITapGestureRecognizer) {
print("OK")
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.isUserInteractionEnabled = true
}
override func layoutSubviews() {
if(icon == nil) {
let rect = CGRect(origin: CGPoint(x: 10,y :20), size: CGSize(width: 64, height: 64))
icon = UIImageView(frame: rect)
addSubview(icon)
}
icon.image = UIImage(named:(a?.icon)!)
if(actionType == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :20), size: CGSize(width: 200, height: 16))
actionType = UILabel(frame: rect)
addSubview(actionType)
}
actionType.text = a.type
if(actionTitle == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :80), size: CGSize(width: 200, height: 16))
actionTitle = UILabel(frame: rect)
addSubview(actionTitle)
}
actionTitle.text = a?.title
}
func configure( a:Action ) {
self.a = a
}
override func setData( type:SignalData ) {
a = (type as! Action)
}
}
I'm simply trying to make it so that this UIView can, you know, know when it's tapped. Is this possible without adding a separate UIViewController? This seems as though it should be fairly simple but it doesn't appear to be, confusingly.
I've stepped through the code and the init method is called and the Gesture Recognizer is added but it doesn't trigger.
If its a table view cell, I'd recommend not using a tap gesture, since it might interfere with the didSelectRowAtIndexPath: and other delegate methods. But if you still wanna keep the tap gesture, try adding tap?.cancelsTouchesInView = false before addGestureRecognizer(tap!) and see if that works.
If you just want to know when its tapped, you could also override the following UIResponder method:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// do your stuff
}
I think, it is easier to override touchesBegan method. Something like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touched")
super.touchesBegan(touches, with: event)
}
Most likely the actionType, ActionTitle, and icon are being tapped and the tap is not falling through because user interaction is disabled by default for labels and images. Set isUserInteractionEnabled = true for each of those fields that are subviews of the main view.
override func layoutSubviews() {
if(icon == nil) {
let rect = CGRect(origin: CGPoint(x: 10,y :20), size: CGSize(width: 64, height: 64))
icon = UIImageView(frame: rect)
icon.isUserInteractionEnabled = true
addSubview(icon)
}
icon.image = UIImage(named:(a?.icon)!)
if(actionType == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :20), size: CGSize(width: 200, height: 16))
actionType = UILabel(frame: rect)
actionType.isUserInteractionEnabled = true
addSubview(actionType)
}
actionType.text = a.type
if(actionTitle == nil) {
let rect = CGRect(origin: CGPoint(x: 100,y :80), size: CGSize(width: 200, height: 16))
actionTitle = UILabel(frame: rect)
actionTitle.isUserInteractionEnabled = true
addSubview(actionTitle)
}
actionTitle.text = a?.title
}
I have a simple pong-like game and so far everything is working well. However I'd like to count the number of times the ball has hit the "paddle" as sort of a high-score. Here's the code for the sliderBar (paddle) touch events:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !self.isBallRolling {
let pushBehavior = UIPushBehavior(items: [self.ball], mode: .instantaneous)
pushBehavior.magnitude = 1.5
self.animator.addBehavior(pushBehavior)
self.isBallRolling = true
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch? = touches.first
let touchLocation: CGPoint? = touch?.location(in: self.view)
let yPoint: CGFloat = self.sliderBarCenterPoint.y
let sliderBarCenter = CGPoint(x: CGFloat((touchLocation?.x)!), y: yPoint)
self.sliderBar.center = sliderBarCenter
self.animator.updateItem(usingCurrentState: self.sliderBar)
}
func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item1: UIDynamicItem, with item2: UIDynamicItem, at p: CGPoint) {
let pushBehavior = UIPushBehavior(items: [self.ball], mode: .instantaneous)
pushBehavior.angle = 0.0
pushBehavior.magnitude = 0.75
self.animator.addBehavior(pushBehavior)
}
// The label that displays the score
let countLabel: UILabel = {
let frame = CGRect(x: 100, y: 100, width: 200, height: 50)
let cl = UILabel(frame: frame)
cl.textAlignment = NSTextAlignment.center
cl.text = ""
cl.textColor = UIColor.red
cl.translatesAutoresizingMaskIntoConstraints = false
return cl
}()
func setupCountLabel() {
countLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
countLabel.bottomAnchor.constraint(equalTo: self.sliderBar.topAnchor, constant: -3).isActive = true
countLabel.widthAnchor.constraint(equalToConstant: 150).isActive = true
countLabel.heightAnchor.constraint(equalToConstant: 73).isActive = true
}
I've never worked with UICollisionBehaviorDelegate before - where and how should I be detecting the amount of times the ball has hit the sliderBar?
Thanks for any help!
I am developing an application which the user will be able to drag and drop items on a canvas and when he releases the image it is drawn on the canvas.
This is my DragImage class which handle the touches:
class DragImages: UIImageView {
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder : NSCoder){
super.init(coder : aDecoder)
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget{
let position = touch.location(in: self.superview)
if target.frame.contains(position){
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: nil))
}else {
self.center = originalPos
}
}
print(self.center.x, self.center.y)
self.center = originalPos
}
func getEndPosX() -> CGFloat{
return self.center.x
}
func getEndPosY() -> CGFloat {
return self.center.y
}
}
In my ViewController class I added this piece of code to handle the touches etc:
ornament1.dropTarget = xmasTree
ornament2.dropTarget = xmasTree
ornament3.dropTarget = xmasTree
ornament4.dropTarget = xmasTree
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.itemDroppedOnTree(_:)), name: NSNotification.Name(rawValue: "onTargetDropped"), object: nil)
}
func itemDroppedOnTree(_ notif : AnyObject){
}
I managed to get the X and Y position when the image is dragged on the canvas but i cant find a way to recognise which of the 4 images is being dropped in order for me to draw that specific one!
You could add the sender to your notification (and also the position):
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "onTargetDropped"), object: self, userInfo: ["position":position]))
and get it later in itemDroppedOnTree:
func itemDroppedOnTree(_ notif : NSNotification){
let position = notif.userInfo["position"]
let sender = notif.object as! DragImage
if sender === dragImage1 {
//...
} else if sender === dragImage2 {
//...
}
}
I recommend against it though and plead to use a delegate to inform the ViewController instead. (Opinion based: In general, use Notifications for to-many broadcasts only.)
The delegate function should have the sender as first parameter. According to func tableView: tableView:UITableView, cellForRowAt indexPath:IndexPath).
This way you know which image is sending its new position and can compare it to your property like in the above example:
if dragImage === dragImage1 {...
Your code plus working delegate to paste to Playground:
import UIKit
import PlaygroundSupport
protocol DragImageDelegate: class {
func dragimage(_ dragImage:DragImage, didDropAt position:CGPoint)
}
class DragImage: UIImageView {
weak var delegate: DragImageDelegate?
var originalPos : CGPoint!
var dropTarget: UIView?
override init (frame : CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches : Set<UITouch>,with event: UIEvent?){
originalPos = self.center
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let position = touch.location(in: self.superview)
self.center = CGPoint(x : position.x, y : position.y)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let target = dropTarget {
let position = touch.location(in: self.superview)
if target.frame.contains(position){
print(self.center.x, self.center.y)
guard let delegate = self.delegate else {
print("delegate not set")
return
}
print(self.center.x, self.center.y)
delegate.dragimage(self, didDropAt: position)
return
}
}
self.center = originalPos
}
}
class MyVC: UIViewController, DragImageDelegate {
let dragImage1 = DragImage(frame: CGRect(x: 0.0, y: 0.0, width: 30.0, height: 30.0))
let dragImage2 = DragImage(frame: CGRect(x: 0.0, y: 100.0, width: 30.0, height: 30.0))
override func viewDidLoad() {
let target = UIView(frame: CGRect(x: 200.0, y: 400.0, width: 30.0, height: 30.0))
target.backgroundColor = .black
view.addSubview(target)
dragImage1.backgroundColor = .white
dragImage2.backgroundColor = .white
dragImage1.dropTarget = target
dragImage2.dropTarget = target
view.addSubview(dragImage1)
view.addSubview(dragImage2)
dragImage1.delegate = self
dragImage2.delegate = self
}
private func move(_ view:UIView, to position:CGPoint) {
view.frame = CGRect(x: position.x, y: position.y, width: view.frame.size.width, height: view.frame.size.height)
}
// MARK: - DragImageDelegate
func dragimage(_ dragImage: DragImage, didDropAt position: CGPoint) {
if dragImage === dragImage1 {
move(dragImage1, to: position)
} else if dragImage === dragImage2 {
move(dragImage2, to: position)
}
}
}
var container = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0))
let myVc = MyVC()
myVc.view.frame = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 600.0)
myVc.view.backgroundColor = .green
container.addSubview(myVc.view)
PlaygroundPage.current.liveView = container
Result:
I created a subclass of UIimageView because i want to use this imageview multiple times. I'm using touchesmoved to drag the image. So when i use multiple images of same class, i want to find whether they intersect each other. Here's the image class code
import UIKit
class imgBall: UIImageView {
private var xOffset: CGFloat = 0.0
private var yOffset: CGFloat = 0.0
required init(coder aDecoder:NSCoder) {
fatalError("use init(point:")
}
init(point:CGPoint) {
let image = UIImage(named: "ball.png")!
super.init(image:image)
self.frame = CGRect(origin: point, size: CGSize(width: 100, height: 100))
self.userInteractionEnabled = true
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let point = touches.anyObject()!.locationInView(self.superview)
xOffset = point.x - self.center.x
yOffset = point.y - self.center.y
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
let point = touches.anyObject()!.locationInView(self.superview)
self.center.x = point.x - xOffset
}
}
and this is how I'm using it in gameviewcontroller
viewDidLoad() {
var ball1 = imgBall(point: CGPoint(x: 20, y: 0))
self.view.addSubview(ball1)
var ball2 = imgBall(point: CGPoint(x: 50, y: 70))
self.view.addSubview(ball2)
}
so how do i find out if they intersect/collide each other?
You've got to use CGRectIntersectsRect with something like this :
if (CGRectIntersectsRect(ball1.frame,ball2.frame) {
// they intersect
} else {
// they're safe
}