Accessibility on custom UISlider - ios

I have created a custom range UISlider with two thumb. However in VoiceOver mode, I am not able to swipe up and down to adjust the thumb.
UIAccessibilityElement *minElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
minElement.accessibilityFrame = [self convertRect:currentThumbRect
toView:nil];
minElement.accessibilityLabel = NSLocalizedString(#"Minimum", nil);
minElement.accessibilityTraits = UIAccessibilityTraitAdjustable;
[_accessibleElements addObject:minElement];
UIAccessibilityElement *maxElement = [[UIAccessibilityElement alloc]
initWithAccessibilityContainer:self];
maxElement.accessibilityFrame = [self convertRect:currentUpperThumbRect
toView:nil];
maxElement.accessibilityLabel = NSLocalizedString(#"Maximum", nil);
maxElement.accessibilityTraits = UIAccessibilityTraitAdjustable;
[_accessibleElements addObject:maxElement]
I have added code above so that VoiceOver can recognize both thumb separately, but I couldn't adjust the thumb. Any idea to make the thumb adjustable in VoiceOver.
Problem Solved:
I use 'UIAccessibilityCustomAction' to add in custom behavior. Instead of slide up and down to adjust the value, in custom behavior slide up and down can be use to choose the action and double tap to perform it.
UIAccessibilityElement *minElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
minElement.accessibilityLabel = NSLocalizedString(#"Minimum", nil);
UIAccessibilityCustomAction *increaseMinAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(#"Increase minimum", #"action to increase min")
target:self selector:#selector(accessibilityMinIncrement)];
UIAccessibilityCustomAction *decreaseMinAction = [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(#"Decrease minimum", #"action to decrease min")
target:self selector:#selector(accessibilityMinDecrement)];
minElement.accessibilityCustomActions = #[increaseMinAction, decreaseMinAction];
[_accessibleElements addObject:minElement];

An adjustable element will be called with accessibilityIncrement and accessibilityDecrement When the user swipes up or down. When that happens it is expected to change its accessibilityValue. If the value doesn't change, VoiceOver will interpret that as having reached the boundaries of possible values (either the lowest or heights value that's allowed) and will play a "ding" sound to indicate to the user that the gesture had no effect.
A plain UIAccessibilityElement doesn't (to the best of my knowledge) implement the increment and decrement methods and you don't seemt to be modifying the value of those elements. As such, when the user focuses on one of those elements, the method isn't implemented and the value doesn't change so VoiceOver plays the "ding" sound.
One possible solution is to subclass and have each knob element either hold the current value, or to have each knob element ask a delegate for the value and forward the increment and decrement methods to that delegate.

You may use custom actions to solve your problem but I don't think that's the recommended solution for a UISlider with VoiceOver.
You were initally following the right path using the increment and decrement methods with the adjustable trait.
The problem dealt with your implementation : in my opinion, the best way is to define the former 2 methods so as to adapt the slider value according to the knob location.

Related

Key-Frame Animation appears fuzzy when moving Frames?

I use JazzHands to create a key frame based animation in a UIScrollView.
Here is an example. Look at the view at the top. When you move from page to page. While the animation is running the view at the top is slightly moving from left to right. The animation appears a bit fuzzy.
Here is the code taken from the example here:
IFTTTFrameAnimation *titleView1FrameAnimation = [IFTTTFrameAnimation new];
titleView1FrameAnimation.view = self.titleView1;
[self.animator addAnimation:titleView1FrameAnimation];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(1)
andFrame:self.titleView1.frame]];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(2)
andFrame:CGRectOffset(self.titleView1.frame, timeForPage(2), 0)]];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(3)
andFrame:CGRectOffset(self.titleView1.frame, timeForPage(3), 0)]];
[titleView1FrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(4)
andFrame:CGRectOffset(self.titleView1.frame, timeForPage(4), 0)]];
When running the demo take a look at the part marked with red in the following screenshot:
Edit: Here is the code containing this problem: https://github.com/steffimueller/Intro-Guide-View-for-Talk.ai
How can I make the animation running smooth and less fuzzy?
This is due to the frame rate in the JazzHands IFTTTAnimatedScrollViewController being set for non-retina displays. You need to double the number in timeForPage, and also use double the number of the contentOffset in animate, but use the original non-doubled values of timeForPage in places where you were using that for laying out the positions of views instead of using it for the animation time.
Here's a Gist of the changes you'd have to make to your example to get it working. Fixed Demo Gist
You need this method for setting the animation times:
#define timeForPage(page) (NSInteger)(self.view.frame.size.width * (page - 1) * 2.0)
And this one for setting the centers of your views based on the page dimensions:
#define centerForPage(page) (NSInteger)(self.view.frame.size.width * (page - 1))
Then you need to override the scrollViewDidScroll: method in IFTTTAnimatedScrollViewController to use double the numbers it's currently using.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self.animator animate:(NSInteger)(scrollView.contentOffset.x * 2)];
}
We'll work on getting the JazzHands demo updated!
Before you start your animation call:
view.layer.shouldRasterize = YES; (seems that you are using objective-c so I put YES here, for swift should be true)
As soon as the animation is finished call
view.layer.shouldRasterize = NO; (seems that you are using objective-c so I put NO here, for swift should be false)
You should always use it when you animating a view
You can see more details about it in the WWDC 2012 Polishing Your Interface Rotations video (paid developer subscription needed)
I hope that helps you!
[EDIT]
Every Time you call the method animate set shouldRasterize to YES before it, like in the example bellow:
titleView1FrameAnimation.view.layer.shouldRasterize = YES;
[self. titleView1FrameAnimation animate:scrollView.contentOffset.x];
I've messed around a bit with the code and it seems that keeping those top views in place while the scrollview is scrolling made it jiggle left and right.
What I did is take out the title view from the scroll view and add it to the view controller view.
You can see it in action here
Later edit:
To actually see what I've changed you can check the file differences in Git. Basically I moved your titleViews (titleView1, titleView2, etc) from the scrollView to the view controller's view (so basically I've replaced all the lines that were like this:
[self.scrollView addSubView:self.titleView1]
to something like this:
[self.view addSubView:self.titleView1]
After that I've also took out the keyframe animations that were keeping your title views in place since they were not moving with the scrollview anymore. Practically I've deleted all the lines that were adding a frame animation to your titleviews from each configurePageXAnimation.
2nd question answer:
Say your screenshot view is called screenshotView. You can go ahead and create it like this:
UIImageView *screenshotView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ScreenshotImage"]];
[self.view addSubview:screenshotView];
self.screenshotView = screenshotView;
[self.screenshotView setCenter:CGPointMake(self.view.center.x + self.view.bounds.size.width, <#yourDesiredY#>)];
And animate it like this:
//screenshotView frame animation
IFTTTFrameAnimation *screenshotViewFrameAnimation = [IFTTTFrameAnimation new];
screenshotViewFrameAnimation.view = self.screenshotView;
[self.animator screenshotViewFrameAnimation];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(1) andFrame:self.screenshotView.frame]];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(2) andFrame:CGRectOffset(self.screenshotView.frame, -self.scrollView.bounds.size.width, 0.0)]];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(3) andFrame:CGRectOffset(self.screenshotView.frame, -self.scrollView.bounds.size.width, 0.0)]];
[screenshotViewFrameAnimation addKeyFrame:[[IFTTTAnimationKeyFrame alloc] initWithTime:timeForPage(4) andFrame:CGRectOffset(self.screenshotView.frame, -self.scrollView.bounds.size.width * 2, 0.0)]];

Keep text in UITextView highlighted when the popover is dismissed

I have a UIPopover that shows up a plain view containing a UITextView filled with some text. I have managed to highlight the text. When the popover is dismissed, and re-opened, the highlight disappears. I want to keep the text highlighted even if if the application is closed. Any ideas how to achieve that?The code i used is the following :
- (void)highlight {
NSRange selectedRange = self.textViewAll.selectedRange;
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithAttributedString:self.textViewAll.attributedText];
[attributedString addAttribute:NSForegroundColorAttributeName
value:[UIColor redColor]
range:selectedRange];
// [highlightedRange addObject:];
// This is where i tried to save each location and length in a mutable array but didn't work
[highlightedRangeLocation insertObject:[NSNumber numberWithInteger:selectedRange.location] atIndex:indexOfHighlight];
[highlightedRangeLength insertObject:[NSNumber numberWithInteger:selectedRange.length] atIndex:indexOfHighlight];
///////////////////////////////////////////////////////////////////////////////
self.textViewAll.attributedText = attributedString;
indexOfHighlight ++ ;
}
- (void)viewDidLoad {
UIMenuItem *highlightMenuItem = [[UIMenuItem alloc] initWithTitle:#"Highlight" action:#selector(highlight)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObject:highlightMenuItem]];
float sysVer = [[[UIDevice currentDevice] systemVersion] floatValue];
if (sysVer >= 8.0) {
self.textViewAll.layoutManager.allowsNonContiguousLayout = NO;
}
}
Could anyone point out how to continue from here?
Edit 1 :
The code that close the popover :
- (IBAction)closeFun:(id)sender {
// self.popoverPresentationController set
[self dismissViewControllerAnimated:YES completion:nil];
// [self dismis]
}
Can't you juste save the Highlighted text range in [NSUserDefaults standardUserDefaults] whenever the popover is dismissed, and retrieve it when the popover reappears ?
I think the problem is in the fact that the popover is responsible for the highlighted state, i.e .it is the popover who keeps that fact/state.
The popover is a part of presentation layer / user interface. Surely the highlight represents some fact that ( now comes the catch ) - is completely independent of the popover.
For example highlighting a task could represent that the task is due. Or, highlighting a label to red color could mean that the balance in the bank is in negative numbers.
You see, no matter what user interface element you use, they only represent some underlying business reality.
But what probably happens you create a popover instance, you set it to have a highlighted element. But then this concrete popover instance dies, when it is closed.
And the highlight dies with it.
When you click some button (I guess), a popover shows up, but it is a different instance. This instance doesn't know about highlight.
Even if you somehow managed to keep the one instance of popover alive, and just hide and show it again, the popover should NOT be responsible to know whether something is red or due, (and thus highlighted.)
In you application, you should have a well separated model layer...which is basically a set of related objects that represent state ie. fact that are related to what the application solves from business perspective (for ex. draws lines, calculates interest..stores music..anything really). This model layer, some object in it, should store the facts..ie.e. the task is due, or the balance is low.
Every time you show your popover, you should investigate what are the underlying facts in your model layer right when the popover is being shown. Ivestigating means find a programmatic way to look into model objects, find out about values there and and set up the highlight in that moment again based on this "investigation". You should not rely on the fact that it was highlighted in the not so distant past.

How do I hide controls and cause others to shift place in iOS?

I'm programming an app for iOS 7 in Xcode 5.
I have three text boxes at the top of my page, each right above the other. Let's call them topTextBox, middleTextBox, and bottomTextBox.
My intent is that depending on a particular condition, the topTextBox might not be visible (or present, really) when the view loads.
If topTextBox isn't present (or hidden, or whatever) I would like middleTextBox and bottomTextBox to be placed further up the page...as if topTextBox wasn't ever there in the first place (so middleTextBox is in the spot topTextBox used to be in, and bottomTextBox is in the spot middleTextBox was in).
I'm using storyboards with AutoLayout ON. I can't seem to figure out what to do with the constraints for each of the three textBoxes to make this work. Making middleTextBox and bottomTextBox sit higher on the page when topTextBox is hidden doesn't need to be dynamic--I make the decision to show or not show topTextBox in ViewDidLoad()--I just need to get them to show up in the right place depending on my conditions.
Questions:
a. Is making topTextBox.hidden = YES the right way to get topTextBox to not be shown? Or is there some way to make it not noticed by the view at all?
b. What do I do with these constraints on middleTextBox and bottomTextBox to move them up on the page in this condition?
c. Is there anything else I should know to get this to work that I'm not thinking of? Perhaps a better method?
Thanks.
Have you tried just hard coding the frame logic? This could be very effective. All you have to do is conditionally populate an array containing pointers to your text fields and then perform a little frame arithmetic on their y origins. For example, this could easily display all the fields:
NSArray *fields = #[topTextField, middleTextField, bottomTextField];
for (int i = 0 ; i < fields.count ; i ++) {
UITextField *field = fields[i];
[field setFrame:CGRectMake(20.0, 20.0 + (i * 50.0), 280.0, 34.0)];
}
Produces this:
Or at its most basic level, something like this could be used to check whether any of the text fields should/shouldn't be shown, and either hides them, or adjusts their frame accordingly.
BOOL shouldShowTop = YES;
BOOL shouldShowMiddle = NO;
BOOL shouldShowBottom = YES;
NSMutableArray *fields = [NSMutableArray new];
if (shouldShowTop) {
[fields addObject:topTextField];
}else{
[topTextField setHidden:YES];
}
if (shouldShowMiddle) {
[fields addObject:middleTextField];
}else{
[middleTextField setHidden:YES];
}
if (shouldShowBottom) {
[fields addObject:bottomTextField];
}else{
[bottomTextField setHidden:YES];
}
for (int i = 0 ; i < fields.count ; i ++) {
UITextField *field = fields[i];
[field setFrame:CGRectMake(20.0, 20.0 + (i * 50.0), 280.0, 34.0)];
}
Will produce this:
Setting the height constraint of topTextField to 0 will help you to achieve this.
provided all textfields are connected through vertical space constraint
Setting the height of topTextField so that it has zero height could also work for you.
CGRect frame = topTextField.frame;
frame.size.height =0;
topTextField.frame = frame;
This is a good choice should you want to animate its re-appearance later on.
As for constraints, constrain the top of the topTextField to the superview, and then the top of middleTextField and bottomTextField to the bottom of the textfield above.
Do not set a constraint for the height of topTextField, but do set width constraints. You'll need to set the height of topTextField in viewDidLoad:

Continuously Update Label Based on UISlider Value

I currently have a series of 5 UISliders that continuously update their respective labels to display what value the user is selecting in an hh:mm:ss format. I have the properties of each slider set to continuous and have their targets set to when the slider is changed (This example would be for just the first slider).
sliderOne.continuous=YES;
[sliderOne addTarget:self action:#selector(sliderOneChanged:) forControlEvents:UIControlEventTouchUpInside];
Each slider's individual label updates the time selected perfectly. Now however, I need to update another label underneath all of these sliders that continuously sums up the value of the sliders.
At the same time, I am also doing some conversions with the value of the slider using two segmented controllers -- a miles/kilometers segmented controller and various distances in another segmented controller. Each conversion results in a time in seconds that is then converted back into the hh:mm:ss format and displayed in the last label when the user selects which combo from the segment controller they want. I have all the math worked out for this, and the label will update correctly when changing segments, but not continuously.
How could I update a label to continuously show the sum of every UISlider as the user changes them?
For a continuous updating, use the UIControlEventValueChanged event along with the continuous property
sliderOne.continuous=YES;
[sliderOne addTarget:self action:#selector(sliderOneChanged:) forControlEvents:UIControlEventValueChanged];
Use KVO to get notified of changes in UISliders and update the UI accordingly. You want to wrap UI updates with dispatch_async to avoid locking the interface.
Using KVO-Notification-Manager it can be something like this (in viewDidLoad or similar):
id token1 = [slider1 addKVOBlockForKeyPath:#"value" options:NSKeyValueObservingOptionNew handler:^(NSString *keyPath, id object, NSDictionary *change) {
dispatch_async(dispatch_get_main_queue(), ^{
whateverSliderNeedsUpdate.value = /* your calculation result */;
});
}];
id token2 = [slider2 /* repeat for as many sliders as you want */];
...
It's important to remove the observers at some point.
- (void)dealloc {
[slider1 removeKVOBlockForToken:token1];
[slider2 ...];
}
If you feel adventurous you can try with ReactiveCocoa.
You could create a method that does all of your math and updates your UILabel with the result. For example:
- (void)updateSumLabel {
// this is obviously very simple
float num1 = [_sliderOne value];
float num2 = [_sliderTwo value];
float sum = num1 + num2;
[_sumLabel setText:[NSString stringWithFormat:#"#f",sum]];
}
The you can call updateSumLabel from any of your existing methods like sliderOneChanged. This is probably the most simple way todo this.

iOS alert banner/label?

I apologize if this question is very basic. I have been googling around but can't seem to find the api/reference for a drop down alert banner/label (I do not know the proper term for this), therefore I am posting here.
This: The label/banner which has "Please enter valid email address" in it.
So here a my questions:
What is the proper term for this (alert banner? notification? label?)
I am trying accomplish similar functionality to what is shown in the image, so basically if any field is invalid, the "label/banner" expands from underneath the navigation bar with the message in it:
If this is just a UILabel, what is the simplest way of adding the expand animation?
If it is something built in, since I have seen bunch of apps do it for alerting, please let me know what its called.
Have a look here, I'm sure you will be able to find something to suite your needs.
The basic idea is that its simply a UIView that you animate down from the top of the screen (at the very basic). You can get a lot fancier by adding gradients, touch recognizers to dismiss it, etc. But pretty much to get the base line functionality you would just do something like this:
//Create a view to hold the label and add images or whatever, place it off screen at -100
UIView *alertview = [[UIView alloc] initWithFrame:CGRectMake(0, -100, CGRectGetWidth(self.view.bounds), 100)];
//Create a label to display the message and add it to the alertView
UILabel *theMessage = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(alertview.bounds), CGRectGetHeight(alertview.bounds))];
theMessage.text = #"I'm an alert";
[alertview addSubview:theMessage];
//Add the alertView to your view
[self.view addSubview:alertview];
//Create the ending frame or where you want it to end up on screen, in this case 0 y origin
CGRect newFrm = alertview.frame;
newFrm.origin.y = 0;
//Animate it in
[UIView animateWithDuration:2.0f animations:^{
alertview.frame = newFrm;
}];
Check out my project - it might be just the thing you're looking for.
https://github.com/alobi/ALAlertBanner
For easier control over animating the alert, you can embed your custom view in a UIStackView and simply show/hide it in an animation block. That way will significantly reduce the amount of code needed to animate the visibility of the alert.

Resources