I have seen this for a normal UIButton:
NSArray *actions = [viewController.addButton actionsForTarget:viewController forControlEvent:UIControlEventTouchUpInside];
XCTAssertTrue([actions containsObject:#”addNumbers:”], #””);
But now I want to do same thing for a rightBarButtonItem! I have tested this button exist on the VC but there is no interface actionForTargets!
I tried this also but it did not work:
NSArray *actions = [self.navigationItem.rightBarButtonItem actionsForTarget:self forControlEvent:UIControlEventTouchUpInside];
or
NSArray *actions = [[self.navigationItem.rightBarButtonItem target] actionsForTarget:self forControlEvent:UIControlEventTouchUpInside];
Non of them works. Anyone has written test code for a UIBarButton to check if button is connected to correct IBAction?
You can loop through the subviews of the navigationBar, and find a UIControl whole allTargets method contains the rightBarButtonItem.
Once you have that, you can call actionForTarget:forControlEvents: on that UIControl, where the rightBarButtonItem is the target, and UIControl's allControlEvents is the forControlEvents parameter:
//get the underlying control
for(UIControl * control in self.navigationController.navigationBar.subviews)
{
if([control isKindOfClass:UIControl.class])
{
//we found the right one
if([control.allTargets containsObject:self.navigationItem.rightBarButtonItem])
{
NSArray <NSString*> * actions = [control actionsForTarget:self.navigationItem.rightBarButtonItem forControlEvent:[control allControlEvents]];
//do something with actions.firstObject;
return;
}
}
}
However, I believe the subviews of the UINavigationBar has no publicly defined layout, so this is a fragile solution.
Related
So I'm doing some ugly hacks to modify the appearance of individual segments in a UISegmentedControl. When I walk down the UISegmentedControl's subview array and print each immediate subview's class name, I can see there are instances of UISegment for each segment in the control:
for (UIView *view in [[self segments] subviews])
{
NSLog(#"%#", NSStringFromClass([view class]));
}
Now apparently Xcode can't resolve this class, so I can't use the familiar [view isKindOfClass:[UISegment class]].
I assumed I was just missing a header to include, but I also can't find any docs for it on Apple's developer reference site.
So how would I go about testing if an object is an instance of UISegment? And what exactly is this class - whose name can be resolved at run-time but not at compile-time?
edit
What I'm trying to achieve is a clear indication that a particular segment is enabled or disabled. For instance, when I disable an entire UISegmentedControl, the following works great to update both selected and unselected segment colors/fonts:
- (void)setIsEnabled:(BOOL)isEnabled
{
[super setIsEnabled:isEnabled];
// be careful and ensure you use the isEnabled getter because it takes isEnabledForConfiguration into account
[[self segments] setUserInteractionEnabled:[self isEnabled]];
[[self segments] setTintColor:[self interactionColor]];
[[self segments] setTitleTextAttributes:[NSDictionary dictionaryWithObject:[self enabledTextColor]
forKey:NSForegroundColorAttributeName]
forState:UIControlStateNormal];
}
But sometimes only a subset of the segments should be disabled, and I would like to visually make that apparent (the -(void)setEnabled:forSegmentAtIndex: method doesn't seem to have any visible effect).
So I've found that I can modify these UISegment subviews to achieve the visual updates (nasty hack, isn't guaranteed to work in the future, I know...).
To try protecting against those future API changes, I've done my best to implement a flexible way to grab a reference to the UISegment objects by segment index.
First, I grab the title of the segment at the input segment index, then traverse all the way down the UISegmentedControl view hierarchy until I find something that responds to the text selector and whose text value equals the title. That view always seems to be a UISegmentLabel (which is another private class!), and his superview is the UISegment I'm looking for. So I've come up with:
- (UIView *)getSegmentAtIndex:(NSInteger)index
{
NSString *title = [[self segments] titleForSegmentAtIndex:index];
id segment = nil;
if (nil != title && 0 < [[EFBGlobalUtil trimWhitespace:title] length])
{
segment = [[EFBGlobalUtil viewWithCondition:^BOOL(UIView *view)
{
return [view respondsToSelector:#selector(text)] && [title isEqualToString:[view valueForKey:NSStringize(text)]];
}
inView:[self segments]] /* this dude -> */superview];
}
return segment;
}
The question really stems from that superview getter I stuck on the end. I would like to verify the superview is actually a UISegment, but the solution to that will also help me check for UISegmentLabel objects as well (instead of the text selector).
And for reference, here's the +(void)viewWithCondition:inView: utility routine:
+ (UIView *)viewWithCondition:(BOOL (^)(UIView *view))condition inView:(UIView *)view
{
// non-recursive BFS traversal of a view hierarchy until specified UIView matching condition is found
NSMutableArray<UIView *> *stack = [[NSMutableArray alloc] initWithObjects:view, nil];
UIView *curr;
while ([stack count] > 0)
{
curr = [stack firstObject];
[stack removeObject:curr];
if (condition((UIView *)curr))
{
return (UIView *)curr;
}
[stack addObjectsFromArray:[curr subviews]];
}
return nil;
}
I have an object with the class FSDDropdownPicker (a dropdown menu) and I want to edit one of the entries in my dropdown menu. Upon doing so I change the object to nil, and the right bar button item to nil yet the old dropdown menu remains even after instantiating the new dropdown and adding it to the right bar button item.
if(_picker){
// _picker.tableView.delegate = nil;
// _picker.tableView.dataSource = nil;
// _picker.delegate = nil;
self.navigationItem.rightBarButtonItem = nil;
// [_picker removeFromSuperview];
// _picker.view
}
_picker = [self.navigationItem addDropdownPickerWithOptions:[arr copy]];
_picker.delegate = self;
I also have functionality such that if the menu becomes empty then I erase the dropdown completely and just do
if([api.myGroups count]==0){
self.navigationItem.rightBarButtonItem = nil;
return;
}
However my rightBarButtonItem remains unchanged. Added Notes: I'm also using AMSlideMenu if that makes a difference but it really shouldn't.
Even after re-adding new picker, the FSDDropdownPicker keeps the old tableView still in the view hierarchy, so try calling [_picker removeFromSuperview]; before adding a new one.
Also there's no need to call [self.view setNeedsDisplay]; and nil the delegates and data source. Seems you just have to assign it again.
Additionally, the line self.navigationItem.rightBarButtonItem = _picker; is redundant as the category method addDropdownPickerWithOptions: on navigationItem already assigns the newly created picker to navigationItem.
EDIT: My bad, you need to call this code before creating new picker:
for (UIView *view in self.navigationController.navigationBar.superview.subviews) {
if ([view isKindOfClass:[UITableView class]]) {
[view removeFromSuperview];
}
}
Unfortunately the library doesn't take care of this by itself, so you either do it this way or create a pull request with a fix for the FSDDropdownPicker library.
EDIT EDIT:
If you're not using CocoaPods, you could try to put the table view and options definition in the header file - not a nice solution, but probably the easiest for you now. Move this
#property (strong, nonatomic) UITableView *tableView; and this #property (strong, nonatomic) NSArray *options; line to FSDDropdownPicker.h and you won't have to create new picker instance every time you want to add/delete an item. Just add/delete an item to options and reload the table view.
In my app I have three UIButtons, each with an associated UIView. When one of the buttons is pressed, I want to:
Highlight the pressed button
Un-highlight the other buttons
Hide the UIViews associated with the other buttons
Un-hide the UIView associated with the pressed button
My solution (below) works and isn't horrible, but I can't help but think there's a cleaner, more efficient way. Any suggestions?
-(IBAction)buttonPressed:(id)sender {
NSArray *buttonArray = [NSArray arrayWithObjects:button1, button2, button3, nil];
NSDictionary* buttonViewDict = #{button1.titleLabel.text : view1,
button2.titleLabel.text : view2,
button3.titleLabel.text : view3};
for (UIButton* button in buttonArray) {
[button setHighlighted:[button isEqual:sender]];
[((UIView*)[buttonViewDict objectForKey:button.titleLabel.text]) setHidden:![button isEqual:sender]];
}
}
You can use the tag property to identify your buttons and views.
Set up the tag values in Interface Builder or in -viewDidLoad, then use the tag value to identify which button was pressed:
- (IBAction)buttonPressed:(UIButton*)sender {
for (UIButton* button in _buttons) {
button.highlighted = button.tag == sender.tag;
}
for (UIView* view in _views) {
view.hidden = view.tag != sender.tag;
}
}
For what it's worth, I like your way. I would consider using the buttons as the keys and simplifying it like so -
NSDictionary *buttonViewDict = #{button1 : view1,
button2 : view2,
button3 : view3};
[buttonViewDict enumerateKeysAndObjectsUsingBlock:^(UIButton *button, UIView *view, BOOL *stop) {
view.hidden = !button.highlighted = sender == button;
}];
You may also want to store the dictionary as a property.
I am trying to find the best approach to doing this. I have 5 custom buttons on a view controller and I am trying to have the button stay highlighted if it is clicked. I know how to do this but I am trying to only allow 1 button to be highlighted at a time. So if a user clicks a button and highlights it, but clicks another, then the most recent button clicked will stay highlighted and the previous will unhighlight. What would be the best way to accomplish this?
You should keep a reference to all your buttons (for example, if you use IB, have links in your code like #property (nonatomic, strong) IBOutlet UIButton *button1; for all your buttons).
Then link all your buttons to the same method for a press on the button. I'll call it buttonPressed.
Impement it like this :
- (IBAction)buttonPressed:(id)sender {
UIButton *buttonPressed = (UIButton*)sender;
NSArray *buttons = [NSArray arrayWithObjects:_button1, _button2, _button3, nil];
bool buttonIsHighlighted = NO;
// Check if a button is already highlighted
for (UIButton *button in buttons) {
if (button.highlighted) {
buttonIsHighlighted = YES;
}
}
// If a button is highlighted, un-highlight all except the one pressed
// If no button is highlighted, just highlight the right one
if (buttonIsHighlighted) {
for (UIButton *button in buttons) {
if (buttonPressed == button) {
buttonIsHighlighted = YES;
} else {
button.highlighted = NO;
}
}
} else {
buttonPressed.highlighted = YES;
}
}
I can't test this code but I'm pretty sure it should work. Let me know if something's wrong.
Solution 1:
Put your buttons in an NSArray and when user clicks on a button check if another is highlighted. If YES, unhighlight it and highlight the one was pressed. If NO, highlight directly the one pressed.
Solution 2:
You can save the highlighted button in a global variable declared in #interface or in a #property. When users click the new one unhighlight the previous.
I have 2 buttons on my view and i want to disable the first button when i click on an other button and disable the second when I click again on the button.
I have tried with this code
if (button1.enable = NO) {
button2.enable = NO;
}
So I have in a NavigationBar a "+" button and 5 disable buttons in my view.
When I push the "+" button I want to enable the first button and when I push again that enable the second…
Thanks
if (button1.enabled == YES)
{
button1.enabled = NO;
button2.enabled = YES;
}
else (button2.enabled == YES)
{
button2.enabled = NO;
button1.enabled = YES;
}
Is that what your looking for? It would be an IBAction for the other button.
button1.enable = YES should be button1.enable == YES
a better readable form: [button1 isEnabled]
You're saying
if (button1.enabled = NO) {
when you probably mean
if (button1.enabled == NO) {
= is the assignment operator, and == is the boolean equality operator. What you're doing at the moment is assigning YES to button1.enable, which obviously enables button1. Then, because button.enable is true, control enters the if's clause and enables button2.
EDIT: To answer your new question ("When I push the "+" button I want to enable the first button and when I push again that enable the second..."), let's say that you initialise the button states somewhere. In your #interface add an instance variable
NSArray *buttons;
so your interface declaration looks something like
#interface YourViewController: UIViewController {
IBOutlet UIButton *button1;
IBOutlet UIButton *button2;
IBOutlet UIButton *button3;
IBOutlet UIButton *button4;
IBOutlet UIButton *button5;
NSArray *buttons;
}
and then initialise buttons like so:
-(void)viewDidLoad {
[super viewDidLoad];
buttons = [NSArray arrayWithObjects: button1, button2, button3, button4, button5, nil];
[buttons retain];
for (UIButton *each in buttons) {
each.enabled = NO;
}
-(void)viewDidUnload {
[buttons release];
[super viewDidUnload];
}
Let's say you hook up the + button's Touch Up Inside event handler to plusPressed:. Then you'd have
-(IBAction)plusPressed: (id) button {
for (UIButton *each in buttons) {
if (!each.enabled) {
each.enabled = YES;
break;
}
}
}
Each time plusPressed: is called, the next button in the array will be enabled. (I'm writing the above away from a compiler; there may be syntax errors.)
You could also make buttons a property. I didn't, because other classes have no business accessing buttons.