Arranging programmatically created NSButtons - objective-c

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

Related

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)

Enable user interaction on a UIButton underneath a UIVew?

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.

Dragging and dropping controls in Objective C (iOS)

This is my first question on Stack Overflow, after reading many results from Googling various programming problems.
My question is about iOS development.
Basically I have a button that, when pressed, creates another button on the view. I want the user to be able to drag that button around the form. From searching, I found code that shows how to move a button, but that requires having an outlet connected to it (so you can set the center property). Since the button has been created programatically in another method, I can't figure out how to set the center property.
Here's my addButton: method:
-(IBAction)addButton:(id)sender{
CGRect frame = CGRectMake(5.0, 25.0, 40.0, 40.0);
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:#selector(moveButton:) forControlEvents:UIControlEventTouchDragInside];
[button setTitle:#"Test Button" forState:UIControlStateNormal];
button.frame = frame;
[myView addSubview:button];
}
And what I have typed into the touchesMoved: method:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
}
I'm pretty new to iOS development, so I'm not really sure how to solve this.
You should make the second button a property of your class. First declare the property in the .h+.m file just like the first one was automatically by xCode. Then at the end of your example code add self.secondButtonPropertyNameHere = button;
Once it's a property of the class, any method can set it's properties.
Edit:
Instead of a UIButton property make an NSMutableArray property. Then just add the new button to the array so you can keep a reference to them. Just remember if you go to remove one call [buttonToRemove removeFromSuperview] and remove from the array.
Edit(2):
Your initial question was about making a second button and moving it around. The property of UIButton will work well for that. Because you're moving one object on the view receiving the touches* events. If I were you I would stick with this approach until you are comfortable with the touches* events.
When you are ready to move on. You can manage multiple runtime created UIViews (in your case you want to use buttons) you have to choices:
1) In the touchesBegan: of your view controller iterate thru your array of buttons to see if perhaps the touch fell on one of them and then track the touch until the touchesEnded: call.
2) Subclass the UIView and let it handle it's own touches* events.
The second is much easier to manage.
Edit(3):
You will need to set [button setUserInteractionEnabled:NO] if you wish the superview to handle the touches* events instead of the buttons.
I've done something similar, but in a different way: you can create buttons, then move them around in a rect (actually a scaled view of the later real sized view) and change the size too, with 4 sliders and other buttons for fine tuning. It's very simple by just setting the frame of the target button to new values all the time. I didn't do it with dragging because the button could get too small for the finger.

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

Adding a button (UIButton) to a custom tableview cell drawn with drawRect:

I am currently working a on project where I have lots of custom table view cells. Part of the requirements is that the cells be able to expand if their default size can not hold all of the content. If they need to be able to expand I have to add a UIButton to the cell and when it is tapped redraw it in a bigger view where all the data fits. Currently in draw rect I essentially do this:
if([self needsExpansion]) {
[self addExpansionButton];
}
-(void)addExpansionButton {
self.accessoryButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.accessoryButton setShowsTouchWhenHighlighted:YES];
UIImage *buttonImage = [UIImage imageNamed:#"blue_arrow_collaps_icon.png"];
[self.accessoryButton setFrame:CGRectMake(280, 82, buttonImage.size.width, buttonImage.size.height)];
[self.accessoryButton setImage:buttonImage forState:UIControlStateNormal];
[self.accessoryButton addTarget:self action:#selector(toggleExpanded) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.accessoryButton];
}
This works fine, except for when I click anywhere else in the cell the button flickers and disappears. Anyone know how to propertly do this?
From the UITableViewCell Class Reference:
You have two ways of extending the
standard UITableViewCell object beyond
the given styles. To create cells with
multiple, variously formatted and
sized strings and images for content,
you can get the cell's content view
(through its contentView property) and
add subviews to it.
Instead of adding the accessory button as a subview of the UITableViewCell, you should add it as a subview of the contentView:
[[self contentView] addSubview:self.accessoryButton];
Have you worked out the following problem in your design approach?: Let's say one of your cells (let's call it A) determines it needs expansion, so you add a button to it. What happens when the user scrolls through the UITableView? For performance reasons, your UITableView delegate should be using dequeueReusableCellWithIdentifier in tableView:cellForRowAtIndexPath:. So you'll be reusing A to display a different row of the table. Do you really want A to have an accessory button? Probably not, since it's now representing a different object.
You're probably better off doing the cell customization at the UITextViewDelegate. In tableView:cellForRowAtIndexPath:, you can determine if the object being displayed at the row specified needs a button, and if it does add it as a subview to the contentView.
Then again, if your table size is always relatively small (say < 50), you can use this approach Jeremy Hunt Schoenherr suggests.