I have the following screen:
The X is the image of a UIButton, I have add the appropriate action to the button. Yet when I click on the button it never fires off the code in the action.
Here is some code:
#IBAction func CloseProfilePage(sender: AnyObject) {
self.removeAnimate();
}
This is the code that is used to launch the view controller seen:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let selectedAppointment = self.dayDatasource!.appointmentAtIndex(indexPath.item);
let profilePageViewController = ProfilePageViewController.init(withAppointment: selectedAppointment);
profilePageViewController.view.frame = self.view.frame
self.view.addSubview(profilePageViewController.view)
profilePageViewController.didMoveToParentViewController(self)
}
The button is definitely connected in the xib to the action:
When I check in the view hierachy, there isn't anything else on top of the button that would prevent the button but registering clicks. I'm assuming here that the imageView in the UIButton is clickable as its part of the button iteself.
The X in the image is not an image view I added, it is the image view that comes with the UIButton. With that said I've also resorted to the following:
self.profilePageClosePopUpButton.imageView?.userInteractionEnabled = true;
Still the button remains unclickable. Where am I going wrong?
It pains me to say this but I'm only writing a solution here just in case someone in the future struggles with the same issue and maybe this post could help them.
In the view, seen in the image below, I had some how unintentionally switched off User Interaction Enabled in interface builder. Because this was off, all other children didn't have interaction enabled on them and hence why the button was not clickable...
Moral of the story is, check your parent views and make sure their user interaction is enabled as well.
Related
I have a UIButton on top of every other views, pushing the next screen, but it's unresponsive to tapping.
Debugging, I have noticed that the User Interaction is ON but also OFF. I mean:
When I select the button at the view hierarchy) it says is ON.
When I select the button directly at the storyboard, it says is OFF.
Take a look at the image above.
Also, the outlets are connected and the call is correct:
code
#IBAction public func onFrontPressed(_ sender: UIButton) {
let destination = OnboardingAViewController(nibName: "OnboardingAViewController", bundle: nil)
self.navigationController?.pushViewController(destination, animated: true)
}
Does anyone knows what's going on here?
Solved!
There was 1 fully transparent gradient view above the button.
When I deleted it, everything was working again.
Thanks, community!
I have a UITextView subclass where I specifically disable all context menu options:
class MyTextView: UITextView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
I add an instance of MyTextView to a view that shows up in my app. I give the instance of MyTextView the following value: isEditable = false
When I long press on the UITextView however, I get the following:
This seems like a bug since there is nothing in this menu? Any ideas on how to prevent this?
Thanks!
Because selectable property is active. So you can "select" a part of text and iOS default behaviour is to show this popover.
You can disable this property by storyboard, or by code.
Storyboard:
At storyboard, select the textview and go to attribute inspector tab... Search for behavior and uncheck selectable checkbox.
or, if you prefer, you can solve it by code:
at viewDidLoad method, set the property isSelectable to false.
MyTextView.isSelectable = false
That's not "the menu". It's just the thing that magnifies the region where the press occurs:
It's empty in your screen shot only because you've no text, so we're magnifying nothing. The menu appears after your long press ends and the magnifier thing goes away — and it doesn't appear, so your code is working fine.
You can see easily that that's true by changing your code to return true. The empty magnifier will appear, just as it does now, and then when it disappears, the menu appears. Thus, we have proved that what you are seeing is not "the menu".
At the moment I am setting up my buttons for my keyboard with the following code:
func setupButtons() {
for subview in self.view.subviews {
if subview.isKindOfClass(UIButton) {
setupButton(subview as! UIButton)
}
}
}
func setupButton(btn: UIButton) {
btn.addTarget(self, action: #selector(KeyboardViewController.keyPressed(_:)), forControlEvents: .TouchUpInside)
}
But is there anyway I can skip this and all the target inside the layout builder so I can save a little bit of time looping through buttons on each keyboard view?
Sure, there are two ways to connect objects to Interface Builder, through outlets and actions.
class ViewController: UIViewController {
#IBOutlet weak var doStuffButton: UIButton!
#IBAction func doStuff(sender: UIButton) {
//do stuff
}
}
After adding that code look in interface builder and click on the view controller for this class.
Click on the connections inspector
Under outlets you will now see doStuffButton, however over the circle to the right of that, press control and click with your mouse, and drag it over to the button you want to connect it to and release. Setting outlets is NOT required for enabling actions. I just wanted to show this to you as well.
In the same pane you will also see received actions and the doStuff: method. Click and drag in the same way and release on the button. Select which type of event you want to process this action (normal is touch up inside).
Once you're all hooked up it should look like this:
There are many other variations of how to do this, but I thought this would get you a quick start.
If you're trying to ask how to do this without coding anything, just go into the assistant editor view of Xcode and Ctrl-drag from your button to the controllers class file. Then when the pop up displays, change outlet to action and give it a method name. This creates the IBAction method for you.
But in reality, the way you are doing it now with the for loop is far better. Especially if you have many buttons.
I'm three days new into swift development, even Xcode for that matter. I've created a UIView called EncounterMenu that appears over my app when i click the menu button. My goal is to have the EncounterMenu UIView close when I do anything outside of it.. This is what I have so far
override func viewDidLoad() {
super.viewDidLoad()
//create a button that spans the width & height of the encounter menu
let btnEncounterMenu = UIButton(type: UIButtonType.System) as UIButton
btnEncounterMenu.frame = CGRectMake(0, 0, EncounterMenu.frame.width, EncounterMenu.frame.height)
//(this is whats not working) If user clicks outside of button, call function "closeEncounter:"
btnEncounterMenu.addTarget(self, action: "closeEncounter:", forControlEvents: .TouchUpOutside)
//Add the button to the EncounterMenu
EncounterMenu.addSubview(btnEncounterMenu)
}
func closeEncounter(sender: UIButton!) {
EncounterMenu.hidden = true;
}
I tried changing it to TouchUpInside and it worked when i clicked inside the EncounterMenu UIView, so I figured it should be as easy as TouchUpOutside?
Any direction to what I'm doing wrong or how I can accomplish what I'm trying to do?
Touch up outside will not work because you need to be pressing on the menu first, then drag your finger outside for that to register. instead, the easiest option for you to do is to create a button that is the entire size of your view, and on that buttons touch down event, fire off close menu. just make sure that this big button is at the very back of the view, and everything else is built on top of it.
I have a UIButton on a form, and want to put it in a disabled state when the form is incomplete. However, I still want to be able to detect if a user attempts to press the button even in its disabled state so that the interface can let the user know that certain required fields on the form are not filled-in yet (and perhaps scroll to that field and point it out, etc.).
There doesn't seem to be any straightforward way to do this. I tried simply attaching a UITapGestureRecognizer to the UIButton but it doesn't respond when the button is in a disabled state.
I'd like to avoid subclassing UIButton if possible, unless it's the only way.
Create a fallback button. Put it behind the main button. Set its background and text colors to [UIColor clearColor] to ensure it won't show up. (You can't just set its alpha to 0 because that makes it ignore touches.) In Interface Builder, the fallback button should be above the main button in the list of subviews, like this:
Give it the same frame as the main button. If you're using autolayout, select both the main and fallback buttons and create constraints to keep all four edges equal.
When the main button is disabled, touches will pass through to the fallback button. When the main button is enabled, it will catch all the touches and the fallback button won't receive any.
Connect the fallback button to an action so you can detect when it's tapped.
Based on #rob idea, I sub-class a UIButton, and add a transparent button before someone addSubview on this button.
This custom UIButton will save many time about adjusting the UI components on the storyboard.
Update 2018/08
It works well, and add some enhanced detail to this sub-class. I have used it for 2 years.
class TTButton : UIButton {
// MARK: -
private lazy var fakeButton : UIButton! = self.initFakeButton()
private func initFakeButton() -> UIButton {
let btn = UIButton(frame: self.frame)
btn.backgroundColor = UIColor.clearColor()
btn.addTarget(self, action: #selector(self.handleDisabledTouchEvent), forControlEvents: UIControlEvents.TouchUpInside)
return btn
}
// Respect this property for `fakeButton` and `self` buttons
override var isUserInteractionEnabled: Bool {
didSet {
self.fakeButton.isUserInteractionEnabled = isUserInteractionEnabled
}
}
override func layoutSubviews() {
super.layoutSubviews()
// NOTE: `fakeButton` and `self` has the same `superView`.
self.fakeButton.frame = self.frame
}
override func willMoveToSuperview(newSuperview: UIView?) {
//1. newSuperView add `fakeButton` first.
if (newSuperview != nil) {
newSuperview!.addSubview(self.fakeButton)
} else {
self.fakeButton.removeFromSuperview()
}
//2. Then, newSuperView add `self` second.
super.willMoveToSuperview(newSuperview)
}
#objc private func handleDisabledTouchEvent() {
//NSLog("handle disabled touch event. Enabled: \(self.enabled)")
self.sendActionsForControlEvents(.TouchUpInside)
}
}
You have a great misunderstanding of user experience.
If a button is disabled, it is meant to be non-interactable.
You can not click on a disabled button, that is why it is disabled.
If you want to warn users about something when that button is clicked (e.g. form not filled correctly or completely), you need to make that button enabled. And just warn users when they click on it, instead of proceeding further with app logic.
Or you can keep that button disabled until form criteria are met, but show what is wrong with the form using another way, like putting exclamation marks near text fields, changing text field colors to red, or something like that...
But never try to add gesture recognizers, or hidden fallback buttons to a disabled button.
Check those and let me know if you see a disabled button:
https://airbnb.com/signup_login
https://spotify.com/us/signup/
https://netflix.com/signup/regform
https://reddit.com/register/
https://twitter.com/signup
https://facebook.com/r.php
https://appleid.apple.com/account
https://accounts.google.com/SignUp
https://login.yahoo.com/account/create
https://signup.live.com/signup
All the proceed buttons on these websites are always enabled, and you get feedback about what is wrong when you try to continue.
And here is really good answer: https://ux.stackexchange.com/a/76306
Long story short: disabled UI elements meant to be not-interactable.
Trying to make them interactable while they are disabled is the same to making them enabled in the first place.
So, for your question's case, it is just a styling issue. Just try styling your button, instead of making it disabled/enabled.