[iOS] วิธีทำ TextField Validator แบบง่ายๆ
แชร์ไอเดียการทำ TextField Validator เพื่อบอกให้ผู้ให้ผู้ใช้งานรู้ว่ากำลังกรอกข้อมูลผิด
แน่นอนว่าหลายๆ แอพพลิเคชั่นต้องมีส่วนของการ กรอกข้อมูล ใช่มั้ยครับ ยกตัวอย่างง่ายๆ เช่น การแก้ไขประวัติส่วนตัว หรือ การลงทะเบียนต่างๆ แน่นอนว่าการทำงานกับผู้ใช้ย่อมมีข้อผิดพลาดเกิดขึ้นง่ายมาก เช่น ผู้ใช้กรอกข้อมูลผิดภาษา หรือมีอักขระพิเศษ เป็นต้น
แล้วจะมีวิธีไหนบ้างเพื่อบอกผู้ใช้งาน ว่าเค้ากำลังกรอกข้อมูลผิดอยู่นะ! เราอาจจะโชว์ PopUp เพื่อบอกเค้าหลังจากที่เค้ากดปุ่มตกลง ซึ่งแน่นอนว่าผู้ใช้งานต้องมานั่งหา ว่ากรอกข้อมูลช่องไหนผิด ซึ่งวิธีนี้อาจจะเป็นประสบการณ์การใช้งานที่ไม่ค่อยน่าประทับใจซักเท่าไหร่
แต่ยังมีอีกนึงคือการ Validate ข้อมูลที่ผู้ใช้กำลังกรอก (สังเกตุรูปที่ 1.0) วิธีนี้ทำให้ผู้ใช้รู้ตัวได้ทันทีระหว่างกรอกข้อมูลหรือหลังจากที่กรอกข้อมูลเสร็จแล้ว ว่าข้อมูลนั้นๆ ถูกต้องหรือไม่ เช่น ถ้าหากหรอกข้อมูลถูกจะมีลูกศรหรือกรอบสีเขียวแสดงขึ้นมา เป็นต้น
เตรียมตัวก่อนซักเล็กน้อย
ก่อนอื่นเราต้องคิดก่อนว่า TableViewCell ที่เราจะสร้างนั้นจะต้องมีอะไรบ้าง? ในที่นี้ผมต้องการสร้างคลาส TableViewCell อันนึงขึ้นมาเป็นตัวกลางแล้วนำไปใช้กับ Cell ต่างๆ (Reusale cell) เพื่อลดการซ้ำซ้อนของโค้ด ซึ่งใน TableViewCell นั้นก็จะมี
- TextField
- TextFieldCellType
- ValidateSuccessHandler()
- ValidateFailHandler()
- ValidateSuccessCallback()
- ValidateFailCallback()
แน่นอนว่า TextField นั้นจะเป็นสิ่งที่ขาดไม่ได้ และการที่ผมจะทำ ResuableCell ผมต้องมีตัวแปรชื่อ TextFieldCellType เพื่อระบุประเภทของ Cell นั้นๆ เช่น Username cell, Password cell, E-mail cell,
สำหรับ ValidateHandler นั้นผมจะเอาไว้เรียกใช้หลังจากที่ Validate เสร็จเรียบร้อย เช่น ผู้ใช้กรอกข้อมูลถูกต้องใช้โชว์กรอบสีเขียว หรือ ผู้ใช้กรอกข้อมูลผิดพลาดให้โชว์กรอบสีแดงพร้อมข้อความแจ้งเตือน
และสุดท้าย ValidateCallback ไว้ใช้สำหรับส่งข้อมูลกลับไปให้ ViewController ของเรา เช่น ถ้าหากผู้ใช้งานกรอกข้อมูลถูกต้องให้เก็บข้อมูลนี้ไว้สำหรับใช้งานต่อไป (หรือใครอยากจะทำเป็น Delegate ก็ได้)
อันดับแรกสร้าง Interface
เพื่อความรวดเร็วผมขอยกตัวอย่างด้วยการนำ UITableView มาใส่ใน ViewController ที่มีมาให้ตั้งแต่ตอนแรก จากนั้นใส่ Constraint พร้อมทั้งต่อ DataSource, Delegate, และ Outlet ของ UITableView กับคลาส ViewController ให้เรียบร้อย
จากนั้นผมนำ UITableViewCell มาใส่เป็น Prototype Cells อันนีงพร้อมทั้งตั้ง Cell ID ว่า “ValidatorCell” ข้างใน Cell ประกอบไปด้วย UILabel สำหรับ Title, Error description แล้วก็มีพระเอกของเรา UITextField ซึ่งเป็น Subview ของ UIView อีกทีหนึ่งสาเหตุที่ต้องมี UIView ครอบไว้เพราะว่า ผมอยากให้เวลา Validate ข้อความข้างในจะมีกรอบสีแดงหรือสีเขียวขึ้นมาโชว์นั่นเอง (สังเกตุรูปที่ 2.0) แต่ถ้าอยากได้ UI แบบอื่นก็สามารถปรับได้ตามความเหมาะสมครับ
อันดับต่อมาสร้าง Class มาควบคุม Interface
ในคลาส ViewController นั้นผมต่อ Outlet กับ UITableView ที่สร้างไว้ใน Storyboard จากนั้นผมก็สร้าง UITableViewDelegate ขึ้นมา แล้ว return ValidatorCell ที่สร้างขึ้นมาเป็นจำนวน 50 cells (สังเกตุรูปที่ 3.0)
จากนั้นสร้างคลาส TextFieldValidatorTableViewCell ข้างในคลาสจะมีตัวแปรและฟังก์ชั่นที่เราได้คิดไว้ในตอนแรก เช่น TextField, ValidatorCellType เอาไว้ระบุว่า cell นี้เป็น cell ประเภทไหน อย่าลืมใส่ Custom Class ให้กับ Prototype Cell และต่อ Outlet ให้กับตัวแปรเหล่านี้ให้ครบนะครับ จากนั้นก็ตั้งค่า Interface เพื่อความสวยงามซักเล็กน้อย :D (สังเกตุรูปที่ 3.1)
ส่วน ValidateSuccessCallback() และ ValidateFailedCallback() นั้นผมเป็นกาศเป็นตัวแปรแบบ Callback เพื่อให้สามารถโยน Callback กลับไปที่ ViewController นั้นได้ โดย Callback นี้จะส่ง Error message ที่ได้จากการ Validate ที่ผู้ใช้งานพิมพ์ และ Text ที่ผู้ใช้งานพิมพ์ใน TextField กลับไป เพื่อให้ ViewController สามารถนำไปใช้งานต่อได้
หลายคนอาจจะสับสนระหว่าง Callback กับ ValidateSuccessHandler() และ ValidateFailedHandler() สองตัวนี้จะทำหน้าที่คนละส่วนกัน โดย Handler จะทำหน้าที่เปลี่ยน UI ต่างๆ เมื่อ validate เสร็จเรียบร้อย เช่น จากโค้ดข้างบน หลังจาก Validate เสร็จจะเปลี่ยนขอบของ View ให้เป็นสีต่างๆ รวมถึงโชว์ข้อความ Error message ให้กับผู้ใช้งานอีกด้วย ซึ่งเป็นการเปลี่ยน UI ต่างๆ ที่อยู่ใน Cell แตกต่างกับ Callback ที่จะโยนข้อมูลกลับไปให้ ViewController
เราสามารถตั้งค่า Interface หลังจาก validate success หรือ validate faile จาก ViewController แต่ถ้าหากว่ามี cell นี้อยู่หลายที่ เราก็ต้องตั้งค่า Interface หลายที่ด้วยเช่นกัน แล้วถ้าเมื่อไรเราอยากเปลี่ยน Interface นั้นหล่ะ? โอโห ตามแก้กันเมื่อยเลยทีเดียว กลับกัน ถ้าเราใส่ไว้ที่นี่ เราก็แก้ที่เดียวจบ ดีงามพระรามแปด
สร้าง Condition สำหรับ Validate TextField
ผมจะ Validate TextField ทุกครั้งที่ผู้ใช้พิพม์ข้อความและพิมพ์ข้อความเสร็จเรียบร้อย ผู้ใช้จะได้รู้ผลทันทีหลังจากพิมพ์ตัวอักษรแต่ละตัว ซึ่งตรงนี้แล้วแต่ความต้องการของแต่ละคนนะครับ
โดยผมจะสร้าง Enumerate ขึ้นมาตัวนึง สาเหตุที่เลือกใช้ Enum เพราะว่าตัวมันเอง สามารถประยุกต์ใช้กับ Switch-case ได้ดีเลยทีเดียว ทำให้เราไม่จำเป็นต้องไปใช้ static function ต่างๆ ลดจำนวน line of code ลงได้ไปเยอะ
ผมจะสร้าง TextFieldCellType ขึ้นมา 4ประเภท คือ Username cell, Password cell, E-mail Cell, และ Firstname Cell ซึ่งแต่ละ cell ก็จะมีเงื่อนไขในการ validate ต่างกันออกไป
โดย Username จะต้องมีความยาว 8–16 ตัวอักษร ต้องมีอย่างน้อยหนึ่งตัวอักษรและห้ามมีอักขระพิเศษ, Password จะต้องมีความยาว 8–16 ตัวอักษร และห้ามมีอักขระพิเศษ, ส่วน E-mail นั้นจะต้องอยู่ในรูปแบบของ E-mail format, และสุดท้าย Firstname ต้องมีความยาวสองตัวอักษรขึ้นไป และห้ามเป็นตัวเลขหรืออักขระพิเศษ และกรณีสุดท้ายคือเป็น None หรือไม่ได้กำหนดค่าลงไป ผมจะให้ return false ไว้
หลังจาก validate text เสร็จเรียบร้อยฟังก์ชั่นนี้จะ return ออกไปสองค่า คือ result เป็นแปรไว้เช็คว่า validate ผ่านตามเงื่อนไขที่ตั้งไว้หรือไม่ และ desc เพื่ออธิบายรายละเอียดของ result (ในที่นี้ผมจะใช้ desc สำหรับแสดง error message) (สังเกตุรูปที่ 4.0)
แล้วก็ถึงเวลา Validate TextField
หลักจากที่เราได้สร้าง Condition สำหรับ validate แล้วก็ถึงเวลาเรียกใช้งาน ซึ่งเราจะเรียกใช้งานผ่าน validatorCellType ของ tableViewCell นั่นเอง
อย่าที่กล่าวไปข้างต้น การเรียกฟังก์ชั่น validate นั้นจะเรียกหลังจากที่ผู้ใช้งานพิมพ์ตัวอักษรลงใน TextField และพิมพ์เสร็จ ดั้งนั้นผมจะสร้างฟังก์ชั่น textFieldDidTextChange และ textFieldDidEndEditing ขึ้นมาและทำการลาด IBAction ให้เรียบร้อยตามรูปด้านล่าง (รูปที่ 5.0)
แต่ถ้าให้แนะนำจริงๆ ผมแนะนำให้ใช้ textFieldDidEndEditing แล้วต่อ Delegate เอาดีกว่าครับ ทีนี้ถ้าหากมีการทำปุ่ม Submit ด้วยก็ให้เรียก self.view.endEditing(_ :) เอาดีกว่า แต่เนื่องจากตัวอย่างผมไม่ได้มีปุ่ม Submit เลยไม่ได้ใช้วิธีนี้ ขออภัยด้วยครับ
ข้างในสองฟังก์ชั่นนั้น ให้ทำการเรียกใช้งานฟังก์ชั่นที่ชื่อว่า validate(_ textField: UITextField) โดยการทำงานของฟังก์ชั่น validate จะทำการเช็คก่อนว่า text นั้นมีค่าหรือไม่ ถ้าหากมีค่าให้นำ text นั้นไปเข้ากระบวนการ validate ต่อโดยใช้ condition ที่เรากำหนดขึ้นมาข้างต้นนั่นเอง
การเรียก Condition มาตรวจสอบนั้น ขึ้นอยู่กับว่า validatorCellType ของเราเป็นประเภทอะไร (ในส่วนนี้ต้องถูกกำหนดค่ามาจาก ViewController แต่เดี๋ยวจะอธิบายในส่วนต่อไป) เช่น ถ้าเป็นประเภท Username ก็จะไปเรียก Condition ของ Username มาใช้งาน
เมื่อโยนข้อความเข้าฟังก์ชั่น Validate เสร็จเรียบร้อย เราจะได้ Tuple ซึ่งข้างในประกอบด้วยตัวแปร 2 ตัว คือ .result และ .desc โดยตัวแปร .result นั้นเอาไว้เช็คว่า validate ผ่านหรือไม่ ถ้าหากผ่านก็จะเรียก SuccessHandler() และ SuccessCallback() แต่ถ้าไม่ผ่านก็จะเรียก FaildHandler() และ FailedCallback() โดย Handler() ทั้งสองจะนำเอา error message (.desc) ที่ได้รับจากการ validate ไปแสดงผล ส่วน Callback ก็จะรับ text และ error message ไปให้ ViewController ใช้งานต่อไป
สร้าง Callback และตั้งค่าให้กับ TableViewCell
ที่ผ่านมานั้นเราได้สร้าง Resuable Cell หรือคลาสที่จะนำไปใช้กับทุกๆ TableViewCell แล้ว แต่เรายังไม่ได้กำหนดว่า CellType แต่ละ Cell จะเป็นประเภทอะไร ซึ่งการที่จะกำหนด CellType นั้น จะต้องมากำหนดที่ UITableViewDataSource นั่นเองที่ ViewController นั่นเอง
ก่อนอื่นผมขอสร้างฟังก์ชั่น SuccessCallback() และ FailedCallback() ขึ้นมาก่อนโดยทั้งสองจะรับ indexPath มาเป็น paramerter เพื่อบอกว่าค่าแต่ละค่านั้นถูกส่งมาจาก cell ไหน และฟังก์ชั่นทั้งสองจะ return ฟังก์ชั่นที่รับ text และ result description อีกทีหนึ่ง (สังเกตุรูปที่ 6.0)
หลังจากที่ได้ประกาศฟังก์ชั่น Callback ทั้งสองเรียบร้อยแล้ว ก็มากำหนดค่าให้กับ TableViewCell ของเราโดย assign ฟังก์ชั่น callback ทั้งสองพร้อมทั้งกำหนดประเภทของ cell นั้นๆ โดยผมจะทำ cell ที่ 1 เป็น Username Cell และ Password Cell, E-mail cell, Firstname cell ตามลำดับ ส่วน Cell ที่เหลือนั้นให้เป็น default (.None) ไปก่อน (รูปที่ 6.1)
เอาหล่ะ เมื่อทั้งหมดพร้อมแล้วก็ลอง Compile and Run ดู
เสร็จเรียบร้อย แต่ยังไม่สมบูรณ์ !
จริงๆ แล้วจากโค้ดข้างบนเราสามารถทดสอบ Validator ได้แต่ยังไม่สมบูรณ์พอที่จะไปใช้ เพราะยังขาดอีกหลายๆ เรื่อง เช่น การตั้งค่า TextField ใน DataSource หลายคนอาจจะสงสัยว่าคืออะไร ลองเปิดแอพพลิเคชั่นเมื่อสักครู่ขึ้นมา กรอกข้อมูลลงไปใน TextField ขึ้นบน แล้ว scroll ลงไปข้างล่างแล้วกลับขึ้นมาดูข้อมูลที่กรอกไป ผมเดาได้ว่าข้อมูลที่กรอกไปอาจจะหาย หรือไม่ถูกต้องใช่ไหมครับ (ลองดูรูปที่ 7.0)
นั่นเป็นเพราะว่าเราใช้ Reuseable Cell ดังนั้นหน้าที่ของเราคือการเก็บข้อมูลต่างๆ ไว้ก่อนที่ Cell นั้นจะถูกทำไป Reuse และเมื่อ Cell นั้นกลับมาแสดงผลบนหน้าจอ ก็เป็นหน้าที่ของเราที่จะต้องนำข้อมูลที่เก็บไว้มาแสดงผลอีกรอบ
ดังนั้นผมจะเก็บข้อมูลที่ผู้ใช้งานพิมพ์เอาไว้ ไม่ว่างจะ validate ผ่านหรือไม่ ถ้าไม่ผ่านก็โชว์กรอบสีแดงและ Error message อีกรอบ แบบนี้น่าจะโอเคกว่า
รูปที่ 7.1 — ผมเพิ่มตัวแปรตัวนึงใน ViewController ชื่อว่า data เป็นตัวแปรประเภท Dictionary โดยใช้ indexPath ของ TableViewCell เป็น key และเก็บข้อความที่ผู้ใช้งานพิมพ์ (ไม่ว่าจะ validate ผ่านหรือไม่ผ่าน) เป็น value วิธีการเก็บข้อความก็คือเอาไปใส่ใน Callback ของ TextFieldValidatorTableViewCell นั้นเอง
รูปที่ 7.2 มีอีกส่วนนึงที่ผมยังไม่ได้พูดถึงคือการกำหนด Title ให้กับ Title Label ที่เราสร้างไว้ตอนโน้นนนนน เราสามารถกำหนดให้ Cell แต่ละประเภทมีชื่อแตกต่างกันได้ จากนั้นก็ใส่ค่าโดยเรียก titleLabel.text = validatorCellType.title (ลองดูโค้ดตัวอย่างในรูป 7.3)
รูปที่ 7.3 — อีกส่วนหนึ่งที่สำคัญคือ เมื่อผู้ใช้งาน scroll กลับมาที่ cell เดิมที่เคยกรอกข้อมูลไว้ ข้อมูลเหล่านั้นจะต้องยังคงอยู่เหมือนเดิม และรวมถึงการ Validate ด้วย ดังนั้นเราจะต้อง Validate ข้อมูลอีกครั้ง แต่เราจะรู้ได้ยังไงว่า Cell นี้เป็น Cell ที่เคยถูกกรอกข้อมูลไปแล้วหรือเป็น Cell ใหม่ที่เพิ่งโผล่มาครั้งแรก? ผมเลยสร้างตัวแปรมาเพิ่มหนึ่งตัวชื่อ editedFlag เอาไว้เช็คว่าเป็น Cell ที่เคยกรอกข้อมูลไปแล้วหรือเปล่า ซึ่งตัวแปรนี้จะถูกตั้งค่าจาก ViewController
รูปที่ 7.4 — หลังจากที่เตรียมทุกอย่างพร้อมแล้ว ก็นำเอาข้อความที่เก็บไว้ใน Dictionary มาใส่ให้กับ TextFieldValidatorTableViewCell ทุกครั้งที่ cell นั้นๆ โผล่ขึ้นมาบนหน้าจอ ตรงนี้สำคัญมากนะครับ ถ้าหากเราไม่ได้กำหนดค่าให้กับมัน มันอาจจะไปเอาค่าขึ้นมาแสดงผลได้
แล้วก็อย่าลืมบอก Cell นั้นด้วยว่าควร Validate ข้อความใน TextField ด้วยหรือเปล่า วิธีของผมคือการเช็คว่า Dictionary[indexPath.row] นั้นมีค่ามั้ย ถ้ามีก็อย่าลืม Validate มันนะ
จากนั้นลอง Build and Run และลองดูผลลัพธ์ที่เกิดขึ้น
จบตั๊ก (บริบูรณ์)
การที่ TableViewCell นั้นมีฟังก์ชั้น Validate ในตัวมันเอง ทำให้ลดความวุ่นวายในการส่งข้อมูลไปมาระหว่าง TableViewCell กับ ViewController ได้ ซึ่งการเขียน Validator โดยใช้ Enum เข้ามาช่วยนั้นทำให้ง่ายต่อการจัดการ เช่น ถ้าเราเพิ่ม case Lastname เข้ามาตัว xcode จะฟ้อง error ทันที ทำให้ลดการ error ในขณะ runtime ไปได้
แล้วก็อย่าลืมเรื่องการกำหนดค่าต่างๆ ให้กับ Interface ที่ cellForRowAtIndexPath ถ้ามี if ควรมี else ดักเอาไว้ให้ครบทุกกรณีเพื่อความถูกต้องของข้อมูลที่แสดงผล
สุดท้ายผิดพลาดประการใดขออภัยมา ณ ที่นี้และยินดีรับคำติชมเพื่อนำไปพัฒนาตัวเองครับ สามารถโหลดโปรเจคนี้ได้ที่ Github ครับ
Download: https://github.com/khemmachart/TextFieldValidator