- (void)resizeImageAtPath:(NSString *)imagePath {

    // Create the image source (from path)
    CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) [NSURL fileURLWithPath:imagePath], NULL);

    // To create image source from UIImage, use this
    // NSData* pngData =  UIImagePNGRepresentation(image);
    // CGImageSourceRef src = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);

    // Create thumbnail options
    CFDictionaryRef options = (__bridge CFDictionaryRef) @{
            (id) kCGImageSourceCreateThumbnailWithTransform : @YES,
            (id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
            (id) kCGImageSourceThumbnailMaxPixelSize : @(640)
    };
    // Generate the thumbnail
    CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options); 
    CFRelease(src);
    // Write the thumbnail at path
    CGImageWriteToFile(thumbnail, imagePath);
}


대용량 이미지를 resize를 할 경우 메모리에 모든 UIImage객체를 올린 뒤 draw하는 방식을 사용하게 되면 resize를 할 경우 메모리 문제에 종종 생기게 된다


파일로 저장된 대용량 이미지를 파일 상태에서 바로 resize한 후 저장하는 코드를 사용할 경우 메모리 문제를 해결 할 수 있다

objective C에서 block을 파라메터를 넘길 때 해당 block을 property에 저장해서 필요할 때 호출하기 위해 copy를 하게 된다.

copy를 하게 되면 블럭안에 사용되는 객체의 레퍼런스 카운트가 증가하게 된다.


@interface TestDummy 
@property (nonatomic, copy) void(^testBlock);
@end

....

[self.dummy testBlock:^ {

      [self complete];

}]; // dummy 인스턴스 내부에 block을 저장하기 위해 block을 copy하여 저장함


이 때 블럭안에 사용되는 self도 dummy객체의 copy 프로퍼티에 의해 참조하게 되여 순환참조 상황이 발생하게 된다.

이 순환참조를 방지하기 위해 weak 레퍼런스를 사용하여 self자체의 레퍼런스카운트 증가를 방지하게 된다



@interface TestDummy 
@property (nonatomic, copy) void(^testBlock);
@end

....

__weak __typeof(self)weakSelf = self;

[self.dummy testBlock:^ {

__strong __typeof(weakSelf)self = weakSelf;

      [self complete];

}]; // dummy 인스턴스 내부에 block을 저장하기 위해 block을 copy하여 저장함


하지만 위 케이스에서도 예외사항이 발생되는데 만약 호출한 클래스의 멤버변수를 block안에 실행하게 되면 다시 순환 참조가 발생하게 된다.


@interface TestDummy 
@property (nonatomic, copy) void(^testBlock);
@end

....

@interface TestOwner
@property (nonatomic, strong) NSArray *list;
@end

....

__weak __typeof(self)weakSelf = self;

[self.dummy testBlock:^ {

__strong __typeof(weakSelf)self = weakSelf;


      [self complete];

     [ _list removeAllObject]; // <-------- self를 사용안하고 멤버변수에 바로 접근하여 순한참조 발생함

}]; // dummy 인스턴스 내부에 block을 저장하기 위해 block을 copy하여 저장함


위 경우 잠재적인 메모리릭 상황이 발생하기 때문에 block안에 멤버변수를 사용하는 것은 주의가 필요하다



@interface TestDummy 
@property (nonatomic, copy) void(^testBlock);
@end

....

@interface TestOwner
@property (nonatomic, strong) NSArray *list;
@end

....

__weak __typeof(self)weakSelf = self;

[self.dummy testBlock:^ {

__strong __typeof(weakSelf)self = weakSelf;


      [self complete];

     [ self.list removeAllObject]; // <-------- 멤버변수에서 프로퍼티 접근으로 변경

}]; // dummy 인스턴스 내부에 block을 저장하기 위해 block을 copy하여 저장함



Html태그에 대해 찾아보다가 아래와 같은 사용방법을 찾았다.

테스트를 해봐야겠다..


let htmlString = "LCD Soundsystem was the musical project of producer <a href='http://www.last.fm/music/James+Murphy' class='bbcode_artist'>James Murphy</a>, co-founder of <a href='http://www.last.fm/tag/dance-punk' class='bbcode_tag' rel='tag'>dance-punk</a> label <a href='http://www.last.fm/label/DFA' class='bbcode_label'>DFA</a> Records. Formed in 2001 in New York City, New York, United States, the music of LCD Soundsystem can also be described as a mix of <a href='http://www.last.fm/tag/alternative%20dance' class='bbcode_tag' rel='tag'>alternative dance</a> and <a href='http://www.last.fm/tag/post%20punk' class='bbcode_tag' rel='tag'>post punk</a>, along with elements of <a href='http://www.last.fm/tag/disco' class='bbcode_tag' rel='tag'>disco</a> and other styles. <br />"


let htmlStringData = htmlString.dataUsingEncoding(NSUTF8StringEncoding)!

let options: [String: AnyObject] = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding]

let attributedHTMLString = try! NSAttributedString(data: htmlStringData, options: options, documentAttributes: nil)

let string = attributedHTMLString.string


출처 : http://stackoverflow.com/questions/25983558/stripping-out-html-tags-from-a-string

NSURL객체의 NSURLString 메소드를 자주 사용한다 (문자열을 URL객체로 변환하기 위해서)

하지만 URL문자열 중 '|' 문자열이 포함되어 있으면 올바르지 않은 문자로 간주하여 nil이 반환된다.

참조 링크 : http://stackoverflow.com/questions/3040632/how-to-make-an-nsurl-that-contains-a-pipe-character


하지만 무조건 URL을 encoding을 하게 된다면 발생 할 수 있는 이슈는 만약 URL의 파라메터 중 일부가 encoding이 포함 된 경우 (key = encoding(value))  이중으로 인코딩이 발생 될 수 있는 문제가 있다.

물론 웹서비스에서 이중 인코딩에 대한 처리가 잘되어있으면 올바르게 동작하겠지만 그렇지 않을 경우 예외 상황도 고려해야 한다.


재미있는 건 각 클라이언트 마다 이 '|'문자열이 포함된 URL을 처리하는 방법이 다른 경우을 발견하였다.


라인 - Mac (링크, OG Tag 정상노출)

라인 - iOS (링크, OG Tag 불가)


라인 - Android (링크 가능, OG Tag 미노출)




테이블뷰나 콜렉션뷰를 사용하는 스토리보드의 viewController의 segue호출 방법에는 두가지가 있다.


1. 각 뷰의 Cell event에 Segue를 연결하여 스토리보드에서 호출하는 방법

  • 특징 :  segue를 호출시 userInfo값이 기본이 nil이기 때문에  prepareForSegue: 가 호출 될 시점에 호출되는 ViewController의 파라메터를 직접 대입해주어야 한다. 
  • 선택된 아이템에 대한 정보를 기억하고 있다가 prepareForSegue 시점에 대입해야함
  • Cell에 의해 호출되는 Segue가 많지 않을 경우 유용함

[Cell에 Segue를 연결시 Cell이 하이라이트되어 있다]



2. Delegate를 통해 select 되었을 경우 menual로 등록된 Segue를 performSegue로 호출하는 방법

  •  특징 : DidSelect된 시점에 아이템에 대한 정보를 바로 알 수 있기 때문에 sender로 호출되는 시점의 아이템을 전달할 수 있다.
  •  prepareForSegue가 호출되는 시점에서 파라메터로 전달 받은 sender를 그대로 파라메터로 넘겨 따로 선택된 아이템에 대한 정보를 관리할 필요가 없다.
  •  Cell에 의해 호출되는 Segue가 많을 경우 유용함


[ViewControll에 menual로 Segue를 연결시 전체가 하이라이트되어 있다]


segueTest.zip





UIActivityIndicatorView 사용시 주의점

  • UITableViewCell에서 UIActivityIndicatorView를 사용할 경우
  • UITableView에서 reloadData등에 의해서 dequeueReusableCellWithIdentifier에 의해 Cell이 reuse될 경우
  • Cell안의 모든 animation은 종료가 발생된다. (UIActivityIndicatorView의 애니메이션 효과도 종료)
  • 이 경우를 방지하기 위해 UITableViewCell의 prepareForReuse에서 reuse될 경우 애니메이션등을 다시 동작하도록한다.


참고 : http://stackoverflow.com/questions/28737772/uiactivityindicatorview-stops-animating-in-uitableviewcell

    self.edgesForExtendedLayout = UIRectEdgeTop;

    self.navigationController.navigationBar.barStyle = UIBarStyleBlack;

    self.navigationController.navigationBar.translucent = YES;

    self.navigationController.navigationBarHidden = YES;

    self.automaticallyAdjustsScrollViewInsets = NO;

 속성

자료형

기본값

버전3

설명 

 NSFontAttributeName

 UIFont

 Helvetica 12p

6.0

 서체 종류와 크기

 NSParagraphStyleAttributeName

 NSParagraphStyle 

 defaultParagraphStyle

6.0

 문장 스타일

 (줄간4문단간5, 정렬, 들여쓰기, 쓰기 방향 등에 대한 정보)

 NSLigatureAttributeName

 NSNumber (integer)

 1 (ligature)

6.0 

 합자6 여부

 NSKernAttributeName

 NSNumber (float)

 Depending on the font

6.0

 자간7

 NSForegroundColorAttributeName

 UIColor

 Black Color

6.0

 글씨 색

 NSBackgroundColorAttributeName

 UIColor

 nil (no background)

6.0

 글씨 배경색

 NSUnderlineStyleAttributeName

 NSNumber (integer)

 0 (no underline)

6.0

 밑줄 스타일

 (None, Single, Thick, Double, Word 등의 스타일과 Solid, Dot, Dash, Dash Dot, Dash Dot Dot 등의 채우기 스타일이 있다)

 NSUnderlineColorAttributeName

 UIColor

 nil (same foreground)

7.0

 밑줄 색

 NSStrokeWidthAttributeName

 NSNumber (float)

 0 (no stroke)

6.0

 글자 테두리 두께

 NSStrokeColorAttributeName

 UIColor

 nil (same foreground)

6.0

 글자 테두리 색

 NSStrikethroughStyleAttributeName

 NSNumber (integer)

 0 (no strikethrough)

6.0

 취소선 스타일

 NSStrikethroughColorAttributeName

 UIColor

 nil (same foreground)

7.0

 취소선 색

 NSShadowAttributeName

 NSShadow

 nil (no shadow)

6.0

 글자 그림자 스타일

 (그림자 위치, 색, 블러효과 등 스타일)

 NSTextEffectAttributeName

 NSString

 nil (no text effect)

7.0

 글자 이펙트

 (미리 알림 앱에서 사용되는 글자 효과. 현재는 Letterpress 스타일 밖에 없다)

 NSBaselineOffsetAttributeName

 NSNumber (float)

 0

7.0

 글자 기준선8

 NSObliquenessAttributeName

 NSNumber (float)

 0 (no skew)

7.0

 글자 기울기 방향(이텔릭체와 흡사)

 NSExpansionAttributeName

 NSNumber (float)

 0 (no expansion)

7.0

 글자 늘리기(균등 정렬과 흡사)

 NSLinkAttributeName

 NSURL or NSString

 nil

7.0

 링크

sudo mkdir -p /usr/local/git
sudo ln -s /Applications/SourceTree.app/Contents/Resources/git_local/share /usr/local/git/share
sudo ln -s /Applications/Xcode.app/Contents/Developer/Library/Perl/5.16/darwin-thread-multi-2level/SVN /System/Library/Perl/Extras/5.16/SVN
sudo ln -s /Applications/Xcode.app/Contents/Developer/Library/Perl/5.16/darwin-thread-multi-2level/auto/SVN/ /System/Library/Perl/Extras/5.16/auto/SVN


http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i

콜렉션뷰의 레이아웃을 변경하는데 자꾸 헤더가 존재할 경우 NSArray 예외상황이 발생되어 앱이 죽는다

그 이유를 찾아봤더니 콜렉션뷰를 reload하고 바로 레이아웃을 변경한 부분이 원인이였다

즉 reloadData를 실행하기 전에 layout을 변경해야함


1. UICollectionView 구조

1) UICollectionView

- 테이블뷰 처럼 Cell을 재활용하여 Layout의 규칙에 맞게 Cell을 배치한다

2) UICollectionReusableView

- 콜렉션뷰에서 사용되는 뷰의 슈퍼 클래스, Header, Footer, Cell등의 상위클래스이다

3) UICollectionViewCell

- 콜렉션뷰에서 사용되는 Cell의 객체, 테이블뷰의 Cell의 단점을 보완하며 구조가 많이 간단해짐

3) UICollectionViewLayoutAttributes

- Cell의 뷰의 정보를 정의한 모델 클래스, Cell의 크기, 위치, 중간값, 변형, 알파값 등의 정보를 가지고 있고 이 정보를 바탕으로 Cell이 반영된다

4) UICollectionViewLayout

- Cell의 위치에 대한 정보를 가지고 있는 클래스


2. UICollectionViewLayout

1) UICollectionViewFlowLayout

- UICollectionViewLayout을 상속 받은 클래스, Cell의 배치 방향을 Vertical, Horizontal등으로 상하, 좌우로 배치 시킬 수 있다

- UICollectionViewFlowLayout에서는 아이템에 대한 Size, 헤더 풋터에 대한 Size, inset 값을 정의하여 여백을 시작할 때 조정할 수 있다

- 또한 델리게이트 방식을 통하여 indexPath 값 마다 델리게이트를 호출하여 각 IndexPath에 대한 값을 정의 할 수 있다


2) CustomLayout

- Layout을 정의 할 때는 다음과 같은 메소드가 호출 된다

- (void)prepareLayout;

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

a. prepareLayout을 통해서 배치가 시작되기 전 기본적인 attributes의 값을 정의한다

- 전체 크기가 적은 양의 Cell을 배치하거나 위치가 고정일 경우 모든 Cell의 Attributes을 미리 생성하여 한다.

b. layoutAttributesForItemAtIndexPath를 통하여 각 indexPath에 대한 attributes를 반환한다

- 만약 동적으로 Cell에 대해 추가, 삭제가 일어나거나 많은 양의 Cell을 배치할 경우에는 이 메소드에서 위치를 계산하여 attributes를 반한환다

c. CollectionView 내 화면에 표현되고 있는 범위를 입력 받아 attributes의 배열을 반환한다.

- custom Layout일 경우 이 때 layoutAttributeForItemAtIndexPath메소드를 사용하여 배열로 생성하는 작업을 진행 한다.



2) Change Layout

3) Layout Animation

4) Layout Event Handle

1. App plist에 정보 추가하기

- Required background modes (Array) > item0 : App plays audio


2. AVAudioSession에 등록하기

- (void)beginBackgroundTaskAndRemoteControl

{

    NSLog(@"<--------------------");

    

    NSError * error = nil;

    

// 오디오 세션을 음악 재생에 사용함을 선언

BOOL state = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];

    

    if(state == NO){

        NSLog(@"Error MusicPlayer : %@", error);

        return;

    }

    

    [[AVAudioSession sharedInstance] setActive: YES error: nil];

// 백그라운드 작업 갱신

    self.prevTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];

NSLog(@"begin taskId : %u", self.prevTaskId);

// 리모컨 이벤트 받도록 선언

[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

    

[self becomeFirstResponder];

}


3. 완료 후 백그라운드 작업 및 리모컨 이벤트 해제

- (void)endBackgroundTaskAndRemoteControl

{

// 리모컨 이벤트 해제

[[UIApplication sharedApplication] endReceivingRemoteControlEvents];

NSLog(@"end taskId : %u", self.prevTaskId);

// 기존 작업 종료

if (self.prevTaskId != UIBackgroundTaskInvalid)

{

        [[UIApplication sharedApplication] endBackgroundTask:self.prevTaskId];

    }

self.prevTaskId = UIBackgroundTaskInvalid;

}


4. 리모콘 이벤트에 대한 처리 메소드 추가

- (void)remoteControlReceivedWithEvent:(UIEvent *)event

{

NSLog(@"remoteControlReceived!!");

// 리모컨 이벤트가 아닌 경우

if(event.type != UIEventTypeRemoteControl)

return;

// 리모컨 이벤트에 따른 동작들

switch (event.subtype)

{

case UIEventSubtypeRemoteControlTogglePlayPause:

            

if(self.mediaPlayer.playbackState == MPMoviePlaybackStatePlaying){

                

[self.mediaPlayer pause];

                

            } else {

                

[self.mediaPlayer play];

            }

break;

            

case UIEventSubtypeRemoteControlStop:

            

[self.mediaPlayer stop];

            

break;

            

case UIEventSubtypeRemoteControlNextTrack:

            

[self playNext];

            

break;

            

case UIEventSubtypeRemoteControlPreviousTrack:

            

[self playPrev];

            

break;

            

default:

break;

}

}


MPMoviePlayerController을 사용하여 미디어 플레이어를 제작하던 도중 문제에 부딛쳤다.

MPMoviePlayerController를 사용하여 음악을 플레이한 경우 seek바만 나타나게 되어 풀스크린의 상태로 변경이 안되었다

MPMoviePlayerController의 fullscreen 속성을 사용하여 해결하였으나 반드시 주의해야할 점은

부모뷰에 addSubview를 한 뒤에 속성값을 지정해줘야한다. addSubview이전에 하면 제대로 동작하지 않는다.


self.mediaPlayer =  [[MPMoviePlayerController alloc]initWithContentURL:url];


    self.mediaPlayer.controlStyle = MPMovieControlStyleFullscreen;

    self.mediaPlayer.scalingMode = MPMovieScalingModeFill;

    self.mediaPlayer.useApplicationAudioSession = YES;

    self.mediaPlayer.view.frame = self.view.bounds;

    

    [self.view addSubview:self.mediaPlayer.view];

    

    self.mediaPlayer.fullscreen = YES;

    

    [self.mediaPlayer play];


UIWebView에서 alert이나 confirm 스크립트를 호출하면 현재 접속한 페이지의 도메인이나 IP가 출력되는 현상이 발생된다. 이때 타이틀이나 내용, 버튼에 대한 부분을 처리하려면 상당히 번거로운 상황에 처하게 되었다.

인터넷에 찾아본 결과 해결방법은 iOS가 아닌 WebKit프레임워크를 델리게이트 메소드를 카테고리로 오버라이딩하여 화면에 노출 부분만 변경하도록 하면 해결되었다.

 #import <UIKit/UIKit.h>
@interface UIWebView (Javascript)
- (BOOL)webView:(UIWebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame;
- (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame;
@end

 

#import "UIWebView+Javascript.h"

@implementation UIWebView (Javascript)

static BOOL diagStat = NO;

- (BOOL)webView:(UIWebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame {
   
//    NSLog(@"javascript ConfirmPanel : %@",message);
   
    UIAlertView *confirmDiag = [[UIAlertView alloc] initWithTitle:nil message:message delegate:self cancelButtonTitle:NSLocalizedString(@"예", @"예") otherButtonTitles:NSLocalizedString(@"아니오", @"아니오"), nil];
   
    [confirmDiag show];
   
    //버튼 누르기전까지 지연.
   
    while (confirmDiag.hidden == NO && confirmDiag.superview != nil) {
       
        [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01f]];
    }
   
    [confirmDiag release];
   
    return diagStat;
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
   
    //index 0 : YES , 1 : NO
    if (buttonIndex == 0){
       
        //return YES;
       
        diagStat = YES;
       
    } else if (buttonIndex == 1) {
       
        //return NO;
       
        diagStat = NO;
       
    }
}

- (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame {
//    NSLog(@"javascript alert : %@",message);
   
    UIAlertView* customAlert = [[UIAlertView alloc] initWithTitle:nil message:message delegate:nil cancelButtonTitle:@"확인" otherButtonTitles:nil];
   
    [customAlert show];
   
    [customAlert autorelease];
}

@end

  1. Add the font file to the project. I recommend creating a Fonts group in the Resources group.

  2. Add a new entry in the app’s info.plist with the key “Fonts provided by application” (UIAppFonts), then add the filename (name and extension) of the font file to the array.

  3. Find out the name of the font. This can be done by opening the font file on your Mac and checking the title on the Font Book window. You can also find the name by using familyNames and fontNamesForFamilyName: UIFont methods.

  4. Use the font. It will work just like the standard fonts available on iOS devices.

 

전에 iOS6 회전에 관련한 글을 올렸었다.

http://devnote2.tistory.com/entry/iOS6-shouldAutorotateToInterfaceOrientation-문제
http://devnote2.tistory.com/entry/iOS6-shouldAutorotateToInterfaceOrientation-문제2

위 문제 외 한가지 더 확인해야 될 상황이 생겼다. 기존 구조에서는 NavigationController를 상속 받아 모달에 넣어 띄우는 것을 기본으로 사용하여 이번에 생긴 문제를 제대로 확인 하지 못했는데 다시 테스트 해보니 navigationController의 회전 설정 값이 우선되어 하위 ViewController의 값이 제대로 적용되지 않았다.

즉 NavigationController의 shouldAutorotate 값이 하위 뷰컨트롤러의 값을 호출하지 못해 회전값을 뷰컨트롤러 단위로 설정하기 어려운 상황이였다.

즉 따로 UINavigationController를 상속받아 회전값을 설정하는 방법외에 기본 UINavigationController로만 회전 값을 제어하기 위해서 알아본 결과 카테고리를 통해 하위 UIViewController에서 UINavigationController의 shouldAutorotate 함수를 덮어 기능을 제한하는 방법이 가장 간편한 방법을 것같다.

아래의 테스트 코드로 확인하였다.

#import "ViewController.h"

@implementation UINavigationController (Rotation_IOS6)

-(BOOL)shouldAutorotate
{
    return [[self.viewControllers lastObject] shouldAutorotate];
}

-(NSUInteger)supportedInterfaceOrientations
{
    return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}

@end


@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
   
    self.view.backgroundColor = [UIColor whiteColor];
 // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(BOOL) shouldAutorotate {
    return NO;
}

-(NSUInteger) supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAll;
}

@end

 

즉 UINavigationController의 shouldAutorotate 가 실행될 때 하위뷰에서 그 메소드를 후킹(?) 해서 회전 값에 대한 정보를 변경하는 것이 포인트이다.

 

앱에 대해 기능을 완성 후 Memory Leack등을 검사하기 위해 Instrument등을 사용하여 확인한다.

하지만 Leack 부분에서는 이상이 없는 특정 상황에서 메모리에 계속 남아있게 되는 현상이 발생된다.

대표적인 예로 Cross Retain 상태인데 아래와 같은 상황이다.

 

보통 저런 관계가 성립되기 위해서는 A Class에서 B Class를 생성 한 후 B Class에서의 결과를 받기 위해 Property retain을 통해 부모를 가르키고 있을 경우 발생된다.

하지만 일반적인 경우 저렇게 부모를 가르킬 경우에는 assign을 통해 참조만 하고 있다면 문제가 생기지 않는다.

그렇지만 요즘 Block Coding을 통해 개발하다 보니 위와 같은 상황이 발생되었다.

@property(nonatomic, copy) void (^executeLoad) ();

이렇게 Block코드를 property를 유지하기 위해서는 copy 키워드를 사용 해야하는데 이때 위와 같은 관계가 성립되어 버린다.

이런 상황은 A Class를 생성했던 부모클래스가 Release 되어도 A, B클래스는 여전히 메모리에 남아있게 되는 상황이 발생되었다.

이를 검사하기 위해 Instrument의 Allocations 도구를 사용하여 확인하였다.

Instrument의 화면이다. 기존까지 단순히 메모리의 증가 감소 현황만 확인하였는데 retain Count를 확인할수 있는 방법을 알고난 부터 엄청 유용하게 되었다.

 

우선 실행되는 Instrument 를 잠시 중단하고 allocations 옆 느낌포를 클릭하면 위와 같은 옵션 창이 나온다

여기에서 Recode reference count를 선택하면 모든 준비는 끝이다.

다시 Instrument를 실행하면 이전과 다른 것이 없어 보이지만 차이점이 존재한다. 

모니터 옵션창에서 Objects List를 선택하면 현재 살아있는 Object의 리스트를 보여준다.

Category를 확인하면 좀더 살아있는 객체를 확인하기 쉽다.

이때 retain Count를 확인하기 위해서는 Address옆의 화살표를 선택한다.

 

 

 

그럼 위와 같이 자세한 Retain Count의 기록이 출력되는데 처음 Recode reference count를 선택하지 않았다면 위 화면과 같이 자세한 retain Count기록이 출력되지 않을 것이다.

이 방법을 사용하여 번번히 retain count를 찍는 수고를 덜게 될것이고 메모리 관리가 한결 수월해 졌다고 생각한다.

 

서버에 이미지를 요청하여 화면에 뿌려주는 작업을 진행 중에 UITableView와 NSURLConnection의 동기화 문제로 고민하게 되었다

테이블뷰가 스크롤과 네트워크 작업은 서로 메인 Thread에서 진행되기 때문이다.

원하는 결과는 스크롤을 하면서도 화면에 출력될때 이미지를 받아올수 있어야 하기 때문에 인터넷을 찾아본 결과 의외로 간단했다

해답은 NSRunLoop의 설정을 변경해주어야 했었다.

아래 일반적으로 사용하는 NSURLConnection이다.

NSURLConnection * mConnection = [[NSURLConnection alloc] initWithRequest:aRequest delegate:self startImmediately:NO];
self.connection = mConnection;
[mConnection release];

[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.connection start];

위에서 보면 connection에 ScheduleInRunLoop:forMode: 메소드를 통해 현재 RunLoop에 Connection을 등록하는 부분이 있다.

여기서 Mode를 보면 NSDefaultRunLoopMode인데 이 부분이 중요했다.

우선 RunLoop에 connection을 연결하면 네트워크 요청 후 응답때까지 RunLoop에서 대기하는 용도로 사용한다. 보통 Opreation이나 Thread같은 경우

main이 실행되고 나면 역활이 끝나기 때문에 비동기적으로 대기하고 있으려면 저렇게 RunLoop에 등록해 두어야 한다.

하지만 기본적으로 사용하는 NSDefaultRunLoopMode을 사용하게 되면 테이블 스크롤 이벤트와 우선순위에서 낮아서 그런지 몰라도 UI Event가 우선 실행되고 Connection응답이 나중에 처리 된다.

이 현상을 수정하기 위해 Mode의 값을 변경하였다.

NSDefaultRunLoopMode -> NSRunLoopCommonModes

RunLoop 모드가 딱 두가지 밖에 없으므로 찾기에 어렵지 않을 것이다.

마지막으로 connection이 종료 후 작업을 마무리할때는 connection을 RunLoop스케줄에서 해제를 해주어야 한다.

[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.connection unscheduleFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

iOS의 자동회전 문제를 수정하면서 다시 한번 난관에 부딪쳤다.

기존 버전에서는 Landscape상태에서 Modal을 presentation 하면 새로운 뷰가 올라오면서 자동으로 Portrait 모드로 변경되어 팝업이 되었는데

iOS6에서는 Landscape상태 그대로 팝업이 되었다.

더군다나 회전을 방지하기 위해 shouldAutorotate값을 NO로 해두었기 때문에 팝업된 이후 rotation은 동작하지 않았다.

그래서 이런 문제를 해결하기 위해 UIViewController의 rotation 메소드를 확인 할 결과 아래와 같은 메소드를 발견하였다.

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation

이름에 presentation도 들어가있고 preferred? 선호?? 라는 이름을 보고 아 이거다 하고 이 메소드를 오버라이드 한 후

다시 모달을 presentation 한 결과 내가 원하는 방향으로 팝업시킬수 있게 되었다.

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}

물론 이 메소드 역시 iOS6에서 새로 추가된 메소드이기 때문에 코드를 추가한 후 iOS5에서 빌드할 때도 문제가 발생하지 안았다. (물론 결과는 반영되지 않는다)

그리고 한가지 팁은 만약  modal로 뛰울때 navigation이 되는 뷰를 올릴 경우 navagation에 들어가는 모든 viewController에 회전값을 입력하기 보다는

navigation뷰를 상속받아 회전에 대한 내용을 명시하고 사용한다면 navigation된 뷰 역시 모두 영향을 받으니 주의해야한다.

 

 

 

이번 ios6 업데이트로 인해 앱을 확인한 결과 shouldAutorotateToInterfaceOrientation의 deprecate로 인해 viewController가 제대로 작동하지 않음을 확인하였다.

그래서 해결 방법을 찾기위해 릴리즈 노트를 확인해본 결과

- (BOOL)shouldAutorotate;

메소드를 사용하여 자동회전 여부에 대한 값을 반환하면 되다고 하였다.

하지만 해당 메소드를 사용하고도 제대로 회전이 되지 않아 다시 릴리즈 노트를 꼼꼼히 본 결과 뭔가 다른 원인이 있다는 생각이 들었다.

그래서 ios6용 테스트 프로젝트용을 생성하고 나서 appDelegate 부터 확인해본 결과 이외로 간단한 곳이 문제였다.

기존 ios5 까지는 앱을 실행할때 appDelegate 내 window 객체에 UIViewController의 view를 addSubview하는 방식으로 사용하였다.

[self.window addSubview:self.rootViewController.view];

하지만 ios6의 내용을 보니 아래와 같았다

self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.window.rootViewController = self.rootViewController;

즉 ios6에서는 UIWindow객체를 생성하여 할당한 다음에 rootviewController 프로퍼티에 해당 뷰를 넣어주면 었다.

아래의 방식으로 앱을 변경하고 실행해보니 기존 처럼 자동회전이 동작하였다. 아마 자동회전에 대한 변경된 로직이 UIWindow서 부터 무엇인가 바껴서 이렇게 방식이 변경된 듯 싶다

그리고 기존 shouldAutorotateToInterfaceOrientation는 앱이 viewDidLoad되고 나서 한번 실행된 후 회전이 있을 때 마다 실행이 되었다면

shouldAutorotate은 처음 load될때 한번 실행된다. 이후 회전에 대한 뷰의 위치 변경은

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration ;

메소드를 사용하여 동일한 동작을 얻을 수 있었다.

한가지 주의할 점은 appDelegate내 시작 방식을 변경할 때 sdk버전을 잘 확인해야 한다.

단말기가 6.0이고 sdk가 5.1 일 경우 위 방식대로 하게 되면 아무 화면도 뜨지 않는 결가가 생긴다. 이건 5.1이하에서는 6.0 방식이 제대로 호환이 안되는 것 같으니 주의해야 한다.

프로젝트를 진행하는 도중 버튼을 통해 뷰의 크기 및 회전을 조절해야하는 요구사항이 있었다.

처음에 간단하게 생각했던 부분이였는데 막상 구현하기 위해서 많은 시행착오가 있었다.

그 이유는 첫번째로 뷰의 transform에 대한 이해 부족이였고 두번째로는 크기 및 회전값을 버튼의 위치에 따라 구하는 것이 생각보다 어려웠다.

아래 함수가 버튼의 위치에 따른 크기 및 회전 값을 구하는 예제 이다.

- (void) touchBeginButton:(UIControl *)control withEvent:(UIEvent *)event {

    if([self pushHistory:NCDecoItemHistoryTypeZoomRotation] == YES){
        [self.delegate decoItem:self withEvent:NCDecoItemEventPushHistory];
    }
   
    i_PanGesture.enabled = NO;
   
    if(i_ButtonRotationAngle == -1){
       
        CGPoint rotationPoint = CGPointZero;
       
        rotationPoint.x = control.center.x - self.center.x;
        rotationPoint.y = control.center.y - self.center.y;
        
        i_ButtonRotationAngle = atan2f(rotationPoint.y, rotationPoint.x) * -1;
    }
   
    if(i_ButtonDistance == -1){
        
        i_ButtonDistance = NaDistanceBetweenTwoPoints(self.center, control.center);
    }

- (void) dragButton:(UIControl *) control withEvent: (UIEvent *) event  {
   
    CGPoint dragPoint = [[[event allTouches] anyObject] locationInView:self.superview];
   
    // 중점과 터치 포인트 간의 거리를 구하여 scale 값을 구한다.
    CGFloat distance = NaDistanceBetweenTwoPoints(self.center, dragPoint);
    
    CGFloat scale = distance / i_ButtonDistance;
   
    self.layer.transform = CATransform3DMakeScale(scale, scale, 1);
    
    // 회전 각을 구한다.
   
    CGPoint rotationPoint = CGPointZero;
   
    rotationPoint.x = dragPoint.x - self.center.x;
    rotationPoint.y = dragPoint.y - self.center.y;
    
    CGFloat angle = atan2f(rotationPoint.y, rotationPoint.x);
   
    // 핸들러의 위치만큼 앵글값을 더해준다.
    angle = i_ButtonRotationAngle + angle;
    
    self.layer.transform = CATransform3DRotate(self.layer.transform, angle, 0, 0, 1);
   
    [self.delegate decoItem:self withEvent:NCDecoItemEventHandlerReload];
}

 

뷰의 크기는 중학교 수준의 간단한 수학이 적용되면 되는데 이 것을 올바르게 적용하는 것이 힘들었다.

간단하게 요약하자면 버튼 이벤트가 시작될 때 핸들러 버튼의 위치를 통해 기준이 되는 반지름과 앵글 값을 얻는다.

이렇게 얻어진 값을 통해 드래그 이벤트가 일어날 때마다 드래그된 위치에 대한 반지름을 계산하여 기준 반지름에 대한 Scale 값을 구하고

드래그된 위치에 대한 angle값에 처음 구했던 버튼의 angle을 더해주어 변형되는 각을 보정해준다.

 

 

UIView의 외형을 변경시기지 위해 두가지 방법이 있다

UIView 내에 transform 값을 변경시키는 것과 UIView.layer 의 transform을 변경시키는 방법이다.

간단하게 사용하기 위해서는 UIView 의 transform을 사용하면 된다.

사용방법 (self = UIView 의 인스턴스)

1. self.transform = CGAffineTransformMakeScale(scale, scale);

2. self.transform = CGAffineTransformScale(self.transform, deltaScale, deltaScale);

위 1번 함수인 CGAffineTransformMakeScale을 통해서 view의 비율을 입력되는 Scale값으로 변경할 수 있다.(절대 크기) 하지만 Make가 들어간 함수는 기존의 설정값을 무시하고 새로운 값으로 덮어버린다.

기존 외형에 추가로 변경을 하기 위해서는 2번 함수를 사용해야 하는데 2번 함수는 현재 transform의 상태와 Scale값을 입력하지만 여기서 중요한 점은 scale값은 기존 값에 대한 변위 값이다.(기존 값에 대한 상대적인 크기)

하지만 UIView 내에 있는 transform을 사용하게 될 경우 한가지 불편한 점은 현재 뷰 상태의 scale값을 알기 힘들다는 점이다. 이 부분은 1번 함수를 사용할때 처럼 절대값을 입력할때는 문제가 안되는데 2번같은 경우에는 문제가 생기게 된다. 이전의 scale값을 알기 힘들기 때문에 상대적인 값을 알기가 힘들다.

이런 문제를 해결하기 위해서는 UIView내 layer의 transform을 사용하면된다.

이 layer의 transform을 사용할 경우 사용방법은 위 함수 사용과 유사하다.

3. self.layer.transform = CATransform3DMakeScale(scale, scale, 1);

4. self.layer.transform = CATransform3DScale(self.layer.transform, deltaScale, deltaScale, 1);

한가지 차이점은 뒤에 z 값을 추가로 입력하게 되는데 함수명에서도 볼수 있듯이 3d 변형이 가능하기 때문에 z축으로 변경하기 위한 값이다. 만약 여기에 0을 입력해도 보이는 결과는 차이는 없이지만 다시 scale값을 얻을때 올바르지 않은 값을 반환한다.

앞에서 지적했단 단점인 현재 scale값을 알기 위해서는 아래의 방법을 사용하면된다.

CGFloat scale = [[self.layer valueForKeyPath:@"transform.scale.x"] floatValue];

즉 valueForKeyPath를 통해 현재 뷰의 scale값을 얻을 수 있게된다. 이렇게 얻어진 값을 바탕으로 scale값의 상대값을 구할 때 편하게 사용할수 있다.

여기서 한가지 재미있는 점은 위 처럼 valeForKeyPath를 통해 값을 얻어올수 있듯이 반대로 값을 지정하는 것도 가능하다.

[self.layer setValue:[NSNumber numberWithFloat:scale] forKeyPath:@"transform.scale.x"]

이렇게 지정하면 3번째 명령 처럼 진행되게 된다. 하지만 이런 방법을 사용할 경우 일부 기능에서 제대로 작동이 안되게 되는 경우가 있다.(예 View 상태를 image로 캡쳐할 때등)

특별한 경우가 아니면 3번의 방법을 사용하는게 올바른 것 같다.

마지막으로 위 예들은 모두 scale값만 표현을했는데 회전을 시킬수 있는 rotation 함수 역시 존재한다. 위 scale과 rotation을 적절히 조합하면 뷰를 자신이 생각하는 모양으로 변경 및 애니메이션 효과를 줄수 있을 것이다.

 

UIView 내에 있는 view를 회전 및 확대 / 축소 기능이 필요하였다. 이때 확대 / 축소 / 회전을 시켜주기 위한 버튼이 필요하였는데

이 버튼은 상위 UIView 내에 존재하여 확대 / 축소시 같이 변경되는 현상을 방지해야했다.

이런 현상을 방지하기 위해 버튼이 있는 위치를 계속 변경 시켜주어야 하는데 많은 고민을하다가 아래의 메소드를 발견했다.

  •  convertPoint:toView:
  •  convertPoint:fromView:
  •  convertRect:toView:
  •  convertRect:fromView:
  • 위 메소드들은 인수로 전달하는 view에대한 절대 좌표 및 Rect값을 반환해주는 메소드 이다.

  • 이 메소드들이 왜 위의 현상들을 해결 한 이유는 하위 뷰에서 회전을 하게 되면 좌표값은 항상 고정된 값만 나오게 된다.

    하지만 위 메소드를 통해 이런 좌표값을 상위 뷰에서 위치를 자동으로 계산을 해주니 회전에대한 복잡한 수식이 필요 없어지게 되었다.

    -(CGPoint) handlerPoint:(UIView *)view {
        return [self convertPoint:i_HandlerPoint.center toView:view];
    }

     

    하위뷰에서 회전에 따라 같이 회전하는 point뷰를 만들고 이 뷰의 center값을 위 메소드들을 통해 상위뷰의 절대좌표를 얻을 수 있게된 것이다.

    평소에는 잘 사용안할 메소드이지만 이렇게 사용하니 정말 강력한 메소드라는걸 느낀다.

     

     

     

    UIView 확대 축소 작업시

     if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
            
            // 이전 scale과 gesture의 scale을 바탕으로 변위값을 계산 후 새로운 scale값을 계산함 - 최대, 최소 크기를 제한할 수 있다.

            CGFloat currentScale = [[[gesture view].layer valueForKeyPath:@"transform.scale"] floatValue];
           
            // Constants to adjust the max/min values of zoom
            const CGFloat kMaxScale = 2.0;
            const CGFloat kMinScale = 0.5;
           
            CGFloat newScale = 1 - (i_PrevScale - [gesture scale]); // new scale is in the range (0-1)
            newScale = MIN(newScale, kMaxScale / currentScale);
            newScale = MAX(newScale, kMinScale / currentScale);

            CGAffineTransform transform = CGAffineTransformScale([[gesture view] transform], newScale, newScale);
            [gesture view].transform = transform;
             
            i_PrevScale = [gesture scale]; 
            
        }

     

    if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
            
            // 이전 scale값을 바탕으로 변위값을 직접 입력하여 형태를 변환시킨다. (최대, 최소 지정 어려움)
            self.layer.transform = CATransform3DScale(self.layer.transform, gesture.scale/i_PrevScale, gesture.scale/i_PrevScale, 1);
           
            i_PrevScale = [gesture scale]; 
        }

     

    UIView 회전시

    if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged)
        { // 회전각의 변위값을 입력하여 회전시킨다.
           self.layer.transform = CATransform3DRotate(self.layer.transform, gesture.rotation-i_PrevRotation, 0, 0, 1);
            i_PrevRotation = gesture.rotation;
        }

     

    if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged)
        { // Geusture부터 얻은 rotation 값을 직접 이력한다.
            self.transform = CGAffineTransformMakeRotation(gesture.rotation);
            i_PrevRotation = gesture.rotation;
        }

     

    Button Handler 로 Zoom, Rotation 사용시

    -(void) reloadButtonPoint {
        
        // 핸들러의 위치는 중점과 거리차로 scale을 구하기 때문에 반드시 원(반지름 = 뷰의 중간 길이)의 좌표내에 존재해야한다.
        
        CGPoint center = i_FrameHandler.center;

        CGFloat point = sqrtf(powf(CGRectGetMidX(self.bounds), 2.0)/2);
       
        center.x = CGRectGetMidX(self.bounds) + point;
        center.y = CGRectGetMidX(self.bounds) - point;
       
        i_FrameHandler.center = center;
    }
      

    /// 버튼의 Drag 이벤트 발생시 수행한다.
    - (void) dragButton:(UIControl *) control withEvent: (UIEvent *) event  {
           
        CGPoint dragPoint = [[[event allTouches] anyObject] locationInView:self.superview];
        
        // 중점과 터치 포인트 간의 거리를 구하여 scale 값을 구한다.
        CGFloat distance = NaDistanceBetweenTwoPoints(self.center, dragPoint);
        CGFloat scale = distance/ CGRectGetMidX(self.bounds);
       
        if(scale >= self.minimumZoomScale && scale <= self.maximumZoomScale){
           
            [self.layer setValue:[NSNumber numberWithFloat:scale] forKeyPath:@"transform.scale"];
        }
       
        // 터치 포인트와 중점의 위치차를 구하여 아크탄젠트 값으로 angle값을 산출한다.
        CGPoint rotationPoint = CGPointZero;
       
        rotationPoint.x = dragPoint.x - self.center.x;
        rotationPoint.y = dragPoint.y - self.center.x;
       
        CGFloat  angle = atan2f(rotationPoint.y, rotationPoint.x);
       
        [self.layer setValue:[NSNumber numberWithFloat:angle] forKeyPath:@"transform.rotation.z"];
    }

     

    참고사항

    뷰의 scale값을 지정할 때 아래의 두 방식을 사용한다.

    1. 직접 key path의 값을 변경

    [view.layer setValue:[NSNumber numberWithFloat:scale] forKeyPath:@"transform.scale"];

    2. transform 프로퍼티 값을 계산하여 변경

    view.transform = CGAffineTransformMakeScale(scale, scale);

    여기에서 1번의 방법인 경우 뷰가 중첩된 상황 각각 scale값을 변경하게되면 오작동하는 경우가 발생하였다. 이럴 경우 1번과 2번 방법을 혼합하면 이런 문제를 피할수 있었다.

     

    UITableView를 사용하다 보면 테이블을 Section별로 나눠 사용해야 할 필요가있다.

    테이블 타입이 Group이 아닌 Plain타입일 경우 Section의 타이틀을 지정할 경우 스크롤시 헤더가 고정되는 현상을 일으킨다.

    하지만 고정이 아닌 스크롤과 동시에 사라지게 하기 위해서는 다음과 같은 코드를 추가하면 해결할수 있다.

    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
       
        // 각 세션의 헤더가 스크롤시 고정되있는 현상을 수정하기 위해 위치를 재조정하는 코드 추가
        CGFloat sectionHeaderHeight = self.tableView.sectionHeaderHeight;
        if (scrollView.contentOffset.y<=sectionHeaderHeight&&scrollView.contentOffset.y>=0) {
            scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
        } else if (scrollView.contentOffset.y>=sectionHeaderHeight) {
            scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 0, 0);
        }
    }

    테이블뷰는 UIScrollView를 상속받아 구현되어있기 때문에 위와 같은 메소드를 오버라이드해서 구현이 가능하다.

    위 코드를 삽입하게 되면 Section의 고정현상이 사라지게 된다.

    추가로 Footer뷰를 사용하기 위해서는 아래와 같은 방식을 적용하였다.

    -(void)scrollViewDidScroll:(UIScrollView *)scrollView {
       
        CGFloat footerHeight = self.clearAllView.bounds.size.height;
       
        CGFloat height = scrollView.frame.size.height;
       
        CGFloat contentYoffset = scrollView.contentOffset.y;
       
        CGFloat distanceFromBottom = scrollView.contentSize.height - contentYoffset - footerHeight;
       
        if(distanceFromBottom < height )
        {
            CGFloat diff =  footerHeight - (height - distanceFromBottom);
            if(diff >= 0 && diff <= footerHeight){
                scrollView.contentInset = UIEdgeInsetsMake(0, 0, -diff, 0);
            }
        }else{
           
            scrollView.contentInset = UIEdgeInsetsMake(0, 0, -footerHeight, 0);
        }
    }

    Preference URL
    About prefs:root=General&path=About
    Accessibility prefs:root=General&path=ACCESSIBILITY
    Airplane Mode On prefs:root=AIRPLANE_MODE
    Auto-Lock prefs:root=General&path=AUTOLOCK
    Brightness prefs:root=Brightness
    Bluetooth prefs:root=General&path=Bluetooth
    Date & Time prefs:root=General&path=DATE_AND_TIME
    FaceTime prefs:root=FACETIME
    General prefs:root=General
    Keyboard prefs:root=General&path=Keyboard
    iCloud prefs:root=CASTLE
    iCloud Storage & Backup prefs:root=CASTLE&path=STORAGE_AND_BACKUP
    International prefs:root=General&path=INTERNATIONAL
    Location Services prefs:root=LOCATION_SERVICES
    Music prefs:root=MUSIC
    Music Equalizer prefs:root=MUSIC&path=EQ
    Music Volume Limit prefs:root=MUSIC&path=VolumeLimit
    Network prefs:root=General&path=Network
    Nike + iPod prefs:root=NIKE_PLUS_IPOD
    Notes prefs:root=NOTES
    Notification prefs:root=NOTIFICATIONS_ID
    Phone prefs:root=Phone
    Photos prefs:root=Photos
    Profile prefs:root=General&path=ManagedConfigurationList
    Reset prefs:root=General&path=Reset
    Safari prefs:root=Safari
    Siri prefs:root=General&path=Assistant
    Sounds prefs:root=Sounds
    Software Update prefs:root=General&path=SOFTWARE_UPDATE_LINK
    Store prefs:root=STORE
    Twitter prefs:root=TWITTER
    Usage prefs:root=General&path=USAGE
    VPN prefs:root=General&path=Network/VPN
    Wallpaper prefs:root=Wallpaper
    Wi-Fi prefs:root=WIFI

    앱간 이미지 객체 공유 방법

    카메라롤의 URL을 사용하지 않고 직접 이미지 path를 통해 공유하는 방법이다.


    [호출자]

    버튼등을 통해 보내기 이벤트가 발생될 경우 아래의 코드로 전송할 파일을 저장하고 path를 지정해 준다.

    생성된 path를 UIDocumentInteractionController를 통해 해당 패스에 맞는 앱의 리스트를 출력시켜준다.

    NSData * data = UIImageJPEGRepresentation(self.importedImage.image, 1);

        

        NSArray * pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

        NSString * docDirectory = [pathArray objectAtIndex:0];

        NSString * resultPath = [NSString stringWithFormat:@"%@/%@", docDirectory, @"test.jpg"];

        

        [[NSFileManager defaultManager] createFileAtPath:resultPath contents:data attributes:nil];

        NSURL* tempURL = [NSURL fileURLWithPath:resultPath];    

        UIDocumentInteractionController * docu = [UIDocumentInteractionController interactionControllerWithURL:tempURL];

        self.document = docu;

        self.document.delegate = self;

        [self.document presentPreviewAnimated:YES];


    위 객체를 통해 호출할 경우 델리게이트를 지정해주어야 한다. (개발문서 참조)


    [수신자]

    수신하는 앱에서는 plist에 아래의 항목을 추가시킨다. 

    (아래 항목들이 dictionary인데도 불구하고 순서가 일치해야만 앱에서 노출된다.)

        <key>CFBundleDocumentTypes</key>

        <array>

            <dict>

                <key>CFBundleTypeIconFiles</key>

                <array>

                    <string></string>

                </array>

                <key>CFBundleTypeName</key>

                <string>JPG</string>

                <key>LSHandlerRank</key>

                <string>Default</string>

                <key>LSItemContentTypes</key>

                <array>

                    <string>public.jpeg</string>

                </array>

            </dict>

        </array>


    마지막으로 해당 앱의 앱델리게이트 내에서 전달받은 URL을 처리하는 내용을 추가하면 된다.

    - (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url

    {

        SALog(@"URL:%@ \n", url);

        

        return [self handleURLScheme:url];   

    }


    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation

    {

        SALog(@"URL:%@ \n App:%@\n Anno:%@\n", url, sourceApplication, annotation);


        return [self handleURLScheme:url];

    }


     

    UIActionSheet로 버튼을 작업하면서 새로운 요구사항이 생겼다.

    특정한 조건일 경우에만 버튼을 노출되어야 하는 요소들이 생겼다.

    기존의 생성 방식은 아래와 같다

    [[UIActionSheet alloc] initWithTitle:title
                                                      delegate:self
                                             cancelButtonTitle:NSLocalizedString(@"취 소", nil)
                                        destructiveButtonTitle:nil
                                             otherButtonTitles:NSLocalizedString(@"A", nil),
                                                               NSLocalizedString(@"B", nil),
                                                               NSLocalizedString(@"C", nil),
                                                               NSLocalizedString(@"D", nil),
                                                               NSLocalizedString(@"E", nil),
                                                               nil];

    위와 같은 방법으로 하게 되면 타이틀이 추가된 순서대로 index번호가 부여된다

    델리게이트를 통해 이벤트 결과를 받을 때도 이 index 번호로 버튼을 구분하게된다.

    하지만 동적으로 버튼을 추가하게 될 경우 아래의 구문이 추가로 필요하게 된다.

    [actionSheet addButtonWithTitle:NSLocalizedString(@"F", nil)];

    즉 액션시트에 타이틀을 추가하는 메소드인데 이걸 사용하게 되면 인덱스 번호가 꼬이게 된다.

    인덱스번호 

    타이틀 

     0

     A

     1

     B

     2

     C

     3

     D

     4

     E

     5

     취소

     6

     F

    즉 인덱스 번호로 타이틀을 구분하려고 할때 switch문 같은 분기문을 써야하는데 저렇게 취소 버튼이 중간에 있다보면 예외상황이 생기게 되고 나중에도 혼란을 야기한다.

    이 문제를 해결하기 위해 아래와 같은 방법을 사용했다.

        NSMutableArray * names = [[NSMutableArray alloc]initWithCapacity:0];
       
        [names addObject:NSLocalizedString(@"A", nil)];
        [names addObject:NSLocalizedString(@"B", nil)];
        [names addObject:NSLocalizedString(@"C", nil)];
        [names addObject:NSLocalizedString(@"D", nil)];
        [names addObject:NSLocalizedString(@"E", nil)];

        if ( SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"5.0") == YES ) {
            [names addObject:NSLocalizedString(@"F", nil)];
        }
        [names addObject:NSLocalizedString(@"취 소", nil)];
        self.sendServiceNames = names;
        [names release];

        UIActionSheet * actionSheet = [[UIActionSheet alloc] initWithTitle:title
                                                                  delegate:self
                                                         cancelButtonTitle:nil
                                                    destructiveButtonTitle:nil
                                                         otherButtonTitles:nil];

        for(NSString * serviceName in self.sendServiceNames){
            [actionSheet addButtonWithTitle:serviceName];
        }
       
        actionSheet.cancelButtonIndex = [self.sendServiceNames count]-1;

    즉 배열을 사용하여 액션시트에 들어갈 타이틀을 배열로 구성한 다음 반복문을 통해 addButtonWithTitle로 버튼을 입력한 다음 마지막에 cancelButtonIndex에 취소 버튼의 위치를 임의로 지정하게 되면 지정된 인덱스 번호를 취소 버튼의 위치로 출력하게 된다.

    물론 델리게이트를 통해 인덱스 번호를 받을 때도 순차적인 인덱스를 얻을 수 있게 되었다.

     

    'iOS' 카테고리의 다른 글

    IOS 설정바로가기 URL  (0) 2012.04.19
    UIDocumentInteractionController을 통한 앱간 문서 전달  (0) 2012.04.09
    스레드 동작시 performSelector 주의점  (0) 2012.03.05
    UITableViewCell 다루기  (0) 2012.02.06
    TCP Dump  (0) 2011.12.29

    스레드에서 특정 작업을 동작시 performSelector를 실행할때 타이머를 지정하게 되면 제대로 작동안하는 경우가 발생된다.

    [self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>]

    원인은 해당 스레드의 런루프에서 지정하 시간만큼 대기를 안하고 스레드가 종료되어 제대로 실행이 안된다고 한다.

    이럴 경우 자체적인 sleep함수등을 이용하여 실행을 늦춰야할것 같다.

    'iOS' 카테고리의 다른 글

    UIDocumentInteractionController을 통한 앱간 문서 전달  (0) 2012.04.09
    UIActionSheet 버튼 타이틀 동적 추가  (0) 2012.04.05
    UITableViewCell 다루기  (0) 2012.02.06
    TCP Dump  (0) 2011.12.29
    아이폰에서 토스트 팝업 만들기  (1) 2011.12.29

    + Recent posts