I think very few people have noticed that the physical coordinates of the touch of a finger and their program display in iOS are slightly different: iOS gives a point displaced by about 1.5 mm upwards relative to the real touch. This is done in the interest of usability - a point close to the nail seems more realistic than lying below the pad of the finger. In addition, you can better see the area of the screen where you click.
To make it clearer what you are talking about, you can download any drawing artist (for example,
Bamboo Paper , the
app is not mine, free ), block the screen auto-rotate, draw a small horizontal line, then turn the device upside down (always when blocking the auto-rotate) and try to continue the drawn line. Most likely the continued line will be lower than the original.
In most applications, this behavior is invisible, but in some it can be harmful. For example, in pictures, where auto-rotate is disabled and it is assumed that the user can rotate the device as he pleases, it is necessary to ensure identical handling of touches, regardless of the current rotation of the device. Or in a board game, such as chess, where the displacement of coordinates can lead to the player playing black (and for which the interface in portrait mode is upside down), when you try to move a piece, it will miss and take the piece from the cage below.
According to my tests on the first generation iPad, the shift was about 7 pixels. Naturally, on other devices, the result will be different, it is necessary to test.
I did not find an open software interface for managing this shift in the Apple documentation, and therefore I had to look for workarounds.
')
In the case of drawing painters, you can simply introduce an amendment to the logic of methods
touchesMoved and the like:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
However, this is not always acceptable, especially if there is a certain amount of ready-made code that you want to reuse without changes.
As a radical solution in the first version of this article, I showed an example with an amendment to absolutely all the calls of
locationInView and
previousLocationInView in
UITouch instances:
#import "objc/runtime.h" @interface UITouch (Adjusted) -(CGPoint)adjustedLocationInView:(UIView *)view; -(CGPoint)adjustedPreviousLocationInView:(UIView *)view; @end @implementation UITouch (Adjusted) -(CGPoint)adjustedLocationInView:(UIView *)view{ CGPoint point = [self adjustedLocationInView:view]; point.y = point.y + 7; return point; } -(CGPoint)adjustedPreviousLocationInView:(UIView *)view{ CGPoint point = [self adjustedPreviousLocationInView:view]; point.y = point.y + 7; return point; } +(void)load{ Class class = [UITouch class]; Method locInViewMethod = class_getInstanceMethod(class, @selector(locationInView:)); Method adjLocInViewMethod = class_getInstanceMethod(class, @selector(adjustedLocationInView:)); method_exchangeImplementations(locInViewMethod, adjLocInViewMethod); Method prevLocInViewMethod = class_getInstanceMethod(class, @selector(previousLocationInView:)); Method adjPrevLocInViewMethod = class_getInstanceMethod(class, @selector(adjustedPreviousLocationInView:)); method_exchangeImplementations(prevLocInViewMethod, adjPrevLocInViewMethod); NSLog(@"UITouch class is adjusted now."); } @end
Here, using the runtime function method_exchangeImplementations, the implementations of the default
UITouch methods are
exchanged for our corrections. Please note that the file with the created category does not have to be imported into any other .h or .m file. Just add it to the project, and the
+ load method will be called automatically, because the
+ load message is automatically sent to each class and category when added to the runtime.
Upd. However, as it turned out in the comments , starting with iOS 5, Apple asks not to use method_exchangeImplementations in applications. Thanks wicharek for the info. Therefore, to amend, it is better to use any other method to your taste, for example, one of those proposed in the comments.So, after the introduction of the amendment, drawing on the view will work identically for any orientation of the device. However, this does not solve problems with chess pieces: in determining which view to send a touch event, the point with a shift will still be used, and we can get into the wrong figure.
To determine which view should handle the touch, use the
hitTest: withEvent: method of
UIView , which works like this:
- pointInside is called: withEvent: self ;
- if the result is NO , then hitTest: withEvent: returns nil , i.e. view does not respond to touch;
- if the result is YES , the method recursively sends hitTest: withEvent: to all of its subviews;
- if one of the subview returned a non-nil object, then the root hitTest: withEvent: returns this object;
- if all subview returned nil , or view has no subview, then self is returned;
Thus, for the correct hit of the touches in the chess figures, it is enough to override
hitTest: withEvent at the root view, which contains all the subviews for which we need to introduce an amendment:
@implementation ChessBoardView - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event { point.y = point.y + 7; UIView *hitView = [super hitTest:point withEvent:event]; return hitView; } @end
After that, the player who plays black gets into the chess pieces as comfortably as the player who plays white.