Prevent parent UIControl from responding to touchDown events - ios

I am creating a reusable MetaControl that consists of a few sub-controls (see pic). It inherits from UIControl, and contains 2 simple UIButtons and a custom slider I created. The CustomSlider also inherits from UIControl.
In the MetaControl's initializer, I add some target actions that receive events from the sub-controls to inform the MetaControl of changes.
class MetaControl: UIControl {
public override init(frame: CGRect) {
self.slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
self.slider.addTarget(self, action: #selector(sliderTouched), for: .touchDown)
self.button1.addTarget(self, action: #selector(button1Pressed), for: .touchDown)
self.button2.addTarget(self, action: #selector(button2Pressed), for: .touchDown)
}
}
and then later I put the selector:
#objc private func sliderTouched() {
print("The slider was touched")
}
The problem I'm having is with the sliderTouched() event. This event gets fired twice whenever I touch the slider.
The first time it seems to be sent from the MetaControl itself.
The second time is the correct event being passed up from the CustomSlider itself.
My question is, how can I prevent the MetaControl itself from responding to events, while still allowing my CustomSlider to send its actions?
I used 2 ways to confirm that it was the MetaControl itself which triggered the extra event:
Commented out the action sending code in the CustomSlider. Even without this line of code, sliderTouched() is still called:
class CustomSlider: UIControl {
var isTouched = false {
didSet {
//sendActions(for: .touchDown)
}
}
}
Replaced the .touchDown action with .applicationReserved. Since I'm no longer using .touchDown, the MetaControl's .touchDown event is never called.
class CustomSlider: UIControl {
var isTouched = false {
didSet {
sendActions(for: .applicationReserved)
}
}
}
Interestingly, the UIButtons do not cause this same problem. Pressing the UIButton does not cause two events to be fired. How can I achieve this same thing?
P.S. The way I am causing the events to be sent from the CustomSlider is through a custom UIPanGestureRecognizer which is embedded into the CustomSlider's own initializer.
I used this as a guideline: https://www.raywenderlich.com/82058/custom-control-tutorial-ios-swift-reusable-knob
P.P.S I hope I'm not going about this the wrong way-- embedding all these sub-controls into a MetaControl that then itself inherits from UIControl. The MetaControl itself never responds to touch events-- only the subcontrols. I'm using the MetaControl mostly as a container to manage the shared state, provide structure to the layout, and to make the control re-usable. Eventually, I need many of them.

The answer was to make sure to also override touchesBegan in the slider.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}

Related

How to add touch event for custom uicontrol and controller?

I have a custom UIControl that has three subviews. Each of those subviews, I add a target:
button.addTarget(self, action: #selector(buttonTapped(clickedBtn:)), for: .touchUpInside)
Within that function buttonTapped, it does some special animations to do some transitions (It mimics the segmented control).
Now, within the ViewController that this custom UIControl exists in must know when it's touched. I created an #IBAction function that interacts with the touch events for the custom UIControl.
The problem is, that isn't possible (as far as I know). If I add a target touch event to the subviews, the parent touch events won't get called. To have the parent view called the #IBAction function, I must set all the subview's setUserInteractiveEnabledtotrue`. When I do that, the subview's touch event functions won't get called.
I need both touch event functions to be called. How can I do this? Or what's the best way to get around this?
Use delegates, add a protocol in your UIControl that needs to be implemented in your ViewController.
This way you can detect if a button is clicked in your UIControl and invoke a specific function in your VC.
For Example:
//YourUIControl.Swift
protocol YourUIControlDelegate {
func didTapFirstButton()
}
class YourUiControl : UIView { //I'm assuming you create your UIControl from UIView
var delegate : YourUIControlDelegate?
//other codes here
.
.
.
#IBAction func tapFirstButton(_ sender: AnyObject) {
if let d = self.delegate {
d.didTapFirstButton()
}
}
}
//YourViewController.Swift
extension YourViewController : UIControlDelegate {
func didTapFirstButton() {
//handle first button tap here
}
}

How to detect when user finished moving Range slider in Swift

I use this is Range slider in my project. How use it.
I want to detect, when user finished moving Range slider.
I tried to use function SliderAction(sender: RangeSlider), but I get each moving points in the slider. I think I need to use this function: func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?), but it is doesn't work.
How can I make it?
To detect when user finished moving the range slider you can add a controlevent to your slider , you can add it programatically :
mySlider.addTarget(self, action: "sliderDidEndSliding:", forControlEvents: .UIControlEventTouchUpInside)
then you have to do your logic the recieving methode .
func sliderDidEndSliding(sender: UISlider) {
}
For swift 3 I used this:
mySlider.addTarget(self, action: #selector(self.sliderDidEndSliding), for: .touchUpInside)
func sliderDidEndSliding() {
// process stuff
}
reading values is done with:
#IBAction func mySlider(_ sender: Any) {
}
based on this example viewcontroller, you should check the value sender.selectedMax value changed should be equal to the one you set in storyboard for that slider view
You need to override endTrackingWithTouch. It'll get called when tracking ends by the underlying machinery, giving you a chance to respond.
Be sure and call the super of this during your version of endTrackingWithTouch so the the parents classes can do their bit.
Swift 4
slider.addTarget(self, action: #selector(sliderDidEndSliding), for: .touchUpInside)
and add
#objc func sliderDidEndSliding(sender: UISlider) {
}

Ios swift replace selector by function

I just want to write one or two lines of code on button click like
print("Button Clicked")
for this i dont want to create a seperate function and call via selector
as
action: #selector(BtnKlkFnc(_:))
I want to simplify like
action: { action in print("Button Clicked")}
I also tried
#selector({print("Button Clicked")})
Can anyone help me to simplify this
Am new to stackoverflow and do not have enough reputations yet, So kindly vote for my question up, so i can vote for your ans
Short answer: You can't do that. Button actions are part of the target/action mechanism built into Cocoa/Cocoa touch. It's based on selectors, and you must create a named method and use it's selector. You can't use a Swift closure as a button action.
EDIT:
Note that it is possible to create a custom subclass of UIButton that has a closure property and invokes that closure when the button is tapped. What you'd do is to make the button's init method set itself up as the target of a touchUpInside event and invoke a method of the button that in turn invokes your closure (after making sure the closure property isn't nil.)
EDIT #2:
Note that it is pretty straightforward to create a custom subclass of UIButton that sets itself up as the target for button presses and keeps a closure.
Here is a sample implementation:
class ClosureButton: UIButton {
var buttonClosure: ((UIButton) -> Void)?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: #selector(handleTap(_:)), for: .touchUpInside)
}
#objc func handleTap(_ sender: UIButton) {
if let buttonClosure = buttonClosure {
buttonClosure(sender)
} else {
print("No button closure defined")
return
}
}
}
And in your view controller:
override func viewDidLoad() {
super.viewDidLoad()
button.buttonClosure = { _ in
print("You tapped the button")
}
}

How does UIButton addTarget self work?

I try figure out why self point to the GameViewController instead of Answer
GameViewController.swift
class GameViewController: UIViewController {
var gameplay = QuestionsController(colors: colors)
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(gameplay.answersController.answers[0].button)
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from GameViewController class", forState: .Normal)
}
}
QuestionsController.swift
class QuestionsController {
var question: Question
var answersController: AnswersController
}
AnswersController.swift
class AnswersController {
var answers = [Answer]()
func prepareAnswers() {
let answer = Answer()
answers.append(answer)
}
}
Answer.swift
class Answer{
let button: UIButton
func prepareButton() {
let answerButton = AnswerButton(type: .System)
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
button = answerButton
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from Answer class", forState: .Normal)
}
}
addTarget:action:forControlEvents: tells the control (answerButton in this case) what method to call, and what object to call it on, when the user taps the button. Looking at your code in more detail:
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
When the user taps a button, the TouchUpInside event fires on the answerButton, and when that happens we want to invoke a method didPressAnswerButton: on an Answer object
So, we need to tell answerButton what do do when this TouchUpEvent fires. You do this calling the addTarget:action:forControlEvents method on the answerButton
The self argument tells the answerButton what object to notify about the event: it is the target. In this context, self is an Answer object.
The "didPressAnswerButton:" argument indicates what method the answerButton should call in response to the tap event: this is the action
This is the target-action mechanism of Objective-C/Cocoa. It's a very common pattern, it's worth it to read the linked documentation to learn a bit more about how it works. The key is that this is based on Objective-C* message passing: in the code above, "didPressAnswerButton:" indicates a selector, which when paired with a target (self), tells the answerButton how to send a "message" to the target when the user taps the button.
Also, note that when you are editing a storyboard and ctrl-drag from a button to your view controller and select a method, you are also setting up a target/action using this same mechanism. You select the target object by dragging to the view controller icon (or some other icon), and then you pick the action/selector when clicking on a method name in the popup.
* Target-Action was originally designed for Objective-C, but for the common case of implementing a view controller, you can assume Swift works the same way. Just note when reading documentation that Swift uses simple strings for actions, whereas Objective-C uses #selector(...).

Having trouble targeting a DesignableView in a UITableViewCell upon tapping it in order to change

and thanks in advance for taking the time to help.
Inside my CellForRowAtIndexPath, I have the following line:
cell.timeView.addTarget(self, action: "ButtonDidPress:", forControlEvents: UIControlEvents.TouchUpInside)
and my selector function is:
func ButtonDidPress (sender: DesignableView!){
let view:DesignableView = sender
cell.timeView.shadowColor = UIColor.yellowColor()
table.reloadData()
}
and the error i get is:
unrecognized selector sent to instance
I'm thinking that perhaps one can't send a View as a selector (am I using the correct terminology?), but how else can I target that particular view in that cell?
UPDATE:
I also tried using gestureRecognizer instead:
var tap = UIGestureRecognizer(target: self, action: Selector( "viewDidTap:"))
cell.timeView.addGestureRecognizer(tap)
and
func viewDidTap (sender: DesignableView!){
but I got the same error here.
Thanks!
There's a couple of strange things happening in your code. It seems you want to change the shadowColor property of timeView when a user touch it, right?
Two possible solutions are:
(This one is IMO the better one) Change DesignableView to inherit from UIButton. Then you can set:
timeView.addTarget(self, action: "ButtonDidPress:", forControlEvents: .TouchUpInside). Make sure you set it just once for each cell. Otherwise you will get multiple calls on one tap.
Use UITapGestureRecognizer, but you should put it in your UITableViewCell subclass, not to the view controller. Also, the sender in viewDidTap is not the view itself, but the recognizer. So the method will go like this:
func viewDidTap(sender: UITapGestureRecognizer) {
let location = sender.locationInView(sender.view)
if timeView.hitTest(location, withEvent: nil) == timeView {
timeView.shadowColor = UIColor.yellowColor()
// table.reloadData() - you don't need to reload the table
}
}

Resources