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 เป็นต้น