VoiceOver ignores visible views, and speaks accessibilityLabels of hidden views - iphone

I have UIView, that can contain one of two views. When I removeFromSuperview first view and addSubview second view I can still hear accessibiliyLabel of hidden view. And only in 1-2 seconds I can hear correct accessibiilityLabel.
I see that it is common situation when hidden state of view is changed, accessibility can be frustrated and still speak hidden views, and does not note visible views.
Also if in UITableViewCell UIButton is hidden and then hidden state changes to NO, VoiceOver ignores it like it is still hidden. Only manual implementation of UIAccessibilityContainer protocol for cell resolves mentioned problem
No Notifications can solve this issue. Even playing with accessibilityElementsHidden did not help. Struggling with this during several days
Please can you recommend is there any way to say Accessibility that hierarhy of views was changed

You can post a UIAccessibilityScreenChangedNotification or UIAccessibilityLayoutChanged to alert UIAccessibility that the view changed. Since you didn't post any code, I can only give you a generic example, e.g.:
UIAccessibilityPostnotification(UIAccessibilityLayoutChanged,accessibilityelement)
...where "accessibilityelement" would be a button or text field or other accessibility element that VoiceOver switches to next.
Reference: UIKIt Reference

Just ran into this myself with a third party side menu library and had to use accessibilityElementsHidden to fix it. I first tried leveraging the accessibilityViewIsModal property, but that only works on sibling views.
#pragma mark - IIViewDeckControllerDelegate
- (void)viewDeckController:(IIViewDeckController *)viewDeckController didOpenViewSide:(IIViewDeckSide)viewDeckSide animated:(BOOL)animated
{
if (viewDeckSide == IIViewDeckLeftSide) {
[self.topViewController.view endEditing:YES];
self.viewDeckController.leftController.view.accessibilityElementsHidden = NO;
}
}
- (void)viewDeckController:(IIViewDeckController *)viewDeckController didCloseViewSide:(IIViewDeckSide)viewDeckSide animated:(BOOL)animated
{
self.viewDeckController.leftController.view.accessibilityElementsHidden = YES;
}

Related

Why calling [self becomeFirstResponder] caused so many problems?

For the past few extremely frustrating days of my life, I've been trying to figure out what's wrong with me code. In a certain page, if I put UITextViews or UITextFields or a MFMailComposer or a MessageComposer or anything with fields that require editing, the fields just wouldn't respond to touches. I couldn't edit anything when I ran the app. I couldn't edit text views or email fields or anything. I tried everything, but nothing worked. It turns out that on the main page (MainVC) that leads to the page where fields don't respond (GiftVC), in the viewDidAppear method (in the MainVC), I say: [self becomeFirstResponder];.
Now I'm not really sure why I put that there, but it turns out that commenting that line out fixes everything and makes all the fields and textviews and email composers and everything work just fine again.
I also have this in the MainVC page:
-(BOOL)canBecomeFirstResponder {
return YES;
}
and commenting that out fixes the problem as well.
The weird part is that even with the [self becomeFirstResponder] line, everything worked just fine in the new iOS 5 (simulator and device), but in iOS 4 (simulator and device), it wouldn't work at all with that line. Now that I've removed it, it works fine in both cases.
If you have the following in your UIViewController subclass
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.view.window) {
[self becomeFirstResponder];
}
}
then you probably intended to allow that subclass to handle motion events (shaking) or something similar. So that's probably why it's there.
If you weren't able to edit UITextFields then this subclass was probably becoming the first responder and not forwarding the event to the actual UITextField. When a UIViewController subclass calls overrides canBecomeFirstResponder to return YES and makes them self the first responder (ie [self becomeFirstResponder], if you want don't want that custom class to handle the touch events for the UITextField, then you should override the nextResponder method.
An example from my own product -- Essentially I have a UIViewController subclass that does two things: 1) it handles shake events and 2) it displays another view modally when some button is tapped. On the modal view there are some UITextFields. To allow my UIViewController subclass to forward the touch events to my modal view, I added the following:
- (UIResponder *)nextResponder
{
if (!self.view.window) {
// If the modal view is being displayed, forward events to it.
return self.modalViewController;
} else {
// Allow the superclass to handle event.
return [super nextResponder];
}
}
This will work on iOS 4 and 5, with either sdk.
Now, in your case you obviously didn't remember adding the code to become first responder in the first place, so you don't need the above hooks. However, it's good to know for the future.
Back to your actual question -- once you updated your SDK to 5, why wouldn't things work on iOS 4, but they would work on iOS 5? iOS 5 is doing some of the event forwarding for you which is why it works there. It should have never worked on iOS 4 in the beginning. Apple fixed some bugs that allowed it to work on 4, which is why it no longer works on 4.
I know the question had already accepted an accepted answer; I just wanted to clear up any confusion out there.
Check if MainVC has a method called canResignFirstResponder that returns NO (at least sometimes). If so, then once it becomes first responder, it won't let anything else become first responder, until it returns YES from that method. (All the UITextViews, etc. have to become first responder to be edited.)
Actually just look everywhere in all your code for canResignFirstResponder, in case it's in a superclass or something.
Otherwise the only thing that would stop the text fields and views from being editable would probably be if they got set userInteractionEnabled = NO, but since it hinges on the becomeFirstResponder statement, it is more likely to do with canResignFirstResponder.
In iOS 4, a subclass must override canBecomeFirstResponder in order to be able to become first responder. Maybe this is different for iOS 5 or it's a bug.
Try this,
Make sure you have added the uiTextViewDelegate and
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
NSLog(#"textViewShouldBeginEditing:");
return YES;
}

Why isn't UIButton's exclusiveTouch property set to YES by default?

I'm sure there are a lot of reasons why someone would like to have more than one button accept touches at the same time. However, most of us only need one button to be pressed at one time (for navigation, for something to be presented modally, to present a popover, a view, etc.).
So, why would Apple set the exclusiveTouch property of UIButton to NO by default?
Very old question, but deserves clarification IMO.
Despite the very misleading method documentation from Apple a view "A" with exclusiveTouch set will prevent other views from receiving events so long as A is processing some event itself (e.g. set a button with exclusiveTouch and put a finger on it, this will prevent other views in the window from being interacted with, but interaction with them will follow the usual pattern once the finger from the exlusiveTouch-item is removed).
Another effect is preventing view A from receiving events as long as some other view is interacted with (keep a button without exclusiveTouch set pressed, and the ones with exclusiveTouch will not be able to receive events as well).
You can still set a button in your view to exclusiveTouch and interact with the others, just not at the same time, as this simple test UIViewController will prove (once the correct bindings in the IB are set for both Outlets and Actions):
#import "FTSViewController.h"
#interface FTSViewController ()
- (IBAction)button1up:(id)sender;
- (IBAction)button2up:(id)sender;
- (IBAction)button1down:(id)sender;
- (IBAction)button2down:(id)sender;
#property (nonatomic, strong) IBOutlet UIButton *button1, *button2;
#end
#implementation FTSViewController
- (IBAction)button1up:(id)sender {
NSLog(#"Button1 up");
}
- (IBAction)button2up:(id)sender {
NSLog(#"Button2 up");
}
- (IBAction)button1down:(id)sender {
NSLog(#"Button1 down");
}
- (IBAction)button2down:(id)sender {
NSLog(#"Button2 down");
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Guarantees that button 1 will not receive events *unless* it's the only receiver, as well as
// preventing other views in the hierarchy from receiving touches *as long as button1 is receiving events*
// IT DOESN'T PREVENT button2 from being pressed as long as no event is being intercepted by button1!!!
self.button1.exclusiveTouch = YES;
// This is the default. Set for clarity only
self.button2.exclusiveTouch = NO;
}
#end
In light of this, the only good reason IMHO for Apple not to set exclusiveTouch to YES for every UIView subclass is that it would have made the implementation of complex gestures a real PITA, including probably some of the gestures we are already accustomed to in composite UIView subclasses (like UIWebView), as setting selected views to exclusiveTouch=NO (like button) is faster than doing a recursive exclusiveTouch=YES on pretty much everything just to enable multitouch.
The drawback of this is that in many cases the counter intuitive behaviour of UIButtons and UITableViewCells (among others...) might introduce weird bugs and make testing more tricky (as it happened to me like... 10 minutes ago? :( ).
Hope it helps
the UIView property exclusiveTouch means the view (button) is the ONLY thing in that window that can be interacted with if it is set to YES. As stated in the docs: Setting this property to YES causes the receiver to block the delivery of touch events to other views in the same window. The default value of this property is NO.
Therefore, it is the common behavior that you might have multiple buttons or interaction controls/views in a window and want exclusiveTouch set to NO.
If you set this property to YES for any UIView subclass in a window, you can not interact with anything else in the window for as long as that property is set to YES. That means if you initialize a button with exclusiveTouch = YES, but also have a table view or another button or a scroll view or any other view that is based on interaction, it will not respond to any touches.
exclusiveTouch simply means that any view underneath your UIButton will not receive the touch events.
It's set to no by default because you typically want the view underneath to receive these events. For example, if you have a UIButton on top of a scroll view and the user wants to scroll. You want the scrollView to scroll even if they begin with their finger on the UIButton.
I was just reading release notes for iOS 5 and from this version the exclusiveTouch will be set to YES by default. So just keep in mind that it will change with the new version of iOS.

An easy, clean way to switch/swap views?

I have looked at several sources but I am still very confused! I want to make an application with several views (just standard views, no table views or anything), from which I can click buttons on each view to access the others.
I've seen several ways to do this, but the only one that made any sense to me is to have the application delegate be responsible for swapping views. And I'm not even sure my code correctly cleans up the memory of the view it's swapping!
Is there a cleaner, or easier way to switch between views (I don't care about animations) than using a delegate?
I've also seen presentModalViewController, but that seems sort of unorganized. (E.g. If you have more than one view presenting a modal, how can you easily tell the newest view to close all views up to the root).
There's also creating a window-based application and using the window to addSubView, and removeFromSuperview, but how would you swap one very for another without creating extra, cumbersome coding in the delegate method to detect what view what removed, and which new view should be added? I can't find an addViewToSuperview method, at least.
So please share how you guys swap views, and perhaps the benefit to your system.
Talk really slow, because I feel very slow when it comes to swapping views!
Thank you for your time!
Dealing with multiple views is pretty easy if you understand the concept behind the views. For example your Delegate could be linked to an empty parent View. Then you just create your children views and add them to the parent view with addSubview. The last added view is the one that will be displayed in the foreground.
Now you can bring the views into the front using bringSubviewToFront method. Keep in mind that all your views are kept in memory until you remove them from the parent view using the children view's removeFromSuperview method.
Swapping views is just removing the child view before you add the next one but then you have to decide if you want to retain them or if you want to release them but then you need to *re-create (alloc / init) the views later.
BTW If you views are not at the right place then you either need to adjust the positions relative to the parent view or set them right in the interface builder because iOS will not reposition them because you add them as sub view.
For example I used that code to switch between a view that displays an Ad or a ticker:
- (void) showAds:(BOOL)flag {
if( showAds != flag ) {
while([[self.bottomView subviews] count] > 0 ) {
[[[self.bottomView subviews] objectAtIndex:0] removeFromSuperview];
}
showAds = flag;
if( showAds ) {
[self.tickerViewController stop];
[self.bottomView addSubview:self.adViewController.view];
} else {
[self.bottomView addSubview:self.tickerViewController.view];
[self.tickerViewController start];
}
}}
The 2nd line makes sure I don't remove and add if nothing has changed. The next 3 lines remove any residual sub views. After that either add the Add or the Ticker view as my current sub view (including making sure the ticker is started / stopped properly).
Hope that helps.

Moving view up to accommodate keyboard

I have a view with multiple text fields and I want to do the same effect that the Contacts application does when you click on a text field would otherwise be hidden by the keyboard when it comes up. When I dismiss the keyboard I plan on moving the view back down properly.
I suspect that I do this by changing the Frame value, but I need this to be animated so that it isn't jarring to the user.
Advice? Examples?
Wrapping your view in a UIScrollView is indeed the way to go. As well as on the textFieldDidEndEditing delegate, you could instead subscribe to the UIKeyboardDidHideNotification and UIKeyboardDidShowNotification and when you receive a notification that the keyboard did hide/show then scroll your view appropriately. I can post code examples for the keyboard notifications if you need it : )
Edit
Figured I'd post the code anyway - someone might find it helpful:
You need to declare listeners for the notifications:
NSObject hideObj = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.DidHideNotification, HandleKeyboardDidHide);
NSObject showObj = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.DidShowNotification, HandleKeyboardDidShow);
then your Action methods would look something like:
void HandleKeyboardDidShow(NSNotification notification)
{
scrollView.ScrollRectToVisible(textfield.Frame, true);
}
void HandleKeyboardDidHide(NSNotification notification)
{
// scroll back to normal
}
Edit 2
So if you'd like to remove the Observers when the view is destroyed, first you need to ensure you assign NSObjects when adding the observers then use the following code to remove them:
NSNotificationCenter.DefaultCenter.RemoveObserver(showObj);
NSNotificationCenter.DefaultCenter.RemoveObserver(hideObj);
Hope that helps.
I just did this on an application. I used a scrollview to wrap my entire view, and then used scrollToRectVisible on the textFieldDidEndEditing-delegate method. It worked perfectly!
The Apple documentation on about the keyboard management topic is pretty good and contains code (at the bottom) for most situations that that you can copy/paste right into your app.
Best of luck.

scrollsToTop doesn't work when UIWebView used with pushViewController, looking for solution

I have an app with a table view at the root in a navigation controller, and on selection of a table cell it displays a new view controller that contains only a UIWebView (with a toolbar and a navbar).
Depending on how I present the new web view, the feature where the user can tap on the status bar at the top and have the webview scroll to the top, either works or doesn't work.
If I use:
(void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
on the RootView, then the webview does scroll to the top.
If I change that one line of code and instead use:
(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
on the navigation controller, then the scrollsToTop feature stops working.
What I really want to use however, for other reasons in the context of the app, is the pushViewController method. BUT, I also want to keep the scrollsToTop behaviour.
I have so far tried various approaches, some described here:
-Attempting to set the webview internal scrollView scrollsToTop property
((UIScrollView *)[[webView valueForKey:#"_internal"] valueForKey:#"scroller"]).scrollsToTop = YES;
(No discernible effect).
-Changing the ordering of setting NavBar properties or not setting any at all
-Adding extra "window makeKeyAndOrderFront" calls after the new view push.
I don't believe there are other views there that could be claiming the "scrollsToTop" property (and the first test above proves that in any case).
Short of attempting to embed UIWebView into a UIScrollView, which I expect will be painful, I have run out of routes to explore to resolve this issue.
I am hoping someone else has found a way to correct this?
Try the reverse approach as a workaround.
In the root view controller:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.tableView.scrollsToTop = NO;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tableView.scrollsToTop = YES;
}
Bye!
It turns out that it wasn't working because of the way I was customiing the Navbar. I was overriding leftBarButtonItem with a UIBarButtonItem that then took up the whole navbar (I think I took that approach from some Apple code many months ago, but not sure). I was using the same approach on another view and the scrollsToTop worked fine there.
Taking that code out and putting the custom Navbar view into (eg)self.navigationController.titleView instead solved this problem. I do not really know why, like I say it works fine for other views.
I wasted a lot of time with this problem, so I hope I save someone else some hair by describing it here :)