2013年12月11日 星期三

iOS 開發筆記 - 使用 Core Graphics 進行文字繪圖輸出並設定 NSString Attributes (Font、Alignment) 等

draw string

整理一年多前的程式碼,發現用的函數已經在 iOS 7 宣告為 deprecated 啦,恰好重看 CS193P 時,得知 NSAttributedString 這東西,以為是最新出來的,沒想到這在 iOS 3.2 就存在啦。

順便把一些筆記都記下吧:

+ (UIImage *)buildImageAndDrawText:(NSString *)text size:(CGSize)size ios7:(BOOL)iOS7SDK
{
    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetRGBFillColor(context, 1, 1, 1, 1);
    CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));

    UIFont *font = [UIFont italicSystemFontOfSize:15];
    UIColor *fontColor = [UIColor blueColor];

    if (iOS7SDK) {
        NSMutableParagraphStyle *paragrapStyle = [[NSMutableParagraphStyle alloc] init];
        [paragrapStyle setLineBreakMode:NSLineBreakByWordWrapping];
        [paragrapStyle setAlignment:NSTextAlignmentCenter];
     
        [text drawInRect:CGRectIntegral(CGRectMake(0, size.height/2, size.width, size.height))
          withAttributes:@{
                           NSParagraphStyleAttributeName: paragrapStyle,
                           NSFontAttributeName: font,
                           NSForegroundColorAttributeName: fontColor}
         ];
    } else {
        [fontColor set];
        [text drawInRect:CGRectIntegral(CGRectMake(0, size.height/2, size.width, size.height)) withFont:font lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentCenter];
    }
 
    UIImage *outImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return outImage;
}


用法:

self.imageView = [YourClass buildImageAndDrawText:@"iOS app development @ blog.changyy.org" size:CGSizeMake(300, 300) ios7:YES];

細節可參考 Core Graphics Framework Reference


2013年12月10日 星期二

iOS 開發筆記 - 透過 semaphore 讓 function 特性從 nonblocking 回到 blocking 模式

相信從事 Window Programming 的大多都知道 Main thread(UI thread) 的特性,例如 Main Thread 用來處理事件,須儘量避免做太久的工作,此外,所謂的 nonblocking 的原理就只是在建立一支 thread 去執行任務,任務完成後在依照設計返回到 Main thread 去更動 UI 狀況。
因此,採用一些手機視窗程式等第三方 SDK 時,更是容易碰到 nonblocking functions。這會使得規劃的 function flow 有點亂掉。

在 iOS app development 環境中,以 block 為例,可以透過 semaphore 來達成 blocking 效果,如:

dispatch_semaphore_t waitJobDone = dispatch_semaphore_create(0);
[self callBlock:^{
// do something
dispatch_semaphore_signal(waitJobDone);
}];
dispatch_semaphore_wait(waitJobDone, DISPATCH_TIME_FOREVER);


順便筆記一下其他東西,隨意使用 nonblocking 用法:

dispatch_async( dispatch_queue_create(“job_running”, NULL), ^{


});


在非 UI thread 中,又想動 UI 方式:

dispatch_async( dispatch_queue_create("job_running", NULL), ^{
// do something ...
     
dispatch_async(dispatch_get_main_queue(), ^{
// update ui
});
});


最後一提,如果用到的 SDK 跟網路相關的(NSURLConnection),那大多不能用 semaphore 來使之改成 blocking 模式(或是改法很繁雜要深入or破壞SDK架構),這跟 NSURLConnection Delegate Callback 設計有關(NSRunLoop)。

iOS 開發筆記 - ARC forbids explicit message send of 'dealloc'

最近我也投入 ARC 開發模式 XD 再也不去管到底要不要對哪個 object 執行 release 的議題,這適應其實也不難,就真的不要寫 [object release] 即可 XD

但有些時候還是要在物件銷毀時去做一些處理,例如解決或等待相關的事件完成,就常常會寫這段:

- (void)dealloc
{
    // do something ...
    [super dealloc];

}


結果,Xcode 就噴這段訊息:

ARC forbids explicit message send of 'dealloc'

Transitioning to ARC Release Notes 文件查證:
You may implement a dealloc method if you need to manage resources other than releasing instance variables. You do not have to (indeed you cannot) release instance variables, but you may need to invoke [systemClassInstance setDelegate:nil] on system classes and other code that isn’t compiled using ARC. 
Custom dealloc methods in ARC do not require a call to [super dealloc] (it actually results in a compiler error). The chaining to super is automated and enforced by the compiler.

僅需改成這樣了:

- (void)dealloc
{
    // do something ...
}

iOS 開發筆記 - 使用 NSNotification 傳遞與接收事件

通常會設計多個 UIViewController 供使用者觀看與操作,如果 AUIViewController 提供設定參數後,當使用者切換到 BUIViewController 可以馬上就使用到新資料來操作時,這時可以把資料儲存在指定地方,當 BUIViewController 之 viewWillAppear 裡就能夠去重讀資料來用。

然而,有時希望 AUIViewController 改完資料時,可以馬上讓 BUIViewController 也得知,這時做法至少有兩種,第一種就是讓 AUIViewController 可以接觸到 BUIViewController ,例如把 BUIViewController 當作一個 member variable ,也就是 AUIViewController.member = BUIViewController object,只是這招有點破壞架構的美感,但偶爾使用還滿堪用的!第二種,就是採用 Notification 架構,讓 BUIViewController 聆聽專屬於它的事件,而 AUIViewController 在需要的時候送事件出去即可,此架構更加符合 async 。

此例筆記 NSNotification 的用法(此架構也稱 radio station,Android 派則是 broadcast 啦):

BUIViewController.h:

#define kTargetDataUpdate @"TargetDataUpdate"

BUIViewController.m 之註冊聆聽事件:

- (void)setupOptions:(NSNotification *)event
{
        NSLog(@"event:%@, object:%@", event, event.object);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupOptions:) name:kTargetDataUpdate object:nil];

}


BUIViewController.m 之移除聆聽事件:

- (void)viewWillDisappear:(BOOL)animated
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name: kTargetDataUpdate object:nil];
    [super viewWillDisappear:animated];
}


AUIViewController (或其他物件亦可) 之發送事件:

NSDictionary *info = @{@"status":@"ok"};
[[NSNotificationCenter defaultCenter] postNotificationName: kTargetDataUpdate object:info userInfo:nil];

2013年12月9日 星期一

iOS 開發筆記 - Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier myCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

AttriubuteTableViewCellIndentifier DocumentOutlineTableViewCell

最近摸 Storyboard 一陣子,偶爾還會犯一些傻 bug ,就是 Identifier 設錯地方 XD 例如跑去 Identity Inspector 的 Identity 之 Restoration ID :

IndentityTableViewCellRestorationID

結果程式一跑時,就噴訊息了:

** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier myCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

解法就是開啟 storyboard 後,仔細查看左邊的 Document Outline 之 UITableViewController 裡的 Table View Cell 是不是有被標記起來,接著去看看右邊 Attributes Inspector 顯示的 Identifier 是不是跟程式碼一樣的,如此就能 debug 啦。

忘記是不是 iOS 5 SDK 開始,預設的 UITableViewController 的範例程式都是寫成綁定在 Interface builder (xib) / Storyboard 的寫法:

static NSString *CellIdentifier = @"myCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];


早期 iOS 4 SDK 預設 UITableViewController 之 (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 寫法:

static NSString *CellIdentifier = @"myCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}


早期 Interface builder (xib) 的用法,須寫程式註冊 Nib:

- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:@“MyCell" bundle:nil]
forCellReuseIdentifier:@“myCell”]; // "MyCell" for MyCell.xib
}