Translate/Parse

Parse 튜토리얼, 이미지 저장하기 2/2

KraZYeom 2013. 2. 7. 08:28
반응형

그리드 뷰 새로고침하기

이제 ViewController.m 에서, 새로고침 IBAction 메소드를 구현을 해야 한다.

우선, 새로고침을 위한 다른 HUD를 만든다. 그래서 이전에 업로딩을 위해서 사용한 다른 HUD를 방해하지 않는다.

refreshHUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:refreshHUD];
     
// HUD 콜백을 등록한다. 그래서 알맞은 시간에 윈도우로 부터 제거할 수 있다.
refreshHUD.delegate = self;
     
// 새로운 스레드에서 메소드를 실행하는 동안 HUD를 보여준다.
[refreshHUD show:YES];

다운로드 쿼리 생성하기

대량의 코드가 있다. 한 단계, 한 단계씩 진행하겠다. 

이미지를 다운로드하려면 PFQuery 를 사용 해야한다. 쿼리는 Parse에서 검색 범위를 좁힐 수 있는 조건으로 객체를 찾을 때 사용된다. 

업로드 한 모든 이미지를 추출하기 위해서  이전에 설정한 클래스 이름에 해당하는 PFQuery 를 만들어야 한다. 또한, 검색 결과를 현재 속해 있는 사용자로 제한해야한다. 

쿼리가 생성되면, 결과를 findObjectsInBackgroundWithBlock: 메소드로 검색할 수 있다. 

- (void)downloadAllImages
{
    PFQuery *query = [PFQuery queryWithClassName:@"UserPhoto"];
    PFUser *user = [PFUser currentUser];
    [query whereKey:@"user" equalTo:user];
    [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
    // 만약 사진이 있으면 데이터를 추출한다.
    // 데이터를 추출하는 동안, 객체 ID의 목록을 저장한다.
         
    NSMutableArray *newObjectIDArray = [NSMutableArray array];
    NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
         
    if (objects.count > 0) {
        for (PFObject *eachObject in objects) {
            [newObjectIDArray addObject:[eachObject objectId]];
        }
    }

다운로드를 빠르게 하기위해, NSUserDefaults에서 각 사진의 objectID 를 저장할 것이다. 쿼리 결과가 반환 될 때, 새로운 objectID 가 있는 사진만 다운로드 한다. 

누락된 objectID는 photoScrollView 에서 그것들을 삭제 할 것이다. 데이터베이스에서 항목이 제거되는 경우 발생한다. 예를 들어 Parse의 웹 인터페이스에서 제거 될 때. 삭제하기 위해, 각각 버튼(나중에 설정방법을 보여준다.)의 태그를 확인한다.

// 지난 객체 ID와 새로운 객체 ID를 비교한다.
NSMutableArray *newCompareObjectIDArray = [NSMutableArray arrayWithArray:newObjectIDArray];
NSMutableArray *newCompareObjectIDArray2 = [NSMutableArray arrayWithArray:newObjectIDArray];
NSMutableArray *oldCompareObjectIDArray = [NSMutableArray arrayWithArray:[standardUserDefaults objectForKey:@"objectIDArray"]];
if ([standardUserDefaults objectForKey:@"objectIDArray"]){
    [newCompareObjectIDArray removeObjectsInArray:oldCompareObjectIDArray]; // New objects
             
    // 웹 브라우저를 사용하여 지난 객체를 삭제하는 경우 지난 객체를 삭제한다.
    [oldCompareObjectIDArray removeObjectsInArray:newCompareObjectIDArray2];
    if (oldCompareObjectIDArray.count > 0){
        // objectID 배열에서 위치를 확인하고 삭제한다.
        NSMutableArray *listOfToRemove = [[NSMutableArray alloc] init];
        for (NSString *objectID in oldCompareObjectIDArray){
            int i = 0;
            for (NSString *oldObjectID in [standardUserDefaults objectForKey:@"objectIDArray"]){
                if ([objectID isEqualToString:oldObjectID]){
                    // 삭제한다.
                    for (UIView *view in [photoScrollView subviews]) {
                        if ([view isKindOfClass:[UIButton class]]) {
                            if (view.tag == i){
                                [view removeFromSuperview];
                                NSLog(@"Removing picture at position %i",i);
                            }
                        }
                    }
                             
                    // 삭제하고 싶은 모든 목록을 만들고 마지막에 삭제한다.
                    [listOfToRemove addObject:[NSNumber numberWithInt:i]];
                }
                i++;
            }
        }

이미지가 지나치지 않도록 뒤에서 부터 다시 제거한다.

// 뒤에서 부터 제거
NSSortDescriptor *highestToLowest = [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:NO];
[listOfToRemove sortUsingDescriptors:[NSArray arrayWithObject:highestToLowest]];
                 
for (NSNumber *index in listOfToRemove){                       
    [allImages removeObjectAtIndex:[index intValue]];
    [self setUpImages:allImages];    
}

누락된 객체 제거 외에, 또한 새로운 객체를 추가해야한다. 거꾸로 변환하는 것은 쉽다. 결과에서 각각 PFObject 를 통하여 NSData 로 변환하게 getData 를 호출한다. 다음 로드에 다시 로드 할 수 있도록 NSUserDefautls에 objectIDArrary를 저장하는 것을 잊지마라. 

// 이 메소드는 그리드에서 다운로드된 이미지를 설정하고 배치시킨다.
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSMutableArray *imageDataArray = [NSMutableArray array];
         
        // 모든 이미지를 반복해서 PFFile에서 데이터를 얻는다.
        for (int i = 0; i < images.count; i++) {
            PFObject *eachObject = [images objectAtIndex:i];
            PFFile *theImage = [eachObject objectForKey:@"imageFile"];
            NSData *imageData = [theImage getData];
            UIImage *image = [UIImage imageWithData:imageData];
            [imageDataArray addObject:image];
        }
                    
        // UI 업데이트를 위해 메인 쓰레드에 디스페치.
        dispatch_async(dispatch_get_main_queue(), ^{
            // 지난 그리드 삭제
            for (UIView *view in [photoScrollView subviews]) {
                if ([view isKindOfClass:[UIButton class]]) {
                    [view removeFromSuperview];
                }
            }
             
            // 그리드에서 각 이미지에 필요한 버튼 만들기
            for (int i = 0; i < [imageDataArray count]; i++) {
                PFObject *eachObject = [images objectAtIndex:i];
                UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
                UIImage *image = [imageDataArray objectAtIndex:i];
                [button setImage:image forState:UIControlStateNormal];
                button.showsTouchWhenHighlighted = YES;
                [button addTarget:self action:@selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
                button.tag = i;
                button.frame = CGRectMake(THUMBNAIL_WIDTH * (i % THUMBNAIL_COLS) + PADDING * (i % THUMBNAIL_COLS) + PADDING,
                                          THUMBNAIL_HEIGHT * (i / THUMBNAIL_COLS) + PADDING * (i / THUMBNAIL_COLS) + PADDING + PADDING_TOP,
                                          THUMBNAIL_WIDTH,
                                          THUMBNAIL_HEIGHT);
                button.imageView.contentMode = UIViewContentModeScaleAspectFill;
                [button setTitle:[eachObject objectId] forState:UIControlStateReserved];
                [photoScrollView addSubview:button];
            }
             
            // 그리드에 따른 크기
            int rows = images.count / THUMBNAIL_COLS;
            if (((float)images.count / THUMBNAIL_COLS) - rows != 0) {
                rows++;
            }
            int height = THUMBNAIL_HEIGHT * rows + PADDING * rows + PADDING + PADDING_TOP;
             
            photoScrollView.contentSize = CGSizeMake(self.view.frame.size.width, height);
            photoScrollView.clipsToBounds = YES;
        });
    });

이번 상황에서는 Grand Central Dispatch를 사용하여 getData 를 처리 할 것이다. 이렇게하려면 디스페치 큐를 생성하고 비동기적으로 호출을 수행한다. 완료되면, 전체 photoArray 에 추가한다. 그리고 메인 쓰레드에서 이미지를 깔끔하게 정리하기 위해 setUpImages 를 호출한다. 


그리드 설정하기

setUpImages 메소드에서 이미지가 저장되는 것을 보장하기 위해 이미지를 allImages로 복사한다. 그 후에, 모든 현재 버튼을 삭제하고 새로운 것을 배치시킨다. 


allImages = [images mutableCopy];
     
// 지난 그리드 삭제
for (UIView *view in [photoScrollView subviews]) {
    if ([view isKindOfClass:[UIButton class]]) {
        [view removeFromSuperview];
    }
}

각각의 이미지를 위해서 UIButton 을 만들어서 그리드에 배치시킨다. 나중에 UIButton 을 탭을 했을 때 참조하기 위해 각각의 UIButton 에 태그한다. 또한, 각각의 UIButton 에 이미지를 보여주는 디테일 뷰 컨트롤러를 여는 타겟 메소드를 연결한다.

마지막으로 photoScrollView 에 알맞은 contentSize 를 계산하고 clipsToBounds 에 YES로 설정한다. 그리드에 올바르게 설정한다. 이미지 사이의 패딩값은 4px이다. 썸네일 크기는 75px * 75px이다. 그리고 4개의 컬럼으로 되어있다.

// 이 메소드는 다운로드된 이미지를 설정하고 그것들을 그리드에 알맞게 배치시킨다
UIButton *button;
UIImage *thumbnail;
 
//각각 이미지를 위해 버튼을 생성
for (int i=0; i<images.count; i++) {
    thumbnail = [images objectAtIndex:i];      
    button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setImage:thumbnail forState:UIControlStateNormal];
    button.showsTouchWhenHighlighted = YES;
    [button addTarget:self action:@selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
    button.tag = i;
    button.frame = CGRectMake(THUMBNAIL_WIDTH * (i % THUMBNAIL_COLS) + PADDING * (i % THUMBNAIL_COLS) + PADDING, THUMBNAIL_HEIGHT * (i / THUMBNAIL_COLS) + PADDING * (i / THUMBNAIL_COLS) + PADDING + PADDING_TOP, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
    button.imageView.contentMode = UIViewContentModeScaleAspectFill;
    [photoScrollView addSubview:button];
}
     
int rows = images.count / THUMBNAIL_COLS;
if (((float)images.count / THUMBNAIL_COLS) - rows != 0) {
    rows++;
}
 
int height = THUMBNAIL_HEIGHT * rows + PADDING * rows + PADDING + PADDING_TOP;
     
photoScrollView.contentSize = CGSizeMake(self.view.frame.size.width, height);
photoScrollView.clipsToBounds = YES;

디테일 뷰 컨트롤러 열기

별도의 모달 뷰 컨트롤러에 자세한 사진을 열 수 있다. 여기 할수 있는 방법이 있다.

우선 새로운 UIViewController (File > New > New File > UIViewController 서브클래스)를 PhotoDetailViewController 으로 생성한다. 완료되면, nib을 설정하고 ViewController.m 의 상단에 “PhotoDetailViewController.h”를 import 한다.

UINavigationBarUIBarButtonItem 그리고 자세한 이미지를 보여주는 UIImageView 을 포함하는 nib는 다음과 비슷할 것이다.


UIButton에 해당하는 태그가 있기 때문에, 이전에 저장한 allImages 배열에서 정확한 사진을 얻을 수 있다. 이 이미지로 새로운 디테일 뷰 컨트롤러에 전달하고 컨트롤러를 제시한다. 

- (void)buttonTouched:(id)sender{
    //사진이 터치되었을 때, 이미지를 가지는 뷰 컨트롤러를 연다.
    UIImage *selectedPhoto = [allImages objectAtIndex:[sender tag]];
     
    PhotoDetailViewController *pdvc = [[PhotoDetailViewController alloc] init];
     
    pdvc.selectedImage = selectedPhoto;
    [self presentModalViewController:pdvc animated:YES];
}

이 튜토리얼에서 사진을 PFObjects 로 파일을 랩핑하여 클라우드로 업로드하는 것, PFQueries 를 사용해서 사진을 다운로드 하는 것, 그리드에 사진을 배치하는 것을 다루었다. 또한, 새로운 이미지를 다운로드하고 누락된 것을 제거하는 간단한 캐싱을 했다. 뿐만 아니라 디테일 뷰 컨트롤러 생성도 하였다. 

반응형