Swift hitTest:point withEvent to detect subview - ios

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

Related

Can a UIView assign a UITapGestureRecognizer to itself?

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
}

Can't touch UIImageView that is moving in Swift

I made drop tile game that tiles fall from the top of screen to one's bottom.
This game system is when you touch a tile, the tile will be hidden.
The tiles are custom class (GameTile class), but Touches Began in GameViewController didn't work.
How can I solve it?
GameTile.swift
class GameTile: UIImageView {
init(named: String, frame: CGRect) {
super.init(frame: frame)
super.image = (UIImage(named: named))
super.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameTileNormal: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
var isUserInteractionEnabledDefault: Bool
let colorName: UIColor
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = true,
color: UIColor = UIColor.blue) {
namedDefault = named
isHiddenDefault = isHidden
frameDefault = frame
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
GameView.swift
class GameView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = (UIColor.white)
self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
//make tiles
let tileNormal = GameTileNormal.init(named: "clear",
frame: CGRect(x:0), y:-60, width:60, height:60),isUserInteractionEnabled: true)
self.addSubview(tileNormal)
//move tiles
moveTile(tile: tileNormal, lane: 1)
}
}
func moveTile(tile: GameTile, lane: Int) {
UIImageView.animate(withDuration: TimeInterval(2.0),
delay: 0.0,
options: .curveLinear,
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
tile.removeFromSuperview()
//make new tile
self.makeTiles(lane: lane)
})
}
GameViewController.swift
class GameViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.isUserInteractionEnabled = true
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(trapView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
if let gameView = self.gameView {
// touchEvent.view is "gameView", not the view whose kind of class is GameTileNormal...
if let touchedGameTile = touchEvent.view as? GameTileNormal {
print("Touched normal tile")
touchEvent.view?.isHidden = true
touchEvent.view?.isUserInteractionEnabled = false
}else{
// other view
}
}
}
UPDATE
I changed how to move tiles from UIImageView.animation to Timer.
Then If I touched tiles, it didn't through after if (tile.layer.presentation()?.hitTest(location)) != nil { in touchesBegan, GameViewController.....
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
if let standardView = self.standardView {
for tile in standardView.tiles {
//breakpoint stops here
if (tile.layer.presentation()?.hitTest(location)) != nil {
//breakpoint doesn't through here
if tile is GameTileNormal {
//normal tile touched
}else{
}
break
}
}
}
}
moveTiles
makeTileTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateTilesPositionY(timer:)), userInfo: sendArray, repeats: true)
update tile position (drop tiles)
#objc func updateTilesPositionY(timer: Timer){
//tile info
let timerInfo:[Any] = timer.userInfo as! [Any]
let tile:GameTile = timerInfo[0] as! GameTile
let lane: Int = timerInfo[1] as! Int
//drop tile
tile.frame.origin.y = tile.frame.origin.y+1
//if tile reached on the bottom
if tile.frame.origin.y >= UIScreen.main.bounds.size.height {
if tile is GameTileNormal {
self.showGameOverView()
}
}else{
//drop tile
}
In UIImageView.animate add option .allowUserInteraction:
UIImageView.animate(withDuration: TimeInterval(2.0),
delay: 0.0,
options: [.curveLinear, .allowUserInteraction],
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
...
By default the user interaction is disallowed during animations.
UPDATE
However, to test whether the user hit a moving object, you will have a bit harder time. See for example this SO question. Basically, the UIView object does not really move, you can easily test that after firing the animation, the frame of the animated object is set straight to the end position. Just the presentation layer draws the moving view.
You will have to always go over all your moving tiles in the game and test each one if any of them has been touched (here I assume you have a reference to all the tiles in the game):
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
if let gameView = self.gameView {
for tile in tiles {
// go over all the moving objects in your scene and hit test all of them
if let touchedLayer = tile.layer.presentation()?.hitTest(location) {
// if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop
tile.isHidden = true
tile.isUserInteractionEnabled = false
tile.removeFromSuperview()
break
}
}
}
}
Use layer.presentationLayer to run a hitTest if that hitTest return a CALayer then you are touching that titleView, in fact this will only work if your titles are userInteractionEnabled = false
Full Code
import UIKit
class GameTile: UIImageView {
init(named: String, frame: CGRect) {
super.init(frame: frame)
super.image = (UIImage(named: named))
super.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameTileNormal: GameTile {
let namedDefault: String
var frameDefault: CGRect
let isHiddenDefault: Bool
var isUserInteractionEnabledDefault: Bool
let colorName: UIColor
init(
named: String,
frame: CGRect,
isHidden: Bool = false,
isUserInteractionEnabled: Bool = false,
color: UIColor = UIColor.blue) {
namedDefault = named
isHiddenDefault = isHidden
frameDefault = frame
isUserInteractionEnabledDefault = isUserInteractionEnabled
colorName = color
super.init(named: named, frame: frame)
super.isHidden = isHiddenDefault
super.isUserInteractionEnabled = isUserInteractionEnabledDefault
super.backgroundColor = colorName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GameView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = (UIColor.white)
self.frame = CGRect(x:0, y:0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
//make tiles
let tileNormal = GameTileNormal.init(named: "clear",
frame: CGRect(x:0, y:-60, width:60, height:60),isUserInteractionEnabled: false)
self.addSubview(tileNormal)
//move tiles
moveTile(tile: tileNormal, lane: 1)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.blue.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveTile(tile: GameTile, lane: Int) {
UIImageView.animate(withDuration: TimeInterval(10),
delay: 0.0,
options: .curveLinear,
animations: {
tile.frame.origin.y = UIScreen.main.bounds.size.height
}, completion: {finished in
tile.removeFromSuperview()
//make new tile
//self.makeTiles(lane: lane)
})
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touchEvent = touches.first!
let location = touchEvent.location(in: touchEvent.view)
for tile in self.subviews {
// go over all the moving objects in your scene and hit test all of them
if tile.layer.presentation()?.hitTest(location) != nil {
// if a hittest returns a layer, it means that this tile was touched, we can handle it and break out of the loop
tile.isHidden = true
break
}
}
}
}
class ViewController: UIViewController {
var gameView: GameView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.isUserInteractionEnabled = true
gameView = GameView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 568))
self.view.addSubview(gameView)
}
}

Draw on large UIView take a lot of memory

when I start to draw on large UIView ( width: 3700 , height: 40000 ), it takes a lot of memory
when app starts, memory is 150 MB and when start drawing on it( calling setNeedsDisplay method) take around 1 GB and app is gonna crash
class DrawingVc: UIViewController {
let contentView = DrawableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.backgroundColor = .clear
self.view.addSubview(contentView)
contentView.frame = CGRect(x: 0, y: 0, width:view.frame.width, height:
view.frame.height * 50)
}
here is the code of custom view, as you can see, setNeedsDisplay runs on touchMoves
class DrawableView: UIView {
var mLastPath: UIBezierPath?
weak var scdelegate: DrawableViewDelegate?
var isDrawEnable = true
private var drawingLines : [UIBezierPath] = []
override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
debugPrint("request draw")
drawLine()
}
private func drawLine() {
UIColor.blue.setStroke()
for line in drawingList {
line.lineWidth = 4
line.stroke()
line.lineCapStyle = .round
}
}
var drawingList = [UIBezierPath]()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if touches.count == 2 {
return
}
let location = (touches.first?.location(in: self))!
mLastPath = UIBezierPath()
mLastPath?.move(to: location)
prevPoint = location
drawingList.append(mLastPath!)
}
var prevPoint: CGPoint?
var isFirst = true
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
debugPrint("touchesMoved: " , (touches.first?.location(in: self).x)! , (touches.first?.location(in: self).y)! )
if let coalescedtouches = event?.coalescedTouches(for: touches.first!)
{
for coalescedTouch in coalescedtouches
{
let locationInView = coalescedTouch.location(in: self)
if let prevPoint = prevPoint {
let midPoint = CGPoint( x: (locationInView.x + prevPoint.x) / 2, y: (locationInView.y + prevPoint.y) / 2)
if isFirst {
mLastPath?.addLine(to: midPoint)
}else {
mLastPath?.addQuadCurve(to: midPoint, controlPoint: prevPoint)
}
isFirst = false
} else {
mLastPath?.move(to: locationInView)
}
prevPoint = locationInView
}
}
setNeedsDisplay()
}
}
What makes this problem and how that fix?
Your view is larger than the largest possible screen on an iOS device, so I suppose your view is embedded in a scrollview. You should only draw the visible parts of your view. Unfortunately, this is not supported by UIView directly. You may take a look on CATiledLayer, which supports drawing of only visible parts of a layer, and it supports different levels of details for zoomed layers, too.

How to recognise which image was touched

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:

How could i drug UIButton on subclass of UIControl

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
}

Resources