Простота против оптимальности
Я считаю, что очень сложно написать простой и оптимальный код.
Я взял идею «перекрывающегося» прозрачного представления из ответа MystikSpiral, и он отлично сработал для того, чего я пытался достичь; быстрое и чистое решение.
Короче говоря, у меня был собственный UITableViewCell (разработанный на IB) с MKMapView слева и некоторыми UILabels справа. Я хотел создать настраиваемую ячейку, чтобы вы могли касаться ее где угодно, и это подтолкнуло бы к новому контроллеру представления. Однако прикосновение к карте не передавало прикосновений «вверх» к UITableViewCell, пока я просто не добавил UIView того же размера, что и вид карты, прямо поверх него (в IB) и не сделал его фоном «прозрачным цветом» в коде ( не думаю, что вы можете установить clearColor в IB ??):
dummyView.backgroundColor = [UIColor clearColor];
Думал, это может помочь кому-то другому; конечно, если вы хотите добиться того же поведения для ячейки табличного представления.
Сделайте MKMapView подпредставлением настраиваемого представления и реализуйте
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
в настраиваемом представлении для возврата self вместо подпредставления.
Вам, вероятно, потребуется наложение прозрачного вида, чтобы улавливать касания, как это часто делается с элементами управления на основе UIWebView. Просмотр карты уже выполняет ряд специальных функций касанием, чтобы можно было перемещать, центрировать, масштабировать карту и т. Д., Чтобы сообщения не попадали в ваше приложение.
Два других (НЕПРОВЕРЕНО) ) варианты, которые я могу придумать:
1) Отказаться от первого респондента через IB и установить для него значение «Владелец файла», чтобы позволить Владельцу файла отвечать на прикосновения. Я сомневаюсь, что это сработает, потому что MKMapView расширяет NSObject, а не UIView, и в результате события касания все еще могут не передаваться вам.
2) Если вы хотите поймать ловушку при изменении состояния карты (например, при увеличении ) просто реализуйте протокол MKMapViewDelegate для прослушивания определенных событий. Я предполагаю, что это ваш лучший способ легко захватить какое-либо взаимодействие (если не считать реализации прозрачного вида поверх карты). Не забудьте установить контроллер представления, в котором находится MKMapView, в качестве делегата карты ( map.delegate = self
).
Удачи.
Я не экспериментировал, но есть большая вероятность, что MapKit основан на кластере классов, и, следовательно, создание подклассов сложно и неэффективно.
Я бы предложил сделать MapKit представлением subview настраиваемого представления, которое должно позволить вам перехватывать события касания до того, как они достигнут его.
После дня пиццы и криков я наконец нашел решение! Очень аккуратно!
Питер, я использовал ваш трюк, описанный выше, и немного изменил его, чтобы наконец получить решение, которое отлично работает с MKMapView и должно работать также с UIWebView
MKTouchAppDelegate.h
#import <UIKit/UIKit.h>
@class UIViewTouch;
@class MKMapView;
@interface MKTouchAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UIViewTouch *viewTouch;
MKMapView *mapView;
}
@property (nonatomic, retain) UIViewTouch *viewTouch;
@property (nonatomic, retain) MKMapView *mapView;
@property (nonatomic, retain) IBOutlet UIWindow *window;
@end
MKTouchAppDelegate.m
#import "MKTouchAppDelegate.h"
#import "UIViewTouch.h"
#import <MapKit/MapKit.h>
@implementation MKTouchAppDelegate
@synthesize window;
@synthesize viewTouch;
@synthesize mapView;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
//We create a view wich will catch Events as they occured and Log them in the Console
viewTouch = [[UIViewTouch alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
//Next we create the MKMapView object, which will be added as a subview of viewTouch
mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
[viewTouch addSubview:mapView];
//And we display everything!
[window addSubview:viewTouch];
[window makeKeyAndVisible];
}
- (void)dealloc {
[window release];
[super dealloc];
}
@end
UIViewTouch.h
#import <UIKit/UIKit.h>
@class UIView;
@interface UIViewTouch : UIView {
UIView *viewTouched;
}
@property (nonatomic, retain) UIView * viewTouched;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
@end
UIViewTouch.m
#import "UIViewTouch.h"
#import <MapKit/MapKit.h>
@implementation UIViewTouch
@synthesize viewTouched;
//The basic idea here is to intercept the view which is sent back as the firstresponder in hitTest.
//We keep it preciously in the property viewTouched and we return our view as the firstresponder.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"Hit Test");
viewTouched = [super hitTest:point withEvent:event];
return self;
}
//Then, when an event is fired, we log this one and then send it back to the viewTouched we kept, and voilà!!! :)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Touch Began");
[viewTouched touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Touch Moved");
[viewTouched touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Touch Ended");
[viewTouched touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Touch Cancelled");
}
@end
Надеюсь, это поможет некоторым из вас!
Ура
Спасибо за пиццу и крики - вы сэкономили мне много времени.
multitouchenabled будет работать время от времени.
viewTouch.multipleTouchEnabled = TRUE;
В конце концов, я отключил виды, когда мне нужно было снимать прикосновение (другой момент времени, чем требуются щипки):
[mapView removeFromSuperview];
[viewTouch addSubview:mapView];
[self.view insertSubview:viewTouch atIndex:0];
Я заметил, что вы можете отслеживать количество и местоположение касаний и получать местоположение каждого касания на виде:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Touch Moved %d", [[event allTouches] count]);
NSEnumerator *enumerator = [touches objectEnumerator];
id value;
while ((value = [enumerator nextObject])) {
NSLog(@"touch description %f", [value locationInView:mapView].x);
}
[viewTouched touchesMoved:touches withEvent:event];
}
Кто-нибудь еще пробовал использовать эти значения для обновления уровня масштабирования карты? Это будет вопрос записи начальных позиций, а затем конечных точек, вычисления относительной разницы и обновления карты.
Я играю с базовым кодом, предоставленным Мартином, и похоже, что он будет работать ...
Вот то, что я собрал, что позволяет увеличивать масштаб в симуляторе (не пробовал на реальном iPhone), но я думаю, что все будет хорошо:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Touch Began %d", [touches count]);
reportTrackingPoints = NO;
startTrackingPoints = YES;
[viewTouched touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if ([[event allTouches] count] == 2) {
reportTrackingPoints = YES;
if (startTrackingPoints == YES) {
BOOL setA = NO;
NSEnumerator *enumerator = [[event allTouches] objectEnumerator];
id value;
while ((value = [enumerator nextObject])) {
if (! setA) {
startPointA = [value locationInView:mapView];
setA = YES;
} else {
startPointB = [value locationInView:mapView];
}
}
startTrackingPoints = NO;
} else {
BOOL setA = NO;
NSEnumerator *enumerator = [[event allTouches] objectEnumerator];
id value;
while ((value = [enumerator nextObject])) {
if (! setA) {
endPointA = [value locationInView:mapView];
setA = YES;
} else {
endPointB = [value locationInView:mapView];
}
}
}
}
//NSLog(@"Touch Moved %d", [[event allTouches] count]);
[viewTouched touchesMoved:touches withEvent:event];
}
- (void) updateMapFromTrackingPoints {
float startLenA = (startPointA.x - startPointB.x);
float startLenB = (startPointA.y - startPointB.y);
float len1 = sqrt((startLenA * startLenA) + (startLenB * startLenB));
float endLenA = (endPointA.x - endPointB.x);
float endLenB = (endPointA.y - endPointB.y);
float len2 = sqrt((endLenA * endLenA) + (endLenB * endLenB));
MKCoordinateRegion region = mapView.region;
region.span.latitudeDelta = region.span.latitudeDelta * len1/len2;
region.span.longitudeDelta = region.span.longitudeDelta * len1/len2;
[mapView setRegion:region animated:YES];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (reportTrackingPoints) {
[self updateMapFromTrackingPoints];
reportTrackingPoints = NO;
}
[viewTouched touchesEnded:touches withEvent:event];
}
Основная идея заключается в том, что если пользователь использует два пальца, вы отслеживаете значения. Я записываю начальную и конечную точки в startPoints A и B. Затем я записываю текущие точки отслеживания, и когда я закончу, на touchesEnded я могу вызвать процедуру для вычисления относительной длины линии между точками, с которых я начинаю. , а линия между точкой I заканчивается простым вычислением гипотенузы. Отношение между ними и есть степень масштабирования: я умножаю диапазон области на эту величину.
Надеюсь, это кому-то пригодится.
Итак, после полдня возиться с этим, я обнаружил следующее:
В видеороликах Stanford, посвященных iPhone, парень из Apple говорит, что многие вещи, связанные с UIKit, вызовут множество ошибок, если вы «передадите» запросы касания (или два метода, описанных выше), и вы, вероятно, не заставит его работать.
РЕШЕНИЕ : описано здесь: Перехват / захват событий касания iPhone для MKMapView . По сути, вы «ловите» событие до того, как его получит ответчик, и интерпретируете его там.