I am trying to make a simple app accessible with VoiceOver.
The app loads a view controller with an opaque view, and tracks the location of one finger on this view. However I cannot understand why I have to re-focus (by tapping) on my view, even after its '(void) accessibilityElementDidBecomeFocused' method is called. I am confused because my view's '(void) accessibilityElementDidLoseFocus' is never called either.
I believe I am correctly following the instructions for View Controller containment from SO and the Apple docs:
1 - http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW81
2 - How does View Controller Containment work in iOS 5?
The code to show the new view controller is as follows:
CGRect frame = self.view.bounds;
customViewController = [[FocusIssueCustomViewController alloc] init];
[customViewController loadWithFrame:frame forViewController:self withAlpha:0.5];
// setting properties of the custom view:
[customViewController.view setAccessibilityLabel:#"Touch area"];
[customViewController.view setMultipleTouchEnabled:YES];
[customViewController.view setIsAccessibilityElement:YES];
[customViewController.view setAccessibilityTraits:UIAccessibilityTraitAllowsDirectInteraction];
// inform user the screen has changed:
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, customViewController.view);
and the loadWithFrame method is as follows:
- (void) loadWithFrame: (CGRect) frame forViewController: (UIViewController*) viewController withAlpha: (float) alphaValue
{
// keep a reference to the view controller that requested the Custom View
[self setOriginatingViewController:viewController];
// load the custom view
[self setCustomView:[[FocusIssueCustomView alloc] initWithFrame:frame]];
[self customView].alpha = alphaValue;
// make this the ViewController for this view
self.view = [self customView];
// add the view controller following guidelines from the
// viewController containment in the UIViewController class reference from the Apple docs
[self.originatingViewController addChildViewController:self];
[self.originatingViewController.view addSubview:self.view];
[self didMoveToParentViewController:self.originatingViewController];
}
This custom view then tracks one touch: the implementation file is given below.
#import "FocusIssueCustomView.h"
#define CIRCLE_DIAMETER 110
#interface FocusIssueCustomView ()
{
// coordinate of this one touch
float xCood, yCood;
}
#end
#implementation FocusIssueCustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// make the custom view opaque
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat black[] = {0, 0, 0, 1.0};
CGContextSetFillColor(ctx, black);
CGContextAddRect(ctx, rect);
CGContextFillPath(ctx);
[self setAlpha:0.5];
// clear current context
CGContextRef CurrentContext = UIGraphicsGetCurrentContext();
CGContextClearRect(CurrentContext, rect);
// draw a blue circle, no fill for the touch
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 4.0);
CGContextSetRGBStrokeColor(context, 0, 0, 255, 1);
CGRect rectangle = CGRectMake(xCood - CIRCLE_DIAMETER/2, yCood - CIRCLE_DIAMETER/2, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
- (void) accessibilityElementDidBecomeFocused
{
NSLog(#"Inside the custom view's \"accessibilityElementDidBecomeFocused\" method");
}
- (void) accessibilityElementDidLoseFocus
{
NSLog(#"Inside the custom view's \"accessibilityElementDidLoseFocus\" method");
}
#pragma mark - touch method
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// if 1 touch on screen, track this touch
if ([[event allTouches] count] == 1)
{
CGPoint point = [[touches anyObject] locationInView:self];
xCood = point.x;
yCood = point.y;
NSLog(#"Touch at (%f, %f)", xCood, yCood);
}
// redraw
[self setNeedsDisplay];
}
#end
I genuinely will need a view controller to control this transparent CustomView (hence the view controller containment approach). That is why I am not simply taking an 'addSubview' approach. Any help is greatly appreciated..
Thanks -
VP
Related
I'm rendering CGPDFPage in UIImageView but not zooming how we can zoom if any know plz let me know
PDFDocument = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfUrl);
totalPages = (int)CGPDFDocumentGetNumberOfPages(PDFDocument);
NSLog(#"total pages %i",totalPages);
//struct CGPDFPage *page =CGPDFDocumentGetPage(PDFDocument, 1);
CGFloat width = 600.0;
// Get the page
CGPDFPageRef myPageRef = CGPDFDocumentGetPage(PDFDocument, i);
// Changed this line for the line above which is a generic line
//CGPDFPageRef page = [self getPage:page_number];
CGRect pageRect = CGPDFPageGetBoxRect(myPageRef, kCGPDFMediaBox);
CGFloat pdfScale = width/pageRect.size.width;
pageRect.size = CGSizeMake(pageRect.size.width*pdfScale, pageRect.size.height*pdfScale);
pageRect.origin = CGPointZero;
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// White BG
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,pageRect);
CGContextSaveGState(context);
// ***********
// Next 3 lines makes the rotations so that the page look in the right direction
// ***********
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(myPageRef, kCGPDFMediaBox, pageRect, 0, true));
CGContextDrawPDFPage(context, myPageRef);
CGContextRestoreGState(context);
imageView= UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
You should add your imageView as subview of UIScrollView.
This SO answer describes how to zoom UIImageView inside UIScrollView:
Set your view controller up as a <UIScrollViewDelegate>
Draw your UIScrollView the size you want for the rectangle at the center of the view. Set the max zoom in the inspector to something bigger than 1. Like 4 or 10.
Right click on the scroll view and connect the delegate to your view controller.
Draw your UIImageView in the UIScrollView and set it up with whatever image you want. Make it the same size as the UIScrollView.
Ctrl + drag form you UIImageView to the .h of your View controller to create an IBOutlet for the UIImageView, call it something clever like imageView.
Add this code:
-(UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
Run the app and pinch and pan til your heart's content.
Go with this
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.minimumZoomScale=0.5;
self.scrollView.maximumZoomScale=6.0;
self.scrollView.contentSize=CGSizeMake(1280, 960);
self.scrollView.delegate=self;
}
Check Apple Documentation
Another way is to implement UITapGestureRecognizer in your viewController
- (void)viewDidLoad
{
[super viewDidLoad];
// target - what object is going to handle
// the gesture when it gets recognised
// the argument for tap: is the gesture that caused this message to be sent
UITapGestureRecognizer *tapOnce =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tapOnce:)];
UITapGestureRecognizer *tapTwice =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tapTwice:)];
tapOnce.numberOfTapsRequired = 1;
tapTwice.numberOfTapsRequired = 2;
//stops tapOnce from overriding tapTwice
[tapOnce requireGestureRecognizerToFail:tapTwice];
// then need to add the gesture recogniser to a view
// - this will be the view that recognises the gesture
[self.view addGestureRecognizer:tapOnce];
[self.view addGestureRecognizer:tapTwice];
}
Basically this code is saying that when a UITabGesture is registered in self.view the method tapOnce or tapTwice will be called in self depending on if its a single or double tap. You therefore need to add these tap methods to your UIViewController:
- (void)tapOnce:(UIGestureRecognizer *)gesture
{
//on a single tap, call zoomToRect in UIScrollView
[self.myScrollView zoomToRect:rectToZoomInTo animated:NO];
}
- (void)tapTwice:(UIGestureRecognizer *)gesture
{
//on a double tap, call zoomToRect in UIScrollView
[self.myScrollView zoomToRect:rectToZoomOutTo animated:NO];
}
Hope that helps
I draw an initial simple rect using UIBezierPath and fill it with color. How can I change only its color by touching on it?
- (void) drawRect:(CGRect)rect
{
// Drawing code
[self drawRectWithFrame:_myRect fillColor:_firstColor];
}
- (void) drawRectWithFrame:(CGRect)frame fillColor:(UIColor *)color
{
[color setFill];
UIBezierPath *path = [UIBezierPath bezierPathWithRect:frame];
[path fill];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//get the rect color from touches (pixel color)
UIColor *color = [self getRectColorFromTouches:touches];
//chnage the rect color
if (color == _firstColor) {
//doesn't work
//[self drawRectWithFrame:_myRect fillColor:_secondColor]; //??
//How can I do that?
}
else {
//??
}
}
Some remarks :
Instead of overriding UIView touch event methods, you should use a UIGestureRecognizer. In your case, a UItapGestureRecognizer might be enough. (this way, it will be easier to handle conflict between different touch actions, that's the reason why gestureRecognizers were created!)
When you receive tap, change a local property of your view (for example, a BOOL isRectangleDrawn might be changed each time the view receives the tap)
Finally - and that's what missing in your code, that otherwise should be correct - don't forget to call [self setNeedsDisplay] to be sure your view's - (void) drawRect:(CGRect)rect method gets called
I am creating an app where I want to draw lines on scrollView. I am able to draw lines.
Here is my code
#interface GraphOnScrollView : UIScrollView
#property(strong,nonatomic)NSMutableArray *intensityArray;
#end
import "GraphOnScrollView.h"
#implementation GraphOnScrollView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.userInteractionEnabled=YES;
self.scrollEnabled=YES;
UIButton *DirectMsgBtn1 =[UIButton buttonWithType:UIButtonTypeCustom];
DirectMsgBtn1.titleLabel.font =[UIFont systemFontOfSize:12.0];
[DirectMsgBtn1 setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[DirectMsgBtn1 setTitle:#"direct message" forState:UIControlStateNormal];
[DirectMsgBtn1 setBackgroundColor:[UIColor clearColor]];
[DirectMsgBtn1 addTarget:self
action:#selector(DirectMessageViewPopUp:)
forControlEvents:UIControlEventTouchDown];
DirectMsgBtn1.frame = CGRectMake(0.0, 0, 100, 30);
[self addSubview:DirectMsgBtn1];
DirectMsgBtn1 = nil;
}
return self; }
// Only override drawRect: if you perform custom drawing. // An
empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGFloat y_Axix =20.0;
CGFloat lineWidth=1.0;
for (int i=0; i<[self.intensityArray count]; i++)
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, 2.0);
lineWidth+=1;
CGFloat red[4] = {1.0f, 0.5f, 0.0f, 1.0f};
CGContextSetStrokeColor(c, red);
CGContextBeginPath(c);
CGFloat width=[[self.intensityArray objectAtIndex:i] floatValue];
CGContextMoveToPoint(c, 5.0f, y_Axix);
CGContextAddLineToPoint(c, width, y_Axix);
CGContextStrokePath(c);
CGContextAddArc(c,width,y_Axix,1.0f,0,2*3.1415926535898,1);
CGContextDrawPath(c,kCGPathStroke);
y_Axix=y_Axix+50;
}
NSLog(#"intensity array %#", self.intensityArray);
self.contentSize = CGSizeMake(self.frame.size.width, 1000);
}
This is code I am using for adding scrollview on my view
GraphOnScrollView *GraphView =[[GraphOnScrollView
alloc]initWithFrame:CGRectMake(0.0, 150.0, 320.0, 280.0)];
GraphView.backgroundColor =[UIColor whiteColor];
GraphView.intensityArray =[NSMutableArray arrayWithObjects:#"60",#"100",#"40",#"10",#"20",#"40",#"40",#"100",
nil];
[self.view addSubview:GraphView];
Using this code the lines which i have drawn is not getting scrolled but the scrollview is scrolled. I don't know what is the problem.
Thanks
While drawing on any view it draws everything on the canvas of that view.
In the case of your, you are drawing on UIScrollView so all drawing performs on the canvas of the UIScrollView, so it is not scrollable.
To solve that problem you can create on separate UIView with the size you want, perform any drawing on it and add that view in scrollview. I think this will be the best solution.
You can also refer to this link for some more help.
Alternate Approach (i actually used uiview for drawing and made it look like scroll)
save the touch location in
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
record the touch point in touches moved.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
now you can translate coordinate of your points as
newX=x+(touchstart.x-touchMoved.x);//do this for x of every point
newy=y+(touchstart.y-touchMoved.y);// do this for y of every point
put the above lines in a function and call them from touches moved you can do it in touches ended method as well if lag is ok with you.
Point is translate the point and calculate the degree of translation from user touch began and moved or swipe
Note to casual readers: Despite the title, this question has nothing to do with the UIScrollView properties bounces (scrolling related) or bouncesZoom.
I am using UIScrollView to add zooming to a custom view. The custom view uses sublayers to draw its content. Each sublayer is a CALayer instance that is added to the view's main layer with [CALayer addSublayer:]. Sublayers use CoreGraphics to render their content.
After each zoom completes, the custom view needs to redraw its content at the new zoom scale so that the content appears crisp and sharp again. I am currently trying to get the
approach to work that is shown in this SO question, i.e. I reset the scroll view's zoomScale property to 1.0 after each zoom operation, then I adjust the minimumZoomScale and maximumZoomScale properties so that the user cannot zoom in/out more than originally intended.
The content redrawing already works correctly (!), but what I am missing is a smooth GUI update so that the zoomed content is redrawn in place without appearing to move. With my current solution (code example follows at bottom of this question), I observe a kind of "bounce" effect: As soon as the zoom operation ends, the zoomed content briefly moves to a different location, then immediately moves back to its original location.
I am not entirely sure what the reason for the "bounce" effect is: Either there are two GUI update cycles (one for resetting zoomScale to 1.0, and another for setNeedsDisplay), or some sort of animation is taking place that makes both changes visible, one after the other.
My question is: How can I prevent the "bounce" effect described above?
UPDATE: The following is a minimal but complete code example that you can simply copy&paste to observe the effect that I am talking about.
Create a new Xcode project using the "Empty application" template.
Add the code below to AppDelegate.h and AppDelegate.m, respectively.
In the project's Link build phase, add a reference to QuartzCore.framework.
Stuff that goes into AppDelegate.h:
#import <UIKit/UIKit.h>
#class LayerView;
#interface AppDelegate : UIResponder <UIApplicationDelegate, UIScrollViewDelegate>
#property (nonatomic, retain) UIWindow* window;
#property (nonatomic, retain) LayerView* layerView;
#end
Stuff that goes into AppDelegate.m:
#import "AppDelegate.h"
#import <QuartzCore/QuartzCore.h>
#class LayerDelegate;
#interface LayerView : UIView
#property (nonatomic, retain) LayerDelegate* layerDelegate;
#end
#interface LayerDelegate : NSObject
#property(nonatomic, retain) CALayer* layer;
#property (nonatomic, assign) CGFloat zoomScale;
#end
static CGFloat kMinimumZoomScale = 1.0;
static CGFloat kMaximumZoomScale = 5.0;
#implementation AppDelegate
- (void) dealloc
{
self.window = nil;
self.layerView = nil;
[super dealloc];
}
- (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
[UIApplication sharedApplication].statusBarHidden = YES;
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.backgroundColor = [UIColor whiteColor];
UIScrollView* scrollView = [[[UIScrollView alloc] initWithFrame:self.window.bounds] autorelease];
[self.window addSubview:scrollView];
scrollView.contentSize = scrollView.bounds.size;
scrollView.delegate = self;
scrollView.minimumZoomScale = kMinimumZoomScale;
scrollView.maximumZoomScale = kMaximumZoomScale;
scrollView.zoomScale = 1.0f;
scrollView.bouncesZoom = NO;
self.layerView = [[[LayerView alloc] initWithFrame:scrollView.bounds] autorelease];
[scrollView addSubview:self.layerView];
[self.window makeKeyAndVisible];
return YES;
}
- (UIView*) viewForZoomingInScrollView:(UIScrollView*)scrollView
{
return self.layerView;
}
- (void) scrollViewDidEndZooming:(UIScrollView*)scrollView withView:(UIView*)view atScale:(float)scale
{
CGPoint contentOffset = scrollView.contentOffset;
CGSize contentSize = scrollView.contentSize;
scrollView.maximumZoomScale = scrollView.maximumZoomScale / scale;
scrollView.minimumZoomScale = scrollView.minimumZoomScale / scale;
// Big change here: This resets the scroll view's contentSize and
// contentOffset, and also the LayerView's frame, bounds and transform
// properties
scrollView.zoomScale = 1.0f;
CGFloat newZoomScale = self.layerView.layerDelegate.zoomScale * scale;
self.layerView.layerDelegate.zoomScale = newZoomScale;
self.layerView.frame = CGRectMake(0, 0, contentSize.width, contentSize.height);
scrollView.contentSize = contentSize;
[scrollView setContentOffset:contentOffset animated:NO];
[self.layerView setNeedsDisplay];
}
#end
#implementation LayerView
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.layerDelegate = [[[LayerDelegate alloc] init] autorelease];
[self.layer addSublayer:self.layerDelegate.layer];
// super's initWithFrame already invoked setNeedsDisplay, but we need to
// repeat because at that time our layerDelegate property was still empty
[self setNeedsDisplay];
}
return self;
}
- (void) dealloc
{
self.layerDelegate = nil;
[super dealloc];
}
- (void) setNeedsDisplay
{
[super setNeedsDisplay];
// Zooming changes the view's frame, but not the frame of the layer
self.layerDelegate.layer.frame = self.bounds;
[self.layerDelegate.layer setNeedsDisplay];
}
#end
#implementation LayerDelegate
- (id) init
{
self = [super init];
if (self)
{
self.layer = [CALayer layer];
self.layer.delegate = self;
self.zoomScale = 1.0f;
}
return self;
}
- (void) dealloc
{
self.layer = nil;
[super dealloc];
}
- (void) drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
CGRect layerRect = self.layer.bounds;
CGFloat radius = 25 * self.zoomScale;
CGFloat centerDistanceFromEdge = 5 * self.zoomScale + radius;
CGPoint topLeftCenter = CGPointMake(CGRectGetMinX(layerRect) + centerDistanceFromEdge,
CGRectGetMinY(layerRect) + centerDistanceFromEdge);
[self drawCircleWithCenter:topLeftCenter radius:radius fillColor:[UIColor redColor] inContext:context];
CGPoint layerCenter = CGPointMake(CGRectGetMidX(layerRect), CGRectGetMidY(layerRect));
[self drawCircleWithCenter:layerCenter radius:radius fillColor:[UIColor greenColor] inContext:context];
CGPoint bottomRightCenter = CGPointMake(CGRectGetMaxX(layerRect) - centerDistanceFromEdge,
CGRectGetMaxY(layerRect) - centerDistanceFromEdge);
[self drawCircleWithCenter:bottomRightCenter radius:radius fillColor:[UIColor blueColor] inContext:context];
}
- (void) drawCircleWithCenter:(CGPoint)center
radius:(CGFloat)radius
fillColor:(UIColor*)color
inContext:(CGContextRef)context
{
const int startRadius = [self radians:0];
const int endRadius = [self radians:360];
const int clockwise = 0;
CGContextAddArc(context, center.x, center.y, radius,
startRadius, endRadius, clockwise);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillPath(context);
}
- (double) radians:(double)degrees
{
return degrees * M_PI / 180;
}
#end
Based on your sample project, the key is that you're manipulating a CALayer directly. By default, setting CALayer properties, such as frame, cause animations. The suggestion to use [UIView setAnimationsEnabled:NO] was on the right track, but only affects UIView-based animations. If you do the CALayer equivalent, say in your setNeedsDisplay: method:
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layerDelegate.layer.frame = self.bounds;
[CATransaction commit];
It prevents the implicit frame-changing animation and looks right to me. You can also disable these implicit animations via a CALayerDelegate method in your LayerDelegate class:
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
return (id)[NSNull null]; // NSNull means "don't do any implicit animations"
}
Original Suggestions:
Perhaps you are in an animation block without knowing it? Or, perhaps one of the methods you're calling is setting up an animation block? What if you [UIView setAnimationsEnabled:NO] before your code and re-enable them after?
If it's not an animation, then it's probably as you suspect; two view updates of some kind. (Perhaps one from the scroll view, and one from your code somehow?) Some runnable sample code would be great in that case.
(Out of curiosity, have you tried using CALayer's shouldRasterize and rasterizationScale rather than faking out the zoom level?)
In the X Code user interface builder there's a Bounce setting (it's under Scroll View).
I have custom view Chart and view controller for that view GraphViewController.
I'm implementing simple graph.
Here is code for Chart.h
#import
#interface Chart : UIView {
BOOL hideChart;
}
#property BOOL hideChart;
- (void) drawAxesInContext:(CGContextRef)context;
- (void) drawUserChartinContext:(CGContextRef)context;
#end
Chart.m
- (void)drawRect:(CGRect)rect
{
CGContextRef ctxt = UIGraphicsGetCurrentContext();
[self drawAxesInContext:ctxt];
[self drawUserChartinContext:ctxt];
}
- (void) drawAxesInContext:(CGContextRef)context {
CGContextTranslateCTM(context, nullX, nullY);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetLineWidth(context, 3);
CGContextBeginPath(context);
for (int i=0; i
As you can see on screenshot I have uibutton "hide". I want to hide graph (not axes) by pressing this button.
In viewController I created ibaction
- (IBAction) hideAndShow {
self.chart.hideChart = YES;
}
and in view u can see
if (hideChart) {
[[UIColor blackColor] setStroke];
[self setNeedsDisplay];
NSLog(#"hide");
}
But it not works, do you any ideas how can I make this?
http://dl.dropbox.com/u/866144/Voila_Capture7.png
I suggest adding a custom setter for hideChart, something like this
- (void)setHideChart:(BOOL)isHidden {
self.isHidden = isHidden;
}