Enable user interaction on a UIButton underneath a UIVew? - objective-c

In my program I have configured a UIView to be placed over a number of UIBUttons. This is so you can pan across the view and have the buttons move over as well as selecting the buttons themselves. However, it is not possible to select the buttons when they are underneath this UIView. Does anybody know how to configure a UIButton's userInteractionEnabled while it is under these conditions?

This technique has worked for me - caveat: I just typed this in it's not compiled or nuthin it's given as a starting point.
Create the buttons and place them at the top of the view hierarchy (i.e. where they interact normally and respond to taps, on top of the UIView)
When creating the buttons - keep a reference to them in an array.
NSMutableArray* arrayOfPeskyButtons;
Keep a record of the button frames
NSMutableArray* arrayOfPeskyFrames = [NSMutableArray mutableArray];
for ((UIButton*)button in arrayOfPeskyButtons)
{
[arrayOfPeskyFrames addObject:[NSValue valueWithCGRect:button.frame];
}
When your app responds to an action on the UIView - panning or if you change it to a UIScrollView (probably best btw), then move the frame of the buttons correspondingly so that they appear fixed in place to the user.
I have used this technique to create 'overlays' of buttons which sit on top of scrollviews and it works surprisingly well. Simply in the scrollView delegate method.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
take note of the scrollviews offset and adjust each of the frames of the buttons accordingly depending on the offset of the scrollView
CGPoint offset = scrollView.contentOffset;
Iterate through the array of buttons and move them.
int index = 0;
for ((UIButton*)button in arrayOfPeskyButtons)
{
CGRect originalFrame = [[arrayOfPeskyFrames objectAtIndex:index]CGRectValue];
CGRect currentFrame = button.frame;
//adjust frame
//e.g.
currentFrame.origin.x = originalFrame.origin.x + offset.x;
button.frame = currentFrame.;
index++;
}

Instead of changing the value of the UIButton's userInteractionEnabled, you should set the userInteractionEnabled value of the UIView on top of the UIButton to no.
When the UIView's userInteractionEnabled is set to YES, the UIView was 'eating up' the events. The button would then never receive any events.

Related

Arranging programmatically created NSButtons

I have an array that varies in size. I want to create NSButtons for each element in a Mac OSX Application. So say this is the array: "thing1", "thing2", "thing3", then I want three buttons on the screen titled "button1", "button2", "button3" respectively.
This obviously needs to be done programmatically. I do know how to create buttons programmatically and how to give them a fixed position.
But this cannot be the solution. How would I make the whole thing dynamic, so that no matter how many buttons are going to be created, it won't lead to massive UI issues? (So basically: place all buttons next to another within a certain container for example)
Thank you.
you can create button programmatically this way:
NSButton *myButton = [[[NSButton alloc] initWithFrame:NSMakeRect(x, y, width, height)] autorelease];
[[windowOutlet contentView] addSubview: myButton];
[myButton setTitle: #"Button title!"];
[myButton setButtonType:NSMomentaryLightButton]; //Set what type button You want
[myButton setBezelStyle:NSRoundedBezelStyle]; //Set what style You want
[myButton setTarget:self];
[myButton setAction:#selector(buttonPressed)];
}
-(void)buttonPressed {
NSLog(#"Button pressed!");
//Do what You want here...
}
I think you need to use horizontal NSStackView for this. Each item will be a custom NSView (create custom nib) with buttons and elements as its subviews. For right arranging controls inside the item use AutoLayout.
For dynamically placing views you should use constraints. This way you can set up relationship's between your views, instead of placing them at fixed positions. Check out the documentation over here:
https://developer.apple.com/library/prerelease/mac/documentation/AppKit/Reference/NSLayoutConstraint_Class/
There are a few possible approaches.
1) NSStackView
A new-ish class (appeared in 10.9) which handles horizontal or vertical stacking of subviews.
2) Constraints
Constraints allow you to define views' spatial relationships to one another, but they're most easily managed in Interface Builder and can be a bit complicated to work with programmatically.
3) Setting each button's frame in code
With a fairly straightforward loop, you can position the buttons how you like. This is the most "manual" of the three approaches, but also arguably the most clear and predictable. Here's an example of a method which will position buttons.
- (void)arrangeViews:(NSArray <NSView *> *)views startingAt:(NSPoint)startPt spacing:(NSSize)spacing
{
NSPoint loc = startPt;
for (NSView *view in views) {
NSRect viewFrame = view.frame;
viewFrame.origin = loc;
view.frame = viewFrame;
loc.x += spacing.width;
loc.y += spacing.height;
}
}
If you want to arrange 3 buttons vertically, each one 50 pixels apart, then you'd call the method like this:
[self arrangeViews:#[btn0, btn1, btn2] startingAt:NSMakePoint(100,100) spacing:NSMakeSize(0,-50)];

How to prevent certain object from scrolling in a UIScrollView

I don't know if this is possible and I highly doubt that it is but I'm wondering if there's a way where I can prevent a button for example from scrolling in a UIScrollView using Objective-C programming?
Sure. Simply update the button's origin as the scroll view scrolls.
In your view controller, implement the appropriate scroll view delegate method. If not done already, setup your view controller as the scroll view's delegate.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = scrollView.contentOffset;
CGRect frame = self.fixedButton.frame;
frame.origin.y = offset.y + 40;
self.fixedButton.frame = frame;
}
This will keep the self.fixedButton button 40 points below the top of the visible portion of the scroll view. Adjust as needed.
The above all assumes the button is a subview of the scroll view.
Of course it may be a lot easier if the button and the scroll view share a common parent view. Then the button isn't a subview of the scroll view and won't scroll at all.

UIButton inside UIView doesn't respond to touch events

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)

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

hitTest based on center of UISegmentmentedControl returns UIToolbar

I'm experimenting with some code which probes a UI to see it's structure. As part of this I am doing a hitTest on a UISegmentedControl which is embedded in a UIToolbar at the bottom of the screen. I have a structure like this:
UIToolbar
UISegmentedControl
UISegment
...
I get the centre of the UISegmentedControl and hit test it like this:
UIView *view = // code which gets a reference to the UISegmentedControl.
frameInWindow = [view.window convertRect:view.frame fromView:view.superview];
CGPoint locationInWindow = CGPointMake(
frameInWindow.origin.x + 0.5 * frameInWindow.size.width,
frameInWindow.origin.y + 0.5 * frameInWindow.size.height);
UIView *target = [view.window hitTest:locationInWindow withEvent:nil];
I've been over this numerous times and I cannot see why it sets target to the UIToolbar instead of the UISegmentedControl. I would have expected target to be the same control. Or some UIView inside the segmented control.
The docs say:
"This method ignores view objects that are hidden, that have disabled
user interaction, or have an alpha level less than 0.01. This method
does not take the view’s content into account when determining a hit.
Thus, a view can still be returned even if the specified point is in a
transparent portion of that view’s content."
Is user interaction with your segmented control enabled?