Document Directory
การเขียนและอ่านไฟล์ JSON ใน Documents Directory
ในการพัฒนาแอปพลิเคชันสมัยใหม่ ความสามารถในการจัดเก็บข้อมูลของผู้ใช้ถือเป็นองค์ประกอบพื้นฐานที่แยกไม่ออกจากการออกแบบระบบ ไม่ว่าจะเป็นแอปจดบันทึก แอปการเงิน แอปสุขภาพ หรือแอปเพื่อการศึกษา ล้วนต้องเผชิญคำถามเดียวกันคือ ข้อมูลที่ผู้ใช้สร้างขึ้นจะถูกเก็บไว้ที่ใด และจะถูกเรียกกลับมาใช้อย่างไร หากแอปสามารถแสดงผลได้เพียงชั่วคราวในหน่วยความจำ เมื่อปิดแอปแล้วข้อมูลสูญหาย ย่อมไม่อาจถือเป็นแอปที่ใช้งานได้จริงในบริบทของผู้ใช้
บนระบบปฏิบัติการ iOS พื้นที่ที่ถูกออกแบบมาเพื่อรองรับการจัดเก็บข้อมูลถาวรของผู้ใช้ คือ Documents Directory ซึ่งเป็นโฟลเดอร์เฉพาะของแต่ละแอปที่ระบบอนุญาตให้เขียนและอ่านไฟล์ได้อย่างปลอดภัย พื้นที่นี้เหมาะสำหรับเก็บข้อมูลที่ต้องคงอยู่ข้ามการเปิด–ปิดแอป เช่น ไฟล์ JSON รูปภาพ เอกสาร หรือไฟล์ที่ผู้ใช้สร้างขึ้น การเข้าใจกลไกของ Documents Directory จึงไม่ใช่เพียงเรื่องของเทคนิคการเขียนไฟล์ แต่เป็นพื้นฐานของการออกแบบสถาปัตยกรรมแอปที่รองรับการใช้งานจริง
บทความนี้นำเสนอแนวทางการจัดกิจกรรมการเรียนรู้ผ่านการพัฒนาแอป Memo Notes ด้วย SwiftUI ภายใต้สถาปัตยกรรม MVVM โดยใช้การสร้างแอปขนาดเล็กเป็นสื่อกลางให้ผู้เรียนค่อย ๆ เข้าใจแนวคิดเรื่องการจัดเก็บข้อมูลถาวร ตั้งแต่การออกแบบโครงสร้างข้อมูล การจัดการสถานะใน ViewModel การรับข้อมูลจากผู้ใช้ ไปจนถึงการบันทึกและโหลดข้อมูลจากไฟล์จริงใน Documents Directory
การออกแบบโครงสร้างข้อมูลและหน้าจอพื้นฐานของแอป
ในการเริ่มต้นพัฒนาแอปไม่ใช่การเขียนไฟล์ลงเครื่องทันที แต่คือการออกแบบโครงสร้างของแอปให้รองรับการจัดเก็บข้อมูลได้อย่างเหมาะสมตั้งแต่ต้น กิจกรรมในส่วนนี้จึงมุ่งเน้นไปที่การสร้างพื้นฐานของแอป ทั้งในเชิงโครงสร้างข้อมูลและสถาปัตยกรรม เพื่อให้ผู้เรียนห็นภาพว่า persistence เป็นเพียงส่วนหนึ่งของระบบที่ต้องเชื่อมโยงกับองค์ประกอบอื่นอย่างเป็นระบบ
ขั้นตอนที่ 1 เริ่มต้นจากการสร้างโปรเจกต์ SwiftUI ใหม่ และจัดโครงสร้างไฟล์ตามแนวคิด MVVM โดยแยกส่วน Model, ViewModel และ View ออกจากกัน
MemoApp
│
├── Models // ชั้นข้อมูล (Data Model Layer)
│ └── Note.swift // โครงสร้างข้อมูลของ "โน้ต" (รองรับ Codable เพื่อเตรียมสำหรับการบันทึก JSON)
│
├── Services // ชั้นบริการ (Persistence / Data Access Layer)
│ └── FileManagerService.swift // จัดการอ่าน–เขียนไฟล์ JSON ใน Documents Directory
│
├── ViewModels // ชั้นตรรกะของแอป (Business Logic Layer)
│ └── NoteViewModel.swift // จัดการ state ของโน้ตทั้งหมด
│
├── Views // ชั้นแสดงผล (User Interface Layer)
│ ├── NoteListView.swift // หน้าหลักของแอป
│ │
│ └── NoteEditorView.swift // หน้าสำหรับเพิ่มหรือแก้ไขโน้ต
│
└── MemoAppApp.swift // จุดเริ่มต้นของแอป (Entry Point)ขั้นตอนที่ 2 ออกแบบโครงสร้างข้อมูลของโน้ตผ่านการสร้าง Note model ซึ่งทำหน้าที่แทนข้อมูลหลักของระบบ
โปรโตคอล Identifiable และการกำหนด let id: UUID ให้กับโน้ต มีความสำคัญโดยตรงต่อการทำงาน โดยเฉพาะในการแสดงผลผ่าน List และ ForEach SwiftUI จำเป็นต้องทราบตัวระบุที่ไม่ซ้ำกันของแต่ละรายการเพื่อคำนวณความแตกต่างระหว่างข้อมูลเดิมกับข้อมูลใหม่ในกระบวนการอัปเดตหน้าจอ คุณสมบัติ id จึงถูกกำหนดเป็น UUID ซึ่งรับประกันความเป็นเอกลักษณ์ของแต่ละโน้ต การใช้ UUID แทนการใช้ลำดับตัวเลขธรรมดาช่วยลดโอกาสเกิดปัญหาการซ้ำของข้อมูล โดยเฉพาะในกรณีที่มีการเพิ่มและลบรายการจำนวนมาก
โปรโตคอล Codable มีบทบาทสำคัญต่อการจัดเก็บข้อมูลถาวร แม้ในขั้นตอนนี้จะยังไม่ได้เขียนไฟล์ลง Documents Directory โดย Codable ทำให้โครงสร้างข้อมูลสามารถถูกแปลงเป็น JSON ด้วย JSONEncoder และแปลงกลับเป็นวัตถุด้วย JSONDecoder ได้โดยตรง การเตรียมความพร้อมในลักษณะนี้ช่วยลดการปรับแก้โครงสร้างข้อมูลในภายหลัง และเป็นตัวอย่างของการออกแบบที่คำนึงถึง persistence ตั้งแต่ระดับ Model
โปรโตคอล Equatable ช่วยให้สามารถเปรียบเทียบค่า Note สองรายการได้โดยตรง ซึ่งมีประโยชน์ในกระบวนการตรวจสอบความเปลี่ยนแปลงของข้อมูล หรือในกรณีที่ต้องการค้นหาตำแหน่งของโน้ตในอาร์เรย์เพื่อทำการอัปเดต การกำหนดให้ Model สอดคล้องกับ Equatable ช่วยเพิ่มความยืดหยุ่นในการจัดการข้อมูลโดยไม่ต้องเขียนตรรกะเปรียบเทียบเพิ่มเติม
ขั้นตอนที่ 3 การสร้าง NoteViewModel ซึ่งทำหน้าที่เป็นตัวกลางในการจัดการข้อมูลของแอป
ขั้นตอนที่ 4 การสร้าง NoteListView ในการแสดงรายการโน้ตบนจอภาพ
ผลลัพธ์ที่ได้

การเพิ่ม ลบ และแก้ไขข้อมูลใน Memo Notes
หลังจากที่แอปมีโครงสร้างข้อมูลและหน้าจอแสดงรายการพื้นฐานแล้ว ขั้นตอนต่อไปคือการทำให้แอปสามารถโต้ตอบกับผู้ใช้ได้จริง ในขั้นตอนนี้จึงมุ่งเน้นไปที่การรับข้อมูลจากผู้ใช้ การปรับปรุงข้อมูลในระบบ และการทำให้หน้าจอสะท้อนการเปลี่ยนแปลงของข้อมูลแบบทันที ซึ่งเป็นแนวคิดสำคัญของการพัฒนาแอปแบบ reactive ใน SwiftUI
ขั้นตอนที่ 5 เริ่มต้นจากการขยายความสามารถของ NoteViewModel ให้สามารถจัดการข้อมูลได้มากกว่าการเก็บค่าเริ่มต้นในหน่วยความจำ โดยเพิ่มฟังก์ชันสำหรับการเพิ่ม ลบ และอัปเดตโน้ต
ขั้นตอนที่ 6 สร้างไฟล์ NoteEditorView.swift เพื่อใช้เป็นหน้าสำหรับการกรอกข้อมูล
NoteEditorView ถูกกำหนดให้สอดคล้องกับโปรโตคอล View และมีหน้าที่เพียงแสดงผล UI และรับข้อมูลจากผู้ใช้ ไม่จัดการข้อมูลในระบบโดยตรง ไม่เรียก FileManager และไม่แก้ไขอาร์เรย์ของโน้ตเอง
@Environment(.dismiss) var dismissเป็นการดึงค่าdismissจาก environment ของ SwiftUI ซึ่งเป็นกลไกสำหรับปิดหน้าจอที่ถูกนำเสนอแบบ modal (เช่น.sheet) โดยไม่ต้องรู้ว่าถูกเรียกมาจากที่ใดvar onSave: (String, String) -> Voidคือ ตัวแปรชนิด closure ที่ใช้สำหรับ “ส่งข้อมูลกลับออกจาก View”การวางปุ่ม Save ไว้ใน
.confirmationActionทำให้ UI เป็นไปตามมาตรฐานของ iOS ซึ่งมักวางปุ่มยืนยันทางด้านขวาบน
ขั้นตอนที่ 7 ปรับปรุง NoteListView ให้มีความสามารถในการเพิ่มและลบข้อมูลได้
ขั้นตอนที่ 8 เพิ่มความสามารถในการแก้ไข Note โดยการปรับปรุงคำสั่งในไฟล์ NoteEditorView จาก
เป็น
เพื่อให้สามารถส่งค่าเริ่มต้นจากภายนอกได้
และเพิ่ม state สำหรับโหมดแก้ไขในไฟล์ NoteListView โดยเพิ่มตัวแปร
และแก้ไขส่วน List โดยการเพิ่มคำสั่ง .onTapGesture ดังนี้
และเพิ่มคำสั่งใต้ .sheet(isPresented: $showAdd) ดังนี้
การเพิ่มความสามารถในการจัดเก็บข้อมูลใน Documents Directory
หลังจากที่แอป Memo Notes สามารถเพิ่ม ลบ และแก้ไขข้อมูลได้ครบในระดับหน่วยความจำแล้ว ขั้นตอนสำคัญถัดไปคือการทำให้ข้อมูล “คงอยู่” แม้ผู้ใช้จะปิดแอปไปแล้วก็ตาม โดยแนวคิดสำคัญ คือ ViewModel ไม่ควรจัดการไฟล์โดยตรง เพราะหน้าที่ของ ViewModel คือจัดการตรรกะ ไม่ใช่ติดต่อระบบไฟล์ ดังนั้น เราจึงสร้างชั้นบริการ (Service Layer) ขึ้นมาใหม่
ขั้นตอนที่ 9 สร้างไฟล์ FileManagerService ขึ้นใหม่สำหรับใช้เป็นส่วนจัดการข้อมูลใน Document directory
FileManager.default.urls(for:in:)ใช้ค้นหา Documents Directory ของแอป.userDomainMaskหมายถึงพื้นที่ของผู้ใช้ปัจจุบันappendingPathComponent(fileName)ใช้ระบุชื่อไฟล์saveNoteทำหน้าที่ในการบันทึก Note ลงไฟล์ notes.json ใน Documents DirectoryloadNoteทำหน้าที่ในการดึงข้อมูล Note จากไฟล์ notes.json ใน Documents Directory
ขั้นตอนที่ 10 เชื่อม Service กับ ViewModel
เปิดไฟล์ NoteViewModel.swift และเพิ่ม property สำหรับเรียกใช้งาน Service ดังนี้
ขั้นตอนที่ 11 เพิ่มความสามารถในการค้นหา Note ใน NoteListView
เมื่อเราปรับ vm.notes เป็น vm.filteredNotes คือการเปลี่ยการแสดงข้อมูลจากเดิมที่แสดง “ข้อมูลทั้งหมด”
เปลี่ยนมาแสดง “ข้อมูลที่ผ่านการกรองแล้ว” ผลที่เกิดขึ้นคือ เมื่อผู้ใช้พิมพ์คำค้นหา searchText จะเปลี่ยนค่าและ filteredNotes คำนวณใหม่ และ List จะอัปเดตทันที
คำสั่ง .searchable(text: $vm.searchText) ทำหน้าที่เพิ่ม ช่องค้นหา (Search Bar) ให้กับหน้าจอ และเชื่อมข้อความที่ผู้ใช้พิมพ์เข้ากับตัวแปร searchText ใน ViewModel
บทสรุป
บทความนี้ได้นำผู้อ่านเรียนรู้กระบวนการพัฒนาแอป Memo Notes อย่างเป็นลำดับขั้น ตั้งแต่การออกแบบโครงสร้างข้อมูลด้วย Model ที่รองรับ Identifiable, Codable และ Equatable การจัดการสถานะด้วย ViewModel ภายใต้สถาปัตยกรรม MVVM ไปจนถึงการสร้างหน้าจอที่ตอบสนองแบบ Reactive ด้วย SwiftUI
ผู้อ่านได้เข้าใจว่า การเพิ่ม ลบ และแก้ไขข้อมูลไม่ใช่เพียงการเปลี่ยนค่าบนหน้าจอ แต่เป็นการจัดการวงจรข้อมูลทั้งระบบ ตั้งแต่ View → ViewModel → Service → Documents Directory และย้อนกลับมาแสดงผลอีกครั้งเมื่อเปิดแอปใหม่ แนวคิดเรื่อง Persistence จึงไม่ได้เป็นเพียงเทคนิคการเขียนไฟล์ JSON แต่เป็นส่วนหนึ่งของการออกแบบสถาปัตยกรรมที่รองรับการใช้งานจริง
นอกจากนี้ การเพิ่มความสามารถในการค้นหาด้วย filteredNotes และ .searchable() ยังช่วยให้เห็นภาพของ Reactive Data Flow อย่างชัดเจน โดยข้อมูลหลัก (Single Source of Truth) ถูกประมวลผลและแสดงผลใหม่อัตโนมัติเมื่อสถานะเปลี่ยนแปลง
โดยสรุป ผู้อ่านได้เรียนรู้ทั้งมิติของการออกแบบข้อมูล การแยกหน้าที่ของส่วนประกอบในระบบ การจัดการสถานะแบบ Reactive และการสร้าง Persistence Layer ที่เชื่อถือได้ ซึ่งเป็นพื้นฐานสำคัญสำหรับการพัฒนาแอป iOS ในระดับวิชาชีพ และสามารถต่อยอดไปสู่เทคโนโลยีการจัดเก็บข้อมูลขั้นสูงในอนาคตได้อย่างมั่นคง
Last updated