iOS5 에서, UIPickerView가 UITableViewDateSource Protocol을 Adopting 한다는것외에 
따로 문서화로 달라진것은 없습니다.  

그런데, frame.size.width값을 이제 받아 들이게 되었습니다.
UIPickerView *pickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 0, 160, 100)];
    [pickerView setDataSource:self];
    [pickerView setDelegate:self];
    
    [self.view addSubview:pickerView];
    [pickerView release];

위의 코드는 pickerView의 width를 160으로 설정한 예입니다.
iOS4를 비롯한 모든 예전 버전에는 width 값은 무시되고 항상 320으로 설정되었습니다.

iOS 4.3 Simulator

 
그런데, 같은 코드를 iOS5 에서 실행 시키면.

iOS5 Simlurator

 
 위와 같이 width 속성이 먹는것을 알 수 있습니다.

어차피 적용안되는거,, height 값만 신경쓰고, width는 대강 작성했던 코드가 문제를 일으켰습니다. ;ㅂ;
여튼 width가 적용이 안되서 좀 답답한것도 있었는데, 이제 되는군요 :)

내년쯤.. iOS4를 더이상 Follow-up 하지 않을 정도 되면 사용해볼수 있겠군요 :)

 



  
Accounts.framework

Accounts Framework  은 Twitter 계정을 관리 하는 Framework 이며, 목적은 아래의 3가지라고 볼수 있습니다.
 

1. Account 접근
2. Account 추가
3. Account 유효성 체크


Accounts Framework의 Class 들

ACAcccountStore
ACAccount 
ACAccountType
ACAccountCredential


Account 목록 가져오기.
 
Default로 account를 접근하는것이 금지되어있기 때문에, 권한을 요청해야 합니다.
  ACAccountStore *store = [[ACAccountStore alloc] init]; 
    ACAccountType *twitterType = [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    [store requestAccessToAccountsWithType:twitterType
                     withCompletionHandler:^(BOOL granted, NSError *error) {
                         if(granted){
                            //accessgranted !
                             NSLog(@"granted = %d",granted);
                             
                             NSArray *accounts = [store accountsWithAccountType:twitterType];
                             NSLog(@"accounts = %@", accounts);
                         }
                         }];
    [store release];

 
권한을 요청하면, 위와 같은 권한 요청 팝업이 나오게 되며,  사용자가 허락하거나 거절 할 수 있습니다.
한번 허락을 했다고 해서,  계속 허락되는것은 아니며,
사용자는 Settings -> Twitter 에서 허락 여부를 On/Off 할 수 있습니다.

 
Allow 한 후에,  위의 코드를 다시 실행 시켜 보면,

2011-10-14 16:34:07.600 TwitterTest[3364:12103] accounts = (

    "type:com.apple.twitter\nidentifier: 527A4FE0-FBAA-40A1-A7C8-C6FA2EEE9698\naccountDescription: @liketaehoon\nusername: liketaehoon\nobjectID: x-coredata://0102DFCE-5B1C-438B-B14B-D9BB471DD885/Account/p2\nenabledDataclasses: {(\n)}\nproperties: {\n    \"user_id\" = 48573923;\n}\nparentAccount: (null)\nowningBundleID:com.apple.Preferences"

)

 
이렇게 계정 정보가 읽어 집니다.

Twitter 계정 추가.

계정을 추가 하는 방법도 상당히 쉽습니다. 
ACAccount *account = [[ACAccount alloc] initWithAccountType:twitterType];
ACAccountCredential *oauthCredential = [[ACAccountCredential alloc] initWithOAuthToken:token
tokenSecret:secret]; [accountStore saveAccount:account withCompletionHandler:^(BOOL success,
account.credential = oauthCredential; NSError *error) {
if (success) {
//account validated and saved
} else {
//save failed, handle your errors! !}
}];
[oauthCredential release];
[account release];

OAuthToken을 얻어 오는것은 해당포스트의 내용을 벗어나는 일이라 설명하지 않겠습니다.
ACAccount  Object 생성,
OAuthToken을 이용해, ACAccountCredential Object를 만들어, ACAcount 에 넣어주고,
ACAccountStore를 통해 save 하면됩니다.

ACAccountStore단에서 validating도 해주게 됩니다.

나의 App에서 Twitter 계정을 로그인하게 해주는 기능을 제공해야 할까는 고민해봐야 겠지만,
상당히 쉽게 사용할 수 가 있습니다.


Accounts.framework 에 대한 짧은 생각.

Accounts.framework이 Twitter뿐아니라,
Facebook 이나 기타 서비스들을 품을 수 있게 설계된 framework인것으로 판단되어,
앞으로의 방향이 기대되는 framework이라고도 볼수 있겠네요 :)

 


Twitter 가 iOS 에 Integrate 되었습니다.
"드디어, MGTwitterEngine 과 같은 Library에서 벗어 날수 있겠군요! "
라고 생각하시면 큰일 납니다 ;ㅂ;  ( 위에 Optional 로 Link 되어 있는 이유를 아시겠죠? ^^ )

아직 iOS5 보전보다 iOS4 버전이 많을테니, 하위 호환성을 생각해줘야 하기 때문에 여전히 버릴수가 없습니다.
어쨌든 들어 왔습니다 !
그래서 제가 개발하고 있는 것들에 Twitter Framework 을 붙혀 볼까 합니다. 

TWTweetComposeViewController

MFMailComposeViewController와 비슷한 형태의 ViewController 라고 생각하시면 됩니다. 
한번 띄워 볼까요? ^^ 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{    

    TWTweetComposeViewController *viewController = [[TWTweetComposeViewController alloc] init];
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    self.window.rootViewController = viewController;
    [viewController release];
    [self.window makeKeyAndVisible];
    return YES;
}

위와 같이 Code 를 작성하고, 결과가 나오기를 바랬는데 ;ㅂ;

*** Terminating app due to uncaught exception 'TWUnsupportedPresentationException', reason: 'TWTweetComposeViewController cannot currently be used within a popover or as a non-modal view controller'

*** First throw call stack:

 
위와 같은 에러가 나옵니다.  
말인 즉슨 modal view controller 가 아니면 사용할수가 없다는 군요 ;ㅂ;

그래서 코드는 아래로 옮겨 주고, present modal 로 보여줬습니다.
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    TWTweetComposeViewController *composeViewController = [[TWTweetComposeViewController alloc] init];
    [self presentModalViewController:composeViewController animated:YES];
    [composeViewController release];
}


위와 같이 수정하고 나니,


 위와 같이 이쁜 디자인으로 잘 나오는군요.

// Sets the initial text to be tweeted. Returns NO if the specified text will
// not fit within the character space currently available, or if the sheet
// has already been presented to the user.
- (BOOL)setInitialText:(NSString *)text;

// Adds an image to the tweet. Returns NO if the additional image will not fit
// within the character space currently available, or if the sheet has already
// been presented to the user.
- (BOOL)addImage:(UIImage *)image;

// Removes all images from the tweet. Returns NO and does not perform an operation
// if the sheet has already been presented to the user. 
- (BOOL)removeAllImages;

// Adds a URL to the tweet. Returns NO if the additional URL will not fit
// within the character space currently available, or if the sheet has already
// been presented to the user.
- (BOOL)addURL:(NSURL *)url;

// Removes all URLs from the tweet. Returns NO and does not perform an operation
// if the sheet has already been presented to the user.
- (BOOL)removeAllURLs;

// Specify a block to be called when the user is finished. This block is not guaranteed
// to be called on any particular thread.
@property (nonatomic, copy) TWTweetComposeViewControllerCompletionHandler completionHandler;

그럼 TWTweetComposeViewController 의 header파일을 살짝 살펴 볼까요?

setInitialText:
addImage: 
addUrl:
등이 보이는군요.

여기서 재밌는게, addImage:/addUrl:등이 BOOL 을 return 한다는것입니다.
Tweet이 140 자 까지만 허용하기 떄문에, img 혹은 url 을 추가할때마다, url character가 추가 되기 때문에,
더이상 추가할 수 없을때 "NO"를 return 할것 같습니다.
 while([composeViewController addImage:[UIImage imageNamed:@"circle"]] == YES) {
        NSLog(@"abcdefg");
    }
  이런식으로 코딩을 했을때, "abcdefg"가 한번만 호출되는것으로 봐서, 중복체크도 해주는 모양입니다.
그리고, image를 넣어준 후에 viewController를 노출 시키면, 이미지 Thumnail 과 남은 글자수가 140이 아닌 119로 변경됨을 알수 있습니다.

 

그럼, Twitter 계정정보는 어떻게 접근할까요?
아니, Twitter 계정이 로그인안되어 있으면, TWTweetComposeViewController 를 띄워봐야 헛일 아닙니까?

우선, 사용자 입장에서 Twitter 계정설정은 "아이폰 설정"에서 할 수 있습니다.

 
트위터 앱이 설치 되어 있지 않아도, Twitter 계정을 로그인 할 수 있습니다.

 
심지어 여러개의 Account를 설정할 수 있으며,
Twitter App이 설치되어 있는지 여부도 알려줍니다. ( 아이폰에서 확인 )
해당 스크린샷들은 모두 iOS5 Simulator 에서 캡쳐 한것으로, Simulator에서도 다 테스트 가능합니다.

다시 본론으로 들어 가서, 로그인이 되어 있지 않다면, ViewController가 어떻게 동작할까요?

 
트위터계정이 없다고, Settings 로 이동할것인지, 취소 할것인지 묻습니다. 취소하게되면, Modal 로 띄워진,
ViewController가  Dismiss 됩니다.


이건 좀 구리지 않나요? ViewController를 띄우기 전에 미리 알수 있으면 좋을것 같다는 생각이 드네요 ^^
 
UIKIT_CLASS_AVAILABLE(5_0) @interface TWTweetComposeViewController : UIViewController {
}

// Returns if Twitter is accessible and at least one account has been setup.
+ (BOOL)canSendTweet;

TWTweetComposeViewController의 + method 로 canSendTweet이 있습니다.
트위터 계정이 없다면, NO, 있다면 YES를 얻을 수 있습니다.

그럼,,현재 로그인된 Twitter 정보는 얻을 수 없을까?
아니, 난 Tweet 작성말고, TimeLine을 얻어 오고 싶은데, 이런것들은 어떻게 할수 있을까?

글이 길어 진것 같아 :)
이 질문들은 다음 블로깅에서 답해보도록 하겠습니다 ^^ 



예제 이미지 - "Local and Push Notification Programming Guide" 문서에서 발췌




Local Notification 이 무엇인가 ?

Local Notification 이전에, Push Notification 이라는 기능이 있었습니다.
개발사의 서버로부터 Apple Server 로 Notification을 발송하면, Apple Server 에서 iPhone 으로 Notification 을 "Push" 해주는 기능입니다.

Push Notification이 개발사마다 서버를 갖추어야 하고, 이래저래 세팅할것도 많습니다.
정말 필요한 경우가 아니라면 ( 예 : 카카오톡, 마이피플 ) Push Notification을 지원하는 일은 쉬운일이 아닙니다.
서버비용도 발생하고요,

그에 대안(!?) 으로 Local Notification 이라는 기능을 iOS 4.0 부터 지원하기 시작했습니다.

iPhone 내부에서, Notification 을 Scheduling 해주면, Apple Push Server의 도움 없이, Notification Alert을 발생시킬수가 있습니다.
Alram App 같은 것을 만들때 유용하게 사용할 수 있을것 같습니다 ^^

Local Notification이 없던 시절에는 반쪽짜리 Alarm App ( 항상 앱을 켜두어야 하는 ) 밖에 개발할 수 없었지요.


Local Notification Scheduling 하기.


1. UILocalNotification Object를 생성합니다. 

2. fireDate설정

3. 필요하다면 아래의 Property 를 세팅해줍니다.

// alerts
@property(nonatomic,copy) NSString *alertBody;      // defaults to nil. pass a string or localized string key to show an alert
@property(nonatomic) BOOL hasAction;                // defaults to YES. pass NO to hide launching button/slider
@property(nonatomic,copy) NSString *alertAction;    // used in UIAlert button or 'slide to unlock...' slider in place of unlock
@property(nonatomic,copy) NSString *alertLaunchImage;   // used as the launch image (UILaunchImageFile) when launch button is tapped

// sound
@property(nonatomic,copy) NSString *soundName;      // name of resource in app's bundle to play or UILocalNotificationDefaultSoundName

// badge
@property(nonatomic) NSInteger applicationIconBadgeNumber;  // 0 means no change. defaults to 0

4. UIApplication 의 Local Notifiactoin Method들을 통해 scheduling을 합니다.

@interface UIApplication (UILocalNotifications)

- (void)presentLocalNotificationNow:(UILocalNotification *)notification __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);

- (void)scheduleLocalNotification:(UILocalNotification *)notification __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);  // copies notification
- (void)cancelLocalNotification:(UILocalNotification *)notification __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
- (void)cancelAllLocalNotifications __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
@property(nonatomic,copy) NSArray *scheduledLocalNotifications __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);         // setter added in iOS 4.2

@end


Sample Code

- (IBAction) pressPushNow:(id)sender {
    UILocalNotification *localNotif = [[UILocalNotification alloc] init];
    localNotif.fireDate = [[NSDate date] addTimeInterval:10];
    localNotif.timeZone = [NSTimeZone defaultTimeZone];
    localNotif.alertBody = @"AlertBody";
    localNotif.alertAction = @"AlertAction";
    localNotif.soundName = UILocalNotificationDefaultSoundName;
    localNotif.applicationIconBadgeNumber = 1;
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
    [localNotif release];
}

Sample Screen Shot



자~~알 나옵니다 :)

 

- (NSString *)absoluteString;

- (NSString *)relativeString; // The relative portion of a URL.  If baseURL is nil, or if the receiver is itself absolute, this is the 



NSURL 에는 absoluteString/ relativeString 이라는 Method가 있습니다.

http:// 로 시작하는 web url 의 경우 absoulteSting을 사용하면 됩니다.
but, iOS 혹은 OSX 내의 File Path 인 경우에는 absoulteString method를 사용하면,,

file://localhost/Users/

 
우와 같이 이상한 Path 가 나타나서, NSFileManager등에서 Path를 제대로 인식하지 못합니다.

- (BOOL)isFileURL; // Whether the scheme is file:; if [myURL isFileURL] is YES, then [myURL path] is suitable for input into NSFileManager or NSPathUtilities.

 
NSURL 의 isFileURL 이라는 method에 FileURL 인 경우는 -(void) path 를 사용하라고 주석이 달려 있습니다.

Apple 이놈들... 이런건 absoulteString에 주석이 달려 있었어도 좋았을텐데 말이죠.. ㅎㅎ


http://www.tizag.com/xmlTutorial/xpathattribute.php

에 따르면 @ATTRIBUTE 로 찾으면 된다고 합니다.

그래서 "/problem/contents@type" 으로 찾아 보았으나.. 

XPath error : Invalid expression

/problem/contents@type

 
위와 같이 XPath Error  가 뜹니다.. ㅠㅡㅠ


그래서 'content' element를 구하고, 거기서 attribute를 가져오는 방식으로 시도 해보았습니다.

[xmlDoc nodesForXPath:@"/problem/contents" error:&error];
CXMLElement *theNode = [theNodes objectAtIndex:0];
[[[theNode attributeForName:@"type"] stringValue]; 


이렇게 하니 자~알 되는군요 !


원래 Unit Test 가 없던 프로젝트에, Unit Test를 집어 넣은 후 undefined symbol error에 봉착했습니다 ;ㅂ;

Test Code에서 사용하고 있는 Real Code를 찾을 수 없는 에러 인데..


위와 같이, Unit Test Target에 App Target이 Dependency 가 걸려 있음에도 불구하고 에러를 내뱉더군요 ;ㅂ;

구글님.. 아니, stackoverflow 님은 역시 모든것을 알고 계시더군요 ㅎㅎ

http://stackoverflow.com/questions/5783294/link-error-while-building-a-unit-test-target

범인은 바로 !!!


이녀석입니다. 

이 옵션이 뭐냐하면,  compile 된 excutable file 에서 symbol을 찾지 못하게 하는 옵션입니다.
그래서 Unit Test Target에서 symbols not found 에러가 발생한것이지요 ~



Unit Test를 실행시킬 Debug Profile 에서만 No 를 걸어 줍니다.
그러면 에러가 사라집니다 ㅎㅎ


NSString *myDescription = @"String";

[[UIColor colorWithRed:119/255.f green:119/255.f blue:119/255.f alpha:1.f] set];

[myDescription drawInRect:CGRectMake(10, 200, 85, 15

                     withFont:[UIFont systemFontOfSize:12.f]

                lineBreakMode:UILineBreakModeClip

                    alignment:UITextAlignmentCenter];


drawInRect:를 호출하기전에
[[UIColor blackColor] set];

이런식으로 호출해주시면 됩니다 ㅎㅎ 

그림 1.



Corner에 라운드가 처리된 ImageView가 필요해서, 만들게 되어 이러게 공유 합니다 ^^

구글링 해보면, 이런저런 기법들이 많이 나오는데요, 복잡한 해법들이 많은데,
저는 UIBeizerPath라는 녀석을 이용해서, 간단히 구현 하는 방법을 소개 해볼까 합니다.

아직 UIImageView를 이용하면 가장 좋을것 같은데, 방법을 따로 못찾아서, UIView를 상속 받아서, 구현하였습니다.
혹시 아시는 분 있으면 공유! 부탁드립니다 !

RoundRectedImageView.h

#import <Foundation/Foundation.h>

@interface RoundRectedImageView : UIView {

}

@property (nonatomic,retain) UIImage *image;

-(id) initWithImage:(UIImage *) aImage;

@end


RoundRectedImageView.m

#import "RoundRectedImageView.h"


@implementation RoundRectedImageView

@synthesize image = mImage;

-(id) initWithImage:(UIImage *) aImage {

    

    self = [super initWithFrame:CGRectMake(0, 0, aImage.size.width, aImage.size.height)];

    self.image = aImage;

    self.backgroundColor = [UIColor clearColor];

    return self;

}


-(void) drawRect:(CGRect)rect {

    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(16.0, 16.0f)];

    [path addClip];

    [self.image drawInRect:rect];

}

-(void) dealloc {

    self.image = nil;

    [super dealloc];

@end


코드양이 별로 없어서, 설명도 간략하겠군요 :)
drawRect: Method만 살펴 보시면 됩니다.

1. Round가 있는 사각형 UIBezierPath Object를 만든다.
2. addClip 을 호출하여, 앞으로 그려질때, clipping 되도록한다.
3. UIImage를 그려준다.



이렇게 해주면 그림.1 의 우측 그림과 같이 RoundRect 된 ImageView가 됩니다. ^^



 

다들 아시다 시피,  Objective-C 는 Abstract Class가 없습니다.

OO를 Design 할때, Abstract Class를 사용해야 할 일이 있습니다. ( 예 : Factory Method Pattern )

문법단에 없는 녀석이기에, Compiler Level에서는 Error를 발생시킬순 없지만,
RunTime에 강제하도록 하는 방법을 한번 살펴보도록 하겠습니다.
 
아래에 원문이 있으니, 한번씩 읽어보세요 :)

 http://stackoverflow.com/questions/1034373/creating-an-abstract-class-in-objective-c

원리는 간단합니다.

init 과 override를 강제 시키고 싶은 곳에서 Exception을 날린다.

 

-(id) init {

    @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] userInfo:nil];

    

    return nil;

}


 위의 예제 코드 와 같이 말이죠

해당 Class를 그냥 사용하면, 아래와 같이 RunTime Error가 발생됩니다.

 
장문의 Exception Throw를  그냥 매번 쓰고 있자니 답답하군요

prefix header에 아래와 같이 정의 합니다.

#import <Availability.h>


#ifndef __IPHONE_3_0

#warning "This project uses features only available in iPhone SDK 3.0 and later."

#endif


#ifdef __OBJC__

    #import <UIKit/UIKit.h>

    #import <Foundation/Foundation.h>

#endif


#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] userInfo:nil]

 
그리고 -(id) init은 아래와 같이 변경

-(id) init {

    mustOverride();

    return nil;

}

 
자 이러면 쉽게 되는군요.

compile 단에서 이루어지는게 아니라, 아쉽지만,
그렇다고 아무런 안전장치 없이 또 사용하는것보단 나은것 같습니다 ^^

사족으로 Java의 Interface 대신 Protocol 을 사용하고 있긴 하지만, Interface와 abstract class는 문법상으로 좀 제공해줬으면 합니다.
그럼 좀 더 great할것 같군요 :) 

+ Recent posts