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>



2017년 1월 12일 목요일

비디오파일 duration 가져오기

- (NSString *)getVideoFileDuration:(NSString *)fileName {
    NSString *videoFilePath = [[self localVideoStorageDir] stringByAppendingPathComponent:fileName];
    NSURL *url = [NSURL fileURLWithPath:videoFilePath];
    AVURLAsset *sourceAsset = [AVURLAsset URLAssetWithURL:url options:nil];
    CMTime duration = sourceAsset.duration;
    NSInteger seconds = (int)CMTimeGetSeconds(duration);
    
    NSDate* date = [NSDate dateWithTimeIntervalSince1970:seconds];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
    if (seconds >= 360) {
        [dateFormatter setDateFormat:@"HH:mm:ss"];
    } else{
        [dateFormatter setDateFormat:@"mm:ss"];
    }
    NSString* result = [dateFormatter stringFromDate:date];
    return result;

}

이미지 파일의 정보 가져오기

- (IBAction)touchedImageInfo:(id)sender {
    
    NSData *imagedata = [NSData dataWithContentsOfFile:_fileUrl];
    CGImageSourceRef source = CGImageSourceCreateWithData((CFMutableDataRef)imagedata, NULL);
    CFDictionaryRef dictRef = CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
    NSDictionary* metadata = (__bridge NSDictionary *)dictRef;
    
    //NSLog(@"%@", metadata);
    CGFloat width = [metadata[@"PixelWidth"] floatValue];
    CGFloat height = [metadata[@"PixelHeight"] floatValue];
    
    NSMutableString *message = [[NSMutableString alloc] init];
    [message appendFormat:@"\n종류 : %@\n", [_fileUrl pathExtension]];
    [message appendFormat:@"크기 : %.01f KB\n", (float)imagedata.length/1024.f];
    [message appendFormat:@"해상도 : %d x %d\n", (int)width, (int)height];
    
    UIAlertController * alert = [UIAlertController
                                 alertControllerWithTitle:@"파일 정보"
                                 message:message
                                 preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction* yesButton = [UIAlertAction
                                actionWithTitle:@"확인"
                                style:UIAlertActionStyleDefault
                                handler:^(UIAlertAction * action) {
                                }];
    [alert addAction:yesButton];
    [[Util topViewController] presentViewController:alert animated:YES completion:^{
        CFRelease(source);
        CFRelease(dictRef);
    }];

}

겔러리에 이미지/비디오 저장 하기 over iOS 8

#import <Photos/Photos.h>


- (IBAction)touchedImageSave:(id)sender {
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:_imageView.image];
        changeRequest.creationDate          = [NSDate date];
    } completionHandler:^(BOOL success, NSError *error) {
        if (success) {
            [[Util sharedInstance] showToastMessage:@"저장되었습니다."];
        }
        else {
            [[Util sharedInstance] showToastMessage:[NSString stringWithFormat:@"저장에 실패했습니다. (%@)", error.localizedDescription]];
        }
    }];

}



- (IBAction)touchedVideoSave:(id)sender {
    
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^
     {
         NSURL *url = [NSURL URLWithString:_fileUrl];
         [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];
     }
                                      completionHandler:^(BOOL success, NSError *error)
     {
         if (success) {
             [[Util sharedInstance] showToastMessage:@"저장되었습니다."];
         }
         else {
             [[Util sharedInstance] showToastMessage:[NSString stringWithFormat:@"저장에 실패했습니다. (%@)", error.localizedDescription]];
         }
     }];
}

2017년 1월 8일 일요일

겔러리에서 모든 이미지 불러오기

-(void)getAllPhotos
{
    [[Util sharedInstance] showLoadingView];
    
    NSMutableArray *photos = [[NSMutableArray alloc] init];
    PHFetchOptions *allPhotosOptions = [[PHFetchOptions alloc] init];
    allPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
    PHFetchResult *allPhotos = [PHAsset fetchAssetsWithOptions:allPhotosOptions];
    PHFetchResult *fetchResult = @[allPhotos][0];
    
    for (int x = 0; x < fetchResult.count; x ++) {
        
        PHAsset *asset = fetchResult[x];
        photos[x] = asset;
    }
    
    self.assets = photos;
    
    [[Util sharedInstance] hideLoadingView];
    [_collectionView reloadData];

}


- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.selectedAssets = [[NSMutableArray alloc] init];
    self.assets = [[NSMutableArray alloc] init];
    
    self.manager = [PHImageManager defaultManager];
    self.options = [[PHImageRequestOptions alloc] init];
    _options.resizeMode = PHImageRequestOptionsResizeModeFast;
    _options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
    
    [self requestAuthorizationWithRedirectionToSettings];
    
    //[self performSelector:@selector(getAllPhotos) withObject:nil];

}


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    BoardPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"BoardPhotoCell" forIndexPath:indexPath];
    
    cell.checkIndicator.hidden = NO;
    PHAsset *asset = [_assets objectAtIndex:indexPath.item];
    [_manager requestImageForAsset:asset
                       targetSize:CGSizeMake(1024.f,1024.f)
                      contentMode:PHImageContentModeDefault
                          options:_options
                    resultHandler:^void(UIImage *image, NSDictionary *info) {
                        cell.photo.image = image;
                    }];
    
    //update checkbox
    NSUInteger foundIndex = [_selectedAssets indexOfObject:asset];
    if (foundIndex != NSNotFound) {
        [cell setCheck:YES];
    } else {
        [cell setCheck:NO];
    }
    
    return cell;

}

갤러리 권한 요청

- (void)requestAuthorizationWithRedirectionToSettings {
    dispatch_async(dispatch_get_main_queue(), ^{
        PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
        if (status == PHAuthorizationStatusAuthorized)
        {
            //We have permission. Do whatever is needed
        }
        else
        {
            //No permission. Trying to normally request it
            [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
                if (status != PHAuthorizationStatusAuthorized)
                {
                    //User don't give us permission. Showing alert with redirection to settings
                    //Getting description string from info.plist file
                    NSString *accessDescription = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSPhotoLibraryUsageDescription"];
                    UIAlertController * alertController = [UIAlertController alertControllerWithTitle:accessDescription message:@"To give permissions tap on 'Change Settings' button" preferredStyle:UIAlertControllerStyleAlert];

                    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
                    [alertController addAction:cancelAction];

                    UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:@"Change Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
                    }];
                    [alertController addAction:settingsAction];

                    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
                }
            }];
        }
    });
}

출처 : http://stackoverflow.com/questions/13572220/ask-permission-to-access-camera-roll