MPMediaPickerController fails to announce controls with VoiceOver - ios

I'm debugging an app that requires full accessibility using VoiceOver and one feature ask the user to select songs to play. The app uses MPMediaPicker. The problem is that MPMediaPicker does not really meet VoiceOver accessibility requirements, for example it does not announce whether an element of the list is selected or not, it does not clearly announce when the user select an element, when searching it does not announce the number of selected elements in the list as the list get pruned down and, worse of all cases, it does not announce anything at all when the button Add All Song is selected (it just stay silent).
It seems to me that these are pretty big oversights for a standard component so widely used and i'm wondering what can i do to fix these for my client as it explicitly says in the Apple documentation that i can't subclass MPMediaPickerController nor manipulate its private view hierarchy.
Of concern is also the fact that the my app also uses the standard component to select contacts which also seems to have similar issues.
Thank you.
Edit:
The app present the MPMediaPickerViewController using this code, which i believe it is fairly standard (perhaps a bit outdated since it still uses retain/releases)
MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeAnyAudio];
[picker setDelegate: self];
[picker setAllowsPickingMultipleItems: YES];
picker.prompt = NSLocalizedString (#"...", "...");
[[myAppDelegate instance] presentModalViewController: picker animated: YES];
[picker release];
where presentModalViewController is this:
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated {
UIViewController *c = self.window.rootViewController;
while ([c presentedViewController]) {
c = [c presentedViewController];
}
[c presentModalViewController:modalViewController animated:animated];
}
The missing announcements in the voice over belong to components shown as part of the selection process and in the hierarchy of MPMediaPickerController, so i don't know how to access them.
The above code is called inside the IBAction of a simple (+) right bar button of a ViewController that belongs to a NavigationController.
Further note:
Minimal proof of concept: create a default single page iOS application in Xcode. Add #import MediaPlayer; and then add this code to the ViewController.
- (void)viewDidAppear:(BOOL)animated {
MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeAnyAudio];
[picker setDelegate: self];
[picker setAllowsPickingMultipleItems: YES];
picker.prompt = NSLocalizedString (#"...", "...");
[self presentViewController:picker animated:true completion:^{
}];
}
Launch the app with VoiceOver activated, then select or navigate to the cell "Add All Song": the item is not announced by VoiceOver in any way.

I think this summarize things pretty well. From Apple Documentation in reference to the MPMediaPickerController:
This is a preliminary document for an API or technology in development. Apple is supplying this information to help you plan for the adoption of the technologies and programming interfaces described herein for use on Apple-branded products. This information is subject to change, and software implemented according to this document should be tested with final operating system software and final documentation. Newer versions of this document may be provided with future betas of the API or technology.
This control is not complete, and probably never will be complete, given that this has been considered a "beta" API since iOS 3.0.
Also, to summarize my findings, it is not possible to make this control accessible. I was able to play around with its views a bit, and every time I was able to add additional labelling it was soon overridden and reset. Finding the correct callbacks to reset the labels every time they were overridden by the controls default behaviors would be difficult (impossible) and obviously we cannot override the UIAccessibilityProtocol methods of the individual elements, because they are not controls we instantiated. We have instantiated the global control, and it is drawing everything for us. Finally, this would be considered by a reviewer to be accessing private APIs and so would not be allowed on the App Store regardless.
Conclusion: In order to provide the accessibility support you wish, you will have to implement a similar control manually.

Related

Change UIAlertView with swizzling

I've recently been handed a private hardware SDK to integrate into my company's mobile app. Everything works great except they are using a UIAlertView as the result of some opaque method call, and my design team wants to brand this. I don't have access to the SDK's source code. Is there a way I can safely swizzle UIAlertView to preserve all functionality and simply modify the appearance of the UIAlertView so it's more branded/consistent with the app appearance? If so, would I overload drawRect or something else, and how would I figure out what the names of the labels in UIAlertView are so that I can draw them with the size, shape, and color that I want?
for added detail, the app does not currently use UIAlertView or UIAlertViewController so in theory swizzling would only affect whatever is going on with this closed-source SDK.
That wouldn't be straightforward at all.
If the SDK is using UIAlertView, then try swizzling the show method.
Your implementation should do approximately the following:
1) Do not let the original UIAlertView to show -- it is very hard to customize it.
2) Keep reference of old .delegate to be able to notify it when needed.
3) Create your own custom UIView and use [[UIApplication sharedApplication].keyWindow addSubview:myCustomAlertView];.
4) I believe you can get all of the previous UIAlertView variables (i.e. button titles, textFields, etc), using its properties such as
#property(nonatomic,copy) NSString *title;
#property(nullable,nonatomic,copy) NSString *message;
- (nullable NSString *)buttonTitleAtIndex:(NSInteger)buttonIndex;
#property(nonatomic,readonly) NSInteger numberOfButtons;
#property(nonatomic) NSInteger cancelButtonIndex;
5) Create your own designs and call corresponding delegate methods when needed.
Other approach is to use Apple's Private API, but this may lead to bad results.
This is a VERY HACKY approach. Firstly, you have no guarantees that it would even work. Secondly, your app may be rejected. Therefore, I wouldn't really recommend it...
However, if you really want to go with this approach, then try doing this:
After [myAlertView show]; look at its properties after some delay (i.e. 0.01 sec is enough):
Now look for suspicious properties (probably of UIView class), which might have UI-related information. For example, __representer looks quite interesting -- it has constraints, it has labelContainerView... Try playing with those properties.
In order to get that __representer, use KVC and KVO (i.e. start with id theAlertController = [myAlertView valueForKey:#"_alertController"];). Then dive deeper and deeper.
Hopefully, you would be able to find useful properties and would be able to change their values via KVC.

UIActivityViewController, activities not being completed, sharing options not loading

I have an app that has a share button. This share button loads a UIActivityViewController for sharing to Facebook, Twitter, email, text message, etc.
It used to work fine, and I think it still works fine on the simulator, but on devices, the view controller appears with all the right options, and if you click on one, either nothing happens, or if it's Mail, the mail modal view loads and then dismisses itself. Then I get my log "Activity was not performed.", which is when the completion block returns false for completed but the activityType was not null. So it is recognizing the selection, but it isn't loading the activity into the view for some reason
I have checked the stuff I'm trying to share, even replaced it with dummy stuff (as shown below), still no luck. I am using a normal device, I have my Twitter, Mail, and Facebook accounts set up, texting works too. The only thing that works is copy (i.e. when you copy the share contents to the clipboard). In other apps on the same device, the UIActivityViewController and the loading of selected activities works just fine. Same issue observed on other devices running the app as well.
Really don't understand what the issue is here. Very perplexing! Any help or suggestions of things to try would be much appreciated. I don't see any way to debug this issue.
Here's the code: (note I tried removing the image as well, no luck)
- (void)shareTapped {
NSString *shareText = #"Testing";//[self shareText];
NSURL *url = [NSURL URLWithString:#"http://www.google.ca"]; //[self shareURL];
NSArray *activityItems = [NSArray arrayWithObjects:shareText,url, self.shareImage, nil];
UIActivityViewController *shareDrawer = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
shareDrawer.excludedActivityTypes = #[UIActivityTypePostToWeibo,UIActivityTypeAssignToContact,UIActivityTypeSaveToCameraRoll,UIActivityTypePrint];
shareDrawer.completionHandler = ^(NSString *activityType, BOOL completed) {
if (completed) {
NSLog(#"Selected activity was performed.");
} else {
if (activityType == NULL) {
NSLog(#"User dismissed the view controller without making a selection.");
} else {
NSLog(#"Activity was not performed.");
}
}
NSString *result = completed ? #"success" : #"fail";
if (activityType == NULL) {
result = #"dismissed";
}
};
[self presentViewController:shareDrawer animated:YES completion:nil];
}
OK, by process of elimination I finally narrowed down the culprit:
I have a custom UISegmentedControl with a custom font for the text and I manually adjusted the content offset to make it appear properly. Although I have removed these lines and the segmented control actually looks fine.
Here's the code (I've confirmed that it's all three lines that cause the problem)
[[UISegmentedControl appearance] setContentPositionAdjustment:UIOffsetMake(4, 0) forSegmentType:UISegmentedControlSegmentLeft barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setContentPositionAdjustment:UIOffsetMake(0, 0) forSegmentType:UISegmentedControlSegmentCenter barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setContentPositionAdjustment:UIOffsetMake(-4, 0) forSegmentType:UISegmentedControlSegmentRight barMetrics:UIBarMetricsDefault];
Now, seeing as the views that popup after you select a share option DO NOT HAVE SEGMENTED CONTROLS in them, I have no clue why this would cause this problem. But it definitely works now that I removed it.
Thanks for those of you who attempted to help. Of course, there's no way you could have possibly guessed this was the issue. Chances are pretty low that anyone would ever encounter this problem in the first place, since adjusting the content position is probably fairly rare.
To debug this issue I followed the following steps:
I tested if the issue had something to do with the properties on the UIActivityViewController that I was setting, or the activity items I was sharing. It did not.
I tested if UIActivityViewController worked properly when called from a different view controller inside my app. It did not.
I made a blank view controller with a button that causes a generic UIActivityViewController to show on press of a bar button. I made that my root view. That worked, thus showing that it was an isolated issue.
Instead of making that view controller the root of my app, I pushed it from my normal main view. It no longer worked, thus determining that the problem probably had something to do with my main view.
I commented out all the code in viewDidLoad, viewDidAppear, and viewWillAppear, except for adding a button to the nav bar which would load my sharing test. That worked. Then I uncommented viweDidAppear and viewWillAppear, still worked. So I uncommented chunks of viewDidLoad until I figured out exactly what the problem was.
What I learned: For weird problems like this (i.e. ones that seem like an iOS bug or something, but you can't find anyone posting about it), you should spend more time debugging before trying to post to stack overflow.
(I should really know this by now, but every new problem to debug feels like an exception to this rule)
Please comment below if you know why adjusting the content position of UISegmentedControl would mess up the sharing from UIActivityViewController even though those views don't contain segmented controls
Thanks
Had same issue too, here's what I done to overcome it.
I'd recommend adding the Social.framework to your Link Binary and using
SLComposeViewController *socialShare = [SLComposeViewController composeViewControllerForServiceType:shareType];
Where the shareType can be SLServiceTypeFacebook or SLServiceTypeTwitter.
I created a UIAlertview pop-up to show the share options for the user
UIAlertView *social = [[UIAlertView alloc]initWithTitle:#"Share" message:nil delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Twitter", #"Facebook", #"Email", nil];
and depending on which one they picked (using the UIAlertview Delegate methods) then the content can be shared accordingly, and it's a great alternative to UIActivityViewController.
Hope this suggestion helps, cheers, Jim.

Objects disappearing from Xcode Object Library Palette

I am relatively new to iPhone development.
When I open Xcode, my object library palette contains an "Address Book People Picker View" and other objects that disappear when I select a .xib file. This seems to be consistent with the behavior mentioned here: XCode Developer API - Object Library - Objects Disappearing
The above answer suggests that the Xcode palette is initially populated with all iOS and Mac objects, but then amends the list appropriate to the target when the XIB file is selected. I thought that the AddressBook framework/objects were supported in iOS so if only iOS objects remain then why are the AddressBook objects disappearing?
Thanks in advance
I don't believe you can use the AddresBookUi framework objects in Interface Builder, but must instantiate them programmatically. Some of them used in specific ways to function correctly, for example ABUnknownPersonViewController must be embedded in a navigation controller.
Since many of these views cannot be used in a generic fashion they are not exposed in interface builder to prevent confusing users.
For example:
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];

Aviary ios SDK memory not released after dismiss

I am using Aviary SDK 3.0 in my app, I put the Aviary code in a popover like this, but after I dismissed the popover using [popover dismissPopoverAnimated], the Aviary is still in the memory Heap, this is even true, even after I dismissed the parent viewcontroller. is it expected that the Aviary code is cleaned up after I dismiss?
in the action when use tap the photo. I present it here
AFPhotoEditorController *editorController = [[AFPhotoEditorController alloc] initWithImage:imageToEdit];
[editorController setDelegate:self];
if (popover == nil)
popover = [[UIPopoverController alloc] initWithContentViewController:editorController];
else
[popover setContentViewController:editorController animated:YES];
https://dl.dropboxusercontent.com/u/23238574/Screenshots/aviarypopover.png
From the docs:
"Note that pushing the controller onto a UINavigationController's stack is not recommended, since the Photo Editor itself uses a subclass of UINavigationController."
try presenting it modally, then what I do just to make me feel better about all memory getting cleaned is to make a strong property and go through the checklist
[editorController removeFromParentViewController];
editorController.delegate = nil;
editorController = nil;
something about clearing the delegate seems to clear my memory better, although, I don't think you need it.
also, don't forget:
"By default, Aviary keeps a small number of OpenGL objects loaded to optimize launches of Aviary products."
[AFOpenGLManager requestOpenGLDataPurge];
// to request a clear,
// "Calls to requestOpenGLDataPurge only apply to the currently loaded OpenGL data,"
You can set it to clear regularly with
[AFPhotoEditorCustomization purgeGPUMemoryWhenPossible:YES];
if you're doing this, it's a good idea to preload some of the editor if you know they'll be using it soon
[AFOpenGLManager beginOpenGLLoad];
This memory leak has been addressed in subsequent releases of the SDK. Please visit developers.aviary.com to get the latest version.

How to integrate the InAppSettingsKit?

I have just downloaded the InAppSettingsKit and I'm trying to integrate it with my app however I'm having some issues with it since I can't find any documentation to help me out. So far I've done the following steps...
I added the InAppSettingsKit directory to my Xcode project.
I created a new UIViewController class for my settings (which I named settingViewController).
At this point I have become a bit stuck as I'm not sure what needs to be done. If someone could offer some steps on how to integrate this it would be really really helpful as I can't find any up to date documentation online.
Usually, you don't need 2. You just configure a button action to display IASKAppSettingsViewController. This could look like this (in this case for a modal presentation):
appSettingsViewController = [[[IASKAppSettingsViewController alloc] initWithNibName:#"IASKAppSettingsView" bundle:nil] autorelease];
appSettingsViewController.delegate = self;
appSettingsViewController.showDoneButton = YES;
UINavigationController *aNavController = [[[UINavigationController alloc] initWithRootViewController:appSettingsViewController] autorelease];
[self presentModalViewController:aNavController animated:YES];
Check MainViewController.m in the sample app for different ways to present it (navigation push, tabBarItem, etc.).

Resources