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
}
}
Related
I wrote a custom class called PressableView. It recognizes taps and then calls a protocol function on its delegate object. Since it is a subclass of UIView, it does not allow the connection of IBActions by default.
I was wondering whether it is possible to connect a function inside the view controller, the pressable view is in, to the object, so it calls that method on tap – just like you would with a UIButton.
I already tried things like:
class PressableView: UIView {
// ...
#IBOutlet var action: (() -> Void)?
// ...
}
...but, Xcode doesn't allow that type to be an IBOutlet.
Any ideas?
change PressableView parent class as UIControl class then you can connect for actions and handle it.
UIControl is subclass of UIView class only. so you will have all the properties of UIView as well.
class PressableView: UIControl {
#IBAction func uicontrolEventAction(_ sender: Any) {
}
}
I'm trying to create a custom class that creates a button. I'm having trouble adding a target to that button inside it's class. This is my code
class SelectButton{
var button:UIButton = UIButton()
init(button_frame: CGRect, button_title: String, connected: [UIButton]?){
self.button.frame = button_frame
self.button.setTitle(button_title, for: UIControlState.normal)
self.button.addTarget(self, action:#selector(self.buttonPressed), for: .touchUpInside)
}
func construct() -> UIButton {
return self.button
}
#objc func buttonPressed() {
print("Button Clicked")
}
}
The problem is that I can't connect an action on button click. This works if it's used outside my class but not inside.
Usage of the class
let test = SelectButton(button_frame: CGRect(x:50, y:50, width: 250, height:150), button_title: "Test button", connected: nil).construct()
self.view.addSubview(test)
When someone taps the button, usually you want something to happen somewhere else in your app (like in one of your view controllers or in some other UI element). The way the IBAction is set up right now, you have it so that something will trigger or happen within the button itself when someone taps on it. If you want to handle a button tap programmatically instead of ctrl dragging from the button into the view controller, you can do it this way if you prefer. First, add this code into the view controller:
#IBAction func buttonPressed(sender: UIButton) {
}
Then you can either add the selector programmatically by adding this method into your view controller:
myButton.addTarget(self, action:self.buttonPressed(sender), for: .touchUpInside)
Or by going to the connections inspector and dragging from the touch up inside over to the IBAction dot in your view controller code. Also, as someone else pointed out in the comments you should make your button inherit from UIButton by adding this to your class declaration:
class SelectButton: UIButton {
. . .
}
Nothing is holding a strong reference to your SelectButton instance, so as soon as the function that creates test exits, that instance is released.
The button itself is retained because you have added it as a subview. Therefore, it is still visible but there is no longer an object to respond to the action.
You either need to use an instance property rather than a local variable for test, or, preferably have SelectButton inherit directly from UIButton
I see that there are a ton of these questions, and I think I'm following the accepted Swift 3 methodology, but I'm still getting nothing. I can see that the UITapGestureRecognizer has been attached. Here's my code:
let tileClick = UITapGestureRecognizer(target: self, action: #selector(GameManagement.initiateTileClick(_:)))
newView.addGestureRecognizer(tileClick)
newView.isUserInteractionEnabled = true
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
A few things to note:
1) The view that I'm attaching the gesture recognizer to has a two views and a label within it that each cover the entire frame of the view, however, I tried attaching the recognizer to the label, which is the topmost child item and it still doesn't work.
2) Both the function that adds the recognizer and the function that is called on the tap are contained in an NSObject file. I have a variety of interconnected functions that I want to be able to call from multiple view controllers and would prefer to keep this in the separate NSObject file. The process worked when I had everything in a UIViewController file and stopped working when I moved the functions to the NSObject file.
3) I've tried changing GameManagement.initiateTileClick to self.initiateTileClick or just initiateTileClick and none of those worked.
If you are putting your views inside NSObject subclass then these views will lose their behaviors for UIResponder which manages the UI interactions as I am not able to see how you are adding these views to interface.
As you said, it was working inside ViewController because it manages view hierarchy and responder chain.
The solution would be to write extensions to separate code or better abstractions.
extension YourViewController {
newView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(GameManagement.initiateTileClick(_:))))
newView.isUserInteractionEnabled = true
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
}
Giving you an idea how the tap recogniser works.
Firstly add Tap gesture recogniser to your view controller. You have to put the object here as shown in the image.
Then control+drag the tap gesture object to your view and select delegate.
Then control+drag the recogniser to your swift file and action will be like this.
#IBAction func tapGesture(_ sender: UITapGestureRecognizer) {
}
Now you must have seen when you give some input to a text field, the keyboard appears. But if you press outside the text field, that is anywhere in the view, the keyboard hides. This is because of the tap gesture recogniser.
Consider you have a text field such that if you click in that text field, keyboard is appeared. But when you tap outside the textfield, the keyboard must hide.
Add this delegate
UITextFieldDelegate
Implement this:
#IBOutlet var phoneText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
exampleText.delegate = self
}
#IBAction func tapGesture(_ sender: UITapGestureRecognizer) {
exampleText.endEditing(true)
}
Obviously,this function is instance method.
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
-
UITapGestureRecognizer(target: self, action:#selector(GameManagement.initiateTileClick(_:)))
but thisGameManagement.initiateTileClick(_:) looks like a class is calling a class method!The target should be the caller of method.self can't call GameManagement.initiateTileClick(_:).
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?) {
}
I want to subclass UITapGestureRecognizer as TapRecognizer so that navigation between pages within my app are handled in a standardised way:
Dragging a UITapGestureRecognizer onto any navigational elements in the storyboard, setting their class as TapRecognizer, and referencing them within the View Controller as an IBOutlet (#IBOutlet var heroTapRecognizer: TapRecognizer!)
Initialising them like so:
self.heroTapRecognizer = TapRecognizer.init(pageId: 1, pageType: PageType.CategoryPage)
Then in TapRecognizer.swift:
class TapRecognizer: UITapGestureRecognizer {
var pageId:Int!
var pageType:PageType!
convenience init(pageId: Int, pageType: PageType) {
self.init()
self.pageId = pageId
self.pageType = pageType
self.addTarget(self, action: #selector(TapRecognizer.handleTap(_:)))
}
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .Ended {
print("Handle navigation based on pageId + pageType")
}
}
}
But the above isn't working. I'm new to Swift and have only previously used UITapGestureRecognizer's programmatically.
Note: User interaction is enabled on the UIView that the recogniser is associated with.
Dragging a UITapGestureRecognizer onto any navigational elements in the storyboard, setting their class as TapRecognizer, and referencing them within the View Controller as an IBOutlet
Okay, but then your init will never be called. If you want something special to happen, implement awakeFromNib.
Alternatively, implement init(coder:). For some reason this is not documented, but it is the initializer that is actually called.