Scale slides up to fit display 옵션을 아시나요? 



프레젠테이션을 하려는데, 너무 작게 나와, 당황하신적 있으신가요? 
제가 얼마전에 그랬습니다. ;ㅂ; 

프레젠테이션을 마치고, 혼미한 정신을 챙기후에 찾아 보니, Scale slides up to fit display 라는 옵션이 있더군요.
그리고 무려, Default 로 Off 입니다. 
Default On 이라도 참 좋을텐데,


Scale slides up to fit display - Off

Scale slides up to fit display - On


위는 iMac 27 인치에서, 옵션 On/Off를 테스트 한것입니다.
차이가 눈에 확 보이시죠? ^^


 



  
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을 얻어 오고 싶은데, 이런것들은 어떻게 할수 있을까?

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

NSView 는 UIView 처럼 animation method를 지원하지 않기 때문에, 
CAAnimation을 활용해 Animation을 적용할 수 있습니다.
아래 처럼 말이죠..
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    [basicAnimation setRepeatCount:INT_MAX];
    basicAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 0.1)];
    basicAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)];
    [basicAnimation setDuration:0.5];
    [basicAnimation setAutoreverses:YES];
    [self.loadingImageView.layer addAnimation:basicAnimation forKey:@"transform"]; 

그런데, 이상하게 Animation 이 적용이 시작되지 않는 것입니다.
그래서 이래저래 살펴 보니..

-[LoadingView startAnimation],65th - layer = (null)

 
NSView에 달려 있어야할, CALayer가 null 로 return 되어 적용되지 않았던 것입니다.
그래서 NSView.h를 찬찬히 살펴 보았습니다.
- (void)setWantsLayer:(BOOL)flag NS_AVAILABLE_MAC(10_5);
- (BOOL)wantsLayer NS_AVAILABLE_MAC(10_5);

setWantsLayer라는 Method가 있다는것을 발견하고 실행보니, 
아래와 같이 잘됩니다 :)

-[LoadingView startAnimation],65th - layer = _NSViewBackingLayer(0x100525800) a={0, 0} p={0, 0} b=(0,0,87,86) superlayer=0x0 TILED=no


 

http://www.mobclix.com/appstore
http://majicjungle.com/majicrank.html
http://applyzer.com
http://148apps.biz/app-store-metrics/
http://topappcharts.com/
http://www.yappler.com/Apple-iPhone-App-Store-Stats/
 

1. mobclix


한번 써볼려고 하니 :( 정검중이군요.

2. majicjungle.com



 매직 정글은 앱으로 보여주는군요

3. applyzer.com

 

find ./ -name "Thumbs*" -print0 | xargs -0 ls


-print 0 on "find" 

"It prints the pathname of the current file to standard output, followed by an ASCII NUL character" 

-0 on "xargs"

"Change xargs to expect NUL (``\0'') characters as separators, instead of spaces and newlines.
  This is expected to be used in concert with the -print0 function in find(1)." 


xargs 와 find 를 조합하라고, -0 와 -print0 를 조합해서 사용하라고, man page에 소상히 적어 두었군요 :)

 



예제 이미지 - "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에 주석이 달려 있었어도 좋았을텐데 말이죠.. ㅎㅎ


TWUI를 활용하여 작업하고 있는데,
위와 같이 View가 깨지는 상황이 발생했습니다.
해결하고 보니, 참 우습군요 ;ㅂ;

TUITableView 를 사용하고 있는데,
TUITableView에서 Row Count 가 0 인경우 즉 아무것도 그리지 않는 경우에 위와 같은 현상이 발생하였습니다.

그래서

    tableView.backgroundColor = [TUIColor colorWithWhite:0.9 alpha:1.0];

 
BackgroundColor를 지정해주고 나니, BOOM!

 
잘 나옵니다.

TWUI Framework의 view들은 default로 아무것도 그리지 않기에,
view hierachy 상에 아무것도 그리지 않는 곳이 생긴다면 위와같이 View가 깨져버리는 현상이 나타납니다. -_-

혹시 사용하시는 분이 있다면 삽질 하지 마시길 바랍니다 :)
 

일전에 소개 해드린 TWUI를 살짝 분석한걸 얘기해볼까 합니다.

http://taehoonkoo.tistory.com/190

아래 코드는 TWUI Sample에서 긁어온 소스입니다.


TUINSView *tuiTableViewContainer = [[TUINSView alloc] initWithFrame:b];

[tableViewWindow setContentView:tuiTableViewContainer];

[tuiTableViewContainer release];

ExampleView *tableExample = [[ExampleView alloc] initWithFrame:b];

tuiTableViewContainer.rootView = tableExample;

[tableExample release];



현재 TUINSView와 ExampleView Class 의 Object를 생성하고 있는데,
여기서 ExampleView 는 TUIView 의 sub Class 입니다.

그럼 말하고자 하는 TUINSView 와 TUIView 의 object들인 셈이죠.

결론 부터 말하고 넘어가면, TUIView는 NSView의 sub class 가 아닙니다.
NSResponder의 SubClass 입니다.

이말인 즉슨, NSView에 바로 addSubView해서 사용할수 없는것이죠.

그래서 TUINSView라는 녀석이 있고, 이녀석이 Container역할을 해서
NSView와 TUIView 를 연결 시켜 주게 됩니다.

구현체를 좀더 살펴보니,
TUIView 가 가지고 있는  CALayer Object를 TUINSView의 layer에 addSubLayer하는 형식으로 되어 있습니다.
iOS 스타의 Animation을 구현하기 위해서, NSView자체보다 CALayer를 사용한 것같은데..
직관성이 상당히 떨어 지는 군요.
NSView를 subclassing 하고, 해당 NSView의 CALayer를 활용하는 방식도 가능했을텐데.. 말이지요.

"왜 이런 구조 여야만 했는가? " 에 대한 질문은 아직 깊이있게 살펴보지 않아서,.. ㅎㅎ
좀더 알게 되면 Posting하도록 하겠습니다.






 

+ Recent posts