티스토리 툴바



2011/09/03 16:39

SMS, E-mail 전송


 PickerView는 viewController에서만 호출된다

//MPMediaPicker(아이팟 리스트)
//MPMediaPickerControllerDelegate 추가

- (void)showMusicPicker
{
MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeMusic];
picker.delegate = self;
picker.allowsPickingMultipleItems = YES;
picker.prompt = @"MPMediaPicker";
[self presentModalViewController:picker animated:YES];
[picker release];
}

//노래 선택시
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
// Play the item using MPMusicPlayer
MPMusicPlayerController* appMusicPlayer = [MPMusicPlayerController applicationMusicPlayer];
[appMusicPlayer setQueueWithItemCollection:mediaItemCollection];
[appMusicPlayer play];              
    
// Play the item using AVPlayer
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:url];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[player play];
[self dismissModalViewControllerAnimated:YES];
}

//완료 버튼시
- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker
{
[self dismissModalViewControllerAnimated:YES];
}

//문자( SMS) 보내기
#import <MessageUI/MFMessageComposeViewController.h>
MFMessageComposeViewControllerDelegate

-(void)sendSMS
{
if (![MFMessageComposeViewController canSendText]) {
errorAlert = [[UIAlertView alloc] initWithTitle:@"알림"
message:@"SMS를 보내기를 할 수 없습니다."
   delegate:self 
  cancelButtonTitle:nil
  otherButtonTitles:nil];
[NSTimer scheduledTimerWithTimeInterval:1.5f target:self selector:@selector(clearAlert:) userInfo:nil repeats:false];
[errorAlert show];
return;
}
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = self;
picker.body = @"테스트"
[self presentModalViewController:picker animated:YES];
[picker release];
}

- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
{
[controller dismissModalViewControllerAnimated:YES];
}

//메일 보내기
#import <MessageUI/MFMailComposeViewController.h>
MFMailComposeViewControllerDelegate

-(void)sendEmail
{
    if(![MFMailComposeViewController canSendMail])
{
errorAlert = [[UIAlertView alloc] initWithTitle:@"알림"
message:@"아이폰 메일 설정을 확인하시기 바랍니다."
   delegate:self 
  cancelButtonTitle:nil
  otherButtonTitles:nil];
[NSTimer scheduledTimerWithTimeInterval:1.5f target:self selector:@selector(clearAlert:) userInfo:nil repeats:false];
[errorAlert show];
return;
}
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc]init];
picker.mailComposeDelegate = self;
[picker setSubject:@"Message Title "];
[picker setMessageBody:@"Message body Text" isHTML:NO];
[self presentModalViewController:picker animated:YES];
[picker release];
}

- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
[controller dismissModalViewControllerAnimated:YES];
}

저작자 표시
Trackback 2 Comment 1
2011/08/30 04:24

Stuff you learn from reverse-engineering Notes.app


Stuff you learn from reverse-engineering Notes.app

I’m adding note taking to iWoman 2.0 and so I was thinking which metaphor would be one that users would understand and like. So I decided to mimic the look and feel of the built-in Notes app.

There where quite a few interesting things I had to learn and figure out and in this article I am going to share them with you. These are techniques that you can use in many other scenarios besides of making your own Notes view controller.

I was clear from the start that I needed to use a UITextView for the editing itself. Notes.app has several specialities that we have to figure out if we want to capture the look.

  • Font is Marker Felt Thin, Size 19.
  • each line sits on top of a grayish blue horizontal line
  • the text view has a padding at all sides, something that the standard UITextView does not give us
  • There is padding at the top, but still the text goes up to the corner
  • the horizontal lines move together with the text and never end towards the bottom
  • two static vertical brown lines line up with markings at the top and the bottom
  • the body of the notes is not just a yellow gradient, but has some structure and speckles
  • scrolled text disappears behind the images for the top and bottom edge
  • The text view needs to be dynamically resized when the keyboard appears or disappears to prevent hiding of text.

Those where the challenges, in this YouTube video you see my solution and an overview of how I achieved it. More in-depth reasoning you find below.

Setting the font and size for the text view is easy, much harder is to a never-ending sheet with lines to move in synch. My first gut feeling was to simply attach it as a subview to the text view which itself is a descendant of UIScrollView. So it’s possible to insert views as subviews to it and they will get moved together with the contents of the text. If you want to make sure that you don’t disturb the interaction with the text simply add it at index 0, that is behind all views that might be subviews of the scroll view.

Though for this case this approach would not work as we cannot set a padding for the text view. So we have to move the frame of the text view that the text is at a nice distance from the edges. If we add the lines view now we see that it gets clipped at the edges of the text view. Not what we had in mind.

I started out with just a screenshot of the Notes.app where I removed some UI elements, but then I discovered that I needed to draw the lines myself and that I had to split it in three: top edge, bottom edge and body of the note. For the body I had a friend remove the lines from the screenshot with Photoshop.

UITextView is an extended UIScrollView

Since UITextView is a child of UIScrollView all the scrollview delegate methods are also passed to any delegate you might set. So what I did was to simply implement a scrollViewDidScroll method which gets called every time the text view moves. Then I only need to set a transform for the lines view according to the current contentOffset. Having made sure that the top and bottom edge images are on top of the lines view cause them to go underneath those, so I don’t need an extra clipping view to make sure the lines don’t go over the images.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
	CGPoint offset = scrollView.contentOffset;
	linesView.transform = CGAffineTransformMakeTranslation(0, -offset.y);
}

The lines themselves are just regular UIView where I filled in the drawRect to draw horizontal lines, nothing fancy there. Same is true for the second view I have for the vertical lines. I’ve tried to combine the lines into one view, but there I had the problem when you pull down the text view, the vertical lines would break and a whole would appear at the top. So I settled for two line view: one static for the vertical lines and one moving via transform in tandem with the text view. Obviously both have to be non-opaque and have a transparent background.

The next interesting trick necessary becomes apparent when you actually start editing. The keyboard will cover the lower half of the text view causing text to be hidden. My first idea was to resize it but here you have the option to either guess the animation duration and hight or you google it. I found the optimal solution to be present there, onStack Overflow.

Resizing in synch with the Keyboard

Whenever the keyboard appears or disappears notifications are sent that also contain a dictionary giving you all relevant animation parameters. You can simply take those and your animation will be perfectly synchronized with the movement of the keyboard. Be it a toolbar which you want to move from the bottom us so that it rides on top of the keyboard. Or something simple as changing a text view’s frame.

So we register for the name:UIKeyboardWillShowNotification and name:UIKeyboardWillHideNotification when our notepad appears on screen and unregister when it disappears.

- (void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liftMainViewWhenKeybordAppears:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(returnMainViewToInitialposition:) name:UIKeyboardWillHideNotification object:nil];
}
 
- (void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
 
	[_textView resignFirstResponder];
}

Then for the real meat we implement our methods that get called when these events occur.

- (void) liftMainViewWhenKeybordAppears:(NSNotification*)aNotification{
    NSDictionary* userInfo = [aNotification userInfo];
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    CGRect keyboardFrame;
    [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&amp;animationCurve];
    [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&amp;animationDuration];
    [[userInfo objectForKey:UIKeyboardBoundsUserInfoKey] getValue:&amp;keyboardFrame];
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:animationDuration];
    [UIView setAnimationCurve:animationCurve];
    [self.textView setFrame:CGRectMake(self.textView.frame.origin.x, self.textView.frame.origin.y,
        self.textView.frame.size.width, self.textView.frame.size.height - keyboardFrame.size.height + 5.0)];
    [UIView commitAnimations];
}
 
- (void) returnMainViewToInitialposition:(NSNotification*)aNotification{
    NSDictionary* userInfo = [aNotification userInfo];
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    CGRect keyboardFrame;
    [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&amp;animationCurve];
    [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&amp;animationDuration];
    [[userInfo objectForKey:UIKeyboardBoundsUserInfoKey] getValue:&amp;keyboardFrame];
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:animationDuration];
    [UIView setAnimationCurve:animationCurve];
    [self.textView setFrame:CGRectMake(self.textView.frame.origin.x, self.textView.frame.origin.y,
        self.textView.frame.size.width, self.textView.frame.size.height + keyboardFrame.size.height - 5.0)];
    [UIView commitAnimations];
}

You can see that we are getting our values for animation curve, duration and the frame of the keyboard from values contained in the dictionary that the iPhone is so friendly to provide together with those notifications. Really cool stuff.

iPadding

If you have the textView in full view size then you find that it’s text is too close for comfort to the edges. Now as a scrollview descendant you might have guessed that setting the contentInset might solve this problem, but it does not for horizontal padding. The problem with insetting left or right is that you increase the contentSize causing horizontal scrolling to become possible. But we only want vertical scrolling to occur. That’s why we change the frame of the text view such that there is enough space at the left and right.

Padding DOES however work nicely to inset the text from the top. The text view align with the bottom corner of the top edge image. By setting a contentInset for top we can get the spacing that we want, but still be able to have the text go to the very corner of the virtual page.

We’re almost done, the final challenge appears if you add a couple of lines of text to your notepad. The lines view is not yet resized and so the lines just stop.

Peeking under UITextView’s Shirt

Text views somehow know to dynamically resize the content. So my solution for the resizing problem is to add an observer for changes to the contentSize property of UITextView. This technique is called KVO (key value observing) and you can add such a hook to any object so that every time there is a change some method is informed about it.

So in the loadView, where I am creating my view hierarchy, I have this line to install my KVO hook. I choose to only receive the new value by specifying option NSKeyValueObservingOptionNew. If you add a binary OR with NSKeyValueObservingOptionOld you also get sent the value before the change.

[_textView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];

The best way to remove the hook is in dealloc. Removing is necessary because otherwise you get a warning on the console that textview was deallocated with a KVO observer still registered.

[_textView removeObserver:self forKeyPath:@"contentSize"];

All KVO observing for simple values is done in observeValueForKeyPath, as shown below. You receive a change dictionary containing all the before and after values that you chose to get.

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	CGSize newSize = [[change objectForKey:@"new"] CGSizeValue];
 
	linesView.frame = CGRectMake(linesView.frame.origin.x, linesView.frame.origin.y, newSize.width + 124.0, newSize.height + 480.0);
}

Since there is no visible border for the lines view I don’t need to animate the change. I am making the width and height large enough so that the lines view will aways cover the entire screen even if you drag it up so that the bottom rubberbanding occurs.

Conclusion

I’m not as good as a designer as I am a programmer. If you are like me then it is great for practicing your synthetic skills. By synthetic I mean to find a UI that works and then synthesize it out of what you know. You grow as a skilled programmer by finding out the inherent challenges that you have to figure out so that the whole puzzle and UI experience comes together.

One question left unanswered is if Apple will object to me shamelessly reusing their notepad metaphor. If they reject iWoman 2.0 because of this then it’s should be not too difficult to get a designer to create the 3 images. Maybe I’d like pink notepaper for iWoman anyway, what do you think?

I polished the DTNotePadViewController into a component that I can reuse with any kind of app that requires lined note paper. To be able to do so I needed to add a delegation mechanism for and dual mode behavior so that I present it modally for new notes and via navigation controller for editing existing notes. That’s now here.

저작자 표시
Trackback 0 Comment 0
2011/08/29 02:28

UIActionSheet bottom bar 때문에 cancel 버튼 클릭 안 될 때


showInView:[self.view window] 로 로드한다.

As noted in the Apple developer docs for showFromTabBar, it says:

The style of the animation depends on the style of the toolbar, not the receiver.

When I use this method as opposed to the showInView method, it slides in from above, not from below.

I am using showFromTabBar because if I don't, the cancel button is partially untouchable due to some funkiness with the tab bar, which is behind it(pfft).

Question: how do you make the action sheet animate in from the bottom, as it would normally with showInView?

Note: my problem with the tab bar getting in the way has been solved by passing the entire window as the UIView via showInView as follows:

[filterActionSheet showInView:[self.view window]];
저작자 표시
Trackback 0 Comment 0
2011/08/28 14:47

NSFileManager


http://fra3il.tistory.com/73

개념이 맞는지 모르겠지만.. -_ -)
프로젝트에 첨부한 리소스들은 컴파일 과정을 거치면서 mainBundle 에 위치하게 된다.
기본적으로 읽기 전용이며 삭제, 수정을 위해서는 Documents 로 복사한 이후에 사용해야 한다.
참고로 NSSearchPathForDirectoriesInDomains 를 이용해서 경로를 가져오는 것을 추천한다. )

mainBundle 의 파일 목록 가져오기

- (void)mainBundleList {

NSString *path = [[NSBundle mainBundlebundlePath];

NSArray *list = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:path error:nil];

NSLog(@"%@", list);

// .jpg  끝나는 파일만 보여준다

NSArray *onlyJPG = [list filteredArrayUsingPredicate:[NSPredicatepredicateWithFormat:@"SELF ENDSWITH '.jpg'"]];

NSLog(@"%@", onlyJPG);

}


Documents 의 파일 목록 가져오기

- (void)documentsList {

NSString *path = [NSString stringWithFormat:@"%@/Documents/",NSHomeDirectory()];

NSArray *list = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:path error:nil];

NSLog(@"%@", list);

}


mainBundle 의 파일을 Documents 로 복사하기

- (void)copyFile {

// documents 폴더로 복사할 파일

NSArray *fileList = [NSArray arrayWithObjects:@"IMG_0657.MOV",@"maltese.jpg"nil];

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMaskYES)objectAtIndex:0];

NSFileManager *fileManager = [NSFileManager defaultManager];

for (NSInteger i = 0; i < [fileList count]; i++) {

NSString *fileName = [fileList objectAtIndex:i];

NSString *filePath = [[[NSBundle mainBundleresourcePath]stringByAppendingPathComponent:fileName];

NSString *toPath = [documentsDirectorystringByAppendingPathComponent:fileName];

// documents 폴더에 파일이 존재하지 않으면 복사

if (![fileManager fileExistsAtPath:toPath]) {

if ([fileManager copyItemAtPath:filePath toPath:toPatherror:nil]) {

NSLog(@"1");

else {

NSLog(@"0");

}

}

}

}


저작자 표시
Trackback 0 Comment 0
2011/08/28 06:25

iphone libz 예제 sample


압축 해제
#import 
"ZipArchive.h"

NSString *filePath = @"/Users/alex/Test/04/zTest/test.zip";

ZipArchive *z = [[ZipArchive allocinit];

 

[z UnzipOpenFile:filePath];

[z UnzipFileTo:@"/Users/alex/Test/04" overWrite:YES];

[z UnzipCloseFile];

압축

 NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)

                       objectAtIndex:0] stringByAppendingString:@"/CalendarEntry.sqlite"];

    

    NSString *resultPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)

                             objectAtIndex:0] stringByAppendingString:@"/result2.zip"];

    

    

    ZipArchive *z = [[ZipArchive alloc] init];

    

    [z CreateZipFile2:resultPath];

    [z addFileToZip:path newname:@"Result.sqlite"];

    [z CloseZipFile2];

    

    [z release];

 

저작자 표시
Trackback 0 Comment 0
2011/08/28 06:23

iphone libz 빌드 방법


Add libz to your project. To do this, follow these steps in Xcode:

  1. Open your project, select your project target and then click the blue project info icon on your toolbar (or press ⌘I)
    Example 1
  2. Click the + button in the lower-left corner of the screen to add a library, scroll down to the bottom of the library list and select libz.dylib; click the Add button.
    Example 2
  3. Once you've clicked add, you will see that the library name appears in oyur list of Linked Libraries. You will now be able to build your project without linking errors.
    Example 3

You can use this same method to add any library from the list. If the library does not appear on the list, then you know that it is not part of the standard iPhone SDK and you may need to rethink your solution or build the library statically yourself and link your target to that (if even possible).

저작자 표시
Trackback 0 Comment 0
2011/08/28 06:18

iphone 파일 경로 얻기


보안을 위해 샌드박스 내에서만 동작을 수행할 수 있는데 

(애플리케이션 번들을 수정할 수도 없음)

 

어플리케이션이 설치될때 유일한 식별자를 시스템에서 할당받으며 홈 디렉토리경로가 생성되는데

다음과 같은 구조를 가진다 /ApplicationRoot/ApplicationID


이렇게 생성된 홈 디렉토리가 접근할 수 있는 최상위 경로이다.

 

상수 디렉토리 설명 변경 iTunes
 백업
NSApplicationDirectory <어플 홈>/AppName.app 앱을 포함하는 번들 디렉토리 X X
NSDocumentDirectory <어플 홈>/Documents 사용자 데이터. 백업 등 맘대로 써도 됨 O O
  <어플 홈>/Library/Preferences 앱 의존 설정 저장

O ( NSUserDefault 클래스

CFPreferences API 이용해 읽고 쓸것 )

O
NSCashesDirectory <어플 홈>/Library/Caches 캐시 데이터 기록 O X
NSTemporaryDirectory(); <어플 홈>/tmp 캐시랑 비슷하나 어플이 실행중이 아닐때면 언제든 지워질 수 있다. O X

 


//***아래는 도큐먼트 경로를 얻는

NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)

objectAtIndex:0] stringByAppendingString:@"/people.txt"];


//텍스트 파일 읽는다. 경로에 파일이 없으면 nil들어옮
NSString *contents = [NSStringstringWithContentsOfFile:path encoding:NSUTF8StringEncodingerror:nil];

 

if (contents == nil)

{

NSString *path = [[[NSBundlemainBundle] resourcePath] stringByAppendingString:@"/bacakup.txt"]; //안 읽어지면 번들에 있는거 읽게

}

//쓰기

[@"ㅁㅁㅁㅁ" writeToFile:path atomically:NO

encoding:NSUTF8StringEncodingerror:nil];

 

 



프로퍼티 리스트로 저장하기


 

다음 클래스만 저장할 수 있다.(콜렉션이면 가지고 있는 객체도 역시 허용된 클래스여야 한다.)


NSData,NSString,NSArray,NSDictionary,NSDate, NSNumber

(Mutalbe 포함)

 

 

//예시 (words 가 저장할 인스턴스 /dictionary.plist 가 저장할 파일 이름)

//경로 얻기

 

NSString* path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)   objectAtIndex:0]stringByAppendingString:@"/dictionary.plist"];

 

//저장

 

[wordswriteToFile:path atomically:YES]; //atomically옵션은 만약을 위해 사본에 저장한 후 성공하면 원본과 바꾼다.

 

//읽기

words = nil;

if( [[NSFileManager defaultManager] fileExistsAtPath:path] ) //파일 존재 여부 확인

{

words = [[NSDictionaryalloc] initWithContentsOfFile:path];

}

 

if (words == nil)

words = [NSMutableDictionarynew];

 

 


//아래 처럼 nsdata로 변환해서 저장할 수 있다 포맷은 NSPropertyListXMLFormat_v1_0 도 있음(뭔 차인지...)

//쓰기

NSData *xmlData = [NSPropertyListSerializationdataFromPropertyList:words format:NSPropertyListBinaryFormat_v1_0errorDescription:&error];

 

if(xmlData) 

 

{

 

[xmlData writeToFile:path atomically:YES]; 

 

}

 

else

 

 

NSLog(error); 

 

[error release]; 

 

}

 


//읽기

 

 

NSData* xmlData = [NSData dataWithContentsOfFile:path];

 

NSString *error; 

 

NSPropertyListFormat format;

 

self.words = [NSPropertyListSerializationpropertyListFromData:xmlData 

mutabilityOption:NSPropertyListMutableContainersAndLeaves

format:&formaterrorDescription:&error];

 



아카이버를 이용


 

@interface archiveClass : NSObject

{

NSString * str;

NSDate * date;

NSIntegerinteger;

}


@property(nonatomic,retain)NSString* str;

@property(nonatomic,retain)NSDate* date;

@property(nonatomic)NSInteger integer;


@end


@implementation archiveClass


@synthesize str, date, integer;


-(void)encodeWithCoder:(NSCoder*)coder

{

[coder encodeObject:strforKey:@"str"];

[coder encodeObject:dateforKey:@"date"];

[coder encodeObject:[NSNumbernumberWithInt:self.integer] forKey:@"integer"];

//[coder encodeInteger:integer forKey:@"integer"]; //int nsnumber 안바꾸고 이렇게 저장하는것도 가능함

}


-(id)initWithCoder:(NSCoder*)coder

{

if ( self = [super init] )

//if ( self = [super initWithCoder:coder] ) //nscoding 프로토콜을 따르는 클래스를 상속받았다면

{

self.str = (NSString*)[coder decodeObjectForKey:@"str"];

self.date = [coder decodeObjectForKey:@"date"];

integer = [[coder decodeObjectForKey:@"integer"] intValue];

//integer = [coder decodeIntForKey:@"integer"];

}

 

returnself;

}

@end

 

//실 사용 예

 

archiveClass * test = [[archiveClassalloc] init];

 

 

 

[test default_data];

 

 

 

//경로

 

NSString * path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]

 

stringByAppendingFormat:@"/archive.data"];

 

 

 

//저장( encodeWithCoder 가 호출되게 된다.)

 

NSData * data = [NSKeyedArchiverarchivedDataWithRootObject:test];

 

[data writeToFile:path atomically:YES];

 

 

 

//읽기( initWithCoder 가 호출되게 된다.)

 

if ( [[NSFileManager

 

defaultManager] fileExistsAtPath:path] )

 

{

 

NSData * read_data = [NSData dataWithContentsOfFile:path];

 

archiveClass * read;

 

if ( read_data )

 

{

 

read = [NSKeyedUnarchiver unarchiveObjectWithData:read_data];

 

 

 

NSLog(@"%@", read);

 

}

 

}

 

 


encodeObject, decodeObjectForKey 에서 키를 사용하지 않을수도 있다.

단 이경우 디코딩시 순서가 인코딩 순서와 일치해야 한다.

그리고 키로 저장과 키없이 저장 조합도 되는듯... 단 이경우 순서는... 키없이 저장만 순서가 맞으면 되는듯 함


 




 

 

 


 


 


 


 











저작자 표시
Trackback 0 Comment 0
2011/08/28 06:10

iphone 압축 zlib 라이브러리 예제


http://code.google.com/p/ziparchive/
저작자 표시
Trackback 0 Comment 0
2011/08/28 04:44

[iPhone] Files and Networking


출처 : http://theeye.pe.kr/entry/iPhone-Development-Files-and-Networking

iPhone OS에서 실행되는 어플리케이션은 Core OS와 Core Services 프레임워크를 분류하여 로컬 파일시스템과 네트워크에 접근할 수 있습니다.

또한 사용자의 데이터를 저장하거나 다음 사용을 위한 어플리케이션의 상태를 저장하기 위해 파일을 읽고 쓰는 것이 가능합니다.

네트워크에 접근함으로써 네트워크상의 서버와 통신하거나 데이터를 주고 받는 등의 원격 작업을 수행할 수 있는 기능을 갖출 수 있습니다.

iPhone OS의 파일은 사용자들의 미디어파일과 개인정보와 같은 파일들과 함께 Flash 메모리를 공유하여 사용하고 있습니다.

보안상의 이유로 개발자가 만든 어플리케이션은 자신이 위치한 자신의 디렉토리에 국한하여 제한적인 데이터 읽기, 쓰기만을 할 수 있습니다.

Commonly Used Directories

보안을 위해 어플리케이션은 몇몇에 불과한 디렉토리에 접근하여 데이터와 환경 설정을 읽고 쓸수 있습니다.

어플리케이션이 장치에 설치될때 어플리케이션을 위한 홈 디렉토리가 생성됩니다. 

다음은 그 내부의 중요한 서브디렉토리의 설명입니다.

<Application_Home>/AppName.app

이 디렉토리는 어플리케이션 자신을 포함하는 Bundle 디렉토리입니다.
실행중에 이 디렉토리 내부의 파일을 변경할 수 없습니다. 
iPhone OS 2.1 혹은 그 이후의 버젼은 iTunes에서 이 디렉토리를 백업하지 않습니다.
그러나 어플리케이션을 구매했을 당시의 초기 싱크 과정에서는 컨텐츠 내용이 복사됩니다.

<Application_Home>/Documents/

이 디렉토리는 어플리케이션이 특정 데이터를 저장하기 위해 사용되는 공간입니다.
사용자의 데이터나 다른 정보를 정기적으로 저장할 수 있습니다.
이 디렉토리 내부의 컨텐츠는 iTunes에 의해서 백업 됩니다.

<Application_Home>/Library/Preferences

이 디렉토리는 어플리케이션의 특정 설정 파일을 포함합니다.
하지만 환경 설정 파일을 직접 생성하여서는 안되고 NSUserDefaults 클래스나 CFPreferences API를 사용해야 합니다.
이 디렉토리 내부의 컨텐츠는 iTunes에 의해서 백업 됩니다.

<Application_Home>/Library/Caches

이 디렉토리는 어플리케이션의 실행때마다 지속적으로 사용해야 하는 파일을 읽고 쓰기 위해 사용합니다.
개발한 어플리케이션은 일반적으로 이곳에 파일을 추가하고 삭제하여야 합니다.
iTunes는 전체 복원시에 이 디렉토리의 내용을 모두 제거 합니다.
그러므로 어플리케이션은 필요할때마다 이곳의 파일을 생성해 낼 수 있어야 합니다.
iPhone OS 2.2 혹은 그 이후의 버젼은 iTunes에서 이 디렉토리를 백업하지 않습니다.

<Application_Home>/tmp/

이 디렉토리는 어플리케이션이 실행때마다 지속될 필요가 없는 임시적인 파일을 읽고 쓰기 위해 사용합니다.
어플리케이션이 실행중이 아닐때 시스템은 파일이 더 필요한가를 판단하여 더이상 필요하지 않다고 판단이 되면 삭제합니다.
iPhone OS 2.1 혹은 그 이후의 버젼에서는 iTunes에서 이 디렉토리를 백업하지 않습니다.

Backup and Restore

어플리케이션이 백업과 복원을 위해 따로 준비해야 하는 것은 없습니다.

iPhone OS 2.2 혹은 그 이후의 버젼은 장치가 컴퓨터에 연결되어 싱크 될때 다음을 제외한 나머지 모든 파일을 증분 백업 합니다.
<Application_Home>/AppName.app
<Application_Home>/Library/Caches
<Application_Home>/tmp

어플리케이션이 매우 큰 파일을 생성하거나 매우 빈번하게 파일 엑세스를 한다면 /Documents가 아닌 /Library/Caches를 이용하는 것이 좋습니다. /Documents에 저장하게 되면 백업/복원 작업시에 시간이 더 걸리게 되는 문제점이 될 수도 있습니다.

Getting Path to Application Directories

다양한 레벨의 시스템에서 어플리케이션의 샌드박스의 위치를 알아내기 위한 다양한 프로그래밍 방식이 있어왔습니다.

하지만 Cocoa는 이러한 경로를 탐색하기 위해 다양한 프로그래밍 인터페이스를 제공합니다.

NSHomeDirectory(Foundation 프레임워크에 포함) 함수는 홈디렉토리의 경로뿐만 아니라 Documents, Library 그리고 tmp같은 디렉토리 경로를 손쉽게 얻어올수 있습니다.

또한 추가적으로 NSSearchPathForDirectoriesInDomains와 NSTemporaryDirectory함수를 사용하여 정확한 Document, Caches, tmp 디렉토리 경로를 얻어올 수 있습니다.

이 NSSearchPathForDirectoriesInDomains 함수를 사용하여 어플리케이션과 연관된 전체 경로를 알 수 있습니다.

iPhone OS에서 이 기능을 사용할려면 적절한 검색 경로를 첫번째 매개 변수로, NSUserDomainMask를 두번째 매개 변수로 지정합니다.

일반적으로 자주 사용되는 경로 상수는 다음과 같은 것들이 있습니다.

NSDocumentDirectory
<Application_Home>/Documents

NSCachesDirectory
<Application_Home>/Library/Caches

NSApplicationSupportDirectory
<Application_Home>/Library/Application Support


어플리케이션의 Documents/ 디렉토리를 찾기 위해 다음과 같이 사용합니다.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

위의 결과값을 찍어보면 /var/mobile/Applications/해쉬코드/Documents 와 같은 값이 나옵니다. 해당 경로가 어플리케이션 Documents 디렉토리의 절대 경로가 되겠죠.

두번째 인자인 NSUserDomainMask말고 다른것을 사용할 수 있습니다. 

NSSystemDomainMask와 첫번째 인자로 NSApplicationDirectory로 검색하여 보면 /Applications 라는 시스템 경로를 얻을 수 있습니다.

Reading and Writing File Data

iPhone OS는 파일을 읽고 쓰고 관리할 수 있도록 몇가지 방법을 제공하고 있습니다.

Foundation Framework :
  • 어플리케이션이 Property List를 사용한다면 NSPropertyListSerialization API를 사용하여 NSData로 변환이 가능합니다.  이렇게 변환 후에 NSData의 메서드를 이용하여 데이터를 쓸 수 있습니다.
  • 어플리케이션의 Model객체가 NSCoding 프로토콜을 채택하였다면 NSKeyedArchiver 클래스를 이용하여 객체를 저장할 수 있습니다.
  • Foundation 프레임워크는 컨텐츠 파일의 랜덤 엑세스를 위한 NSFileHandle 클래스를 제공합니다.
  • Foundation 프레임워크안의 NSFileManager는 시스템에 있는 파일을 생성하고 조작하는 메서드를 제공합니다.

Core OS calls :

  • fopen, fread, fwrite 를 호출하여 데이터를 엑덤 엑세스를 통해 읽고 쓸 수 있습니다.
  • mmap과 munmap을 호출하여 효과적으로 큰 파일을 메모리로 로드하거나 그안의 컨텐츠에 접근할 수 있습니다.

Reading and Writing Property List Data

Property List는 캡슐화 되어있는 데이터를 말합니다. 배열, 문자열, 날짜, 이진데이터, 숫자 및 Boolean 값을 포함합니다.

다른 예로 모든 Cocoa/iPhone 어플리케이션의 설정이 들어있는 Info.plist 파일을 들 수 있습니다.

코드상에서는 NSDictionary, NSArray, NSString, NSDate, NSData, NSNumber를 사용할 수 있습니다.

다음은 Property List를 NSData로 변환하여 저장하는 방법입니다.

- (BOOL)writeApplicationPlist:(id)plist toFile:(NSString *)fileName {
   
NSString *error;
   
NSData *pData = [NSPropertyListSerialization dataFromPropertyList:plist
                              format
:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
   
if (!pData) {
       
NSLog(@"%@", error);
       
return NO;
   
}
   
return ([self writeApplicationData:pData toFile:(NSString *)fileName]);
}
- (BOOL)writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
   
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   
NSString *documentsDirectory = [paths objectAtIndex:0];
   
if (!documentsDirectory) {
       
NSLog(@"Documents directory not found!");
       
return NO;
   
}
   
NSString *appFile = [documentsDirectorystringByAppendingPathComponent:fileName];
   
return ([data writeToFile:appFile atomically:YES]);
}

그러면 읽어와야 겠죠. 다음의 코드를 참고하세요.
- (id)applicationPlistFromFile:(NSString *)fileName {
   
NSData *retData;
   
NSString *error;
    id retPlist
;
   
NSPropertyListFormat format;
    retData
= [self applicationDataFromFile:fileName];
   
if (!retData) {
       
NSLog(@"Data file not returned.");
       
return nil;
   
}
    retPlist
= [NSPropertyListSerialization propertyListFromData:retData
                  mutabilityOption
:NSPropertyListImmutable format:&format errorDescription:&error];
   
if (!retPlist){
       
NSLog(@"Plist not returned, error: %@", error);
   
}
   
return retPlist;
}
- (NSData *)applicationDataFromFile:(NSString *)fileName {
   
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   
NSString *documentsDirectory = [paths objectAtIndex:0];
   
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
   
NSData *myData = [[[NSData alloc] initWithContentsOfFile:appFile] autorelease];
   
return myData;
}

id는 객체 자신을 가리키는 타입입니다. 무엇이든 가리킬 수 있는 자바의 Object라고 생각하시면 이해가 쉬울까요.

Using Archivers to Read and Write Data

실제로 프로그램이 실행할때 메모리에 수많은 객체를 생성합니다. 그리고 각각의 객체는 서로 연결되어있습니다.

이러한 객체를 파일로 저장후에 다시 읽었을때 연결된 다른 객체가 존재하지 않는다면 큰 문제가 되겠죠.

아카이브라 불리는 이 과정은 실행중에 생성된 객체들의 복잡한 상속관계를 나타내는 그래프를 모두 바이트 단위로 변환하는 과정입니다.

이것을 사용하기 위해서는 클래스가 NSCoding을 구현해야 하고 그에 따른 encodeWithCode와 initWithCoder 메서드를 구현해야 합니다.

자세한 내용은 관련 [문서]를 참고하세요. 데이터를 저장하기 위해서는 다음과 같은 방식으로 합니다.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[_myDataSource representation]];
[data writeToFile:myFilePath atomically:YES];

데이터를 읽어 들여 복원할때는 다음과 같이 하면 됩니다.
NSData* data = [NSData dataWithContentsOfFile:myFilePath];
id rootObject
= [NSKeyedUnarchiver unarchiveObjectWithData:data];

File Acess Guidelines

파일을 생성하고 데이터를 쓸때 다음의 가이드 라인을 지키도록 합시다.

파일을 쓰는 횟수를 최소화 해야 합니다. 파일을 엑세스 하는 작업은 느리고 제한적인 수명을 가진 플래시 메모리에 하는 행위입니다.

  • 파일의 일부분이 변경되었을때 바뀐 부분만을 쓸 수 있습니다. 적은 바이트의 변경을 위해 전체 파일을 쓰는 행위를 피하도록 합시다.
  • 파일 포맷을 정의할때, 자주쓰이는 데이터를 최대한 하나의 그룹으로 묶어 저장하면 디스크 엑세스 횟수를 최소화 할 수 있습니다.
  • 랜덤한 엑세스가 필요한 데이터 구조로 되어있는 경우 SQLite 데이터베이스에 데이터를 저장할 수 있습니다.

캐쉬 파일을 디스크에 쓰는것을 피하도록 합니다. 어플리케이션이 종료될때 다음 실행시에 동일한 상태로 시작하기 위해 저장하는 것은 예외로 합니다.

Saving State Information

사용자가 Home 버튼을 누르게 되면 iPhone OS는 당신의 어플리케이션을 종료하고 홈스크린 화면으로 돌아갑니다.

마찬가지로 URL을 여는 경우 어플리케이션이 종료하고 다른 어플리케이션의 URI가 열리게 됩니다.

이 과정은 멀티테스트 환경과 달리 현재 실행되는 어플리케이션이 종료되고 다른 어플리케이션이 수행되는 것을 뜻합니다.

이러한 작업이 자주 일어난다면 어플리케이션의 상태를 관리하는 방법을 변경할 필요가 있습니다.

사용자가 수동으로 디스크에 저장하는 작업과 달리 주요 지점에서 자동으로 저장하도록 프로그램이 변경되어야 합니다.

어플리케이션이 종료될 때 임시 캐시 파일이나 데이터베이스 환경을 이용하여 상태를 저장해야 합니다.

다음에 어플리케이션이 실행될때 저장되었던 정보를 가지고 이전 상태로 복원하는 과정을 수행하여야 합니다.

저장하는 횟수를 최소화 하여야 하지만 최대한 적절한 시점에 저장하여 이 상태를 그대로 복원하여 보여주어야 합니다.

예를 들어 연락처를 수정하고 있다가 어플리케이션을 종료하고 다시 실행했을 때 연락처의 가장 첫화면보다 수정하던 창을 그대로 보여주는 것이 좋습니다.

Networking

iPhone OS의 네트워킹 스택은 아이폰과 아이팟 터치에 있는 무선 하드웨어 장치의 여러 인터페이스를 포함합니다.

주요 프로그래밍 인터페이스로는 BSD소켓으로 만들어진 CFNetwork 프레임워크가 있습니다.

이 문서에서는 구체적인 조언만을 담고 있고 NSStream 클래스를 사용하는 방법의 정보를 원하시면 Foundation Framework 문서를 참고하시기 바랍니다.

Tip for Efficient Networking
네트워크를 통해 데이터를 주고받는 행위는 장치의 많은 전력을 소비하는 행위라는것을 기억하셔야 합니다.
데이터를 주고받는 시간을 최소화 하는 것이 배터리 수명에 도움이 됩니다.

  • 통신 프로토콜을 정의할때 데이터 포맷은 가능한한 작게 만들어야 합니다.
  • 채팅과 같은 무수히 많은 통신이 오가는 방식은 피하도록 합니다.
  • 데이터는 하나의 덩어리로 묶어 보낼 수 있습니다.

Cellular와 Wi-Fi를 이용한 통신은 아무런 행동이 없을때 파워가 꺼지도록 만들어져 있습니다. 하지만 계속해서 작은 용량의 패킷을 주고 받게 되면 이것은 지속적으로 파워가 켜져있게 되는 것을 의미하게 되며 배터리가 소모된다는 것을 뜻합니다.

그러므로 작은 데이터를 자주 주고 받는것보다는 큰 데이터를 긴 시간차를 두고 주고 받는 것이 좋습니다.

네트워크를 이용한 통신 프로그램을 작성할때는 패킷의 손실이 일어날 수 있다는 것을 기억해 두어야 합니다.

코드를 작성할때 통신 실패에 대한 핸들링을 충분히 하여야 합니다. 

그 예로 네트워크가 갑자기 사라지게 될 경우 사용자에게 알리는 시스템을 갖추는 등의 노력이 필요합니다.

저작자 표시
Trackback 0 Comment 0
2011/08/27 04:22

iphone 캘린더 별 목록 가져오기


아이폰의 기본 캘린더 앱에서 특정 calendar에 들어있는 데이터만 가져와야 하는경우의 처리를 살펴보자.
유념해야 될 부분은 EKEventStore를 생성하면 캘린더 앱에 들어있는 모든 이벤트들을 가져온다는 것과 COREDATA에서 사용하는 NSPredicate를 사용해서 필요한 정보만 필터링을 할수 있다는 부분이다. 캘린더앱은 어플리케이션 캘린더를 지칭하고 calendar는 분류해놓은 카테고리를 지칭한다.
EKEventStore *eventstore = [[EKEventStore alloc] init]; // eventStore를 생성한다.
NSArray *calendars = [eventstore calendars];// 단말기에 등록된 캘린더 목록을 가져온다.
 
//가져올 calendar의 Array를 만든다.
 
NSPredicate *predicate = [eventstore predicateForEventsWithStartDate:STARTDATE
endDate:ENDDATE calendars:
nil];
 
// calendars에 nil을 넣으면 모두 가져오고 위에서 calendar의 array를 만들어서 넣는다면
해당 calendar의 목록만을 가져오는 조건이 된다.
NSArray *events = [eventstore eventsMatchingPredicate:predicate];//생성한 조건으로 event목록을 가져온다.

 
 
 
저작자 표시
Trackback 0 Comment 0