August 03, 2012

ทำ .framework

ก่อนหน้านี้เวลาทำ library ใช้กันภายในทีมก็จะเลือกสร้างโปรเจ็คโดยเลือกใช้ template ชื่อ Cocoa Touch Static Library



ซึ่งหลังจากเราสั่ง build โปรแกรมแล้ว จะได้ไฟล์ static library ที่มีนามสกุล .a มาสำหรับส่งให้คนอื่นๆ นำไปใช้งาน ซึ่งเอาเข้าจริงแล้วเวลาเราส่งไฟล์ .a นี้ให้คนอื่น เราจำเป็นต้องแนบไฟล์ .h ติดไปด้วย ทำให้ส่งไปทีนึงก็ต้องส่งไปหลายไฟล์ ทำให้รู้สึกไม่ค่อยสะดวกเท่าไหร่

เราจึงจะมาทำให้รู้สึกสะดวกด้วยการสร้าง static framework มาใช้ ส่วนที่ว่าทำไมถึงต้องสร้าง static framework ก็เพราะว่าเราสามารถส่งไฟล์ .framework ที่ได้ไปให้คนอื่นๆ ใช้ได้เลย ไม่ต้องแยกส่ง .a ที และ .h ต่าๆง นาๆ ที และที่สำคัญอีกอย่างก็คือ iOS ไม่สนับสนุน dynamic framework ยังไงล่ะ :D กำปั้นทุบดินมะ

จริงๆ structure ของ .framework ที่เราจะสร้างมันก็คือ folder หนึ่งที่บรรจุไฟล์จำนวนหนึ่งตามโครงสร้างที่ถูกต้อง แต่ถ้าต้องสร้าง structure ของ framework เองกับมือทุกครั้งที่สร้างก็คงไม่ค่อยสะดวก แต่โลกนี้ยังหลงเหลือคนใจดี ที่ได้ทำ template สำหรับสร้าง static framework ไว้ให้เราแล้ว

หลังจากดาวน์โหลดโปรเจ็คมาแล้วให้เข้าไปรันไฟล์ install.sh เพื่อให้มันติดตั้ง template ให้เรา จากนั้นต่อไปนี้เมื่อเราเข้าไปสร้างโปรเจ็คใหม่ใน Xcode เราก็จะเห็น template ชื่อ Static iOS Framework อยู่ในหมวด Framework & Library แล้ว



วิธีใช้งานก็สร้าง Class ขึ้นมาตามปกติได้เลย และเมื่อ build โปรเจ็คเราจะได้ Products เป็นไฟล์ .framework พร้อมสำหรับการนำไปใช้ในโปรเจ็คอื่นๆ แล้ว

อย่างหนึ่งที่ห้ามลืมก็คือให้เข้าไปกำหนดค่าใน Build Phases ในส่วนของ Copy Headers เพื่อกำหนดให้ header ไฟล์ไหนเป็น private หรือ public นั่นเอง หาก header ไม่ได้มาอยู่ในสว่น public แล้ว คนนำไปใช้จะไม่สามารถมองเห็น header ตัวนั้นได้



:)

July 08, 2012

ถามหาสถานที่รอบๆ จาก foursquare

ความต้องการสำหรับ entry นี้ก็คือ เราจะโยนตำแหน่ง (latitude, longitude) ไปให้ foursquare จากนั้นจะให้มันตอบสถานที่รอบๆ ตำแหน่งนั้นกลับมาให้เรา
หลักๆ แล้วเราสามารถหาสถานที่รอบๆ จุดได้ 2 วิธี คือ
  1. ต้อง login เข้าใช้งาน foursquare ก่อนถึงจะขอสถานที่ได้ แบบนี้ต้องพึ่ง access token ที่จะได้มาหลังจาก login
  2. ไม่ต้อง login ก็สามารถขอสถานที่ได้
โดยความละเอียดของข้อมูลก็จะแต่งต่างกันไป
เริ่มจากแบบที่ต้อง login เข้าใช้งานก่อน

การที่เราจะมีสิทธิ์ในการบอกให้ foursquare ช่วยหาสถานที่ให้เราได้นั้น เราจำเป็นต้องมีสิ่งที่เรียกว่า Access Token ก่อน แต่ก่อนอื่นเราต้องลงทะเบียนโดยการเข้าไปกดปุ่ม Register a new Consumer เพื่อที่เราจะได้เป็น client ที่ foursquare รู้จักก่อน

หลังจากเราลงทะเบียนเสร็จแล้ว เราจะได้สิ่งที่เรียกว่า Client ID ติดไม้ติดมือกลับมา เท่านี้เราก็พร้อมสำหรับการขอ access token จาก foursquare แล้ว

วิธีในการขอ access token เราจะใช้ UIWebView ช่วย ดังต่อไปนี้

NSString *authenticateURLString = [NSString stringWithFormat:@"https://foursquare.com/oauth2/authenticate?client_id=%@&response_type=token&redirect_uri=%@"
                                       CLIENT_ID, CALLBACK_URL];

NSURL *url = [NSURL URLWithString:authenticateURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

[_webView loadRequest:request];

โดยที่ CLIENT_ID และ CALLBACK_URL เราจะได้มาตอนที่ register a new customer เสร็จแล้วดังที่ได้กล่าวไป

เมื่อ UIWebView โหลด request นี้เสร็จแล้ว เค้าก็จะให้เรา login เข้าใช้ foursquare เมื่อเสร็จแล้ว foursquare ก็จะพยายาม redirect ไปยัง CALLBACK_URL ที่เราแนบไปตอนขอ access token โดย URL ที่มันจะพยายาม redirect ไปจะแนบ access token ติดมาด้วย และเดี๋ยวเราก็จะใช้ access token ตัวนี้แหละ แนบติดไปกับตอนที่เราขอให้ foursquare ช่วยหาสถานที่ให้ :D

ซึ่ง flow การ redirect/reload ต่างๆ ให้ observe ได้ที่ delegate method ต่างๆ ของ UIWebView ได้ตามปกติ

ส่วนวิธีการแงะเอา access token ก็ทำได้แล้วแต่ความถนัดประมาณนี้

NSString *URLString = [[_webView.request URL] absoluteString];

if([URLString rangeOfString: @"access_token="].location != NSNotFound){
    NSString *accessToken = [[URLString componentsSeparatedByString: @"="] lastObject];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject: accessToken forKey: @"access_token"];
    [defaults synchronize];
}


หลังจากเราได้ access token มาแล้ว เราก็ได้สิทธิ์ที่จะขอให้ foursquare ทำอะไรๆ ต่างๆ เกี่ยวกับ user ที่ login ไป ยกตัวอย่างเช่นการหาสถานที่รอบๆ latitude, longitude ที่เราส่งไปให้แล้ว โดยใช้ API Explorer ดังนี้


NSURL *url = [NSURL URLWithString: [NSString stringWithFormat: @"https://api.foursquare.com/v2/venues/explore?ll=13.723814,100.549396&oauth_token=%@", access_token]];


__autoreleasing NSError *error;
NSString *string = [NSString stringWithContentsOfURL: url encoding: NSUTF8StringEncoding error: &error];
NSLog(@"response json = %@", string);


ถ้าเราลองพิจารณาดูจากสิ่งที่ได้กลับมา เราจะพบว่ามีข้อมูลมาให้ค่อนข้างครบครันเลยทีเดียว ทั้ง url ของรูปต่างๆ ,Comment และ Tips

แล้วหากเราต้องการหาสถานที่รอบๆ โดยไม่ต้อง Login เข้าใช้งาน foursquare ล่ะ จะทำอย่างไร? นี่คือแบบที่สองที่ว่า ที่จะทำให้เราไม่ต้อง login ก็สามารถเข้าไปขุดคุ้ยฐานข้อมูลเพื่อหาสถานที่ต่างๆ ได้

สำหรับ foursquare เค้าก็ใจดีมี API ที่ให้เราสามารถเข้าไปค้นฐานข้อมูลของเค้าโดยที่เราไม่จำเป็นต้อง login เข้าใช้งานก็ได้ สิ่งที่ foursquare เตรียมไว้ให้ใช้ (และเราจะใช้มัน) เรียกว่า Venues Platform นั่นเอง โดยที่ API หลักๆ ที่เค้าจัดเตรียมไว้ให้ก็เช่น search, tips, photos, check-in counts, here now (รายละเอียดก็อ่านได้ตาม link ที่ให้ไปเองนะ ;) ) โดยเค้าบอกไว้ว่าจำกัด request ไว้ที่ 5,000 ครั้งต่อ 1 ชั่วโมง ถ้าหากอยากได้เยอะกว่านี้ก็ให้ติดต่อเข้าไปเองเป็นกรณีต่างหาก

สำหรับ API ที่เราจะใช้ก็คือ search โดยที่เราสามารถส่ง https GET request ไปได้ตรงๆ แบบนี้เลย


NSString *string = [NSString stringWithFormat: @"https://api.foursquare.com/v2/venues/search?ll=13.723814,100.549396&limit=10&radius=2000&client_id=%@&client_secret=%@",
                        CLIENT_ID, CLIENT_SECRET];


NSURL *url = [NSURL URLWithString: string];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL: url];
[request startSynchronous];
NSLog(@"response = %@", request.responseString);


จากตัวอย่างผมใช้ ASIHttpRequest ช่วยในการส่ง https โดยสิ่งจำเป็นที่ต้องส่งไปก็คือ CLIENT_ID และ CLIENT_SECRET ที่ได้มาตอน create a consumer โดยพารามิเตอร์อื่นๆ ที่จะส่งไปด้วยได้ อ่านได้จากที่นี่

อ่านต่อ

March 24, 2012

Face Detection บน iOS 5

Apple เปิด API ที่ช่วยในการตรวจจับใบหน้าให้มาตั้งแต่ iOS 5.0 ช่วยให้นักพัฒนาสามารถตรวจจับใบหน้าคนในภาพได้ง่ายๆ

คลาสพระเอกของ entry นี้ก็คือ CIDetector ซึ่งเป็นคลาสที่อยู่ใน CoreImage.framework โดยที่คลาสนี้มี method หลักอยู่ตัวเดียวก็คือ (NSArray *)featuresInImage:(CIImage *)image ที่จะคอยรับ CIImage เข้าไปและ return Array ของ CIFaceFeature กลับออกมา ซึ่งขนาดของ Array ก็จะขึ้นอยู่กับจำนวนของใบหน้าคนที่มีการตรวจพบนั่นเอง ซึ่ง CIFaceFeature ก็จะบรรจุตำแหน่งของ ตาซ้าย ตาขวา และปากไว้

เริ่มจากสมมติว่ามี UIImageView อยู่ 1 รูป


__weak IBOutlet UIImageView *_personImageView;


จากนั้นสร้าง object ของ CIDetector ขึ้นมา พร้อมกำหนด option ให้มัน ซึ่ง option จะเป็นการกำหนดความแม่นยำในการหาว่าจะมากหรือน้อย โดยถ้ากำหนดเป็นความแม่นยำมากก็จะใช้เวลาในการตรวจจับใบหน้าคนนานมากขึ้น


NSDictionary *options = [NSDictionary dictionaryWithObject: CIDetectorAccuracyHigh
                                                    forKey: CIDetectorAccuracy];

CIDetector *detector = [CIDetector detectorOfType: CIDetectorTypeFace
                                          context: nil
                                          options: options];


จากโค้ดด้านบนจะสังเกตเห็นตอนสร้าง detector นั้นมีการกำหนด type ให้ด้วย ซึ่งตอนนี้ใน API สามารถกำหนดค่านี้ได้เพียงค่าเดียว (อนาคตอาจกำหนดให้หาส่วนอื่นๆ ได้เพิ่ม?)

เมื่อเราได้ตัว detector มาแล้ว ก็ต้องเตรียม CIImage สำหรับจะส่งเข้าไปให้มันช่วยหาหน้าคนให้หน่อย เนื่องจากในโลกของ CoreImage นั้นมันไม่รู้จัก UIImage ที่เรามักใช้กันนั่นเอง ดังนี้


CIImage *image = [[CIImage allocinitWithImage_personImageView.image];
NSArray *features = [detector featuresInImage: image];


หลังจากได้ features มาแล้ว ก็ลองนำ CIFaceFeature แต่ละตัวใน array มาใช้งาน หรือลองแปะลงบนรูปเพื่อเช็คความถูกต้อง


for(CIFaceFeature *feature in features){
    if(feature.hasLeftEyePosition){
        [self markAtPoint: feature.leftEyePosition];
    }
    
    if(feature.hasRightEyePosition){
        [self markAtPoint: feature.rightEyePosition];
    }

    if(feature.hasMouthPosition){
        [self markAtPoint: feature.mouthPosition];
    }
}


ตอนที่จะ mark ตำแหน่งลงบนรูป ต้องอย่าลืมว่าระบบ coordinate ในโลกของ CoreImage ไม่เหมือนกันกับในโลกของ UIKit ที่คุ้นเคยกันนะ ;)



เท่าที่ทดสอบมาถือว่าใช้งานได้ดีพอสมควร ส่วนความฉลาดในการหาก็น่าจะเป็นแบบเดียวกับที่ใช้ใน iPhone 4S และโปรแกรม iPhoto บน Mac

ที่มาของภาพ: Lenna

การสร้าง Source Path เอง

กรณีที่เรา import ไฟล์ เช่น resource หรือภาพต่างๆ เข้ามาในโปรเจ็คแบบ reference เรามักเจอปัญหาที่ว่าหากวันหนึ่ง เราจำเป็นต้องย้ายที่อยู่ของไฟล์เหล่านั้นไป จนเป็นเหตุให้รายชื่อไฟล์ต่างๆ ใน tree ด้านซ้ายของ Xcode เป็นสีแดง เนื่องจาก Xcode มองหาไฟล์นั้นไม่เจอ จนนักพัฒนาจำเป็นต้องอ้างอิงไฟล์เหล่านั้นใหม่ หลายคนที่ผมเจอมักลากไฟล์เข้ามาในโปรเจ็คใหม่อีกรอบหนึ่ง ซึ่งนั่นทำให้เสียเวลาเป็นอย่างมาก หากในโปรเจ็คมีไฟล์จำนวนมากที่ต้องแก้ไข และที่สำคัญไฟล์ project.pbxproj จะถูกแก้ไข จนอาจทำให้เกิด conflict กับคนอื่นโดยไม่จำเป็น

ในกรณีนี้นี้ เราสามารถแก้ไขตำแหน่งของไฟล์ได้โดยที่ไฟล์ project.pbxproj จะไม่ถูกแก้ไขได้ โดยการให้ไฟล์เหล่านั้น อ้างอิงกับ source path (source path คืออะไร ให้อ่านต่อไปในบทความนี้) ของเราเอง ทำให้ทุกครั้งที่มีการเปลี่ยนตำแหน่งไฟล์ใหม่ เราก็แค่มาแก้ source path ใหม่เท่านั้นเอง โดยจะเป็นการแก้ที่ setting ของตัว Xcode เอง ไม่ได้เกี่ยวกับโปรเจ็คของเราแต่อย่างใดทำให้ไม่มี change เกิดขึ้นกับไฟล์ในโปรเจ็ค

เราสามารถกำหนดการอ้างอิงตำแหน่ง (path) ของไฟล์ โดยสามารถกำหนดเป็น absolute path หรือจะให้อิงกับ path อื่นๆ ก็ได้ และหากดู property ของแต่ละไฟล์ในโปรเจ็ค จะพบว่าแต่ละไฟล์มี Location เป็น Relative to Group ให้อยู่แล้วเนื่องจากค่านี้เป็นค่า Default ของ Xcode



ส่วนค่าอื่นๆ ที่เราสามารถกำหนดให้ได้ ก็จะมีดังนี้



สำหรับความหมายของแต่ละค่านั้นก็พอจะเดาได้คร่าวๆ จากชื่อของมันเอง ส่วนรายละเอียดก็ตามไปอ่านต่อได้ที่ How Files are referenced

ถ้าหากเราต้องการจะให้ไฟล์ของเรา relative กับ path ที่เราต้องการล่ะ จะทำได้ไหม? คำตอบก็คือ ได้ แต่เราต้องไปสร้างสิ่งที่เรียกว่า Source Path ก่อน โดยการเข้าไปใน preference ของ Xcode เลือกแท็บ Locations และ Source Trees จากนั้นให้กดเครื่องหมาย บวก ที่ด้านล่างซ้ายและกำหนดค่าของ Source Path ดังรูปด้านล่าง



โดยที่

  • Setting Name จะเป็นชื่อสำหรับให้ไฟล์ใช้อ้างอิง
  • Display Name จะเป็นชื่อที่จะแสดงใน property panel ของไฟล์
  • Path คือ path directory ของ source path ที่เราจะให้ไฟล์ไป relative ด้วย กรณีนี้เราจะกำหนดค่าให้ Path เป็นที่อยู่ของ resource ที่นำเข้ามาในโปรเจ็คแบบ reference เช่นไฟล์ภาพต่างๆ 

หากตั้งค่าเสร็จแล้ว กลับมาดูที่ property panel ของไฟล์จะพบว่ามี source path ใหม่ที่เราพึ่งสร้างไปปรากฎขึ้นมาแล้วดังรูป





ให้เราเลือกให้ไฟล์ resource มา relative กับ source path ที่เราพึ่งสร้างขึ้นมาใหม่ เท่านี้หากคราวหน้าเรามีการย้ายที่อยู่ของไฟล์ resource ต่างๆ ก็แค่เข้ามาแก้ไข path ของ source path ตัวนี้เท่านั้นเอง โดยที่ไม่กระทบกับไฟล์ project.pbxproj เลยแม้แต่น้อย

อ่านต่อ: Files in Projects

March 22, 2012

เปลี่ยน hex string ให้กลายเป็น NSData จริง

สมมติว่าเรามี NSString ที่เป็นเลขฐาน 16 อยู่แบบนี้

NSString *hexString = @"8A9B1133";

เราจะเห็นว่าถ้าเราแปลง string ชุดนี้ให้เป็น NSData มันจะมีความยาว 4 byte
ส่วนวิธีในการแปลง เราจะใช้วิธีอ่านอักขระใน string เข้ามาเป็นคู่ๆ จากนั้นใช้ฟังก์ชั่น strtol ช่วยในการแปลงอักขระให้กลายเป็นตัว int ก่อน เสร็จแล้วก็ทยอย append ให้กับตัวแปร NSMutableData ตามลำดับ


// ถ้า hex string ที่จะแปลงไม่ครบคู่ก็จะไม่ทำอะไร
if (hexString.length % 2) {
    return nil;
}

NSMutableData *result = [[NSMutableData alloc] init];
unsigned char byte;
char hexChars[3] = {0};
for (int i = 0; i < (hexString.length 2); i++) {
    hexChars[0] = [hexString characterAtIndex:i * 2];
    hexChars[1] = [hexString characterAtIndex:i * 1];
    byte = strtol(hexChars, NULL, 16);
    [result appendBytes: &byte length1]; 
}

NSLog(@"result = %@, length = %d", result, result.length);


ทดลองรันโปรแกรม จะพบผลลัพธ์ออกมาดังนี้


result = <8a9b1133>, length = 4


เท่านี้เราก็มี NSData พร้อมใช้งานแล้ว

March 20, 2012

เปลี่ยน Warning ให้กลายเป็น Error ให้หมด

สิ่งหนึ่งที่ควรทำก็คือ อย่าปล่อยให้โปรแกรมที่เรากำลังพัฒนาอยู่มี warning ค้างคาอยู่ให้ Xcode มันฟ้องเราเวลาเรา build โปรแกรม ก็จริงอยู่ที่แม้จะมี warning ค้างคาเกะกะตา แต่โปรแกรมก็มัก build และรันขึ้นมาได้ด้วยดี (?) ทำให้นักพัฒนาหลายๆ คนมักไม่สนใจ และละเลยปล่อยให้ warning เหล่านั้นค้างอยู่ในโปรเจ็คของเรา

แต่สำหรับนักพัฒนาที่มีวินัย ชอบพัฒนาโปรแกรมอย่างยั่งยืน ไม่ฉาบฉวย ก็คงที่จะอดใจปล่อยให้ warning เหล่านี้มีชีวิตอยู่ในโปรเจ็คต่อไปไม่ได้ และต้องไล่กำจัดให้สิ้นซากให้จงได้

สำหรับการดัดนิสัยที่ไร้วินัยในการเขียนโปรแกรมก็คือไม่อนุญาตให้ build & run โปรเจ็คได้เด็ดขาดหากยังมี warning เหล่านี้อยู่ วิธีทำก็คือให้ไปตั้งค่าได้ใน Build & Setting และ search ด้วยคำว่า treat จากนั้นให้มองหาหัวข้อ Treat Warnings as Erros และกำหนดค่าเป็น YES



เท่านี้จาก warning สีเหลืองๆ ใน Xcode ก็จะกลายเป็น Error สีแดงๆ แล้วล่ะ

March 06, 2012

เรียกโปรแกรมอื่นจากโปรแกรมของเรา ด้วย Custom URL Schemes

เดิมทีนักพัฒนาโปรแกรมบน iOS จะสามารถเขียนโปรแกรมให้ไปเรียกโปรแกรมตัวอื่นๆ ในเครื่องให้เปิดขึ้นมาทำงานได้อยู่แล้ว ผ่าน URL Schemes ต่างๆ โดยที่นักพัฒนาจะรู้จักกันดีอยู่แล้วก็ได้แก่ http, mailto, tel และ sms และพวก http-based ก็ได้แก่ YouTube และ Map

Apple ได้เปิดโอกาสให้เราสามารถกำหนด URL Schemes ให้กับ App ของเราได้เองเช่นกัน โดยมีข้อแม้ว่า  URL Schemes ที่เราจะสร้างขึ้นมาใช้เองนั้นห้ามซ้ำกับของที่ Apple มีอยู่แล้ว แต่ถ้าซ้ำ iOS จะเรียก App ของ Apple ขึ้นมาแทนที่จะเป็นของเรา และในกรณีที่ URL Schemes ของเราไปซ้ำกับ App ของคนอื่นๆ ที่ไม่ใช่ของ Apple เวลาเราเรียกก็จะเงียบ ไม่มีอะไรเกิดขึ้น

วิธีสร้าง URL Schemes ให้กับ App ของเราเอง
ทำได้โดยไปกำหนดค่าในไฟล์ Info.plist โดยสร้าง URL Types จากนั้นกำหนด URL identifier กับ URL Schemes ให้มัน ดังรูปด้านล่าง



โดยที่ URL identifier นั้นควรใช้ reverse domain style ตามธรรมเนียมปฏิบัติ ส่วน URL Schemes นั้น จะเอาไว้ให้ App อื่นๆ อ้างถึงเวลาจะเรียกให้ App ของเราขึ้นมาทำงาน (ในตัวอย่างจะกำหนด URL Schemes เป็น todolist

เมื่อสร้างเสร็จแล้ว ให้ลองเรียก App ของเราจาก App อื่นดู โดยใช้วิธีการเช่นเดียวกับการเรียกใช้ URL Schemes ต่างๆ ของ Apple ได้เลย ดังนี้

NSURL *url = [NSURL URLWithString: @"todolist://"];
[[UIApplication sharedApplication] openURL: url];

หลังจาก App ของเราถูกเรียกขึ้นมาแล้ว จะมี method บางตัวที่อยู่ใน AppDelegate ถูกเรียกขึ้นมาทำงาน ได้แก่


นอกจากเราจะเรียกให้ app ตัวอื่นๆ ทำงานได้แล้ว เรายังสามารถส่ง paramter บางอย่างติดไปกับ url ที่เรียกได้อีกด้วย เช่นเดียวกับ URL Address ที่ใช้กับเว็บไซต์ที่คุ้นเคยกัน แต่ต้องระวังนิดนึงเกี่ยวกับ URL ที่ส่งเข้ามาใน app ของเรา หากสนใจว่าจะจัดการยังไงกับมันดีอ่านต่อที่ ต่อได้ที่นี่

นอกจาก App ของเราที่จะมี URL Schemes แล้ว App คนอื่นๆ ก็มีด้วยเหมือนกันครับ ลอง Google ดูพบว่ามีบางที่ได้รวบรวมไว้ให้เราได้อ่านกันเล่นๆ ด้วย เช่น ที่นี่ ที่รวม App ของ iOS มาแทบทุกตัวเลย ทั้ง Safari, Maps, Phone, SMS, Mail, YouTube, iTunes, App Store และ iBooks (อ่านต่อเกี่ยวกับ format การคุยกับ App เหล่านี้ที่ Apple URL Scheme Reference) นอกจากนั้นก็ยังมี Third Party Application ตัวอื่นๆ อีก แต่ที่เห็นจะเยอะหน่อยก็คือ Facebook นี่แหละ

ส่วนรายละเอียดการสร้าง และวงจรชีวิตของ App ที่เกิดจากการ launch จาก URL โดยละเอียด อ่านได้ที่ Reference ของ Apple ได้เลย


อ่านต่อ

:)

March 03, 2012

อ่านเลข Version และเลข Build

เป็นอะไรที่ไม่ได้ทำบ่อย ไม่เคยจำได้ชัดสักทีว่าต้องอ่าน key ไหน ทำให้ทุกครั้งที่ทำก็ต้องเปิดโปรเจ็คเก่าๆ มาดูอยู่เรื่อยไป


NSString *version = [[[NSBundle mainBundle] infoDictionary
                     objectForKey:@"CFBundleShortVersionString"];
    
NSString *build = [[[NSBundle mainBundle] infoDictionary
                   objectForKey:@"CFBundleVersion"];


:)

February 25, 2012

อ่าน QR Code บน iOS ด้วย ZBar SDK

สำหรับการเขียนโปรแกรมให้สามารถอ่าน QR code นั้น ผมเคยเขียนเกี่ยวกับการอ่าน QR Code บน iPhone โดยการใช้ Zbar มาแล้วครั้งหนึ่ง แต่ครั้งนั้นการใช้งานและติดตั้งอะไรยังไม่ค่อยสะดวก (มาก) เท่าไหร่นัก และสำหรับคราวนี้ ZBar SDK ซึ่งถูกพัฒนาไปมาก ทำให้เราสามารถนำมาติดตั้งและใช้งานบน iOS App ของเราได้ง่ายขึ้นกว่าเดิมเยอะ

เริ่มจากดาวน์โหลด ZBar SDK (เวอร์ชั่นปัจจุบันคือ 1.2) ได้จากที่นี่เลย Download the ZBar iPhone SDK 1.2 จากนั้นเราจะได้ไฟล์ .dmg ขนาดประมาณ 7.1 MB มา ให้ mount disk image และลากโฟลเดอร์ ZBar SDK ลงไปใน Xcode project ของเรา

ต่อไปก็ link AVFoundation.framework, CoreMedia.framework, CoreVideo.framework, QuartzCore.framework และสุดท้าย libiconv.dylib

วิธีใช้งาน เริ่มจากให้ import header ที่จำเป็นเข้ามาในโปรเจ็คก่อน


#import "ZBarSDK.h"


จากนั้นสร้าง object ของ ZBarReaderViewController และกำหนดค่าต่างๆ ที่จำเป็นตามโค้ดต่อไปนี้


ZBarReaderViewController *reader = [ZBarReaderViewController new];
reader.readerDelegate = self;
reader.supportedOrientationsMask = ZBarOrientationMaskAll;

ZBarImageScanner *scanner = reader.scanner;

[scanner setSymbology: ZBAR_I25
               config: ZBAR_CFG_ENABLE
                   to: 0];

[self presentModalViewController: reader animated: YES];


วิธีกำหนดค่าและใช้งานแบบอื่นๆ

หลังจากเราสั่งให้แสดง modal view ไปแล้ว โปรแกรมจะเปิดกล้องขึ้นมาพร้อมสแกนภาพ QRCode ให้โดยอัตโนมัติ หลังจากโปรแกรมตรวจเจอ QR Code แล้ว delegate method ที่เราคุ้นเคยในการถ่ายรูปตัวนี้จะทำงาน


- (void) imagePickerController:(UIImagePickerController *)picker 
 didFinishPickingMediaWithInfo:(NSDictionary *)info


ซึ่งนอกจาก info จะส่งค่าต่างๆ ที่เกี่ยวกับการถ่ายรูปปกติแล้ว มันยังส่ง object มากับ key พิเศษอีกตัวเพิ่มเข้ามานั่นก็คือ ZBarReaderControllerResults ซึ่ง object ตัวนี้จะเก็บข้อมูลที่ตัว ZBar SDK อ่านออกมาได้


id<NSFastEnumeration> results = [info objectForKey: ZBarReaderControllerResults];
ZBarSymbol *symbol = nil;
for(symbol in results){
    NSLog(@"symbol.data = %@", symbol.data);
}


ที่มา: ZBar iPhone SDK

February 24, 2012

การขยายรูปโดยไม่ให้ผิดสัดส่วนบริเวณขอบ

UIImage เป็นคลาสคลาสนึงที่มี API เปลี่ยนแปลงไปใน iOS 5 ทำให้มีบาง method ถูก Deprecate ไป แต่ก็มีบาง method ถูกเพิ่มเข้ามาใหม่ เช่น resizableImageWithCapInsets:

หากเรามีรูปแบบนี้อยู่



รูปนี้เราอาจจะเอามาใช้เป็นปุ่ม หรือ background ของอะไรบางอย่าง เวลาเรามาเอาใช้เป็น background ที่มีขนาดเดียวกับภาพก็จะไม่เป็นปัญหา แต่เมื่อนำมาใช้กับพื้นที่ที่ใหญ่กว่ารูปนี้ ปัญหาจะเกิดกับ Designer ทันที ที่เราต้องบอกให้ designer แก้รูปให้ใหม่ เพราะถ้าไม่แก้ จากโค้ดด้านล่าง


UIImage *image = [UIImage imageNamed: @"myButton.png"];    
UIImageView *imageView = [[UIImageView alloc] initWithImage: image];
imageView.frame = CGRectMake(20, 20, 250, 44);
imageView.contentMode = UIViewContentModeScaleToFill;
[self.view addSubview: imageView];


รูปต้นแบบมี resolution จริงๆ อยู่ที่ 80x44 เท่านั้น แต่เวลาเอามาใช้งานกลับนำมาใช้กับพื้นที่ขนาดใหญ่ถึง 250x44 ทำให้รูปมันยืดอย่างที่เห็นด้านล่าง



เดือดร้อนถึง designer หรือเราเองที่ต้องแก้รูปใหม่ แต่ด้วย method resizableImageWithCapInsets:  ทำให้เราสามารถแก้ไขรูปเองได้เลย เพียงแค่ตอนสร้าง UIImage ให้กำหนดค่าบางอย่างตามไปด้วยแบบนี้


UIImage *image = [[UIImage imageNamed: @"myButton.png"]
                  resizableImageWithCapInsets: UIEdgeInsetsMake(0, 10, 0, 10)];


ผลลัพธ์ที่ออกมาก็จะเป็นแบบนี้


โดยพารามิเตอร์ของ method พระเอกตัวนี้มีตัวเดียว ก็คือ UIEdgeInsets นั่นเอง ส่วนค่าทั้ง 4 ตัวที่ใช้ตอนสร้าง UIEdgeInstes นั้น เค้านิยามไว้ใน reference ดังนี้


typedef struct {
    CGFloat top, left, bottom, right;
} UIEdgeInsets;


ค่าทั้ง 4 นั้นเป็นค่าของ ขอบด้านบน, ซ้าย, ล่าง และขวา เวลาเรากำหนดค่าเหล่านี้ ก็จะเป็นการกำหนดว่าให้ขอบด้านไหนบ้าง ที่ไม่ให้มีการยืดหด หรือถูกเปลี่ยนแปลงขนาดไป ดังนั้นจากตัวอย่างโค้ดก็คือการกำหนดให้ขอบด้านซ้าย และด้านขวาเข้ามาข้างละ 10 pixel ไม่ต้องยืดเพื่อรักษาสัดส่วนไว้นั่นเอง

Masking Image

CoreGraphic มีฟังก์ชั่นสำหรับช่วยทำ Masking รูปมาให้เราแล้ว นั่นก็คือ ฟังก์ชั่นชื่อ CGImageMaskCreate นั่นเอง พารามิเตอร์ของฟังก์ชั่นนี้เยอะไปหน่อย เลยนำมาจดไว้กันลืม ดังนี้

UIImage *maskImage = [UIImage imageNamed: @"mask.png"];
CGImageRef maskRef = maskImage.CGImage

CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                    CGImageGetHeight(maskRef),
                                    CGImageGetBitsPerComponent(maskRef),
                                    CGImageGetBitsPerPixel(maskRef),
                                    CGImageGetBytesPerRow(maskRef),
                                    CGImageGetDataProvider(maskRef), NULL, false);

UIImage *image = [UIImage imageNamed: @"myImage.jpg"];
CGImageRef masked = CGImageCreateWithMask([image CGImage], mask);

return [UIimage imageWithCGImage: masked];


ส่วน input และ output ก็ตามรูปด้านล่างนี้เลย

myImage.jpg

mask.png

ภาพผลลัพธ์ที่ได้หลังจากการ Mask
เท่านี้ก็สามารถ Mask รูปภาพได้ตามต้องการแล้ว

February 22, 2012

แปลง Latitude, Longitude ให้เป็น Address


ก่อนหน้า iOS 5.0 เราจะ Reverse Geocoding (แปลง latitude, longitude ให้กลายเป็น Address) ด้วยการใช้ MKReverseGeocoder (entry เก่า) ซึ่งถ้าหากว่ากด link เข้าไปดู API reference แล้วจะพบว่า MKReverseGeocoder นั้นถูก deprecated แล้วใน iOS 5.0 และทาง Apple เองก็ได้แนะนำให้ใช้ CLGeocoder แทน

เราจะใช้ method – reverseGeocodeLocation:completionHandler: ในการหาชื่อสถานที่จาก latitude, longitude วิธีใช้งานก็คือสร้าง geocoder object ขึ้นมา และเตรียม CLLocation ไว้ให้พร้อม จากนั้นก็ call method ได้เลย

CLGeocoder *geocoder = [[CLGeocoder alloc] init];
CLLocation *location = [[CLLocation alloc] initWithLatitude: latitude longitude: longitude];
[geocoder reverseGeocodeLocation: location
               completionHandler: ^(NSArray *placemarks, NSError *error){
                   NSLog(@"placemarks = %@", placemarks);
                   NSLog(@"error = %@", error);
   }];

ก่อนใช้งาน อย่าลืม link CoreLocation.framework ด้วยแล้วก็อย่าลืม

#import <CoreLocation/CLGeocoder.h>

นอกจาก CLGeocoder จะหาชื่อสถานที่จาก latitude, longitude (เรียกว่า Reverse-geocoder) ได้แล้ว มันยังสามารถแปลงชื่อสถานที่ให้กลายเป็น latitude, longitude ได้อีกด้วย (เรียกเท่ๆ ว่า Forward-geocoder) ทำอย่างไรนั้นก็อ่านได้จาก API reference เองได้เลย :)

สำหรับข้อตกลงในการใช้ CLGecoder นั้น ทาง Apple กำหนดแนวทางการใช้งานไว้ 4 ข้อหลักๆ ดังนี้
  1. ณ ตำแหน่งใดๆ ให้ส่ง request ไปแค่ครั้งเดียวก็พอ
  2. ให้พยายาม reuse ใช้ผลลัพธ์เดิม ไม่ต้องส่งที่เดิมไปขอตำแหน่งซ้ำๆ 
  3. หากต้องการให้โปรแกรมมีการอัพเดทสถานที่โดยอัตโนมัติ (เช่นกำลังเคลื่อนที่อยู่) ให้รอจนกว่าจะมีการเปลี่ยนตำแหน่งที่อยู่อย่างมีนัยยะสำคัญก่อน ไม่ต้องถึงกับขยับนิดขยับหน่อยก็ขอข้อมูลอยู่เรื่อยๆก็ได้
  4. ถ้าเป็นไปได้ อย่าขอตำแหน่งหากผู้ใช้งานยังไม่อยากรู้ หรือขอไปผู้ใช้งานก็ไม่รู้อยู่ดี เช่นโปรแกรมยังไม่ได้เปิดหน้าที่จะต้องแสดงผลลัพธ์ของการขอ หรือโปรแกรมกำลังรันอยู่ใน background เป็นต้น