การพัฒนาแอพพลิเคชันบน iPhone หรือบนแพลตฟอร์มของ iOS สำหรับ Developer มือใหม่รอบนี้จะเป็นการใช้งาน UITableView แสดงแถวที่ดึงมาจากฐานข้อมูล SQLite อย่างง่ายหลังจากที่เราได้รู้วิธีการดึงข้อมูลจากฐานข้อมูล SQLite มาแสดงผลที่หน้าแอพพลิเคชันบน iOS ของเราได้แล้วต่อมาก็น่าจะเป็นส่วนที่สำคัญสำหรับนักพัฒนาแอพพลิเคชันที่สุดแล้วล่ะครับ นั่นคือการเชื่อมต่อฐานข้อมูล SQLite ไปแสดงผลในแถวของตาราง UITableView และส่งค่าตัวแปรไปยังหน้า DetailView เพื่อดึงข้อมูลมาปรากฏ ในรูปแบบที่เนื้อหาครบครัน หรือจะบอกว่านี่เป็นวิธีการสร้างแอพพลิเคชันเต็มรูปแบบที่ดึงข้อมูลได้จริงจากฐานข้อมูล SQLite นั่นเองครับ
วิธีการใช้งาน SQLite ร่วมกับ UITableView
ให้ลองนำแนวทางพัฒนาแอพพลิเคชันด้วย UITableView และการส่งตัวแปรจากบทเรียนบทก่อนหน้ามาพัฒนาร่วมกันครับ นั่นคือบทเรียน
- iOS Developer ตอนที่ 4 การเรียกใช้งาน UITableView
- iOS Developer ตอนที่ 6 ใช้งาน UITableView กับการส่งค่าผ่าน StoryBoard
ซึ่งวิธีการพัฒนาแอพพลิเคชันสามารถทำได้ดังนี้ ให้ทำการ New Project ขึ้นมาใหม่เป็น Single View Application
สร้างฐานข้อมูลใหม่ขึ้นมาว่า “student.sqlite” โดยใช้คำสั่ง SQL สร้างตารางชื่อ “students” ขึ้นมาโดยพิมพ์คำสั่ง
CREATE TABLE "students"(
"id" INTEGER PRIMARY KEY NOT NULL ,
"name" VARCHAR,"email" VARCHAR,
"faculty" VARCHAR,
"detail" TEXT)
เราจะได้ตารางข้อมูลของ “students” ปรากฏขึ้นมา เพื่อนำไปใช้กับ Project ของเรา ซึ่งอาจจะต้องทำการ Insert ข้อมูลลงไปในตารางสัก 10 -20 รายการสำหรับนำไปใช้ เป็นตัวอย่าง
หากใครที่สงสัยว่าทำยังไงให้อ่านบทเรียนก่อนหน้าคือ
- iOS Developer ตอนที่ 9 ทำความรู้จักกับฐานข้อมูล SQLite
- iOS Developer ตอนที่ 10 การดึงข้อมูล SQLite มาแสดงผลบน iPhone Apps
ไปที่หน้า Project Detail ก่ทำการเพิ่ม Framework ของ SQLite ลงไปใน Project ของเรา เลือกไปที่แถบ Build Phases ของตัว Project เพื่อทำการเพิ่ม Library ของ SQLite ลงไป ในช่อง “Link Binary With Libraries” ที่ปุ่ม “+” บริเวณด้านล่างซ้ายของแถบนี้กด “+” แล้วทำการค้นหา โดยใส่คำค้นหาว่า “SQL” จะพบไฟล์ libsqlite3.dylib และ libsqlite3.0.dylib ให้เลือก libsqlite3.0.dilib ลงไป ใน Project
ไปที่ไฟล์ AppDelegate.h ทำการประกาศ หน้า ViewController เป็น Class ตัวใหม่ขึ้นมาก่อนโดยคำสั่ง
#import
@class ViewController;
@interface AppDelegate : UIResponder {
UIWindow *window;
ViewController *viewController;
}
@property (strong, nonatomic) UIWindow *window;
@end
ทำการประกาศตัวแปล เพิ่มเติมในไฟล์ Controller.h โดยเรียก Libraries ของ SQLite เข้ามาในส่วนนี้
#import
#import
@interface ViewController : UIViewController{
sqlite3 *database;
}
สร้างฟังก์ชันสำหรับ ทำการเก็บข้อมูลจาก SQLite ขึ้นมาด้านล่างก่อน @end
- (void) initDatabase;
@end
เป็นการเตรียมพร้อมการดึงข้อมูลจากไฟล์ SQLite ลงไปในตัวแอพพลิเคชัน ต่อจากนั้นให้นำไฟล์ SQLite ที่เราสร้างขึ้นมาที่ชื่อว่า student.sqlite มาไว้ใน Project โดยการเพิ่ม
ไปที่ไฟล์ ViewController.m ทำการเพิ่มคำสั่ง initDatabase เข้าไปในไฟล์ เพื่ออ่านค่าข้อมูลจากฐานข้อมูล
- (void) initDatabase
{
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory
stringByAppendingPathComponent:@"student.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success)
{
return;
}
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"student.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath
toPath:writableDBPath error:&error];
if (!success)
{
NSAssert1(0, @"Failed to create writable database file with message
'%@'.", [error localizedDescription]);
}
}
เมื่อเพิ่มคำสั่งดังกล่าวลงไปแล้ว ให้ไปเรียกใช้งานฟังก์ชันนี้ที่ Method ของ ViewDidLoad อีกที
- (void)viewDidLoad
{
[super viewDidLoad];
[self initDatabase];
// Do any additional setup after loading the view, typically from a nib.
}
ต่อมาเป็นการใช้คำสั่งในการเรียกอ่านข้อมูลมาเก็บไว้ในตัวแปรชุดหนึ่ง ให้ทำการเพิ่มฟังก์ชันใหม่เข้าไปในไฟล์ DetailViewController.h อีกครั้งด้วย
@interface ViewController : UIViewController{
sqlite3 *database;
}
- (void) initDatabase;
- (void) getStudent;
เรียกข้อมูลจากฐานข้อมูลมาแล้วให้เข้าไปเพิ่ม ฟังก์ชันการทำงานที่ไฟล์ ViewController.m อีกครั้ง
- (void) getStudent
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory
stringByAppendingPathComponent:@"student.sqlite"];
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
const char *sql = "SELECT * FROM students";
sqlite3_stmt *searchStatement;
if (sqlite3_prepare_v2(database, sql, -1,
&searchStatement, NULL) == SQLITE_OK)
{
while (sqlite3_step(searchStatement) == SQLITE_ROW) {
NSString *name = [NSString stringWithUTF8String:(char *)
sqlite3_column_text(searchStatement, 1)];
NSString *email = [NSString stringWithUTF8String:(char *)
sqlite3_column_text(searchStatement, 2)];
NSString *details = [NSString stringWithUTF8String:(char *)
sqlite3_column_text(searchStatement, 4)];
NSLog(@"Name: %@ ,Email: %@ ", name, email);
}
}
sqlite3_finalize(searchStatement);
}
}
เพิ่มคำสั่งให้แอพพลิเคชันของเราเรียกใช้งานฟังก์ชัน getStudent() ในฟังก์ชัน Method ของ viewDidLoad()
- (void)viewDidLoad
{
[super viewDidLoad];
[self initDatabase];
[self getStudent];
}
เพียงแค่รอบนี้ มีการดึงข้อมูลมาทั้งชุดของตาราง student จากคำสั่ง “SELECT * FROM students” เพื่อทำการเรียกข้อมูลทุกรายการออกมาจากฐานข้อมูล และต่อจากนี้เราจะทำการ นำข้อมูลแต่ละชุดไปแสดงผลใน UITableView แบบทีละตาราง
ในไฟล์ ViewController.m ให้เพิ่มบรรทัดตัวแปร เป็น NSMutableArray ลงไปเพื่อดึงค่าข้อมูลจาก รายชื่อ อีเมล และ รายละเอียดของรายชื่อในตาราง student
#import "ViewController.h"
@implementation ViewController{
NSMutableArray *listOfItems;
NSMutableArray *listOfEmails;
NSMutableArray *listOfDetails;
}
เข้าไปแก้ไข บรรทัดของคำสั่ง getStudent() โดยแทรกคำสั่งดังต่อไปนี้
const char *sql = "SELECT * FROM students";
sqlite3_stmt *searchStatement;
if (sqlite3_prepare_v2(database, sql, -1,
&searchStatement, NULL) == SQLITE_OK) {
// ตำแหน่งที่แทรกเข้าไปใหม่ 3 บรรทัด
listOfItems = [[NSMutableArray alloc] init];
listOfEmails =[[NSMutableArray alloc] init];
listOfDetails =[[NSMutableArray alloc] init];
เป็นการ เริ่มประกาศให้ฟังก์ชัน getStudent() นั้นรู้จักกับตัวแปร 3 ตัวคือ listOfItems, listOfEmails และ listOfDetails ว่ามันเป็นตัวแปร Array และให้มันประกาศพื้นที่จองสำหรับเก็บข้อมูลไว้
ต่อมาให้ไปที่ตำแหน่งฟังก์ชันของ While Loop ที่เราได้ทำการเก็บตัวแปรของ แถวข้อมูลลงตัวแปรไว้นั้น ให้เพิ่มคำสั่งลงไปดังนี้
while (sqlite3_step(searchStatement) == SQLITE_ROW)
{
NSString *name = [NSString stringWithUTF8String:(char *)
sqlite3_column_text(searchStatement, 1)];
NSString *email = [NSString stringWithUTF8String:(char *)
sqlite3_column_text(searchStatement, 2)];
NSString *details = [NSString stringWithUTF8String:(char *)
sqlite3_column_text(searchStatement, 4)];
NSLog(@"Name: %@ ,Email: %@ ", name, email);
// คำสั่งใหม่ที่เพิ่มเข้าไป 3 บรรทัด
[listOfItems addObject:name];
[listOfEmails addObject:email];
[listOfDetails addObject:details];
}
เป็นการดึงข้อมูล จากการวนลูปของ ฐานข้อมูลเอาข้อมูลแต่ละแถวมาเก็บในตัวแปรใหม่ของเราทั้ง 3 ตัว จนกว่าจะหมดชุดข้อมูลที่มีในตาราง student ทั้งหมด
หลักการต่อไปที่เราจะทำคือการสร้าง UITableView ขึ้นมา และมีกระบวนการดังนี้
- ดึงข้อมูลจากตาราง student ออกมาแถวต่อแถวมาแสดงผลผ่าน Cell ของตาราง
- Cell ของตารางมีการเปิดไปหน้า DetailViewController เพื่อแสดงผลข้อมูลเฉพาะรายการ
- Cell ของตารางมีการส่งข้อมูลจากฐานข้อมูลผ่าน Segue แบบปรกติ
นั่นแปลว่า เอาจะหยิบเอาบทเรียนการสร้าง UITableView จากบทเรียนก่อนหน้านี้มาใช้งาน
- iOS Developer ตอนที่ 9 ทำความรู้จักกับฐานข้อมูล SQLite
- iOS Developer ตอนที่ 10 การดึงข้อมูล SQLite มาแสดงผลบน iPhone Apps
ขั้นตอนต่อมาให้ไปที่หน้า MainStoryBoard ทำการลากเอา UITableView ไปวางไว้บนแอพพลิเคชันของเรา
ทำการเชื่อม UITableView โดยการกดแป้น Control ที่แป้นคีย์บอร์ดค้างไว้ คลิกและลาก UITableView ไปวางที่ไอคอนของ ViewController ข้างล่างเลือกทั้ง Delegate และ Datasource
ทำการวนลูปแถวบน UITableView ของตารางที่เราไปวาง แต่ให้อ้างอิงการนับจำนวนแถวในฐานข้อมูลตาราง students แทน
- (NSInteger)tableView:(UITableView *)
tableView numberOfRowsInSection:(NSInteger)section{
return [listOfItems count];
}
เชื่อมข้อมูลในฐานข้อมูลจากตัวแปร NSMutableArray ทั้ง 3 ตัวที่เราจับค่าทั้งหมดใส่ในตัวแปรไปแล้วมาทำงาน
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *simpleTableIdentifier =@"Cell";
UITableViewCell *cell=[tableView
dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if(cell == nil){
cell=[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text =
[listOfItems objectAtIndex:indexPath.row];
cell.detailTextLabel.text =
[listOfEmails objectAtIndex:indexPath.row];
return cell;
}
ทำการ “Run” ตัวแอพพลิเคชันของเราเพื่อดูผลลัพธ์การแสดงผลข้อมูลจากฐานข้อมูลบน UITableView ว่าผิดพลาดหรือเปล่า
ต่อไป เป็นขั้นตอนที่จะทำให้แอพพลิเคชันของเราทำงานได้อย่างเต็มรูปแบบนั่นคือการ เลือกตำแหน่ง Cell บน UITableView แล้วมีการเปลี่ยนหน้าและแสดงผลข้อมูลเฉพาะแถว หรือข้อมูลแต่ละชุด ให้ทำการ New File ขึ้นมาใหม่ เป็น UIViewController subclass กด Next
ให้เลือก Option ข้างล่างให้เป็น และทำการ เอาเครื่องหมายในช่องที่เลือกอยู่ออกให้หมด (กรณีที่มีเครื่องหมายเลือกไว้อยู่)
สร้างไฟล์ DetailViewController.h และ DetailViewController.m ใหม่ขึ้นมาโดยเลือก Subclass of ให้เป็น UIViewController ไม่ใช่ UITableViewController แล้วทำวาง UILabel และ UITextView เข้าไปที่หน้า DetailViewController สร้าง Outlet ใหม่ และทำการแก้ไขที่ไฟล์ DetailViewController.h ครับ
เพิ่มคำสั่งประกาศ Outlet ใหม่ขึ้นไปในไฟล์ DetailViewController.h ด้วยคำสั่งชุดนี้
#import
@interface DetailViewController : UIViewController
//ชุดข้อมูลสำหรับ UILabel
@property (nonatomic, strong) IBOutlet UILabel *dataLabel;
@property (nonatomic, strong) NSString *dataName;
//ชุดข้อมูลสำหรับ UITextView
@property (weak, nonatomic) IBOutlet UITextView *dataTextView;
@property (nonatomic, strong) NSString *dataText;
@end
ตั้งชื่อ IBOutlet ของ UILabel ขึ้นมา กำหนดตัวแปรให้กับมันว่า dataLabel และสร้าง IBOutlet สำหรับ UITextView ตั้งตัวแปรว่า dataTextView และสร้างตัวแปร String เพิ่มมาอีก 2 ตัวครับ ชื่อ dataName สำหรับเก็บค่าร่วมกับ dataLabel และสร้างตัวแปร String ชื่อว่า dataText สำหรับใช้งานร่วมกับ dataTextView
เมื่อมีการประกาศตัวแปร และ Outlet เบื้องต้นไปแล้วต่อมา ให้เลือกไปที่ไฟล์ DetailViewController.m แล้วเพิ่มฟังก์ชันของ Synthesize เข้าไปที่บรรทัดข้างล่างต่อจาก @implementation DetailViewController ด้วยคำสั่งนี้ครับ
#import "DetailViewController.h"
@implementation DetailViewController
@synthesize dataLabel;
@synthesize dataName;
@synthesize dataTextView;
@synthesize dataText;
ให้ทำการImport ไฟล์ DetailViewController.h ไปแทรกไว้บนส่วน #import ของไฟล์ ViewController.m
#import "ViewController.h"
#import "DetailViewController.h"
กลับไปที่ MainStoryboard อีกครั้งที่ไฟล์ View Controller ตัวใหม่ที่เป็นหน้าเนื้อหา ให้คลิกที่ตัว View Controller ให้เกิดเส้นขอบสีน้ำเงินครอบแล้ว เลื่อนไปที่แถบด้านขวาในส่วนของ Identity Inspector ทำการเปลี่ยน Class ใน Custom Class ให้เป็น “DetailViewController”
เมื่อเลือก Class ของหน้า View controller ตัวนี้ให้กลายเป็น Class ของ DetailViewController เสร็จแล้ว ให้ทำการ Link ตัว UILabel และ UITextView ที่วางอยู่ในหน้า View Controller นี้เข้ากับข้อมูลตัวแปรโดยการคลิกที่ไอคอน View Controller ด้านล่างแล้วกดแป้นคีย์บอร์ด Control ค้างไว้ ทำการลากไปยัง UILabel เชื่อมข้อมูลเข้ากับ dataLabel และลากไปยัง UITextView เชื่อมข้อมูลเข้ากับ dataTextView
เพิ่มคำสั่งเข้าไปในไฟล์ ViewController.m คราวนี้เป็นคำสั่งในการส่ง ค่าตัวแปรผ่าน Segue ไปยังหน้า DetailViewController เพื่อแสดงผลข้อมูลที่หน้าอีกหน้าเมื่อทำการกดที่แถวของ UITableView ก่อนอื่นต้องไปที่ไฟล์ ViewController.h ก่อนเพิ่มค่าตัวแปรสำหรับเก็บค่าตัวแปรส่งผ่าน segue โดยการเพิ่มคำสั่งดังนี้
#import
#import
@interface ViewController : UIViewController{
sqlite3 *database;
}
@property (nonatomic, strong) IBOutlet UITableView *sendDataTable;
อย่าลืมไป ประกาศ synthesize ที่หน้า ViewController.m บริเวณส่วนบนหรือ Header ของไฟล์
@implementation ViewController
{
NSMutableArray *listOfItems;
NSMutableArray *listOfEmails;
NSMutableArray *listOfDetails;
}
@synthesize sendDataTable;
ต่อมาคือการเขียนคำสั่งให้ UITableView นั้นส่งค่าตัวแปรจากการกดที่ Cell แล้วเปลี่ยนหน้าไป โดยการใช้คำสั่งที่เพิ่มเข้าไปในหน้า ViewController.m นั้นคือคำสั่งการทำงานผ่าน Segue ให้พิมพ์คำสั่งดังนี้
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender {
if ([segue.identifier isEqualToString:@"Cell"]) {
NSIndexPath *indexPath =
[self.sendDataTable indexPathForSelectedRow];
DetailViewController *destViewController =
segue.destinationViewController;
destViewController.dataName =
[listOfItems objectAtIndex:indexPath.row];
destViewController.dataText =
[listOfDetails objectAtIndex:indexPath.row];
}
}
กลับไปที่หน้า MainstoryBoard แล้วไปคลิกให้ขึ้นกรอบสีน้ำเงินที่หน้า ViewController หน้าแรก ทำการกดปุ่ม Control ที่แป้นคีย์บอร์ดค้างไว้ แล้วคลิกที่ไอคอน ViewController สีเหลือง ลากไปวางที่ตัว UITableView ทำการเชื่อมเข้ากับ Outlet ของ “sendDataTable” ให้เรียบร้อย
ให้สังเกตว่าชื่อของ Segue ที่เราตั้งไว้ในฟังก์ชัน prepareForSegue นั้นเราตั้งชื่อว่าอะไร ในที่นี้ผมตั้งว่า “Cell” ให้ใช้ชื่อว่า Cell
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"Cell"]) {
กลับไปที่ MainStoryboard คลิกที่ ViewController ของตัวแอพพลิเคชันของเราให้เกิดเส้นขอบสีน้ำเงินปรากฏขึ้น แล้วเลือกเมนูด้านบน “Editors -> Embed In -> Navigation Controller”
เมื่อสร้าง Navigation Controller เป็นที่เรียบร้อยแล้ว ให้ทำการกดที่ Cell ของ UITableView พร้อมกับกดปุ่ม Control ค้างไว้แล้วลากจาก Cell ไปวางที่หน้า DetailViewController แล้วทำการเลือกความสัมพันธ์แบบ “Push” ตั้งชื่อ Segue ว่า “Cell”
ทดลอง “Run” ตัวแอพพลิเคชันของเราดูจะเห็นว่า UITableView นั้นจะดึงข้อมูลมาจาก SQLite และส่งค่าแต่ละแถวไปแสดงผลที่หน้า DetailViewController ผ่าน segue อย่างสมบูรณ์
ซึ่งแนวคิดในการพัฒนาแอพพลิเคชัน และเก็บข้อมูลด้วย SQLite นั้นเป็นแนวคิดพื้นฐานของแอพพลิเคชันสำเร็จรูปมากมายที่ให้เราได้เลือกใช้บน App Store อาจจะประกอบไปด้วย แอพพลิเคชันประเภทให้ข้อมูล รายการข่าวสาร แอพพลิเคชันแปลภาษา หรือข้อมูลของสถานที่ต่างๆ สินค้าต่างๆ ขององค์กร และธุรกิจนั้นๆ
สำหรับ Source Code สามารถดาวน์โหลดได้ที่
http://code.google.com/p/daydev/downloads/detail?name=UITableViewSQLite.zip
อ่านบทเรียนทั้งหมด ย้อนหลังที่
บทความการพัฒนาแอพพลิเคชันบน iPhone
ของผมมันขึ้น แบบนี้อ่ะหะ
erminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to create writable database file with message 'The operation couldn’t be completed. (Cocoa error 260.)'.' เกิดจากอะไรหรอคับ.