2013년 4월 13일 토요일

객체를 생성하지 않고도 instance method를 호출하게 해주는 instanceMethodForSelector

NSObject를 상속받은 클래스의 인스턴스를 생성하지 않고도 모든 인스턴스 메소드를 호출할 수 있다.
+ (IMP)instanceMethodForSelector:(SEL)aSelector 는 aSelector함수를 구현한 어드레스(함수포인터)를 반환한다.

아래 소스는 NSOjbect의 클래스 메소드 instanceMethodForSelector를 어떻게 사용하는지에 대한 예제이다.

// Utility.h
@interface Utility : NSObject
{
}

+ (void)showLoadingView;



// Utility.m
static UIView *loadingView=nil;
static UIView *loadingContainerView=nil;
static bool isShowActivityIndicator = false;

+ (void)showLoadingView
{
IMP func = [Utility instanceMethodForSelector:@selector(performSelector:withObject:afterDelay:)];
func(self, @selector(performSelector:withObject:afterDelay:), @selector(doShowLoadingView),nil,0);
}

+ (void)doShowLoadingView
{
UIWindow *mainWindow = [[UIApplication sharedApplication].delegate window];

if (loadingView == nil) {

loadingContainerView = [[UIView alloc] initWithFrame:mainWindow.frame];
loadingView = [[UIView alloc] initWithFrame:CGRectMake(mainWindow.center.x-70,mainWindow.center.y-50,140,100)];
loadingView.opaque = NO;
loadingView.backgroundColor = [UIColor darkGrayColor];
loadingView.alpha = 0.7;
loadingView.layer.cornerRadius = 8;
loadingView.layer.masksToBounds = YES;
UIActivityIndicatorView *spinningWheel = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[spinningWheel setCenter:CGPointMake(loadingView.frame.size.width/2,(loadingView.frame.size.height/2)-10.0f)];
[loadingView addSubview:spinningWheel];
[spinningWheel release];
[spinningWheel startAnimating];

UILabel *loadingLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 62, 140, 25)];
[loadingLabel setFont:[UIFont boldSystemFontOfSize:14.0f]];
[loadingLabel setTextAlignment:UITextAlignmentCenter];
[loadingLabel setBackgroundColor:[UIColor clearColor]];
[loadingLabel setTextColor:[UIColor whiteColor]];
loadingLabel.text = @"Loading...";
[loadingView addSubview:loadingLabel];
[loadingLabel release];
[loadingContainerView addSubview:loadingView];
}

if (isShowActivityIndicator == false) {
[mainWindow addSubview:loadingContainerView];
isShowActivityIndicator=true;
}
}

어떤 곳에서도 [Utillity showLoadingView]를 호출하면 activity indicagtor view를 띄운다.
아래 그림은 activity indicator view를 띄운 화면을 캡쳐한 화면이다.






이와 더불어 class method에 대한 함수 포인터를 얻고 싶으면 - (IMP)methodForSelector:(SEL)aSelector 를 사용하면 된다.

숫자를 포함하고있는 NSString을 여러가지 형으로 변환

NSScanner 클래스를 이용해서 NSString을 float, double, hex,int 등의 다른 형으로 변환이 가능하다.

아래 예제는 헥사값을 읽어와서 integer값으로 변환하는 예이다.

NSString *hexaString = @"0x24DA1";
NSScanner* pScanner = [NSScanner scannerWithString: hexaString];
unsigned int iValue;
[pScanner scanHexInt: &iValue];
NSLog(@"iValue=%d",iValue);

결과 값:
iValue = 150945

이와 비슷하게 여러가지 형으로 상호 변환이 가능하다.

NSString이 숫자인지 아닌지 체크

아래 예제는 testString이 숫자만 포함하는지 아니면 숫자가 아닌 문자를 포함하고 있는지를 체크한다.

(BOOL)isDigit:(NSString)testString
{
NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSRange nond = [testString rangeOfCharacterFromSet:nonDigits];
if (NSNotFound == nond.location) {
return YES;
} else {
return NO;
}
}

testString = @"12345" 이면 true 이고,
testString = @"12345ABY"이면 false 이다.

NSArray의 모든 Object들에서 같은 메세지를 보내는 방법.

NSArray의 모든 Object들에게 한번에 같은 메세지를 보낼 수 있는데 makeObjectsPerformSelector를 이용하면 된다.
이 함수는 잘 만 이용하면 아주 유용하게 사용할 수 있다.
예를 들어서 ScrollView의 subview들을 한번에 모두 제거한다든가 하는 작업이 가능하다.
단, 조건이 있는데 selector에 들어갈 메세지는 argument가 없는 함수만 가능하다. (명심할 것)

예제1)
UIScrollView의 subview들을 한번에 제거
[[myScrollView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];

예제2)
IB에서 collection으로 묶인 UIButton들 한방에 unselect
xx.h

@property (retain, nonatomic) IBOutletCollection(UIButton) NSArray *allButtons;

xx.m

[_allButtons makeObjectsPerformSelector:@selector(setSelected:) withObject:NO];


iOS에서 broadcasting 하는 방법.

iOS의 어느곳에서든지 broadcast 메세지를 보낼 수 있다. 메세지를 보낼때는 이름(유일하게 구분이 되어야 함)을 정의하게되고, 그 broadcast message를 받고 싶은 곳에서는 notification센터에 같은 이름으로 observer 등록하면 받을 수 있다.

아래 예는 sender클래스가 로그아웃 메세지를 broadcasting하고, receiver클래스에서 그 broadcast message를 받아서 처리하는 간단한 예제이다.
broadcasting의 장점은 거미줄 같은 객체간의 상호 의존성을 줄일 수 있다는 것이다. 그렇지만 broadcasting을 남발하게 되면 오히려 프로그램의 복잡성을 키울 수 있다.
따라서, 적절한 곳에 적절히 사용해야 한다.



//sender.m
- (IBAction)touchedLogoutButton:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:UserNotificationHttpRequestLogout object:self];
}





//receiver.m
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doLogout:) name:@"notification_logout" object:nil];
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"notification_logout" object:nil];
[super dealloc];
}

- (void)doLogout:(NSNotification *)notification
{
//로그아웃 처리
}

알아두면 좋은 것들

 CGPointEqualToPoint(CGPoint point1, CGPoint point2)
 : 포인트 point1와 point2가 같으면 TRUE, 다르면 FALSE 값 리턴.


//간단히 Mutable Array 생성하는 방법
NSMutableArray myarr = [@[] mutableCopy];


//간편 설정
CGRect CGRectOne = (CGRect){.origin.x = 1.0f, .origin.y = 1.0f, .size.width = 1.0f, .size.height = 1.0f};
CGRect CGRectOne = { { 0.0f, 0.0f }, { 1.0f, 1.0f } };
CGRect CGRectOne = {
        .origin = { .x = 0.0f, .y = 0.0f },
        .size   = { .width = 1.0f, .height = 1.0f }

    };

// center position
CGPoint centerPosition = (CGPoint){CGRectGetMidX(imageContainerLayer.bounds), CGRectGetMidY(imageContainerLayer.bounds)}



sqlite3 *gTheDb // db

NSInteger lastRowId = sqlite3_last_insert_rowid(gTheDb);
:마지막에 추가된 레코드의 rowid값 리턴



CGRect를 string으로 변환 하는 함수.
NSString rectString = NSStringFromCGRect(buttonRect);
output : rectString = {{75, -1}, {25, 21}}

반대로 string을 CGRect로 변환


CGRect rect = CGRectFromString(rectString);

이외에

NSStringFromCGSize, NSStringFromUIOffset 등의 많은 함수들이 있다. 

UIKit Function Reference를 살펴보면 알 수 있을 것임.

NSDictionary *dict = @{ kNamekey : @"jacobs kim" };   //딕셔너리에 직접 값 입력.


label의 matrix 와 text 정렬


iOS 7 이상에서는  boundingRectWithSize:options:attributes:context:
를 사용할 수 있음.
예제)
CGSize maximumLabelSize = CGSizeMake(_lyricsLabel.frame.size.width, CGFLOAT_MAX);
    
    CGRect expectSize = [_lyricsLabel.text boundingRectWithSize:maximumLabelSize
                                               options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
                                            attributes:@{NSFontAttributeName:_lyricsLabel.font}
                                               context:nil];
    _lyricsLabel.frame = CGRectMake(_lyricsLabel.frame.origin.x,
                                             _lyricsLabel.frame.origin.y,
                                             _lyricsLabel.frame.size.width,

                                             expectSize.size.height);



NSString의 sizeWithFont:constrainedToSize:lineBreakMode: 메써드를 통해서 text가 그려질 layout metrics를 받아올수 있다.

아래는 그 사용 예제이다:
- (void)alignSingleLineLabels
{
    [_label1 setText:@"첫번째 레이블."];
    [_label2 setText:@"두번째 레이블 입니다.!"];
   
    CGSize _label1_Size = [_label1.text sizeWithFont:_label1.font
                                              constrainedToSize:CGSizeMake(320, 640)
                                                  lineBreakMode:UILineBreakModeWordWrap];
    CGSize _label2_Size = [_label2.text sizeWithFont:_label2.font
                                   constrainedToSize:CGSizeMake(320, 640)
                                       lineBreakMode:UILineBreakModeWordWrap];
   
    [_label1 setFrame:CGRectMake(_label1.frame.origin.x, _label1.frame.origin.y, _label1_Size.width, _label1_Size.height)];
    float _label2_posX = _label1.frame.origin.x + _label1_Size.width;
    [_label2 setFrame:CGRectMake(_label2_posX, _label2.frame.origin.y, _label2_Size.width, _label2_Size.height)];
}

- (void)alignMultipleLineLabels
{
    [_label3 setText:@"첫번째 레이블.\n다은줄입니다."];
    [_label4 setText:@"두번째 레이블.\n다음줄 입니다.\n다은줄 입니다."];
    [_label5 setText:@"세번째 레이블.세번째 레이블.\n다음줄 입니다."];
   
    CGSize _label3_Size = [_label3.text sizeWithFont:_label3.font
                                   constrainedToSize:CGSizeMake(320, 640)
                                       lineBreakMode:UILineBreakModeWordWrap];
    CGSize _label4_Size = [_label4.text sizeWithFont:_label4.font
                                   constrainedToSize:CGSizeMake(320, 640)
                                       lineBreakMode:UILineBreakModeWordWrap];
    CGSize _label5_Size = [_label5.text sizeWithFont:_label5.font
                                   constrainedToSize:CGSizeMake(320, 640)
                                       lineBreakMode:UILineBreakModeWordWrap];
   
    [_label3 setFrame:CGRectMake(_label3.frame.origin.x, _label3.frame.origin.y, _label3_Size.width, _label3_Size.height)];
    float _label4_posX = _label3.frame.origin.x + _label3_Size.width;
    [_label4 setFrame:CGRectMake(_label4_posX, _label4.frame.origin.y, _label4_Size.width, _label4_Size.height)];
   
    float label5_posY = _label3_Size.height > _label4_Size.height?(_label3.frame.origin.y+_label3_Size.height):(_label4.frame.origin.y+_label4_Size.height);
    [_label5 setFrame:CGRectMake(_label5.frame.origin.x, label5_posY, _label5_Size.width, _label5_Size.height)];
}


레이블의 line 갯수를 가져와서 text가 그려질 line의 갯수와 비교하여 뉴라인문자(\n)을 리에블의 text에 append해줌으로써 text를 label의 맨 상단에 그려질 수 있도록 할 수있다.

- (void)alignTopLeft {
    CGSize labelSize = [_label6.text sizeWithFont:_label6.font
                                              constrainedToSize:CGSizeMake(_label6.frame.size.width, _label6.frame.size.height)
                                                  lineBreakMode:_label6.lineBreakMode];
    int lineCount = labelSize.height/_label6.font.lineHeight;
    int maxLineCount = _label6.numberOfLines;
    for (int i=lineCount; i < maxLineCount; i++) {
        _label6.text = [_label6.text stringByAppendingString:@"\n "];
    }
   
}


아래는단말에서 실행된 화면이다.

숫자를 통화로 변경

NSNumberFormatter를 이요하면, 숫자를 통화로 변경할 수 있습니다.

NSString *number = @"숫자";
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSNumber *numberFromString = [NSNumber numberWithInt:[number intValue]];
NSString *balance = [formatter stringFromNumber:numberFromString];
[formatter release];

지역포멧이 대한민국인 경우 변환 결과:
232220    ==> ₩232,220
-10263522   ==>  -₩10,263,522

지역포멧이 미국인 경우 변환 결과:
232220   ==>  $232,220.00
-10263522  ==>  ($10,263,522.00)      *()표기는 음수를 나타냄.

만약 "xxxx원" 이런식으로 보여지기를 원한다면 약간의 추가 작업이 필요합니다.
아래 함수는 그 처리를 하는 함수 입니다:
+ (NSString *)convertFromNumberToCurrency:(NSString *)number
{
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    NSNumber *numberFromString = [NSNumber numberWithInt:[number intValue]];
    NSString *balance = [formatter stringFromNumber:numberFromString];
    [formatter release];
   
    balance = [balance stringByReplacingOccurrencesOfString:@"(" withString:@"-"];
    balance = [balance stringByReplacingOccurrencesOfString:@")" withString:@""];
   
    NSRange range = [balance rangeOfCharacterFromSet:[NSCharacterSet symbolCharacterSet]];
    while (range.location != NSNotFound) {
        balance = [balance stringByReplacingCharactersInRange:range withString:@""];
        range = [balance rangeOfCharacterFromSet:[NSCharacterSet symbolCharacterSet]];
    }
    balance = [balance stringByAppendingString:@"원"];
    return balance;
}

결과는 지역포멧에 상관없이 다음과 같음:
232220    ==> 232,220원
-10263522   ==>  -10,263,522원

다른 어플 실행 방법

UIApplication:openURL: 함수를 이용해서 어플을 실행할 수 있다.
- (BOOL)openURL:(NSURL *)url
url은 http:, https:, tel:, mailto: ,schemes 등이 가능하다.

<예제>
BOOL r = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:urlScheme]];
    if (r) {
//어플 실행
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlScheme]];
    } else {
//어플 다운로드
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:downloadURL]];
    }

align Justified( left and rignt ) with UILabel

NSString *text1 = @"Sample text : A his is my weblog in English, German and Korean. If you want to know more about a pizza stone once it’s cooled down.";

NSMutableParagraphStyle *style =  [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setAlignment: kCTJustifiedTextAlignment];

NSAttributedString * subText1 = [[NSAttributedString alloc] initWithString:text1 attributes:@{
                                                NSParagraphStyleAttributeName : style
                                     }];

_myUiTextView.attributedText = subText1;



참조사이트 : http://stackoverflow.com/questions/1301519/justified-alignment-in-uitextview-iphone

day of year 계산


NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"D"];
NSUInteger dayOfYear = [[formatter stringFromDate:[NSDate date]] intValue];
[formatter release];
NSLog(@"오늘은 일년중 %d일 입니다.",dayOfYear);


결과
today = 오늘은 일년중 71일 입니다.

UINavigationController 기반의 어플리케이션 만들기

View base application 프로젝트 생성한후에 application delegate에 다음과 같은 코드만 추가해 줌으로써 간단히 navigationcontroller 기반 어플리케이션으로 변환 된다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease];
    UINavigationController* naviController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
    [naviController setNavigationBarHidden:YES];   //sub viewcontroller에서 이부분을 NO로 설정해 주면 뒤로가기가 가능하도록 navigation bar가 보임.
    self.window.rootViewController = naviController;
    [self.window makeKeyAndVisible];
    [self.viewController release];
    return YES;
}