달력

9

« 2019/9 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  •  
  •  
  •  
  •  
  •  

iOS9의 새로운 피쳐들 중에 비교적 쉽게 만들어 볼 수 있고.. 또 지금 하고 있는 프로젝트에도 적용해볼만한 기능이라 한번 끄적여봤다..

우선 결론부터 보자면 이렇다..





검색 화면에서 ㅇ 이라고 한글자 눌렀더니 내 Path 피드에 있던 데이터들 중에 ㅇ 들어간 녀석들을 찾아준다.. 검색된 것을 누르면 해당 모멘트(Path에서는 모멘트라고 부름 ㅎ)의 상세 화면으로 이동하는 것은 물론이다..


여기서 검색을 한다고 Path 서버를 뒤져서 데이터를 긁어오는 것은 당연히 아니고 앱에서 어떤 방법으로 어떤 데이터들을 가져오게할 것인지 코딩을 해주면 된다.. 처음 하는 거라서 약간 삽질을 하느라 시간이 좀 걸렸는데 한번 해보면 코드 자체는 간단하고 어떻게 어떤 데이터들을 검색하게 할 것인지에 대한 고민이 더 많을 것 같다..


일단 검색할 데이터를 만들어보자.. 우선


#import <CoreSpotlight/CoreSpotlight.h>
#import <MobileCoreServices/MobileCoreServices.h>

요거 두개가 필요하다..



        
    NSMutableArray *aItems = [NSMutableArray arrayWithCapacity:[[m_resultsController fetchedObjects] count]];
    
    for (PTPost *post in [m_resultsController fetchedObjects])
    {
        PTPostType postType = [post postType];
        if (postType == PTPostTypePhoto || postType == PTPostTypeVideo)
        {
            CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeImage];
            if (post.title == nil)
                attributeSet.title = @"Path feed";
            else
                attributeSet.title = post.title;

            NSArray *aComments = [post sortedComments];
            if (aComments != nil && aComments.count > 0)
            {
                PTComment *comment = [post sortedComments][0];
                attributeSet.contentDescription = [comment text];
            }
            else
                continue;
            
            attributeSet.thumbnailData = [NSData dataWithContentsOfFile:post.photoFilePath];
            
            CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:post.id domainIdentifier:itemDomain attributeSet:attributeSet];
            [aItems addObject:item];

        }
    }

    if (aItems.count > 0)
    {
        [[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:aItems completionHandler: ^(NSError * __nullable error) {
            
            if(error != nil)
            {
                NSLog(@"indexSearchableItems error : %@", error);
            }
        }];
    }
    else
        NSLog(@"aItems count is 0");



피드 데이터가 업데이트가 되면 요 메쏘드를 한번 호출해주면 된다..

하나의 모멘트에 대해서 CSSearchableItemAttributeSet 객체와 CSSearchableItem 객체 하나씩 필요한것 같은데.. 왜 이걸 굳이 두개로 나누었을까 싶기도 한데.. 여튼.. 각 모멘트 데이터(위 코드에서는 PTPost 객체가 되시겠다)에서 제목을 가지고 오고(Path의 모멘트는 title을 쓰는 경우가 없어서 그냥 Path feed라고 나온다;;) contentDescription에 검색이 될 텍스트를 넣어주면 된다.. 그리고 검색 결과에 사용될 이미지를 thumbnailData에 넣어주면 되는데 지금 생각을 해보니 file URL을 thumbnailURL에 넣어줘도 될것 같은 필이 느껴지네.. -_-;;


이렇게 CSSearchableItemAttributeSet 객체에 셋팅을 해주고 이걸 기준으로 CSSearchableItem 객체를 만들어서 어레이를 만들고 그걸 넣어주면 끝.. 앱 설치하고 데이터 읽어와서 넣어준 후에 iOS 검색 화면을 들어가서 검색어를 입력하면 첨부한 화면처럼 검색이 된다.. 그런데 이게 시뮬레이터라 검색 결과가 상단에 나오는데 실제 폰에 올려서 확인을 해보니 SMS 내용, 메모 내용, 메일, 주소록 등등 지들이 만든게 상단에 나오고 개별 앱의 결과는 저 밑에 나오더만.. -_-;; 그래서 이걸 사람들이 많이 쓸까 살짝 걱정이 좀 되더만;;


참.. CSSearchableItem 객체를 만들 때, 넣어주는 post.id 요게 실제 검색 결과에서 선택된 항목으로부터 앱이 건네 받게 되는 스트링이고 이걸 받아서 해당 모멘트 데이터를 찾아와서 그 모멘트의 상세 페이지로 이동을 시키게 된다.. 그리고 itemDomain이라고 되어있는건 앱을 구분하는 유니크한 스트링인데 com.회사이름.어쩌고 뭐 이렇게 써주면 될듯 싶다.. 



자.. 그러면 이제 데이터는 검색 창쪽으로 보내줬으니 그쪽에서 해당 모멘트의 id를 받아와서 상세페이지로 이동하는 방법을 보자.. 요건 참 쉽다.. app delegate에다가 요거 하나 추가해주면 된다..



        
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler
{
    NSString *identifier = userActivity.userInfo[CSSearchableItemActivityIdentifier];
    NSString *strMomentID = identifier;

    PTPost* post = [PTPost fetchWithID:strMomentID];
    
    if (strMomentID.length == 0 || post == nil)
        return NO;

    // go to detail page
    
    return YES;
}


뭐.. 코드만 봐도 이해가 될 듯..


근데 이거 앱들이 서로 자기들꺼 밀어 넣기 시작하면 왠지 난장판이 될 것도 같아서 좀 걱정은 된다.. -_-;; 속도 문제도 있을 것이고.. 우리 앱이 저 아래로 밀리면 곤란한데.. 왠지 애플이니까 자주 선택된 앱들을 위쪽에 노출을 해줄 수도 있을 것 같은데.. 아직 이 기능을 제공하는 앱이 애플앱들 밖에 없다보니 어떻게 될지 잘 모르겠네;;


참.. 하나 빠진게있구나.. CSSearchableItemAttributeSet 요거 멤버 중에 keywords라는 것이 있는데.. 예를 들어 @[@"path", @"패스"] 요렇게 값을 넣어줬다면 iOS 검색 화면에서 'path 울랄라' 혹은 '패스 울랄라' 요렇게 입력하면 path(혹은 패스)라는 키워드를 넣은 앱들에서만 검색을 해주는데.. 이렇게 정성들여 검색할 유저가 과연 있을까 싶어서 예제에는 keywords는 사용하지 않았다.. (나라도 안쓰겠다 -_-;; 그리고 다른 앱에서 path라고 키워드 입력하면 그 앱도 검색할텐데.. 뭐야 이게.. -_-;; 아님 다른 뭔가가 더 있는건가.. -_-a)


Posted by 도노보노

테이블 뷰에 editing을 yes로 주고 드래그 앤 드랍으로 셀 위치를 변경해줘야 할 필요가 생겼다.. 여기까지는 흔히 있는 일인데.. 문제는 첫 셀은 움직여서는 안된다는 조건..


        
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == 0)
        return NO;
    
    return YES;
}


요걸 사용해서 우선 첫 셀 오른쪽에는 드래그 앤 드롭용 가로줄3개가 안나오도록 했고 움직일 수 없도록 조치를 했는데.. 다른 셀을 끌어와서 첫 셀 자리로 들고오면 첫 셀이 밀리는 문제가 발생!!


이런 경우는 처음이라 어떻게 해야 하나 싶었는데 옆자리에 있던 고마우신 분이 찾아주심.. ㅎㅎ


  
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
    if (proposedDestinationIndexPath.row == 0)
        return sourceIndexPath;
    
    return proposedDestinationIndexPath;
}


움직이지 않게 하고 싶은 셀의 indexPath를 체크해서 그런 경우에는 sourceIndexPath를 리턴해주면 해당 셀은 드래그 앤 드롭에서 제외돼서 꼼짝도 안하게 된다.. 그 외의 경우는 proposedDestinationIndexPath를 리턴해주면 끝!


참 쉽죠..


이렇게 오늘도 하나 배워가는 구나.. ㅎㅎ

퇴근해야지.. ㄷㄷㄷ

Posted by 도노보노

시작은 iOS7까지는 잘 되던게 iOS8로 올라오면서 CameraW에 제대로 동작을 안하는게 발견이 되어서였다.. 정확하게 얘기를 하면 iOS8의 문제는 아니었고 아이폰 6+의 문제였다.. 스프링보드에서 가로모드를 지원하면서 생긴 버그(?)가 아닐까 생각된다.. (애플느님아.. 우리 잘 좀 하자구요.. 응?)


우선 CameraW는 상태바가 없고 UIDeviceOrientationLandscapeLeft로 고정이 되어있는 형태다.. 단말이 돌아갈때 프리뷰 화면까지 덩달아 돌아가게 되면 무척이나 보기가 싫기 때문에 요렇게 구현이 되어있다.. 뭐 대부분의 카메라 앱들도 다 비슷할 듯.. 그래도 단말이 돌아갔다는 사실은 알아야 하기 때문에..


        
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];


요걸 사용해서 디바이스가 돌아간걸 체크하고 버튼들도 돌려주고 이미지를 저장할 때 오리엔테이션 값을 알아낸다..


그런데 발견된 문제가 앱을 실행한 직후의 오리엔테이션 값.. 세로로 실행을 해도 UIDeviceOrientationLandscapeLeft, 가로로 실행을 해도 UIDeviceOrientationLandscapeLeft, 뒤집어서 실행을 해도 UIDeviceOrientationLandscapeLeft.. -_-;; 그래서 실행하자마자 사진을 찍으면 항상 일정한 방향으로 잘 못찍히는 문제가 발견이 된 것이다..

구글링을 해보니 디바이스 오리엔테이션 말고 스테이터스 바의 오리엔테이션으로 체크를 해보라는 말들이 많아서 그걸로 해봤지만 스테이터스바를 안써서 그런지 아님 역시 같은 이유인지 여전히 UIDeviceOrientationLandscapeLeft로만 시작했다..


그래서 구글링을 더 해봤는데 뾰족한 수가 발견이 되지는 않았다.. 아이폰 6+만 발생하는 문제고 뭐든 한번 방향을 바꾸고나면 그 다음부터는 잘 되니까 그냥 묻을까, 아니 어떻게든 해결을 해야지!! 이 두가지 선택의 길에서 잠시 고민을 하던 중에..

CameraW 처음부터 가지고 있던 문제 하나가 로테이션 락이 걸린걸 어떻게 알아내는지 방법을 몰라서 락이 걸린 상태에서 사진을 찍으면 항상 가로로만 사진이 찍히는 현상이 있었는데, 이왕 디바이스 오리엔테이션에 대한 문제를 해결해야지 마음을 먹은 김에 그것도 찾아보기로 했다.. 전에 한번 찾다가 못찾았었는데 이번에는 의외로 아주 쉽게 그 해답을 찾았다.. (전엔 뭐한거야 대체 -_-;;) 설정된 값을 직접 읽어오는 방법은 여전히 못찾았지만 core motion과 액셀러레이터를 사용해서 디바이스의 오리엔테이션을 찾는 방법을 발견했다.. 소스부터 보면..


        
- (void)initCoreMotion
{
    _motionManager2 = [[CMMotionManager alloc] init];
    _motionManager2.accelerometerUpdateInterval = .2;
    _motionManager2.gyroUpdateInterval = .2;
    
    [_motionManager2 startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
                                        withHandler:^(CMAccelerometerData  *accelerometerData, NSError *error) {
                                            if (!error) {
                                                [self outputAccelertionData:accelerometerData.acceleration];
                                            }
                                            else{
                                                NSLog(@"%@", error);
                                            }
                                        }];
}

- (void)outputAccelertionData:(CMAcceleration)acceleration
{
    UIInterfaceOrientation orientationNew;
    
    if (acceleration.x >= 0.75) {
        orientationNew = UIInterfaceOrientationLandscapeLeft;
    }
    else if (acceleration.x <= -0.75) {
        orientationNew = UIInterfaceOrientationLandscapeRight;
    }
    else if (acceleration.y <= -0.75) {
        orientationNew = UIInterfaceOrientationPortrait;
    }
    else if (acceleration.y >= 0.75) {
        orientationNew = UIInterfaceOrientationPortraitUpsideDown;
    }
    else {
        // Consider same as last time
        return;
    }
    
    if (orientationNew == _orientationLast)
        return;
    
    _orientationLast = orientationNew;
    [self rotateViewForOrientation:_orientationLast];
}


어떤 양덕 형님이 친절하게 코드 만들어 주신거 그대로 가져다 쓰니까 잘 된다.. 그리고 이걸 사용을 하니 처음 얘기했던 앱을 딱 실행했을 때 오리엔테이션 값을 잘못 가져오는 일도 덩달아 해결이 됐다.. 므하하하하..


한번에 두마리 토끼를 잡고나니 기분이 매우 좋아져서 이렇게 간만에 블로깅도 한번.. ㅎㅎ


참잘했어요


오늘까지 테스트 좀더 해보고 섭밋 해야쥐..

Posted by 도노보노