November 20, 2011

JSON บน iOS 5

ปัจจุบันเราคงปฏิเสธไม่ได้ว่าเราใช้ JSON กันบ่อยขึ้น และในวงกว้างขึ้นเรื่อยๆ
เดิมหากจะทำอะไรกับ JSON เราก็จะมักจะต้องพึ่งพา third party library อยู่เสมอ แต่เมื่อมาถึงยุคของ iOS 5  Apple ก็ได้เพิ่ม JSON library เข้ามาให้เราพร้อมใช้ได้ง่ายๆ แล้ว

ทำให้เราสามารถแปลง Object อย่าง NSString, NSNumber, NSArray หรือ NSDictionary ให้กลายเป็น JSON ได้ง่ายๆ โดยไม่จำเป็นต้องพึ่งพา third party library อีกต่อไป

พระเอกของงานนี้ก็คือ NSJSONSerialization  เราจะใช้มันช่วยในการแปลง Object ให้กลายเป็น JSON และแปลงจาก JSON กลับมาเป็น Object ได้ ดังนี้


แปลง JSON Data ให้กลายเป็น Object

    // สมมติว่าเราได้รับ data มาจากอินเทอร์เน็ต
    NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString: urlString]];

    // แปลงเป็น Object ด้วยพระเอกของเรา
    NSDictionary* json = [NSJSONSerialization JSONObjectWithData: data
                                                     optionskNilOptions 
                                                       error: &error];


สำหรับ options ของการแปลง JSON ให้กลายเป็น Object นั้น จะเกี่ยวกับเรื่องการความต้องการให้สิ่งที่แปลงออกมามีลักษณะเป็นอย่างไรในลักษณะของ Mutable หรือ Immutable  และอยากจะให้ top level object แยกกันเป็น array หรือ dictionary หรือไม่ อ่านรายละเอียด options ได้ที่นี่


แปลง Object เป็น JSON Data

    // เริ่มจากเรามี Object ที่ต้องการแปลงเป็น JSON
    NSDictionary* objectInfo = ...

    // แปลงเป็น JSON ด้วยพระเอกของเรา
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject: objectInfo 
                                 optionsNSJSONWritingPrettyPrinted
                                   error: &error];

    // ลองดูหน้าตาของ JSON ที่เราแปลงได้
    NSString *jsonString = [[NSString allocinitWithData:jsonData
                                             encoding:NSUTF8StringEncoding];



กรณีนี้ถ้าหากเราต้องการส่งข้อมูลไปที่อื่นๆ เช่นส่งไปยัง server เราสามารถกำหนด option เป็น kNilOptions ได้ เพื่อตัดอักขระส่วนที่ไม่จำเป็นออกไป แต่ที่เรากำหนด options เป็น NSJSONWritingPrettyPrinted ก็เพื่อให้เราสามารถอ่านได้ง่ายนั่นเอง

เพียงเท่านี้เราก็สามารถทำงานร่วมกับ JSON ได้อย่างมีความสุขแล้ว

November 11, 2011

พักผ่อนที่ The Living Hill Resort

เคยไปพักผ่อนมาหลายๆ ที่ แต่ที่ The Living Hill Resort นี่ต้องเขียนถึงสักหน่อย เนื่องจากเป็นรีสอร์ทที่มีประวัติโชกโชนอยู่ในเว็บไซต์ชื่อดังหลายเว็บ ลองหาดูจะพบว่าชื่อเสียงในทางลบจะค่อนข้างเยอะนิดนึง ชวนให้คนไม่กล้าไปพักกันสักเท่าไหร่

คำชม
  • พนักงานพูดจาดีมาก บริการก็ดีมากเลย ดูเป็นกันเองคอยช่วยเหลือนู่นนี่ โทรมาถามไถ่เป็นห่วงว่าเราจะหารีสอร์ทไม่เจอด้วยล่ะ (แหงล่ะ ทางไปซับซ้อนมาก)
  • เงียบสงบ ที่พักคนน้อยดี (อาจเพราะไปวันที่คนน้อย) มีที่นั่งชิลๆ ตากลมเย็นๆ สบายดีจัง มีดาดฟ้าให้นอนดูดาวตอนกลางคืนด้วยถ้าโชคดีไปวันที่ฟ้าเปิด
  • ที่พักอยู่บนยอดเนินเขาสูง มองไปเห็นวิวรอบๆ ตัวเป็นแนวเขายาวล้อมรอบ มีลมพัดเย็นตลอด
  • ไม่ได้แย่อย่างที่ในเว็บส่วนใหญ่ที่หาเจอบ่นๆ กัน
  • ถ้าไม่มีดีลคงไม่ไป ห้องแพงเกิน แต่ถ้า 2 พันเหมือนเดิมมีโอกาสอาจไปพักอีกสัก 2-3 วัน
  • สัญญาณ truemove ชัดเจนใช้ได้ ทำให้ผมมี Google Map นำทางไปจนถึงจนได้ ไม่งั้นหลงชัวร์
  • ระหว่างทางแวะกินสเต๊กและไอติมอร่อยๆ ที่ร้าน Dairy Home
  • รูป

คำติ:
  • โทรศัพท์ติดต่อรีสอร์ทติดยากมาก (ดังคนเขาว่า) ถ้าเทียบกับรีสอร์ทที่อื่นๆ ที่เคยไปพัก
  • ตอนเช้างง ไม่รู้ว่าอาหารเช้าไปเอาตรงไหน ที่ไหนได้เดินลงมาแล้วจะมีพนักงานมาถามว่าห้องไหน แล้วจะมีคนเอามาเสริฟให้เลยถึงที่
  • แผนที่ในเว็บ thelivinghill.com การเดินทางไปไม่ชัดเจน เขียนแค่นั้นใครจะเดินทางไปถูก -_-" หลงได้ง่ายๆ เลย เส้นทางก็วกวนซับซ้อน ผมโชคดีที่คนรู้จักไปพักมาก่อน แล้วบันทึก location ไว้เลยเปิด Google Map ตามไปได้ไม่ยาก (ลองเทียบแผนที่จากในเว็บ กับแผนที่ใน Google Map ดูสิ ว่ามันช่างต่างกันมากเหลือเกิน)
  • หาที่นี่ไม่เจอใน Google Map



View Larger Map แผนที่ The Living Hill Resort จากแยกบางพลัด กรุงเทพ ไปถึงทางเข้า The Living Hill Resort


แสดงตำแหน่ง วิธีการเดินทางไป และสถานที่ตั้งของ The Living Hill Resort
ถึงจุด B แล้วให้เลี้ยวขวาไปครับ จะเป็นทางดิน ขึ้นเนินชันมาก

October 06, 2011

ลาก่อน สตีฟ จ็อบส์

สู่สุขติเถิด ลาก่อนสตีฟ จ็อบส์​





He changed them. He changed us. He changed you.

วาดกราฟใน iOS ด้วย CoreGraphic

เห็นหลายๆ App มีการวาดกราฟสวยๆ กัน โดยเฉพาะ App เกี่ยวกับหุ้นเช่น App ของ Bloomberg เป็นต้น จึงลองวาดเล่นดูสักหน่อย ถือเป็นการทบทวนทักษะทางด้าน CoreGraphic ก็แล้วกัน เพราะตั้งแต่ทำงานมาแทบไม่ได้ใช้ทักษะทางด้านนี้เลย

โดยรูปเป้าหมายที่ต้องการ จะมีหน้าตาดังนี้



หลักการการวาดรูปต่างๆ ลงไปก็คือเราจะเข้าไป override method  - (void)drawRect:(CGRect)rect ของ UIView และสั่งวาดสิ่งต่างๆ ข้างใน method นี้

เมื่อเราต้องการวาดอะไรลงไปก็ตาม เราต้องการสิ่งที่เรียกว่า context และเพื่อให้เข้าใจได้ง่ายๆ ให้มองว่า context ตัวนี้ก็คือผืนผ้าใบที่เราจะวาดสิ่งต่างๆ ลงไปนั่นเอง ดังนั้นเมื่อเราจะเริ่มทำอะไร เราต้องได้ context มาก่อน เพื่อเอาไว้ใช้อ้างอิงในภายหลัง ว่าเราจะทำอะไร จะวาดอะไรลงไปใน context (ผืนผ้าใบ) อันนี้ (context เป็น stateful ซึ่งแปลว่าหากเรากำหนดค่าอะไรให้มันไปแล้ว มันก็จะยังคงค่านั้นเสมอ จนกว่าเราจะเปลี่ยนแปลงค่าให้มันเองในภายหลัง) ดังนี้


    // get current context
    CGContextRef context = UIGraphicsGetCurrentContext();


เริ่มจากการวาดเส้นประ (dash) ที่เป็นพื้นหลังของกราฟก่อน เริ่มวาดเส้นประ (dash) โดยกำหนดความกว้างของเส้น สีที่จะวาด ลักษณะของเส้นประ ดังนี้

    // draw bg line
    CGContextSaveGState(context);
    CGContextSetLineWidth(context, 1.0);
    CGContextSetStrokeColorWithColor(context, [UIColor lightGrayColor].CGColor);
    CGFloat dashArray[] = {4,4,4,4};
    CGContextSetLineDash(context, 3, dashArray, 4);

เมื่อเรากำหนดลักษณะของเส้นที่จะวาดเรียบร้อยแล้ว ต่อไปก็กำหนดเส้นที่จะวาด ว่าจะวาดเส้นโดยเริ่มจากจุดไหน ไปถึงจุดไหนดังนี้

    for(int i = 50; i <= 300; i += 50){
        CGContextMoveToPoint(context, i, 200);
        CGContextAddLineToPoint(context, i, 480);
        
        CGContextMoveToPoint(context, 0, 200 + i);
        CGContextAddLineToPoint(context, 320, 200 + i);
    }


เมื่อกำหนดเส้นที่จะวาดเรียบร้อยแล้ว ก็สั่งให้วาดลงไปใน context (ผืนผ้าใบ) ด้วยฟังก์ชั่น CGContextStrokePath(context) ได้เลย

    CGContextStrokePath(context);
    CGContextRestoreGState(context);


สำหรับความหมายของ CGContextSaveGState(context) และ CGContextRestoreGState(context) ให้อ่านได้จากบทความนี้ สั้นๆ ก็คือมันเป็นการบันทึกสถานนะของการกำหนดค่าต่างๆ ให้กับ context หลังจากนั้นเมื่อเราทำอะไรเสร็จแล้ว เราก็จะย้อนกลับไปสถานะของ context ก่อนหน้าที่เราจะทำอะไรกับมันด้วยการ restore context นั่นเอง เมื่อเราสั่งให้วาดเส้นประแล้วเราจะได้ผลลัพธ์ดังนี้





ถ้าหากเราสั่ง gradient ลงไปใน context ตอนนี้ เราจะได้ gradient เต็มหน้าจอเนื่องจาก context ของเราจะมีรูปร่างเป็นสี่เหลี่ยมผืนผ้าเต็มหน้าจอนั่นเอง แต่ที่เราต้องการจริงๆ ก็คือเราต้องการให้ gradient เฉพาะพื้นที่ใต้กราฟเท่านั้น ซึ่งเทคนิคที่เราจะใช้ก็คือ เราจะสร้าง shape ที่เป็นพื้นที่ใต้กราฟ จากนั้นเราจะ clip context ให้เป็นรูปเดียวกับ shape ที่เราวาดขึ้นมา จากนั้นจึงจะวาด gradient ลงไปใน context ที่ clip แล้ว

สมมติว่าเรามีข้อมูลอยู่ 40 ชุด โดยเราสามารถ generate ข้อมูล 40 ชุดขึ้นมาได้ดังนี้ก่อน


    // generate 40 data, each limited to 150
    int r = 0;
    int array[40];
    for(int i = 0; i <= 40; i ++){
        r = arc4random() % 150;
        array[i] = r;
    }


เก็บ data ไว้ในตัวแปร array ขนาด 40 ต่อไปเราจะวาด Shape ที่ที่เป็นพื้นที่ใต้กราฟโดยอิงจากข้อมูล 40 ชุดที่เรามี ดังนี้


    // draw graph by points stored in array
    CGContextSaveGState(context);
    CGContextMoveToPoint(context, 0, array[0] + graphBase);
    for(int i = 0; i <= 40; i ++){
        CGContextAddLineToPoint(context, i * 8, array[i] + graphBase);
    }
    
    // wrap it as a shape
    CGContextAddLineToPoint(context, 320, 480);
    CGContextAddLineToPoint(context, 0, 480);
    CGContextAddLineToPoint(context, 0, array[0] + graphBase);
    CGContextSetLineWidth(context, 3);
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    CGContextStrokePath(context);
    CGContextRestoreGState(context);



เหมือนกับการวาดเส้นประในตอนแรก โดยเราจะวนลูปวาดเส้นไปทีละจุดๆ ใน array เมื่อวาดครบทุกจุดแล้วเราจะวาดย้อนกลับมายังจุดเริ่มต้นใหม่อีกครั้งให้เป็น Shape ปิด และเมื่อเราทดลองวาดเส้นเพื่อตรวจสอบ Shape ที่เราวาดขึ้นมา เราจะได้ Shape หน้าตาดังนี้



Shape ถูกวาดเส้นด้วยสีแดง ต่อไปเราจะทำการ clip Context ด้วย Shape ที่เราวาดขึ้นมาโดยใช้ฟังก์ชั่น CGContextClip(context) และเอาโค้ดในการวาดเส้นสีแดงให้ Shape ออกไป ทำให้จากโค้ดด้านบนเราจะเหลือแบบนี้


    // wrap it as a shape
    CGContextAddLineToPoint(context, 320, 480);
    CGContextAddLineToPoint(context, 0, 480);
    CGContextAddLineToPoint(context, 0, array[0] + graphBase);
    CGContextSetLineWidth(context, 3);
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    CGContextClip(context);


ต่อไปจะวาด Gradient ลงไปใน context ที่เหลือจากการถูก clip โดยเริ่มจากการนิยาม Gradient ก่อนว่าจะให้มีลักษณะเป็นอย่างไร


    // gradient definition
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat locations[] = { 0.55, 1.0};
    CGColorRef endColor = [UIColor colorWithRed:0.0f 
                                          green:153.0f/256.0f 
                                           blue:249.0f/256.0f 
                                          alpha:1.0].CGColor;
    
    CGColorRef startColor = [UIColor colorWithRed:1
                                            green:1
                                             blue:1
                                            alpha:0.5].CGColor;
    
    NSArray *colors = [NSArray arrayWithObjects:(id)startColor, (id)endColor, nil];
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef) colors, locations);
    CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));


โค๊ด เป็นการกำหนด color space (ส่วนใหญ่แทบทั้งหมดจะใช้ RGB)  กำหนดตำแหน่งของสีที่จะใช้วาด gradient กำหนดสีเริ่มต้น และสีปลายของ gradient กำหนดจุดเริ่มต้นของ gradient และจุดปลายของ gradient

วาด gradient ลงไปใน context ที่ clip ไปแล้ว และ restore state เดินกลับมาแบบนี้


    CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
    CGContextRestoreGState(context);


จะได้ผลลัพธ์ดังนี้



เราได้ gradient ใต้กราฟมาแล้ว ต่อไปเราจะวาดเส้นกันล่ะ โดยจะวนลูปวาดเส้นจากข้อมูลทั้ง 40 จุดที่เรามี ดังนี้


    // draw graph line
    CGColorRef graphColor = [UIColor colorWithRed:0.0f 
                                            green:153.0f/256.0f 
                                             blue:249.0f/256.0f 
                                            alpha:1].CGColor;
    CGContextSaveGState(context);
    CGContextSetStrokeColorWithColor(context, graphColor);
    CGContextSetLineWidth(context, 3);
    CGContextMoveToPoint(context, 0, array[0]);
    for(int i = 0; i <= 40; i ++){
        CGContextAddLineToPoint(context, i * 8, array[i]);
    }
    CGContextStrokePath(context);
    CGContextRestoreGState(context);


กำหนดสีที่จะวาด จากนั้นลูปวาดเส้นตามจุดต่างๆ ตามข้อมูลทั้ง 40 จุดที่เรามี เราก็จะได้กราฟหน้าตาแบบนี้ออกมาแล้ว



ส่วนการวาด text หรือข้อความลงไปด้วย CoreGraphic นั้น ถ้าหากเราสั่งวาดแบบปกติลงไป เราจะได้ข้อความที่กลับหัว ต้อง transform มันด้วยโดยการกำหนด text matrix ก่อนการสั่งวาด ดังนี้


    // draw text
    char* text ="Apple API";
    CGContextSelectFont(context, "Helvetica", 16, kCGEncodingMacRoman); 
    CGContextSetTextDrawingMode(context, kCGTextFill);
    CGContextSetRGBFillColor(context, 1, 1, 1, 1);
    
    CGAffineTransform xform = CGAffineTransformMake(1.00.0,
                                                    0.0, -1.0,
                                                    0.00.0);
    CGContextSetTextMatrix(context, xform);
    CGContextShowTextAtPoint(context, 120, 450, text, strlen(text));



ซอสโค้ดของตัวอย่างนี้ดาวโหลดได้ที่นี่ หรือดาวน์โหลดจาก GitHub ก็ได้

อ้างอิง:
An iOS 4 iPad Graphics Drawing Tutorial using Quartz 2D
Quartz 2D Programming Guide

September 24, 2011

สั่งให้ UIView เคลื่อนที่แนวโค้ง

การสั่งให้ UIView ทั้งหลายแหล่เคลื่อนที่เป็นแนวเส้นตรงนั้นไม่ยาก เพียงแต่กำหนดจุดเริ่มต้น กับจุดปลายผ่านบล็อค beginAnimations:context: และ commitAnimations เท่านั้น

สำหรับการเคลื่อนที่ของ UIView เป็นเส้นโค้งนั้น หลักการก็คือเราจะกำหนดเส้นทางการเคลื่อนที่ให้มัน จากนั้นกำหนด animation ให้กับ layer ของ UIView ที่ต้องการให้เคลื่อนที่

ค้นหาเส้นทางการเคลื่อนที่
เราจะกำหนดเส้นทางการเคลื่อนที่ แบบเดียวกับการวาดเส้นโค้งอย่าง Bézier curve ซึ่งเจ้า Bézier curve นี้ก็จะประกอบด้วยจุดเริ่ม จุดปลาย และจุดควบคุมหรือ control point (อ่านรายละเอียดเกี่ยวกับลักษณะของ Bézier curve ที่นี่)

ผมจะวาดเส้นโค้งขึ้น จากขอบทางซ้าย ไปทางขวาของจอ iPad ผมสามารถกำหนดเส้นโค้งได้ประมาณนี้

CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, 0, 512);
CGPathAddQuadCurveToPoint(curvedPath, NULL, 768/2, 0, 768, 512);

ซึ่งขนาดความโค้งว่าจะโค้งมากหรือน้อย โค้งขึ้นหรือโค้งลง สามารถกำหนดได้ที่จุดควบคุม จากโค๊ดด้านบนลองวาดออกมาเป็นเส้นดูจะได้เส้นหน้าตาแบบนี้



ส่วนวิธีการวาดนั้น สามารถทำได้ดังนี้

//Create a bitmap graphics context, you will later get a UIImage from this
UIGraphicsBeginImageContext(CGSizeMake(768,1024));
CGContextRef ctx = UIGraphicsGetCurrentContext();

//Set variables in the context for drawing
CGContextSetLineWidth(ctx, 1.5);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);

CGContextMoveToPoint(ctx, 0, 512);
CGContextAddQuadCurveToPoint(ctx, 768/2, 0, 768, 512);

//Draw the line
CGContextDrawPath(ctx, kCGPathStroke);

//Get a UIImage from the current bitmap context we created at the start and then end the image context
UIImage *curve = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

//With the image, we need a UIImageView
UIImageView *curveView = [[UIImageView alloc] initWithImage:curve];
//Set the frame of the view - which is used to position it when we add it to our current UIView
curveView.frame = CGRectMake(0, 0, 768, 1024);
curveView.backgroundColor = [UIColor clearColor];

[self.view addSubview:curveView];

เมื่อดูจากเส้นที่วาดออกมาแล้ว พบว่าเป็นเส้นทางการเคลื่อนที่ในแนวโค้งที่เราต้องการ เราก็เอาจุดที่นิยาม Bézier curve นี้แหละมาใช้งาน

กำหนดเส้นทางการเคลื่อนที่
เราจะใช้ CAKeyframeAnimation ในการกำหนดเส้นทางการเคลื่อนที่ โดยการกำหนดค่าต่างๆ ให้กับ CAKeyframeAnimation ดังนี้

//Prepare the animation - we use keyframe animation for animations of this complexity
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 5.0;
//Lets loop continuously for the demonstration
pathAnimation.repeatCount = 1000;

จากโค๊ดจะบอกว่าให้เล่น animation นี้ 5 นาทีต่อ 1 รอบ กำหนดให้เล่น 1000 รอบ สำหรับรายละเอียดของ property ตัวอื่นๆ ที่ไม่ได้กล่าวถึงแต่ละตัวสามารถอ่านได้จากที่นี่เลย

ต่อไปก็กำหนดเส้นทางเดินให้ animation ตัวนี้ล่ะ โดยใช้เส้นที่เราได้เลือกแล้วดังกล่าวไปข้างต้น ดังนี้

CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, 0, 512);
CGPathAddQuadCurveToPoint(curvedPath, NULL, 768/2, 0, 768, 512);

pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);

เมื่อ animation และเส้นทางการเคลื่อนที่พร้อมแล้ว เราก็กำหนดเส้น animation นี้ให้กับ UIView ที่ต้องการได้เลย ดังนี้

[myView.layer addAnimation:pathAnimation forKey:@"position"];

เราก็จะได้การเคลื่อนที่เป็นเส้นโค้งจากจุดต้นของเส้นทางซ้ายสุด ไปจุดปลายของเส้นทางขวาสุดสมใจ

แอบเอาโค๊ดมาจากที่นี่

September 21, 2011

ใส่รูปเป็น Background ให้ UINavigationBar

ในเมื่อ UINavigationBar มันทำงานไม่ถูกใจเรา ดังนั้นเราก็สร้าง Category ให้ UINavigationBar ใหม่ แบบนี้


@implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
    UIColor *color = [UIColor orangeColor];
    UIImage *img  = [UIImage imageNamed: @"nav_bar.png"];
    [img drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
    self.tintColor = color;
    [super drawRect: rect];
}

@end


override method drawInRect: ให้มันวาดรูปที่เราต้องการลงไปแทน และกำหนดสีให้ property tintColor เพื่อให้ปุ่ม back ใน Navigation bar มีสีเข้ากับรูปที่เราวาดลงไปเป็น background

ปัญหาจะตามมาอีกนิดนึงคือ title ที่อยู่บน Navigation bar อาจมีสีไม่เหมาะสมกับรูปที่เราวาดลงไปเป็น background วิธีแก้ไขก็คือเราจำเป็นต้องไปกำหนดสี และฟอนต์เอง ดังนี้


    CGRect frame = CGRectMake(0, 0, 200, 44);
    UILabel *label = [[[UILabel alloc] initWithFrame:frame] autorelease];
    
    label.backgroundColor = [UIColor clearColor];
    label.font = [UIFont systemFontOfSize:18];
    
    label.textAlignment = UITextAlignmentCenter;
    label.textColor = [UIColor blackColor];
    self.navigationItem.titleView = label;
    label.text = @"First View";


เท่านี้เราก็จะได้ UINavigationBar หน้าตาดังรูปนี้



:)

September 10, 2011

กำหนดขนาดให้ UILabel มีขนาดพอดีกับข้อความ

Developer หลายคนมักเคยเจอปัญหาในเรื่องของการกำหนดข้อความยาวๆ ให้กับ UILabel แล้วก็ไม่รู้ว่าจะต้องกำหนดขนาดให้ UILabel เป็นเท่าไหร่ดี? เนื่องจาก UILabel นั้นมันไม่ยอมขยายขนาดให้ฟิตพอดีกับปริมาณของข้อความ หรือ text ที่เราจะเอามากำหนดค่าให้มัน

จริงๆ แล้วที่เห็นปัญหาได้ชัดเจนกว่า UILabel ก็คือการกำหนดขนาดให้กับ Cell ในตารางของ UITableView ที่เรามักจะน้ำข้อมูลมาใส่ในตาราง และแต่ละ cell ของตารางก็บังเอิญมีขนาดเท่าๆ กันหมดด้วยสิ ทำให้การหาขนาดความสูงของ cell ให้มีขนาดเหมาะสมกับ content ที่จะวางลงไปในตารางจึงเป็นเรืองที่หลีกเลี่ยงแทบไม่ได้เลย

ดังนั้นเราจำเป็นต้องกำหนดขนาดให้กับ UILabel ให้พอดีกับปริมาณข้อความเอง ซึ่งวิธีหาขนาดที่เหมาะสมให้กับ UILabel สามารถหาได้ดังนี้


CGSize size = [label.text sizeWithFont: label.font 
                         constrainedToSize: CGSizeMake(300, 5000
                             lineBreakMode: UILineBreakModeWordWrap];


ถ้าโค๊ดเต็มๆ สำหรับนำไปใช้งานก็จะมีหน้าตาประมาณนี้


UILabel *label = [[UILabel alloc] init];
    label.text = @"It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).";
    

    label.numberOfLines = 0;
    label.backgroundColor = [UIColor whiteColor];
    label.font = [UIFont fontWithName:@"Helvetica"size:14];
    
    CGSize size = [label.text sizeWithFont: label.font 
                         constrainedToSize: CGSizeMake(300, 5000
                             lineBreakMode: UILineBreakModeWordWrap];
    
    label.frame = CGRectMake(10, 10, 300, size.height);
    
    [self.view addSubview:label];
    [label release];


เท่านี้เราก็จะได้ UILabel ที่มีขนาดเปลี่ยนไปตามปริมาณของข้อความแล้ว

ปล. text ตัวอย่างเอามาจากเว็บนี้

เพ่ิมฟอนต์ให้ iOS

นอกจากฟอนต์ที่มีให้ใช้อยู่แล้ว เราสามารถเพิ่ม font ที่เราต้องการลงไปได้โดยมีขั้นตอนดังนี้

  1. import ฟอนต์ที่ต้องการเข้ามาในโปรเจ็ค
  2. เพิ่มค่า property ลงในไฟล์ info.plist โดยให้เพิ่มลงใน key ที่ชื่อ UIAppFonts (ถ้าไม่มีให้สร้างใหม่) ซึ่งค่านี้จะมีชนิด value เป็น Array จากนั้นกำหนดชื่อฟอนต์ที่ต้องการ (ใส่นามสกุลด้วย เช่น .ttf)
  3. เอาไปใช้ได้เลย โอยอ้างอิงเช่นเดียวกับ iOS system font ตามปกติ


    UIFont *font = [UIFont fontWithName: @"TH Charmonman" size: 60];
    _textLabel.font = font;


หน้าตาออกมาก็จะเป็นแบบนี้



ปล. จากตัวอย่างใช้ฟอนต์ชื่อ TH Charmonman เป็น 13 ฟอนต์เทพแห่งชาติ สามารถดาวน์โหลดได้จากเว็บของ SIPA เลย หรือจะลองดูหน้าตาของฟอนต์ต่างๆ ก่อนตัดสินใจดาวน์โหลดก่อนได้ที่นี่

แถม: iOS Fonts

:)


September 06, 2011

GeekTool ลง Mac App Store แล้ว

ในที่สุด GeekTool ก็ลง Mac App Store แล้ว

แต่เดิมผมไม่ได้สนใจมันเท่าไหร่ แต่ไหนๆ มันก็ลง Mac App Store แล้วก็เลยเอามาเล่นสักหน่อย ทำแบบรีบๆ ได้ Desktop หน้าตาแบบนี้มาดูเล่นล่ะ



หลักๆ แล้วการใช้ GeekTool จะเป็นการเอารูป และ output ของการรัน command ต่างๆ มาแปะไว้บน Desktop ที่นิยมก็เช่น วันที่ เวลา ปฏิทิน การใช้เมมโมรี่ และสถานะ CPU เป็นต้น

สำหรับการนำรูปมาแปะสามารถเลือกรูปในเครื่องเราได้เลย และสามารถตั้งค่าให้มันเปลี่ยนรูปไปเรื่อยๆ ได้

ยกตัวอย่างเช่นรูปรายงานสภาพอากาศนั้นผมไปดึงรูปมาจาก Yahoo Weather โดยมีขั้นตอนดังนี้

1. ไป Yahoo Weather แล้วกำหนดสถานที่เป็น Bangkok Thailand จะได้ URL มาของสภาพอากาศมา
2. เปิด GeekTool แล้วแปะ Shell ลงไป จากนั้นตรงช่อง command ให้ใส่คำสั่งดังนี้


curl --silent "WEATHER_URL" | grep "forecast-icon" | sed "s/.*background\\:url(\\'\\(.*\\)\\')\\;\\ _background.*/\\1/" | xargs curl --silent -o /tmp/wpicture.png

ให้แทน WEATHER_URL ด้วย url ของสภาพอากาศที่ได้มา

จากคำสั่งจะเป็นการดึง HTML จาก URL ของสภาพอากาศที่ได้มาจากนั้นแกะเอาแต่ชื่อภาพของสภาพอากาศและสั่งเซฟลงไปที่ /tmp/wpicture.png

และให้กำหนดให้มัน refresh ทุกๆ เวลาเท่าไหร่ก็ได้ อาจจะสัก 1 ชั่วโมงต่อครั้ง (การสั่ง refresh ที่ว่านี้จะเป็นการกำหนดให้คำนั่งที่เรากำหนดไว้ทำงานทุกๆ เวลากี่วินาที)

3. ไปที่ GeekTool แล้วแปะ Image ลงไป ตรงค่า URL ของรูปนั้นให้ใส่เป็น file:///tmp/wpicture.png พร้อมทั้งกำหนดให้มัน refresh รูปด้วย (ประมาณ 1 ชม. ต่อครั้งก็ได้)

เท่านี้รูปสภาพอากาศก็จะเปลี่ยนแปลงไปเรื่อยๆ แล้ว


4. สำหรับอุณหภูมิและสภาพอากาศนั้นให้ใช้คำสั่งดังนี้

curl --silent "http://xml.weather.yahoo.com/forecastrss?p=THXX0002&u=c" | grep -E '(Current Conditions:|C<BR)' | sed -e 's/Current Conditions://' -e 's/<br \/>//' -e 's/<b>//' -e 's/<\/b>//' -e 's/<BR \/>//' -e 's/<description>//' -e 's/<\/description>//' -e 's/,//' | tail -n1

แทนพารามิเตอร์ p ด้วยรหัสของสถานที่ที่เราต้องการ ดูได้จาก RSS ในหน้าสภาพอากาศนั่นแหละ
เช่น หากกำหนดสภาพอากาศเป็น Bangkok แล้วกดดู RSS จะได้แบบนี้ และค่า p คือ THXX0002 นั่นเอง


ตัวอย่าง Desktop สวยๆ ที่เอา GeekTool ไปใช้:
http://desktopspotting.com/25/awesome-geektool-mac-os-x-desktop/
http://tjdyo.deviantart.com/art/My-Desktop-with-Geektool-195121469
http://www.fuelyourinterface.com/making-your-desktop-look-awesome-again/
http://www.stumbleupon.com/su/2NzrKf/smokingapples.com/software/15-geektool-desktop-inspirations-for-the-weekend
และอันนี้เซ็ตใหญ่จาก flickr ครับ http://www.flickr.com/photos/tags/geektool/interesting/


:)

September 02, 2011

ใช้ Mail.app ใน Lion แล้วส่งเมลล์ออกไม่ได้

พยายามส่ง e-mail โดยใช้โปรแกรม Mail.app ที่ติดมากับ Mac OS X Lion อยู่หลายที ไม่เคยจะส่งออกได้สักที ลอง google ดูพบว่าแทบทุกที่ให้ทำตามนี้กันหมดเลย แล้วมันจะทำให้เราส่ง e-mail ได้!

  1. Quit mail.app
  2. Go to https://www.google.com/accounts/DisplayUnlockCaptcha
  3. Unlock the captcha
  4. Launch mail
  5. Done!

August 31, 2011

สร้าง Object จากชื่อคลาส

เราสามารถสร้าง Object แบบกำหนดชื่อคลาสได้ดังนี้


id *object = [[NSClassFromString(@"MyClassName") alloc] init];

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

August 29, 2011

บังคับให้ UI Orientation เปลี่ยน

เราสามารถบังคับให้ User Interface Orientation ใน iOS Device เปลี่ยนได้
ยกตัวอย่างการ toggle ไปมาระหว่ง UI แนวตั้งกับแนวนอน สามารถทำได้ดังนี้

    if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
        [[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
    }else{
        [[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
    }

August 24, 2011

Exception กับ @try, @catch และ @finally

ผมไม่ค่อยเห็นใครใช้การดักจับ Exception โดย try...catch บล็อคสักเท่าไหร่ในการเขียนโปรแกรมด้วยภาษา Objective-C เท่าที่เห็นจะเป็นการโยน NSError เข้าไป และอ้างถึงมันในภายหลัง หรือไม่ก็มี delegate message back กลับมาบอกว่ามันมีการทำงานผิดพลาดอย่างไร


ในภาษา Object-C การดักจับ Exception ให้ทำดังนี้


    // 1 สร้าง Array เปล่าๆ ขึ้นมาก่อน
    NSArray *array = [[NSArray alloc] init];
    
    @try {
        // 2 อ้างถึง element ใน Array ที่ไม่ได้มีอยู่จริง
        NSLog(@"object = %@", [array objectAtIndex: 0]);
        
    }@catch (NSException *exception) {
        // เมื่อเกิดข้อผิดพลาดขึ้นโค๊ดข้างในนี้จะถูกเรียกให้ทำงาน
        NSLog(@"Exception name = %@", exception.name);
        NSLog(@"Exception reason = %@", exception.reason);
        
    }@finally {
        // โค๊ดในบล็อคนี้จะถูกเรียกให้ทำงานเสมอ
        NSLog(@"finally");
        [array release];
    }


จากโค๊ด เป็นการยกตัวอย่างการเข้าถึง Element ใน Array ที่ไม่มีอยู่จริง โดยเริ่มจากสร้าง Array เปล่าๆ ขึ้นมาก่อน จากนั้นในจุดที่ 2 จะพยายามอ้างถึง Element ที่ 0 ของ Array ที่สร้างขึ้นมา ซึ่งไม่ได้มีอยู่จริง และทำให้เกิดข้อผิดพลาดเกิดขึ้น เมื่อใดก็ตามที่โค๊ดในบล็อค @try เกิดข้อผิดพลาดขึ้น มันจะหยุดทำงานทันทีและโค๊ดในบล็อค @catch จะเริ่มทำงานต่อ ส่วน @finally นั้นจะทำงานเสมอไม่ว่าจะเกิด Exception ขึ้นหรือไม่ก็ตาม


ส่วนผลลัพธ์ที่ได้จะเป็นดังนี้


Exception name = NSRangeException
Exception reason = *** -[NSArray objectAtIndex:]: index 0 beyond bounds for empty array
finally


การโยน Exception ออกจามาจาก function ที่เรียกใช้

- (void) testException{
    
    if(YES){
        // สร้าง Exception และโยนมันออกไปจากตรงนี้
        NSException *exception = [NSException exceptionWithName: @"Exception"
                                                         reason: @"There are someting wrong"
                                                       userInfo: nil];
        
        @throw exception;
    }
}


เมื่อมีการใช้งาน method - (void) testException  และเกิดข้อผิดพลาดขึ้น เราสามารถโยน Exception ออกจาก function ได้ตามโค๊ดด้านบน


ลองเอา function ที่จะมีการ throw exception ออกมามาใช้งานใน @try บล็อค


@try {
        
        [self testException];
        
    }@catch (NSException *exception) {
        NSLog(@"Exception name = %@", exception.name);
        NSLog(@"Exception reason = %@", exception.reason);
        
    }@finally {
        NSLog(@"finally");
        [array release];
    }


จากนั้นลองรันโปรแกรมดูใหม่ ผลจะออกมาดังนี้

Exception name = Exception
Exception reason = There are someting wrong
finally


:)