2017년 6월 19일 월요일

뷰컨트롤러 밖에서 이벤트 추적하기

어플리케이션에서 투명 뷰를 윈도우에 추가하여 이벤트 추적하는 방법.

MyEventHandler.h

@interface MyEventHandler : UIView <UIGestureRecognizerDelegate>
@property (strong, nonatomic) NSMutableArray *touchCouple;
@property (strong, nonatomic) NSMutableArray *touchEvents;
@property (assignnonatomic) SMonService *refSMonService;
@property (strong, nonatomic) NSMutableArray *controllerStack;
@property (strong, nonatomic) NSMutableArray *updatedCallStack;

@end


MyEventHandler.m

@implementation MyEventHandler

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.touchEvents = [[NSMutableArray alloc] init];
        self.touchCouple = [[NSMutableArray alloc] init];
        self.controllerStack = [NSMutableArray new];
        self.updatedCallStack = [NSMutableArray new];
    }
    return self;
}

- (void)dealloc {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

// hitTest 처리할 뷰컨트롤러 리턴
- (UIViewController *)hitTestViewController:(UIViewController*)viewController {
    
    UIViewController *hitTestVC = nil;
    
    if (viewController.navigationController) {
        hitTestVC = viewController.navigationController;
    } else if (viewController.tabBarController) {
        hitTestVC = viewController.tabBarController;
    } else {
        return viewController;
    }
    
    return hitTestVC;
}

// hitTest 리턴
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIViewController *visibleVC = [_refSMonService topViewController];
    UIViewController *hitTestVC = [self hitTestViewController:visibleVC];
    
    CGPoint convPoint = [self convertPoint:point toView:hitTestVC.view];
    UIView *handlerView = [hitTestVC.view hitTest:convPoint withEvent:event];
    
    if (_touchCouple.lastObject && [_touchCouple.lastObject doubleValue] != event.timestamp) {
        [_touchCouple removeAllObjects];
    }
    [_touchCouple addObject:[NSNumber numberWithDouble:event.timestamp]];
    
    if (_touchCouple.count == 2) {
        
        [_touchCouple removeAllObjects];
        
        UIViewController *vc = [_refSMonService getViewControllerFromView:handlerView];
        __block NSString *title = nil;
        
        if ([handlerView isKindOfClass:[UIButton class]]) {
            title = [(UIButton *)handlerView titleForState:UIControlStateNormal];
        }
        else if ([handlerView isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                title = [[[handlerView valueForKey:@"tabBar"] valueForKey:@"selectedItem"] title];
                NSLog(@"TouchEvent raised: %f, %@ (view:%@-\"%@\", controller:%@)", event.timestamp, NSStringFromCGPoint(point), handlerView.class, title, vc.class);
            });
            [self processAfterTouch];
            return handlerView;
        }
        else if ([vc isKindOfClass:[UIAlertController class]]) {
            title =[[handlerView valueForKey:@"action"] title];
        }
        else {
        }
        NSLog(@"TouchEvent raised: %f, %@ (view:%@-\"%@\", controller:%@)", event.timestamp, NSStringFromCGPoint(point), handlerView.class, title, vc.class);
        [self processAfterTouch];
    }
    
    return handlerView;
}

// 터치이벤트가 발생한후에 뷰스텍 변화 감지
- (void)processAfterTouch {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.8f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self detectViewControllerStackChange];
    });
}

- (void)detectViewControllerStackChange {
    [self exploreViewController:[_refSMonService getRootViewController]];
    
    NSMutableSet *previousStack = [[[NSMutableSet alloc] initWithArray:_controllerStack] mutableCopy];
    NSMutableSet *newStack = [[[NSMutableSet alloc] initWithArray:_updatedCallStack] mutableCopy];
    [newStack minusSet:previousStack];
    if (newStack.count) { // push
        NSLog(@"%@ => %@", _controllerStack.lastObject, _updatedCallStack.lastObject);
    }
    previousStack = [[[NSMutableSet alloc] initWithArray:_controllerStack] mutableCopy];
    newStack = [[[NSMutableSet alloc] initWithArray:_updatedCallStack] mutableCopy];
    [previousStack minusSet:newStack];
    if (previousStack.count) { // pop
        NSLog(@"%@ <= %@", _updatedCallStack.lastObject, _controllerStack.lastObject);
    }
    
    if (newStack.count || previousStack.count) {
        self.controllerStack = [_updatedCallStack mutableCopy];
    }
    
    [_updatedCallStack removeAllObjects];
}

- (void)exploreViewController:(UIViewController *)viewController {
    [_updatedCallStack addObject:viewController];
    
    if ( [viewController isKindOfClass:[UITabBarController class]] ) {
        [self exploreViewController:[(UITabBarController *)viewController selectedViewController]];
    }
    else if ( [viewController isKindOfClass:[UINavigationController class]] ) {
        for ( UIViewController *childVC in ((UINavigationController *)viewController).viewControllers ) {
            [self exploreViewController:childVC];
        }
    } else if (viewController.presentedViewController) {
        [self exploreViewController:viewController.presentedViewController];
    }
}


@end


위의 클래스 사용방법 =>


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static SMon *sharedInstance;
    dispatch_once(&once, ^{
        
        sharedInstance = [[self alloc] init];
        
        // 화면 가로.세로 이벤트 받기
        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
        [[NSNotificationCenter defaultCenter] addObserver:sharedInstance
                                                 selector:@selector(didRotateDeviceChangeNotification:)
                                                     name:UIDeviceOrientationDidChangeNotification
                                                   object:nil];
        
        // 이벤트 받기
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            UIWindow *mainWindow = [[UIApplication sharedApplication] keyWindow];
            sharedInstance.eventHandler = [[MyEventHandler alloc] initWithFrame:mainWindow.bounds];
            sharedInstance.eventHandler.alpha = .2f;
            sharedInstance.eventHandler.tag = 100;
            sharedInstance.eventHandler.backgroundColor = [UIColor yellowColor];
            [mainWindow addSubview:sharedInstance.eventHandler];
            
            [sharedInstance runLoop];
        });
    });
    return sharedInstance;
}

......

- (void)runLoop {
    self.loopTimer = [NSTimer scheduledTimerWithTimeInterval: .5f
                                                  target: self
                                                selector:@selector(checkPeriodically:)
                                                userInfo: nil repeats:YES];
}

- (void)checkPeriodically:(NSTimer *)timer {
    [self makeEnablingEventMonitoring];
}

// 터치 이벤트 로그를 기록하는 뷰가 최상위에 위치하는지 체크
- (void)makeEnablingEventMonitoring {
    [self.eventHandler.superview bringSubviewToFront:self.eventHandler];
}

- (void)dealloc {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    
    [_loopTimer invalidate];
    _loopTimer=nil;
}

// 이벤트 핸들러 프레임 조정
-(void)didRotateDeviceChangeNotification:(NSNotification *)notification
{
    self.deviceOrientation =  [UIApplication sharedApplication].statusBarOrientation;
    UIWindow *mainWindow = [[UIApplication sharedApplication] keyWindow];
    [_eventHandler setFrame:mainWindow.bounds];

}


화면 출력 예제 : 

2017-06-20 15:23:12.631433+0900 S-MonitorTest[2305:527739] TouchEvent raised: 118397.829529, {90.666656494140625, 175.66665649414062} (view:UITableViewCellContentView-"(null)", controller:ViewController)
2017-06-20 15:23:13.505765+0900 S-MonitorTest[2305:527739] (null) => <UITestMainViewController: 0x12bd44e90>
2017-06-20 15:23:15.134760+0900 S-MonitorTest[2305:527739] TouchEvent raised: 118400.333632, {119.66665649414062, 265} (view:UITableViewCellContentView-"(null)", controller:UITestMainViewController)
2017-06-20 15:23:16.006614+0900 S-MonitorTest[2305:527739] <UITestMainViewController: 0x12bd44e90> => <UITest3ViewController: 0x12bd58e40>
2017-06-20 15:23:20.316494+0900 S-MonitorTest[2305:527739] TouchEvent raised: 118405.515406, {372.66665649414062, 65.666656494140625} (view:UIButton-"Close", controller:UITest3ViewController)
2017-06-20 15:23:21.197020+0900 S-MonitorTest[2305:527739] <UITestMainViewController: 0x12bd44e90> <= <UITest3ViewController: 0x12bd58e40>



댓글 없음:

댓글 쓰기