Fahlunn ปี 2014
Table of Contents
อธิบายตัวแอปพลิเคชัน #
ฟ้าลั่นเป็นแอปพลิเคชันที่ช่วยตัวแทนประกันชีวิตนำเสนอแผนการขายได้ชัดเจนมากขึ้น โดยผ่านการใช้รูปภาพกราฟ และการปรับตัวเลขต่างๆโดยใช้แค่การลากนิ้ว ใช้การพิมพ์ keyboard แค่ตอนกรอกชื่อเท่านั้นเอง จากความยืดหยุ่นใรการปรับแผนได้อย่างอิสระ ก็สามารถทำให้ตัวแทนปิดการขายได้เร็วมากขึ้น อีกทั้งยังได้แผนความคุ้มครองที่เหมาะกับลูกค้าคนนั้นจริงๆเพราะเขาเลือกด้วยตัวเอง
โจทย์สำคัญ #
- ตัวแอปถูกออกแบบมาเพื่อเป็น salekit และ presentation ในตัว ทำให้เรื่องความสวยงามและการนำเสนอข้อมูลที่กระชับเป็นเรื่องสำคัญมาก มีการ custom
UIKit
หลายจุดมากๆ - มีการบันทึกข้อมูลลูกค้าที่ผู้ใช้งานทำการเสนอขายไปด้วยเพื่อเป็นประวัติและข้อมูลในการวางแผนการขายต่อไป
- เนื่องจากแผนประกันมีการเปลี่ยนแปลงอยู่ตลอดเวลา การคำนวณหรือเงื่อนไขต่างๆในแอปจำเป็นต้อง update ตัวเองได้ทันที โดยไม่ต้องผ่าน App Store ซึ่งต้องใช้เวลาในการอนุมัติ 3-7 วัน (อ้างอิงจากปี 2014) เนื่องจากลูกค้าของเราต้องออกไปขายทุกวัน
การพัฒนา feature เพื่อตอบโจทย์สำคัญ #
ด้านการนำเสนอเราใช้ภาพที่สวยงามจากทีม design จำนวนมาก มาใช้ร่วมกับ UIKit
ไม่ว่าจะเป็น
- วงล้อเลือกอายุ ที่ประยุกต์ใช้
UIScrollView
มาบังหน้าUIImageView
ที่เป็นรูปจานเลข หลังจากนั้นเราก็หาความยาวรอบรูปด้วยสูตรl = 2π * r
แล้วนำมากำหนดเป็นcontentSize.width
ของ scrollview หลังจากนั้นจึงสั่งให้UIImageView
ด้านหลังหมุนไปตามการ scroll และทำการดักตำแหน่ง x ของ scroll เพื่อนำมาคำนวณหาว่า ณ จุดกึ่งกลางตอนนี้คืออายุเท่าใด

- ภูเขา เป็นเสมือนสัญลักษณ์ของแอปนี้ หน้านี้จะเป็นการ custom เอา
UIView
ที่มีการใส่UIPanGestureRecognizer
เข้าไปด้วยเพื่อตรวจจับลากการลากนิ้วของผู้ใช้งานขึ้นลง โดยที่ขนาดความสูงเหล่านี้จะส่งผลต่อค่าความคุ้มครองของประกันแต่ละตัวที่ผู้ใช้เลือก

- กราฟความคุ้มครอง ส่วนนี้ถึงจะเป็นการใช้ภาพ static ที่จะถูกโหลดมาแสดงตามแผนประกันที่ลูกค้าเลือก แต่ตัวเลขปีและเงินคืนก็เป็นส่วนที่ต้องใช้
UILabel
และใช้การดักจับ touch บนUIView
มาเพื่อหาตำแหน่งว่าแตะเลขตัวไหนอยู่เพื่อที่จะได้แสดง popover ขึ้นมาถูก (ที่ไม่ใช้ UIButton เพราะต้องการให้เอานิ้วลากได้เลย ไม่อยากให้เป็นเหมือนการกดปุ่ม)

- การ Print หรือ Export เอกสาร นอกจากการดูบนจอแล้ว ผู้ใช้สามารถสั่งพิมพ์หรือส่งอีเมล์ไฟล์สรุปในรูปแบบ PDF ก็ได้ โดยไฟล์นี้ถูกสร้างจาก
UIView
ขนาดใหญ่เท่ากระดาษA4
(เพื่อป้องกันอาการภาพแตก) แล้ว render เป็นภาพหรือ PDF เพื่อนำไปใช้งานต่อตามที่ผู้ใช้ต้องการ

- เก็บประวัติการนำเสนอ แปลงเล่มกรมธรรม์ที่นำเสนอไปเก็บอยู่ใน plist ไฟล์ก่อนที่จะทำการ upload ไปเก็บไว้บน server ด้วยเพื่อป้องกันความผิดพลาดหากลูกค้าเผลอลบแอปทิ้ง ก็จะยังมีข้อมูลที่สำรองไว้กับเรา

- JavascriptCore เนื่องจากแผนประกันมีการ update อยู่ตลอด(ทุกเดือน) เพื่อไม่ให้งานขายของลูกค้าต้องสะดุดจากการรอ App Store อนุมัติ ผมจึงต้องเก็บ business logic ต่างๆ ทั้งการคำนวณเบี้ย หรือแม้แต่การกำหนดค่า max / min ของอนุสัญญาแต่ละตัวด้วย Javascript เพราะเราสามารถ js code ซึ่งเป็น text มา run บน iOS ได้แปลว่า ทุกครั้งที่มีการใช้แอปจะมีการตรวจสอบอยู่เสมอว่า core ของตัวคำนวณยังเป็น version ล่าสุดอยู่หรือไม่ ถ้าหากไม่ใช่ก็จะทำการ download ลงมาในรูปแบบ zip แล้ว unzip เก็บไว้ใน directory ของแอปเองเพื่อเป็นการ update ตัว business logic ทั้งหมด
ตัวอย่างการเขียน feature บางส่วน #
การสร้างภูเขา (mountain slider) #
- สร้าง view โดยการกำหนดความสูง max ได้พร้อมกับมี closure block สำหรับทำงานเวลาที่มีการเปลี่ยนค่าของภูเขา
func createMainMountain() {
let width = 360
let height = 370
let y = 768 - height
let x = (1024/2) - (width/2)
mainMountainView = MountainView(
name: "mountain-main",
maxHeight: 550,
frame: CGRect(x: x, y: y, width: width, height: height),
valueChanged:{(value: CGFloat, name:String) in
print("\(name): \(value)")
}
)
self.mountainScrollView.addSubview(mainMountainView)
}
- ภายใน MountainView จะมี PanGestureRecognizer คอยตรวจสอบการลากนิ้วบน view เพื่อจะยืดหดความสูงตาม
@objc func didPan(sender: UIPanGestureRecognizer) {
guard let mountainView = sender.view else { return }
if sender.state == .began {
startPosition = sender.location(in: mountainView)
}
if sender.state == .began || sender.state == .changed {
translation = sender.translation(in: mountainView)
let newY = self.frame.origin.y + translation.y
if let superview = self.superview {
let newHeight = superview.frame.size.height - newY
if newHeight >= originalHeight && newHeight <= maxHeight {
self.frame = CGRect(x: self.frame.origin.x, y: newY, width: self.frame.size.width, height: newHeight)
self.setMountainValue()
if let block = valueDidChanged {
block(self.mountainValue, name)
}
}
}
}
sender.setTranslation(CGPoint(x: 0.0, y: 0.0), in: mountainView)
mountainImageView.frame.size = self.frame.size
}
- ความสูงของ view จะถูกเอามาเปรียบเทียบเพื่อหาค่าจาก 0 - 100 เพื่อนำไปแปลงเป็นค่าของภูเขาแต่ละลูก (หรือความคุ้มครองของประกันชีวิตนั่นเอง)
func setMountainValue() {
let max = self.maxHeight - self.originalHeight
let difference = self.frame.height - self.originalHeight
mountainValue = round((difference / max) * 100)
self.mountainLabel.text = String(format: "%.0f", mountainValue)
}

FLMountainView #
https://gitlab.com/clonezer/flmountainviewcontroller
การใช้ Javascript Core ในการ evaluate javascript code #
เนื่องจากบน iOS SDK มี JavascriptCore Framework ให้เราได้เรียกใช้อยู่แล้ว เราจึงไม่จำเป็นต้องติดตั้งอะไรเพิ่ม โดยคำสั่งหลักๆที่มักเจอบ่อยๆก็คือ evaluateScript()
ก็คือการ complie ตัว javascript code ให้พร้อมใช้งานได้แล้ว เช่น
import JavaScriptCore
func getJSContext(with jsCode: String) -> JSContext {
guard let context = JSContext() else { return }
//เพื่อให้ Javascript รู้จัก Object ของฝั่ง swift เราสามารถประกาศได้ดังนี้
context.setObject(Book.self, forKeyedSubscript: "book" as Book)
context.setObject(InsuranceList.self, forKeyedSubscript: "insuranceList" as InsuranceList)
context.setObject(Assured.self, forKeyedSubscript: "assured" as Assured)
context.setObject(Payer.self, forKeyedSubscript: "payer" as Payer)
context.setObject(Insurance.self, forKeyedSubscript: "insurance" as Insurance)
context.setObject(Insurance.self, forKeyedSubscript: "mainInsurance" as Insurance)
context.setObject(Insurance.self, forKeyedSubscript: "rider" as Insurance)
context.evaluateScript(jsCode)
return context
}
หลังจากได้ JSContext แล้วหากเราต้องการคำนวณเราก็สามารถส่ง object เข้าไปใน javascript function ได้เลย เช่น ตัวอย่างการส่ง mainInsurance
(แผนประกันหลัก) และ assured
(ผู้เอาประกัน) เข้าไปใน function getMultiplierRate
เพื่อที่จะได้เบี้ยของแผนประกันออกมา
func getMainInsuranceRate(mainContext: JSContext, mainInsurace: Insurance, assured: Assured) -> JSValue? {
guard
let getMultiplierRate = mainContext.objectForKeyedSubscript("getMultiplierRate")
let value = getMultiplierRate.call(withArguments: [mainInsurance, assured])
else { return nil }
return value
}
วิธีนี้มีประโยชน์มากนอกจากจะทำให้เรา update business logic ที่ซับซ้อนได้โดยไม่ต้องผ่าน Store แล้ว เรายังสามารถนำ Javascript Code ชุดเดียวกันไปใช้กับ Web หรือ platform อื่นๆก็ได้ ทำให้ตัว core การคำนวณถูกพัฒนาแค่ครั้งเดียวบน Javascript ไม่ต้องเขียนหลายรอบ