최근 앱을 만들때 중요도에서는 떨어지지만 반드시 들어가는 기능을 꼽는다면 튜토리얼(Help) 화면일 것이다.

아무도 중요하게 생각안하는 이 화면이 릴리스 거의 막판이되면 디자인때문에 종종 귀찮아지곤 한다.

예를 들어 "이 앱에 대해서 자세히 알고 싶으시면 여기를 클릭하세요" 라는 문구에 "여기"를 클릭하면 화면이동을 하게 한다던지 사파리로 넘겨준다는가 하는 단순한  것들이다.


이런기능들을 예전엔 UIWebView로 구현했었다. (네이티브 화면이동의 경우엔 딜리게이트를 이용하여 후킹하는 방법으로)


그러나 링크기능때문에 UIWebView를 사용한다는건 마치 "Hello World"를 출력하기 위해 멀티스레드로 코드를 짜는것과 같이 미련한 짓이라는 생각이 들어 간단한 방법을 찾아보기로 했다.






역시나! 구현하는 방법이 있었다.


첫번재 방법. UILabel을 이용한 링크 구현이다.

지금까지 조사한 결과 모양만 흉내가능하고 실제 링크터치 이벤트는 못만드는듯 하다.


NSString *text = @"한지민짱! 그럼 여길 클릭하세요.";

    NSRange find_range = [text rangeOfString:@"여길"];

    linkLabel.userInteractionEnabled = YES;

    NSMutableAttributedString *textString = [[NSMutableAttributedString alloc] initWithString:text

                                                                                   attributes:@{ NSForegroundColorAttributeName:[UIColor blackColor],

                                                                                                 NSFontAttributeName:[UIFont systemFontOfSize:17.f]}];

    NSMutableAttributedString *linkString = [[NSMutableAttributedString alloc]

                                             initWithString:[text substringWithRange:find_range]

                                             attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:24.f],

                                                          NSLinkAttributeName:@"native://popuplink/?touched"}];

    

    

    //UILabl 이용하는 방법..그러나 클릭안됨.

    [textString replaceCharactersInRange:find_range withAttributedString:linkString];

     linkLabel.attributedText = textString;


두번째 방법은 UITextView로 링크 구현하는 것이다. 물론 delegate를 이용하면 UIWebView처럼 이벤트 후킹이 가능하다.


    NSString *text = @"한지민짱! 그럼 여길 클릭하세요.";

    NSRange find_range = [text rangeOfString:@"여길"];

    linkLabel.userInteractionEnabled = YES;

    NSMutableAttributedString *textString = [[NSMutableAttributedString allocinitWithString:text

                                                                                   attributes:@{ NSForegroundColorAttributeName:[UIColor blackColor],

                                                                                                 NSFontAttributeName:[UIFont systemFontOfSize:17.f]}];

    NSMutableAttributedString *linkString = [[NSMutableAttributedString alloc]

                                             initWithString:[text substringWithRange:find_range]

                                             attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:24.f],

                                                          NSLinkAttributeName:@"native://popuplink/?touched"}];

    

    [textString replaceCharactersInRange:find_range withAttributedString:linkString];

     linkTextView.attributedText = textString;


//UITextView 이용한 방법. 클릭이벤트 발생됨.

    linkTextView.scrollEnabled = NO;

    linkTextView.editable = NO;

 linkTextView.selectable = YES;

    linkTextView.textContainer.lineFragmentPadding = 0;

    linkTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);

    linkTextView.delegate = self;

    linkTextView.attributedText = textString;

    

    //사이즈 맞춤.

    [linkTextView sizeToFit];

    

    //사이즈를 맞추기 위해 이거 메소드를 호출해줘야 한다고함.

    [linkTextView layoutIfNeeded];

    [linkTextView setTextContainerInset:UIEdgeInsetsMake(0, 0, 0, 0)];

    

    //텍스트뷰는 텍스트를 넣어둔후 폰트설정을 해야 적용된다능...

    linkTextView.font = [UIFont systemFontOfSize:13.f];

}



#pragma mark - UITextView delegate methods

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange

{

    NSLog(@"%s", __FUNCTION__);

    

    NSString *scheme = [url scheme];

    NSString * query = [url query];

    NSURL *path = [url absoluteURL];

    NSLog(@"scheme : %@   query : %@   path : %@ ", scheme , query, path);

    

    

    return NO;

}








돌려보니 잘 된다. 웹뷰쓰는것보다는 기분상 더 좋아 보인다.




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

Assets Catalog를 사용하고 있는 앱안에서 아이콘 이미지(AppIcon라고 되어 있는 놈)를 사용하고 싶을 경우엔 아래와같이 해주면 될줄 알았다..


UIImage *appIcon = [UIImage imageNamed"AppIcon"];


그러나 값을 확인하니 정확하게 nil을 반환하는것이 아닌가..




스택플로우 검색결과 이미지사이즈를 포함한 네이밍 문자열을 넘겨줘야 한다는 사실을 알았다.

요렇게..


UIImage *appIcon = [UIImage imageNamed:@"AppIcon40x40"];



참고한곳.

http://stackoverflow.com/questions/22808416/how-to-get-uiimage-of-appicon




나만몰랐나?

Posted by 악당잰 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply BlogIcon 차동수 2015.02.03 09:47

    저도 몰랐어요!

작년에 만든 앱(Hakenman)에 기능 몇개와 버그를 대응한 후 새해첫날에 업데이트 레뷰를 제출했었다.

그러나 생각치도 않던 리젝(메타데이터)이 되어 이유를 읽어보니 사용방법을 동영상으로 보내라는 내용이었다. (게다가 요구하는 동영상화면은 예전부터 있던 기능이었다능...)





정말 기능 설명도 필요없는 간단한 앱인데 말이지... 진정한 갑질은 애플이 아닐까..

어쩔수 없이 동영상을 만들어야 했는데 기왕 만드는거 앱프리뷰 동영상을 만들어 버리기로 했다.

일단 작업전에 앱프리뷰에 대해 사전조사를 한 결과 아래와 같은 정보를 얻을 수 있었다.


App Previews로컬라이즈 안됨. (하나 등록하면 전부 적용되버림)
그러나 표시할지 말지 국가별로 제한이 가능함.
3.5인치 이외에 디바이스 별로 사이즈 맞추어서 동영상을 올려야함. 사이즈가 맞지 않으면 에러남.

동영상은 화면캡춰영상 이어야 함. (사람손이 보이거나하면 안됨.)
음악, 자막, 나레이션 등은 OK.
동영상은 30초 이내, 그리고 30fps.


참고링크.

http://www.apptamin.com/blog/app-previews/








이제 위에 조건이 맞게 동영상을 만들어야 하는데 상당히 까다롭다. 다행히 저 귀찮은 작업들을 어느정도 해소해 줄 몇가지 툴을 찾을 수 있었는데 그중에서도 전혀 디자인초보인 개발자가 간단히 작업할 수 있는 AppShow라는 툴을 찾았다.


AppShow링크.

http://appshow.techsmith.com


물론 무료인데다가 인터페이스가 직관적이다. 게다가 위의 애플에서 요구하는 귀찮은 조건들을 한방에 클리어 할 수 있는 툴이다.








어쨋든 이걸 이용하여 만들어 보았다. 만져본 소감으로는 상당히 다루기 쉬웠고 Movie정도를 다룰수준이면 메뉴얼도 필요없을 수준이다.

조금 아쉬운점은 각각의 동영상 씬추가는 간단하지만 동영상에 텍스트를 넣거나 편집하는 기능은 없었다.

이 문제를 궁여지책으로 키노트(Keynote)로 슬라이드를 만든 다음 그걸 이미지로 출력해서 동영상에 삽입하는 꼼수를 썻다. 


요렇게..








게다가 만든 동영상은 디바이스별로 사이즈를 조정하여 출력이 가능하기에 전부 iTuens Connect에 무사히 등록까지 마쳤다. 

어쨋든 까칠한 애플덕분에 3시간정도 시간을 소비하기는 했지만 쓸모있는 경험을 할 수 있었다.








마지막으로 Hakenman Priview 영상은 유투브에 업로드 링크.









Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

iPhone 캘린더에 등록된 내용을 보여주거나 표시해주기 위해서는 EventKit을 이용하면 된다. 캘린더 이외에 리마인더도 제어 할 수 있으나 여기선 캘린더 등록 위주로 간단히 설명할 생각이다.



사실 애플도큐먼트만 봐도 굳이 설며이 필요없는 간단한 내용이지만 중요한 포인트는 

EventKit을 사용할 클래스는 싱글톤 클래스로 만들어 사용할 것이라는 내용.

(도큐먼트에 보면 로드하는데 시간이 걸리므로 로드 후 사용하는것이 좋다고 소개되어 있다.)

그러므로 일단 여기서도 싱글톤으로된 매니져클래스를 샘플로 만들어 보았다.


자세한 사용법은 아래의 링크를 참고하면 된다.(일본어임)

https://developer.apple.com/jp/devcenter/ios/library/documentation/EventKitProgGuide.pdf



본론으로 들어가서,

물론, EventKit, EventKitUI 두개의 프레임워크를 먼저 추가해 주어야 한다.

(이름만 봐서도 알겠지만 EventKitUI는 이벤트 편집을 위한 UI프레임워크이다.)






먼저 EventManager.h

필요한 이벤트킷을 임포트 해준 다음 싱글톤 인스턴스 메소드(sharedInstance)와 이벤트 검색을 위한 메소드(fetchEvent...) 그리고 이벤트 추가, 수정을 위한 화면표시용 메소드(eventEditController...)를 선언해 준다.



#import <EventKit/EventKit.h>

#import <EventKitUI/EventKitUI.h>


@interface EventManager : NSObject


+ (EventManager *)sharedInstance;


- (NSArray *)fetchEvent;

- (NSArray *)fetchEventStartDate:(NSDate *)startDate EndDate:(NSDate *)endDate;

- (void)eventEditController:(id)owner event:(EKEvent *)event;


@end




그다음 EventManager.m

싱글톤 클래스를 구현해 주기 위한 메소드들을 추가해 준 다음 헤더파일에 선언된 메소드를 정의해 준다.

그리고 이벤트정보에 접근하기 위한 이벤트스토어를 프로퍼티(EKEventStore)로 선언.

마지막으로 이벤트 추가, 수정을 위한 EventKitUI를 사용하기 위한 프로토콜을 명시해 준다.



@interface EventManager() <EKEventEditViewDelegate>


@property (nonatomicstrongEKEventStore *store;


@end



@implementation EventManager 


static EventManager *_sharedInstance;


#pragma mark - singleton

+ (EventManager *)sharedInstance

{

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        _sharedInstance = [[self alloc] init];

    });

    

    return _sharedInstance;

    

}


+ (id)allocWithZone:(NSZone *)zone {

    

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        _sharedInstance = [super allocWithZone:zone];

    });

    

    return _sharedInstance;

    

}


- (id)init {

    self = [super init];

    if (self) {

        _store = [[EKEventStore alloc] init];

        

        if([EKEventStore authorizationStatusForEntityType:EKEntityTypeReminder] == EKAuthorizationStatusAuthorized) {

            //접근거부

        } else {

            EKEventStore *eventStore = [[EKEventStore alloc] init];

            [eventStore requestAccessToEntityType:EKEntityTypeReminder

                                       completion:^(BOOL granted, NSError *error) {

                                           if(granted) {

                                               // 처음 접근시 대화장자가 표시되는 경우 OK버튼을 누르면 여기로 들어옴

                                           } else {

                                               dispatch_async(dispatch_get_main_queue(), ^{

                                                   //접근권한 없음

                                                   UIAlertView *alert = [[UIAlertView alloc]

                                                                         initWithTitle:NSLocalizedString(@"This app does not have access to you reminders.", nil)

                                                                         message:NSLocalizedString(@"To display your reminder, enable [YOUR APP] in the \"Privacy\" → \"Reminders\" in the Settings.app.", nil)

                                                                         delegate:nil

                                                                         cancelButtonTitle:NSLocalizedString(@"OK", nil)

                                                                         otherButtonTitles:nil];

                                                   [alert show];

                                               });

                                           }

                                       }

             ];

        }

    }

    

    return self;

}


- (id)copyWithZone:(NSZone *)zone

{

    return self;

}


- (void)dealloc {

    

}


#pragma mark - Event fetch

- (NSArray *)fetchEvent {

    

    //기본값은 일주일로 잡아준다.

    

    NSCalendar *calendar = [NSCalendar currentCalendar];


    // 시작시간 작성

    NSDateComponents *oneWeekAgoComponents = [[NSDateComponents alloc] init]; oneWeekAgoComponents.day = -7;

    NSDate *oneDayAgo = [calendar dateByAddingComponents:oneWeekAgoComponents

                                                  toDate:[NSDate date]

                                                 options:0];

    // 종료시간 작성

    NSDateComponents *oneYearFromNowComponents = [[NSDateComponents alloc] init]; oneYearFromNowComponents.year = 1;

    NSDate *oneYearFromNow = [calendar dateByAddingComponents:oneYearFromNowComponents

                                                       toDate:[NSDate date]

                                                      options:0];

    

    return [self fetchEventStartDate:oneDayAgo EndDate:oneYearFromNow];

    

}


- (NSArray *)fetchEventStartDate:(NSDate *)startDate EndDate:(NSDate *)endDate {

    

    // 조건생성

    NSPredicate *predicate = [_store predicateForEventsWithStartDate:startDate

                                                            endDate:startDate

                                                          calendars:nil];

    // 조건에 따른 패치 실행.

    return [_store eventsMatchingPredicate:predicate];

    

}


#pragma mark - Event editing controller

- (void)eventEditController:(id)owner event:(EKEvent *)event {


    //event가 nil 일 경우에는 신규이벤트 등록이 된다.

    

    EKEventEditViewController *eventEditViewController = [EKEventEditViewController new];

    eventEditViewController.editViewDelegate = self;

    eventEditViewController.event = event;

    eventEditViewController.eventStore = _store;

    

    [owner presentViewController:eventEditViewController animated:YES completion:nil];

}


#pragma mark - EKEventEditViewDelegate

- (void)eventEditViewController:(EKEventEditViewController *)controller didCompleteWithAction:(EKEventEditViewAction)action

{

//UI에서 이벤트를 받을경우 처리할 딜리게이트 메소드.


NSError *error = nil;

switch (action) {

case EKEventEditViewActionCanceled:

break;

case EKEventEditViewActionSaved:

[controller.eventStore saveEvent:controller.event span:EKSpanThisEvent error:&error];

break;

case EKEventEditViewActionDeleted:

[controller.eventStore removeEvent:controller.event span:EKSpanThisEvent error:&error];

break;

default:

break;

}

    

[controller dismissViewControllerAnimated:YES completion:nil];

}


@end



기술한 샘플코드는 전혀 테스트를 하지 않았기 때문에 동작안할 수도 있으므로 참고만 하시길...






Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

아이폰에서 챠트 또는 그래프를 그려야 하는 경우가 종종 있다. 개인적으로 만드는거라면 오픈소스를 가져다 쓰는게 가장 편하지만 디자인너의 요구사항이 까다롭다면 어쩔수 없이 직접 그려줘야 한다.


그리는 방법은 다음과 같은 순서로 작성한다.


1. UIView를 상속받은 차트용 뷰를 작성

2. 제대로 만들었다면 주석처리된 drawRect 메소드가 나오는데 이걸 주석을 해제.

3. drawRect메소드에 그리고 싶은 코드를 넣어줌


*참고로 drawRect메소드는 첫 화면에 뿌려질때 불려지는 콜백함수이다.

인위적으로 불러줘야 할 경우에는 setNeedDisplay라는 메소드를 호출해 준다.



그릴때 자주쓰는 기본적인 메소드를 만들어 실장해 보았다.


// Only override drawRect: if you perform custom drawing.

// An empty implementation adversely affects performance during animation.

- (void)drawRect:(CGRect)rect

{

    // Drawing code

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    

    [self drawLine:ctx

         color:[UIColor grayColor]

         width:1.0f

        startPoint:CGPointMake(10, 20)

          endPoint:CGPointMake(100, 40)];

    

    [self drawCircle:ctx

           fillColor:[UIColor blackColor]

         strokeColor:[UIColor grayColor]

              radius:30.0f

         CenterPoint:CGPointMake(250, 40)];

    

    [self drawText:ctx text:@"Graph TEST!!" size:13.0f point:CGPointMake(100, 100)];

}


- (void)drawLine:(CGContextRef)ctx color:(UIColor *)c width:(float)w startPoint:(CGPoint)sp endPoint:(CGPoint)ep {


    CGContextSetStrokeColorWithColor(ctx, c.CGColor);

    CGContextBeginPath(ctx);

    CGContextMoveToPoint(ctx, sp.x, sp.y);

    CGContextAddLineToPoint(ctx, ep.x, ep.y);

    

    CGContextStrokePath(ctx);


}


- (void)drawCircle:(CGContextRef)ctx fillColor:(UIColor *)fc strokeColor:(UIColor *)sc radius:(float)r CenterPoint:(CGPoint)cp {

    

    // Set the width of the line

    CGContextSetLineWidth(ctx, 2.0);

    

    //Make the circle

    // 150 = x coordinate

    // 150 = y coordinate

    // 100 = radius of circle

    // 0   = starting angle

    // 2*M_PI = end angle

    // YES = draw clockwise

    CGContextBeginPath(ctx);

    CGContextAddArc(ctx, cp.x, cp.y, r, 0, 2*M_PI, YES);

    CGContextClosePath(ctx);

    

    //color

    CGContextSetFillColorWithColor(ctx, fc.CGColor);

    CGContextSetStrokeColorWithColor(ctx, sc.CGColor);

    

    // Note: If I wanted to only stroke the path, use:

    // CGContextDrawPath(context, kCGPathStroke);

    // or to only fill it, use:

    // CGContextDrawPath(context, kCGPathFill);

    

    //Fill/Stroke the path

    CGContextDrawPath(ctx, kCGPathFillStroke);

    

}


- (void)drawCircle:(CGContextRef)ctx color:(UIColor *)c radius:(float)r CenterPoint:(CGPoint)cp {

    

    [self drawCircle:ctx fillColor:c strokeColor:c radius:r CenterPoint:cp];

}


- (void)drawText:(CGContextRef)ctx text:(NSString *)t size:(float)s point:(CGPoint)p {

    

    CGContextSetTextDrawingMode(ctx, kCGTextFill); // This is the default

    

    [[UIColor redColor] setFill]; // This is the default

    

    [t drawAtPoint:CGPointMake(p.x, p.y)

               withAttributes:@{NSFontAttributeName:[UIFont fontWithName:@"Helvetica"

                                                                    size:s]}];

    

    

    

}





출력결과




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

요즘에 거의 대부분의 앱들은 오픈소스(GitHub와 같은 곳에서 )를 이용해 개발한다.

이렇게 오픈소스를 이용해 만들어진 앱은 각각의 라이센스에 따라 표기 해줘야 하는데 대부분은 헬프나 설정화면에 오픈소스정보항목을 만들어 사용되어진 라이센스정보를 표시해준다.

그럼 라이센스정보화면을 간단히 만들어보자.


.h파일

/**

 * @brief ライセンス情報画面コントローラー

 */

@interface OpenSourceViewController : UIViewController {

    

    IBOutlet UIWebView *licenseWebview;

}


@end


 

.m파일

- (void)viewDidLoad

{

    [super viewDidLoad];

// Do any additional setup after loading the view.

    

    [self initControls];

}


- (void)initControls {

    

    NSString *path = [[NSBundle mainBundle] pathForResource:@"license" ofType:@"html"];

    [licenseWebview loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];

}


-(BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType {

    

    if ( inType == UIWebViewNavigationTypeLinkClicked ) {

        [[UIApplication sharedApplication] openURL:[inRequest URL]];

        return NO;

    }

    

    return YES;

}



.html파일(라이센스정보)

<html>

<body>

    <H3>

        AFNetworking

    </H3>

    <p>

    Copyright (c) 2011 Gowalla (<a href="http://gowalla.com/">http://gowalla.com/</a>)<br>

    Permission is hereby granted, free of charge, to any person obtaining a copy

    of this software and associated documentation files (the "Software"), to deal

    in the Software without restriction, including without limitation the rights

    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

    copies of the Software, and to permit persons to whom the Software is

    furnished to do so, subject to the following conditions:<br>

    <br>

    The above copyright notice and this permission notice shall be included in

    all copies or substantial portions of the Software.<br>

    <br>

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

    THE SOFTWARE.<br>

    </p>


</body>

</html>


만들어진 화면.






Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

iPhone앱안에 있는 /document  저장공간내 파일을 제어할때 필요한 메소드를이다. 내용이야 간단하지만 만들려고 하면 여간 귀찮은게 아니라서 만들어 놓고 쓸 생각이다. 급조한거라 좀 부실하지만 다듬어가면서 업데이트해야지.


이번 프로젝트의 경우 특이하게 파일관련 기능을 자주 썻지만(드롭박스 같은 클라우드 API를 포함해서) 다음 프로젝트에선 어떨지 모르겠다.



1. 파일복사.

- (BOOL)copyFile:(NSString *)srcPath destination:(NSString *)destPath error:(NSError **)error {

    

    NSFileManager *fileManager = [NSFileManager defaultManager];

    BOOL isDirectory;

BOOL result = NO;

    

    if([self isExistFile:srcPath isDirectory:&isDirectory] == YES) {

        

        if (isDirectory) {

            NSString *preFolderPath = [destPath stringByDeletingLastPathComponent];

            

            [fileManager createDirectoryAtPath: preFolderPath

                   withIntermediateDirectories: YES

                                    attributes: nil

                                         error: error];

            result = [fileManager copyItemAtPath:srcPath toPath:destPath error:error];

            

            return result;

            

        } else {

            result = [fileManager copyItemAtPath:srcPath toPath:destPath error:error];

            

            return result;

        }

        

    }else {

        *error = [NSError errorWithErrorCode:ErrorCodeFileNotFoundError];

        

        return NO;

    }

    

}


2. 파일삭제

- (BOOL)removeFile:(NSString *)filePath error:(NSError **)error {

    

    NSFileManager *fileManager = [NSFileManager defaultManager];

BOOL isDirectory;

    BOOL result = NO;

    

    if([self isExistFile:filePath isDirectory:&isDirectory] == YES) {

        

        if (isDirectory) {

            NSArray *contents = [fileManager contentsOfDirectoryAtPath:filePath error:error];

            for(int i=0;i<[contents count];i++) {

                NSString *childFileName = [contents objectAtIndex:i];

                NSString *nextFilePath =[[filePath stringByAppendingString:@"/"] stringByAppendingString:childFileName];

                

                result = [self removeFile:nextFilePath error:error];

            }

            

            result = [fileManager removeItemAtPath:filePath error:error];

            

            return result;

            

        } else {

            result = [fileManager removeItemAtPath: filePath error:error];

            

            return result;

        }

        

    } else {

        *error = [NSError errorWithErrorCode:ErrorCodeFileNotFoundError];

        

        return NO;

        

    }

}


3. 이름변경하기

- (BOOL)renameFile:(NSString *)filePath newfilename:(NSString *)newFilePath error:(NSError **)error {

    

    NSFileManager *fileManager = [NSFileManager defaultManager];

    BOOL isDirectory;

    BOOL result = NO;

    

    if([self isExistFile:filePath isDirectory:&isDirectory] == YES) {

        

        if (isDirectory) {

            NSString *preFolderPath = [newFilePath stringByDeletingLastPathComponent] ;

            [fileManager createDirectoryAtPath: preFolderPath

                   withIntermediateDirectories: YES

                                    attributes: nil

                                         error: error];

            

            result = [fileManager moveItemAtPath:filePath toPath:newFilePath error:error];

            

            return result;

            

        }else {

            NSString *preFolderPath = [newFilePath stringByDeletingLastPathComponent] ;

            

            [fileManager createDirectoryAtPath: preFolderPath

                   withIntermediateDirectories: YES

                                    attributes: nil

                                         error: error];

            

            result = [fileManager moveItemAtPath:filePath toPath:newFilePath error:error];

            return result;

        }

        

    } else {

        *error = [NSError errorWithErrorCode:ErrorCodeFileNotFoundError];

        return NO;

    }

    

}


4. 파일이동하기.

- (BOOL)moveFile:(NSString *)srcPath destination:(NSString *)destPath error:(NSError **)error {

    

    NSFileManager *fileManager = [NSFileManager defaultManager];

BOOL isDirectory;

    BOOL result = NO;

    

    if([self isExistFile:srcPath isDirectory:&isDirectory] == YES) {

        

        if (isDirectory) {

            NSString *preFolderPath = [destPath stringByDeletingLastPathComponent] ;

            [fileManager createDirectoryAtPath: preFolderPath

                   withIntermediateDirectories: YES

                                    attributes: nil

                                         error: error];

            

            result = [fileManager moveItemAtPath:srcPath toPath:destPath error:error];

            

            return result;

            

        }else {

            NSString *preFolderPath = [destPath stringByDeletingLastPathComponent] ;

            

            [fileManager createDirectoryAtPath: preFolderPath

                   withIntermediateDirectories: YES

                                    attributes: nil

                                         error: error];

            

            result = [fileManager moveItemAtPath:srcPath toPath:destPath error:error];

            return result;

        }

        

    } else {

        *error = [NSError errorWithErrorCode:ErrorCodeFileNotFoundError];

        return NO;

    }

}



5. 파일정보보기

- (NSDictionary *)showPropertyFile:(NSString *)filePath {

    

    NSError *error = nil;

    NSDictionary *attr = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];

    

    if (error == nil) {

        return attr;

    }

    

    return nil;

    

}


6. 새폴더 만들기.

- (BOOL)createFolder:(NSString *)currentDirectory folderName:(NSString *)foldername error:(NSError **)error {

    

    NSFileManager *filemanager = [NSFileManager defaultManager];

    

    NSString *newFolderPath = [currentDirectory stringByAppendingFormat:@"/%@",foldername];

    BOOL isDirectory;

    

    if([self isExistFile:newFolderPath isDirectory:&isDirectory] == YES) {

        

        if (isDirectory) {

            return NO;

        }else {

            BOOL result = [filemanager createDirectoryAtPath: newFolderPath

                                 withIntermediateDirectories: YES

                                                  attributes: nil

                                                       error:error];

            

            return result;

        }

        

    }else {

        BOOL result = [filemanager createDirectoryAtPath: newFolderPath

                          withIntermediateDirectories: YES

                                           attributes: nil

                                                error:error];

        

        return result;

    }

}




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

며칠전 운영체제인 iOS7과 iPhone5s와 iPhone5c라는 두가지 디바이스 버전이 발표되었다. 신제품이 출시되면 소비자들은 즐겁겠지만 개발자는 마냥 즐겁지는 않다. 만들어 놓은 앱이나 지금 진행중인 플젝에 반영해야 하기 때문이다.(일이 늘어난다는 얘기임.)

마치 군복무중에 눈이 내리는 장면을 상상하면 딱 맞을듯 싶다.


내 경우는 스마트폰 개발 특성상 안드로이드 개발자들과 일을 같이 하는데 ( 뭐 안드개발자들 바쁘면 도와주기도 한다) 이 디바이스별 대응이라는게 참 손이 많이가는 작업이다. 테스트를 다 할수 없는 거고 안된다고 크레임들어오고.. 대책이 없는거다.


그나마 다행이랄까 애플에서 개발자를 배려해 XCode5의 새로운 기능이 추가되었다.

디바이스별 이미지를 관리하는 어셋기능(Asset)이다.


테스트 해보니 기존소스와도 호환성이 있어 예전방식으로 코딩해도 기본적으로는 인식하는듯 하다.

어쨋든 서론이 길어졌지만 직접 해보지 않으면 답답하니 샘플을 만들어 보자.


우선 샘플 프로젝트를 만들고 UIImageView를 배치 IBOutlet로 선언후 연결해 놓는다.




그리고 이미지명을 "animal" 로 지정해서 불러들인다.

그리고 XCode5에 새로생긴 Images.xcassets 폴더를 선택한 후 오른쪽 마우스를 클릭하여 Image Set 을 추가한다.

참고로 폴더를 만들수도 있고 이외 여러가지 메뉴가 있다.




기본으로 Image라는 이름으로 추가되므로 이 이름을 "animal"로 변경해준다.




위의 작업을 해주면 프로젝트내에 폴더가 생성되는데 이곳에다가 사용할 이미지를 복사한다.



펭귄, 코알라, 고양이들을 복사해주자.



복사함과 동시에 XCode상에 추가된 이미지를이 보이기 시작한다. 

오른쪽 프로파티에 속성을 그림과 같이 변경해준다.



각각 동물들을 필요한 곳에 드롭다운 해준다. 




이제 시뮬레이터 버전을 변경해 가면서 표시되는 이미지를 확인해보자. 역시 예상대로 잘 표시된다.



Asset기능과 AutoLayout기능을 적절히 사용하면 디바이스 화면사이즈에 대응하는것이 그리 귀찮은 일은 아닐것 같다.

예전방식에 익숙해서 AutoLayout기능은 쓰질 않았었는데 나중에 한번 더 연구해 볼생각이다.



Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

XCode로 로 개발중에 C++로 개발된 모듈을 추가해야 할 일이 생겼다.

어차피 Objective-C도 C언어에서부터 나온거라 그냥 추가해주면 될줄 알았지만 당연하게도XCode는  에러를 뱉어내면서 저항을 했다.

나중에 이런작업을 또 할까... 없을거라 생각되지만 혹시나 해서 잊어버리기전에 블로그에 적어놓자.

인터넷을 찾아본 결과 몇가지 설정이 필요하고 XCode에 필요한 규칙이 있었다.


먼저 샘플 cpp 파일을 만들어보자. 파일추가에서 아래와같이 선택하면 간단히 템플릿이 만들어진다.



추가된 파일에 cpp형식으로 샘플코드를 넣어보자.


Sample.h 와 Sample.cpp파일            

 


자..이제 실제로 뷰 콘트롤러에서 불러보자.

참고로 cpp파일을 이용하기 위해서는 ViewController.mm와 같이 확장자를 mm 으로 변경 해 주어야 한다.



Hello 함수가 정상적으로 잘 호출되었다.



그러나.. 실제 프로젝트에 적용하니 또다시 XCode에서 반항하며 알수없는 에러들을 내뱉었다.

인터넷으로 여기저기 알아본 결과 스택플로우에서 해결방법 발견!

http://stackoverflow.com/questions/6913637/xcode-4-2-c0x-libc-cannot-find-iostream


읽어보니 이유는 모르겠지만 설정을 두군데 정도 손으 봐야 한다.

Build Settings에서 C++ Standrad Library 를 libstdc++로 변경 그리고 Compile Sources As 를 Objective-C++로 바꿔준다.



어쨋든 성공은 했으니 좋다.



Posted by 악당잰 트랙백 0 : 댓글 2

댓글을 달아 주세요

  1. addr | edit/del | reply BlogIcon 곱게자란녀석 2015.01.11 17:16 신고

    안녕하세요. 같은 문제에 봉착하였는데.. 전 해결이 안되서.. 여쭤보려고 문의드렸습니다.
    전 c 파일과 cpp 파일을 전부 사용해야 하는 경우인데요.. Apple Mach 0 linker error 를 자꾸 내뱉습니다.
    cpp로 작성된 함수명들에 대해 에러를 내뿜고 있구요.
    댓글엔 그림을 넣을 수 없어 제가 다른곳에 질문글 올렸던 링크도 같이 드립니다...
    http://www.clien.net/cs2/bbs/board.php?bo_table=cm_app&wr_id=12324

    • addr | edit/del BlogIcon 악당잰 2015.01.11 19:07 신고

      도움이 될지는 모르겠으나 제가 아는 한도내에서 답변을 드리자면..

      C, C++로 만들어진 정적 라이브러리를 이용하시나요?
      그렇다면 arm64를 적용하지 않은채 빌드된 라이브러리에선 캡쳐와 같은 에러가 발생한 경험이 있습니다.
      라이브러리를 arm64용으로 빌드해서 달라고 하면 됩니다.

      c, cpp 파일을 직접 프로젝트에 넣어서 사용하신다면 캡쳐에러만 가지고는 알수가 없네요.
      다만, objective-c 에서는 기본적으로 c++은 그냥그대로 사용 못하는걸루 알고 있구요. c의 경우도 구조체 같은 경우 ARC를 먹지 않기 때문에
      결국은 오브젝티브C용으로 랩핑을 해줘야 합니다.

      도움이 되었는지 모르겠네요..

앱을 개발하다 보면 프로토타입을 만들기 위해 종이에 직접 스케치 하거나  키노트, 파워포인트를 이용한다.


실제로 개발하다보면 아이폰 화면이 생각보다 작기때문에 실제 아이폰 화면에서 레이아웃을 확인하지 않으면머리속의 이미지와는 다른결과가 나오기도 한다.



일본블로그에서 키노트로 간단히 프로토타입을 만는 방법이 있었다.

http://design.kayac.com/topics/2011/01/iphone-prototype.php


일본어로 되어 있어도 대충 알수 있는 내용이지만 몇 분간 직접 따라해 만든 것을 간단히 

번역과 설명을 추가한다.


작업순서

01. 슬라이드를 작성 (키노트)

02. PDF로 내보내기

03. 아이폰으로 전송 (내 경우 드롭박스DropBox에 보냄)

04. 앱실행 (PDF열기)



01. 슬라이드 작성시 슬라이드 크기 320x480으로 설정한다.





02. 각각 만들어 놓은 컨트롤의 하이퍼링크로 활성화 를 체크하고 화면 이동할 슬라이드를 설정해 준다.




03. 작성이 끝나면 보내기를 선택. PDF로 출력한다.





04.만들어진 PDF를 드롭박스에 올려 아이폰로 실행시킨 후 확인.


Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요