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;
}
Related
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.
I'm trying to do my app more accessible. I have created a custom UIView that is added to the UIViewController using this code:
emergenteTiempos *emer = [emergenteTiempos shared:self.view];
[self.view addSubview:emer];
This code calls a custom UIView:
I have read in Making app Accessible that you must uncheck the accessibility for container and, in my case, the UIView that contains all elements (UILabel,UIButton), but you must check accessibility for these elements individually. So I did this:
But when the custom view appears in the screen, Voice Over doesn't read anything automatically. I have to press in each element in order to Voice Over says the label of this element.
I want Voice Over pronounces all the fields automatically when the view appears. I have tried many things, such as:
#implementation MultiFacetedView
- (NSArray *)accessibleElements
{
if ( _accessibleElements != nil )
{
return _accessibleElements;
}
_accessibleElements = [[NSMutableArray alloc] init];
/* Create an accessibility element to represent the first contained element and initialize it as a component of MultiFacetedView. */
UIAccessibilityElement *element1 = [[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self] autorelease];
/* Set attributes of the first contained element here. */
[_accessibleElements addObject:element1];
/* Perform similar steps for the second contained element. */
UIAccessibilityElement *element2 = [[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self] autorelease];
/* Set attributes of the second contained element here. */
[_accessibleElements addObject:element2];
return _accessibleElements;
}
/* The container itself is not accessible, so MultiFacetedView should return NO in isAccessiblityElement. */
- (BOOL)isAccessibilityElement
{
return NO;
}
/* The following methods are implementations of UIAccessibilityContainer protocol methods. */
- (NSInteger)accessibilityElementCount
{
return [[self accessibleElements] count];
}
- (id)accessibilityElementAtIndex:(NSInteger)index
{
return [[self accessibleElements] objectAtIndex:index];
}
- (NSInteger)indexOfAccessibilityElement:(id)element
{
return [[self accessibleElements] indexOfObject:element];
}
#end
But Voice Over doesn't read anything automatically.
Could you help me? Thank you very much!
I have custom UIToolbar's that sit ontop of my keyboard when it displays and that I use to insert pre-formatted text. My problem is that I have one UITextView and two UITextField's that are potential users of my pre-formatted text.
When the buttons call an IBAction, how can I tell which element has focus and is the firstResponder?
Attempt #2 at asking my question smartly:
I need to call [textElement selectedRange]; on the UITextField or UITextView that has focus. How can I declare textElement in a way that it doesn't matter which of the view UIText* classes it is?
If I do the following...
UIView *textElement = [self my_method_of_getting_first_responder];
NSRange range = [textElement selectedRange];
I of course get the
UIView does not declare selector 'selectedRange'
Thanks!
If I understood question, you want to click some button, and then get the link to you currently active text field? Then you have to go with category on UIView:
#implementation UIView (FindFirstResponder)
- (BOOL)findFirstResponder
{
if (self.isFirstResponder) {
return YES;
}
for (UIView *subView in self.subviews) {
if ([subView findFirstResponder]) {
return YES;
}
}
return NO;
}
#end
You can call this category method on UIWindow from you view controller. To get window use UIWindow *window= self.view.window;
I am new to Objective-C, and I am looking to limit a user from switching from the alphabet portion of a normal keyboard to the numeric/punctuation side. This being said, I would like to disable the button on the keyboard that does the switch. Maybe I'm putting in the wrong search parameters, but I'm finding little on Google and even less in my reference books. How would I go about actually disabling a button on an iOS keyboard? With alpha/numeric/punctuation characters this would be easily done by just ignoring the inputed characters that were entered, but the button that switches between keyboard portions does an action as opposed to returning a character.
Thank you for your help!
You actually can add and remove buttons of the default UIKeyboard
There's some recipes on the Internet like this: http://www.iphonedevsdk.com/forum/iphone-sdk-development/6573-howto-customize-uikeyboard.html and like this: http://www.iphonedevsdk.com/forum/iphone-sdk-development/6275-add-toolbar-top-keyboard.html
Those posts show you how to add a button, however the same principle can be used to remove.
Below I'll show you a compilation of one of the solutions:
//The UIWindow that contains the keyboard view -
//It some situations it will be better to actually
//iterate through each window to figure out where the keyboard is,
// but In my applications case
//I know that the second window has the keyboard so I just reference it directly
//
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows]
// objectAtIndex:1];
//Because we cant get access to the UIKeyboard throught the SDK we will
// just use UIView.
//UIKeyboard is a subclass of UIView anyways
UIView* keyboard;
//Iterate though each view inside of the selected Window
for(int i = 0; i < [tempWindow.subviews count]; i++)
{
//Get a reference of the current view
keyboard = [tempWindow.subviews objectAtIndex:i];
//Check to see if the className of the view we have
//referenced is "UIKeyboard" if so then we found
//the keyboard view that we were looking for
if([[keyboard description] hasPrefix:#"<UIKeyboard"] == YES)
{
// Keyboard is now a UIView reference to the
// UIKeyboard we want. From here we can add a subview
// to th keyboard like a new button
//Do what ever you want to do to your keyboard here...
}
}
I implemented a neater solution. The trick is to place a disabled key image on top of the keyboard.
To do this
Run emulator (in 100% scale) and screen grab the asset you'd like (in my case, this was a disabled Done button at the bottom right end)
Place this image on top of the keyboard
Note that keyboard is placed in a separate UIWindow (since iOS5 I believe) and thus, you will need to do the following
+ (void) addSubviewToTop:(UIView *)view {
int count = [[[UIApplication sharedApplication] windows] count];
if(count <= 0) {
warn(#"addSubviewToTop failed to access application windows");
}
UIWindow *top_window = [[[UIApplication sharedApplication] windows] objectAtIndex:count-1];
[top_window addSubview:view];
[top_window bringSubviewToFront:view];
}
I'm not sure if the SDK has changed such that #ppaulojr's answer no longer works, or if I just have things set up weirdly on my system, but with the following tweaks I was able to get it to work!
The posts linked in #ppaulojr's answer are great (http://www.iphonedevsdk.com/forum/iphone-sdk-development/6573-howto-customize-uikeyboard.html and http://www.iphonedevsdk.com/forum/iphone-sdk-development/6275-add-toolbar-top-keyboard.html), and they helped me to get this to work.
Apparently the actual keyboard view is now embedded as a subview in some grander UIKeyboard view structure so a bit of recursion is involved. I got this to work:
-(void) findKeyboard {
NSArray* windows = [[UIApplication sharedApplication] windows];
for (int i = 0; i < [windows count]; i++) {
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows]
objectAtIndex:i];
for(UIView *subView in [tempWindow subviews])
{
[self checkViews:subView];
}
}
}
-(void)checkViews:(UIView *)inView
{
for(UIView *keyboard in inView.subviews)
{
NSLog( #"ViewName: %#", [keyboard description] ); // Which view are we looking at
//Check to see if the className of the view we have
//referenced is "UIKeyboard" if so then we found
//the keyboard view that we were looking for
if([[keyboard description] hasPrefix:#"<UIKeyboard"] == YES)
{
// Keyboard is now a UIView reference to the
// UIKeyboard we want. From here we can add a subview
// to th keyboard like a new button
//Do what ever you want to do to your keyboard here...
break;
}
// Recurse if not found
[self checkViews:subView];
}
}
I also found that the best place to call this function is from -(void)textViewDidBeginEditing:(UITextView *)textView like so:
- (void)textViewDidBeginEditing:(UITextView *)textView {
NSLog(#"textViewDidBeginEditing");
[self findKeyboard];
}
This does the keyboard modifications as soon as the keyboard is added to the window, but before it actually shows up, so that the whole time it raises from the bottom, it will have been modified.
I am writing a SplitView iPad app. Inside the DetailViewController, there's a little view that contains a UITableView and a UISearchBar and its controller. This view does not represent the whole screen space reserved for the DetailViewController. Actually, it uses just half of it. There's an UIImageView on the other half.
And this is where trouble comes in: every time I use the search bar, the displaycontroller (I assume) dims everything present inside the DetailViewController, including the image view. That is not consistent with what someone would expect when running the app. Is there any way to set the frame to be dimmed? Or at least disable dimming for good?
Thanks in advance.
You are correct that it is the UISearchDisplayController that is managing the "dimming" effect that you're seeing.
What the UISearchDisplayController is doing is adding a UIControl as a subview to the view of the searchContentsController (a property of UISearchDisplayController), which is likely your detail-view controller. This UIControl is just an alpha'd view with a gray background. It seems to have a touch-up-inside event handler that ends searching when tapped.
To constrain the dimming effect to your sub-view of the detail-view, you need to do three things. (I'm assuming your detail-view-controller is defined via a xib. If not, these steps can be done in code too.)
1) add a new UIViewController to your detail-view-controller xib. Attach this new view-controller to an IBOutlet of your detail-view-controller. In my example I call this "_searchAreaViewController". This is important, even if you wont ever access the view controller (but remember, you'll have to release it at some point)
#interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate, UITableViewDelegate, UITableViewDataSource> {
UIPopoverController *popoverController;
UIToolbar *toolbar;
id detailItem;
UILabel *detailDescriptionLabel;
IBOutlet UIViewController* _searchAreaViewController;
}
2) make the containing view for your search area the view of this new view-controller. To do this, use Interface Builder to set a new referencing outlet for this view by dragging the outlet to the searchAreaViewController and selecting the "view" outlet. You must have a containing view - it should be a subview of your detail-view, and it should contain the UISearchBar and likely your UITableView.
3) make the searchContentsController property of the UISearchDisplayController refer to this new view controller instead of the detail-view-controller. This can only be done via Interface Builder as the property is read-only (IB has some magic to make this work?) If you need to do this step via code you'll have to subclass the UISearchDisplayController and return the correct value from a property override of "searchContentsController".
I made a sample app to demonstrate this and the only line of code I had to add to the SplitView template was the one listed in step 1 above. Everything else was just adding the views/controllers and connecting them properly in IB.
good luck!
iOS 8+
[[UIView appearanceWhenContainedInInstancesOfClasses:#[NSClassFromString(#"UISearchDisplayControllerContainerView")]] setHidden:YES];
iOS 7
[View appearanceWhenContainedIn:NSClassFromString(#"UISearchDisplayControllerContainerView"), nil] setHidden:YES];
I know, that UISearchDisplayController is deprecated for now, but if you still need to use it, you can solve your issue with one line of code perfectly. Add it to viewDidLoad method.
Could you clarify what you mean by "use the search bar" and "dims everything present"? I interpret what you wrote in such a way that the keyboard pops up when you are about to enter text in the text field of the search bar. And that at this point the detail view is dimmed out, preventing user interaction.
The cause is that the search bar implements a modal dialog which prevents user interaction with the view as long as the keyboard is shown. Unfortunately, there doesn't seem to be any way to configure the search bar to prevent this behavior. On the other hand I am not sure that the user won't expect this behavior since search bars are modal consistently and behave like this in general under iOS.
I have tried two work-arounds:
1.) There is a property of the UIViewController called modalPresentationStyle which produces exactly the behavior you describe if it has the value UIModalPresentationFormSheet ("All uncovered areas are dimmed to prevent the user from interacting with them.", see the Apple documentation). But setting this property to a different values does not change the result (at least for me it didn't work).
2.) You would need to write your own non-modal search bar replacement since a standard UITextField is non-modal and thus does not dim out any other UI elements. This approach works, but you might need a little more work to make it look like a "regular" search bar. But, again, since this search bar behaves differently from the modal normal search bars in iOS this might not really be what the users expect.
I know I am late and this is a horrible idea here, but 'setHidden:No' did not work for me.
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
BOOL hasBeenremoved = NO;
hasBeenremoved = [[[[NSThread mainThread] threadDictionary] objectForKey:#"hasBeenremoved"] boolValue];
if (hasBeenremoved)
{
UIView* dimmingView = nil;
dimmingView = [[[NSThread mainThread] threadDictionary] objectForKey:#"dimmingView"];
UIView* dimmingViewSuperView = nil;
dimmingViewSuperView = [[[NSThread mainThread] threadDictionary] objectForKey:#"dimmingViewSuperView"];
[dimmingViewSuperView addSubview:dimmingView];
[[[NSThread mainThread] threadDictionary] setObject:#NO forKey:#"hasBeenremoved"];
}
if ([searchText length] == 0 || [searchText isEqualToString:#""] )
{
[searchBar becomeFirstResponder];
[[[self primarySearchDisplayController] searchResultsTableView] reloadData];
[[[self primarySearchDisplayController] searchResultsTableView] setHidden:NO];
for( UIView *subview in self.view.subviews )
{
if([subview isMemberOfClass:[UIControl class]] ||
([[[subview class] description] isEqualToString:#"UISearchDisplayControllerContainerView"]))
{
for(UIView *subView2 in subview.subviews)
{
for(UIView *subView3 in subView2.subviews)
{
if (subView3.alpha < 1)
{
if ([[[subView3 class] description] isEqualToString:#"_UISearchDisplayControllerDimmingView"])
{
[[[NSThread mainThread] threadDictionary] setObject:subView3 forKey:#"dimmingView"];
[[[NSThread mainThread] threadDictionary] setObject:subView3.superview forKey:#"dimmingViewSuperView"];
[[[NSThread mainThread] threadDictionary] setObject:#YES forKey:#"hasBeenremoved"];
[subView3 removeFromSuperview];
}
}
}
}
}
}
}
}