UIButton inside UIView doesn't respond to touch events - objective-c

I've put a UIButton inside a custom UIView and the button is not receiving any touch events (it doesn't get into the highlighted state, so my problem is not about being unable to wire up a touch inside up handler). I've tried both putting it into the XIB in Interface Builder, and also tried programatically adding the UIButton into the UIView seperately, both ended with no luck. All my views are inside a UIScrollView, so I first though UIScrollView may be blocking them, so I've also added a button programatically exactly the same way I add my custom view into UIScrollView, and the button worked, elimination the possibility of UIScrollView could be the cause. My View's hiearchy is like this:
The button is over the image view, and the front layer isn't occupying my button completely, so there's no reason for me not be physically interacting with the button. At my custom view's code side, I'm creating my view as such:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
UIView *sub = [[[NSBundle mainBundle] loadNibNamed:#"ProfileView" owner:self options:nil] objectAtIndex:0];
[self addSubview:sub];
[sub setUserInteractionEnabled:YES];
[self setUserInteractionEnabled:YES];
CALayer *layer = sub.layer;
layer.masksToBounds = YES;
layer.borderWidth = 5.0;
layer.borderColor = [UIColor whiteColor].CGColor;
layer.cornerRadius = 30.0;
/*layer.shadowOffset = CGSizeZero;
layer.shadowRadius = 20.0;
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowOpacity = 0.8;
*/
}
return self;
}
I've tried all combinations of setUserInteractionsEnabled, and had no luck. (Yes, also set them to checked in Interface Builder too). I've also read in another question with a similar problem that I should try overriding 'canBecomeFirstResponder' to return 'YES' and I've also done that too. But the problem persists, I can't click the button. I've not given any special properties, settings to the button, it's just a regular one. My other objects in the view (labels below, image view behind the button etc.) are working properly without problems. What could be possibly wrong here?
Thanks,
Can.
UPDATE: Here is a quick reproduction of the problem: https://dl.dropbox.com/u/79632924/Test.zip
Try to run and click the button.

Looking at the test project, I believe your problem in the way you create TestView, you do not specify the frame for it, so basically the parent view is 0 size, and the subviews you see from XIB extending out of the parent view and thus do not get anything in responder chain.
You should either specify the frame when creating TestView, or adjust the frame after loading XIB file.

I have had this problem as well. The cause for me was that the UIButton superview frame was of height 0, so I believe that even though a touch was happening, it was not being passed down to the button.
After making sure that the button's superview took a larger rectangle as a frame the button actions worked.
The root cause for this problem on my side was a faulty auto layout implementation (I forgot to set the height constraint for the button's superview).

I've found the solution. I was initializing my custom view as:
MyView *view = [[MyView alloc] init];
I've initialized it instead with a frame of my view's size, and it started responding to events:
CGRect rect = CGRectMake(0,0,width,height);
MyView *view = [[MyView alloc] initWithFrame:rect];

Storyboard Solution
Just for anyone wanting a solution to this when using storyboards and constraints.
Add a constraint between the superview (containing the button) and the UIButton with an equal heights constraint.
In my case, I had selected embed UIButton in a UIView with no inset on the storyboard. Adding the additional height constraint between the UIButton and the superview allowed the UIButton to respond to touches.
You can confirm the issue by starting the View Debugger and visually confirm that the superview of the UIButton is not selectable.
(Xcode 11, *- Should also work in earlier versions)

Related

How to change the Title View of the navigation bar into some Custom View and fill the original size

I want to change the titleView of navigationBar with a regular text field. Then I want to set the textField size to fill the old "normal" titleView.
I can't seem to be able to do this in storyBoard. Dragging a text Field to the place where the navigation title View is doesn't work.
So I added stuff at
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
PO(self.navigationItem.titleView);
CGRect theFrame= self.navigationItem.titleView.frame;
self.navigationItem.titleView=self.searchBar;
//self.searchBar.frame = theFrame;
while (false);
...
It's working with one cached. That PO is a macro that print the content of the object. Turns out at viewDidAppear, self.navigationItem.titleView is null.
So while I can display the searchBar, I cannot make the searchBar "fill" it's space because I do not know the space is.
I prefer not to hard code it because you know, things may change in the future.
So what should I do?
I once saw codes where rather than setting the self.navigationItem.titleView, you would simply add subview to it. The problem with this approach even on viewDidAppear, self.navigationItem.titleView is 0.
I added these codes:
CGRect theFrame= self.navigationItem.titleView.frame;
CGRect theFrame2 = self.searchBar.frame;
CGRect theFrame3 = self.navigationController.navigationItem.titleView.frame;
And, I do not know how to nslog structure value, however, theFrame and theFrame3 are all 0
You can try this inside viewWillAppear:
UIView *customTitleView = [[UIView alloc]initWithFrame:CGRectMake((320-210)/2, 0, 210, 50)];
customTitleView.backgroundColor = [UIColor clearColor];
//create your UITextField or UILabel or other view and add as subview of customTitleView
self.navigationItem.titleView = customTitleView;

How to make detail view scrollable in master detail app

I have only been working in iOS for a few months but I have been banging my head against the wall for hours and hours for something that seems like it should be pretty straightforward. I used the master detail template in Xcode for an iPad app. I want the detail portion to be scrollable to show content below what is visible in the frame, in either orientation. I have tried numerous combinations of adding scrollviews in the DetailViewController in viewWillAppear, viewDidLoad, loadView...and the best I can come up with is what looks like a scrollable view on the top layer as it does show scroll bars and shows me that I did the scrollView.contentSize correctly as I can pan around, but the actual view with the fields and stuff doesn't move and the fields are unable to be edited. Here is my viewDidAppear as it stands at the moment. As you can see in the NSLogs I am trying to understand the view stack. If I uncomment the line before the logs, I lose the scroll bars altogether.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 768, 1024)];
scrollView.contentSize = CGSizeMake(2048, 2048);
UIView *parentView = [[UIView alloc] init];
parentView = [[self view] superview];
[[parentView superview] addSubview:scrollView];
//[scrollView addSubview:[self view]];
NSLog(#"%#", [parentView superview]);
NSLog(#"%#", parentView);
NSLog(#"%#", [super view]);
NSLog(#"%#", [self view]);
[scrollView setDelegate:self];
}
I would sincerely appreciate any guidance or tips on how to properly implement UIScrollView for this scenario.
You should add the UIScrollView in IB. Be sure to move all of your existing views and controls to be subviews of the scroll view. And you'll have to link the scroll view to an IBOutlet so you can set the content size in your view controller.
Instead of trying to wrap a uiscrollview around your main view's superview (which you have incorrectly tried to take ownership of (it should be NULL anyways)), why not herd your UI elements into a full-sized subview and wrap the scrollview around that? It would be much much easier.
Your hierarchy would look like this:
Self.view -> ScrollView (with contentSize set) -> UIView containing elements that need to be scrolled -> various UI elements

Button not working in UITableViewCell subclass when it is a subview of a view inside the UITableViewCell

I have a button in a tableView Cell that I need to respond to touch events. Normally this would easily be solved by
[cell.playButton addTarget:self action:#selector(playButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
inside of -tableView:cellForRowAtIndexPath:
The problem I am having is that my button is inside of a custom UITableViewCell subclass and is also the subview of a view that I am creating inside of that class..
for example:
UIView *view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
self.theView = view;
[self addSubview:view];
[view release];
UIButton *playButton = [UIButton buttonWithType:UIButtonTypeCustom];
playButton.frame = CGRectMake(5, 5, 100, 30);
[playButton setImage:[UIImage imageNamed:#"button_playvid.png"] forState:UIControlStateNormal];
[playButton setImage:[UIImage imageNamed:#"button_playvid_active.png"] forState:UIControlStateHighlighted];
self.playButton = playButton;
[self.theView addSubview:playButton];
When the button is a subview of a view that I create within the custom UITableViewCell then the cell is highlighted instead of the button being pressed so the target that I setup in -tableView:cellForRowAtIndexPath is never called. Not sure why.. any help?
Thanks,
Joel
PS. I realize that there probably isn't a practical reason to create a background view exactly like this since there already is one. This is just example code that I'm using to explain a problem I'm having.. :D
Thanks for Looking!
The UIView you are adding is actually a UIImageView. From the docs:
New image view objects are configured to disregard user events by default. If you want to handle events in a custom subclass of UIImageView, you must explicitly change the value of the userInteractionEnabled property to YES after initializing the object.
I think that either userInteractionEnabled cascades to subviews or the superview will not pass the touch to its subviews in this case, so your button will not be receiving touches. Set your image view to have .userInteractionEnabled = YES and you should be fine.
I had the same problem and tried EVERYTHING. The thing that finally did it was implementing the method
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Just return whatever the height is of your custom cell (look in the NIB file)
Recently I've ran into such issue with legacy code. The reason was that earlier we used to add subviews to custom UITableViewCell instance itself and now we have to add to contentView of the cell instance.
I fixed it by changing:
addSubview(increaseButton)
to
contentView.addSubview(increaseButton)
Of course, isUserInteractionEnabled should be true.
Instead of button, you could try the same with an image. You could define which method to be called when the image is clicked.

Padding around NSTableView

I've a following problem. There is a subclassed NSScrollView with a view based NSTableView in it. I've added a custom background to the scrollview in the -drawRect: method of subclass, and now I would like to add some "padding" around the inner tableview like this:
example http://img.skitch.com/20120117-ktd9g5wy8u9cm37jeebjjxx61u.png
How can I implement this?
If you're targeting Mac OS 10.10 or later, you could use
[scrollView setAutomaticallyAdjustsContentInsets:NO];
[scrollView setContentInsets:NSEdgeInsetsMake(top, right, bottom, left)];
Finally, I've solved the problem. I've created an NSView (let's call it documentContentView), added my NSTableView as a subview of this documentContentView, then I've added the documentContentView to the scrollview's documentView:
NSTableView *docView = (NSTableView *)self.scrollView.documentView;
id newClipView = [[CustomClipView alloc] initWithFrame:[self.scrollView.contentView frame]];
[self.scrollView setContentView:(NSClipView *)newClipView];
[newClipView setDrawsBackground:NO];
NSView *documentContentView = [[NSView alloc] initWithFrame:docView.bounds];
docView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[documentContentView addSubview:docView];
[self.scrollView setDocumentView:documentContentView];
[self.scrollView setDrawsBackground:NO];
I've created my custom NSClipView called CustomClipView (based on this article http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView) and this subclass sets the origin of the documentContentView when the window resized. I've subclassed my tableview as well, and in -reloadData method I can resize the documentContentView when the tableview change it's contents.
The left and right padding can be done inside of the row/cell itself. For the top and bottom padding I suggest to add additional rows with no content and not selectable. This is not sexy, but worked for me.
First of all, don't add backgrounds in drawRect:. Add it in your initWithFrame: if you're subclassing, or change it from the invoker.
Adding the padding is easy: Change the frame of the NSTableView so that it is smaller, and has an origin that isn't at 0,0.

Add lots of views to NSScrollView

I'm trying to add one subview (view from an NSViewController) for every element in a dictionary to a NSScrollView to get kind of a tableview, but with much more flexibility over the cells.
Is it possible to place (programmatically) e.g. 100 subviews underneath each other so that you have to scroll down the NSScrollView to get to the last element?
The short answer is yes. I have done this before, and I assure you that it will work. Let me also assure you that it is not quite as simple as it looks ;)
The way to do this is to maintain one content view, in which you add all of your custom rows as subviews programmatically. Note that you will either have to resize the contentView before adding all of the rows, or you will have to turn off autoresizing for the row views. Set the documentView of your scrollView to this custom contentView, and you should be good to go.
Yes, simply initialize the views programmatically using (i.e.)
NSView *subView = [[NSView alloc] initWithFrame:CGRectMake(10,10,100,100)];
then add to the main using addSubview: method of the main view.
Remember to manually release the subview when you've done with it (that means, when you have added it to the main view).
As example you can do something like
int height x = 10, y = 10, width = 100, height = 100;
for(int i = 0;i<100;i++) {
NSView *subView = [[NSView alloc] initWithFrame:CGRectMake(x,y + height*i,width,height)];
[scrollView addSubview:subView];
[subView release];
}