توسعه اپلیکیشن لیست وظایف با SwiftUI و Core Data — از صفر تا صد
در این مقاله قصد داریم یک اپلیکیشن لیست وظایف با SwiftUI و Core Data توسعه دهیم که شرایط زیر را داشته باشد:
- بتوانیم یک وظیفه اضافه کنیم.
- یک وظیفه را تکمیل کنیم.
- یک وظیفه را حذف کنیم.
- در صورت بسته شدن اپلیکیشن، دادهها حفظ شوند.
اپلیکیشن نهایی ما به صورت زیر خواهد بود:
برای مشاهده کد کامل اپلیکیشن میتوانید به این ریپوی گیتهاب (+) مراجعه کنید. اما اگر میخواهید آن را به صورت گام به گام همراه با ما بسازید، در ادامه این نوشته با ما همراه شوید.
1. ایجاد یک اپلیکیشن تک صفحهای iOS
ابتدا یک پروژه جدید Xcode برای یک اپلیکیشن تکصفحهای iOS ایجاد کنید. تیک گزینه استفاده از SwiftUI و همچنین Core Data را بزنید.
2. موجودیت و خصوصیتهای Core Data
نخستین کاری که باید انجام دهیم افزودن یک موجودیت به مدل Core Data است. به این منظور فایل ProjectName.xcdatamodeld را باز کنید. توجه کنید که ProjectName همان نام پروژه شما است که در گام قبلی تعیین کردهاید. سپس روی Add Entity در انتهای پنجره کلیک کنید. نام موجودیت جدید را Task بگذارید. در تصویر زیر جایی که نام در inspector تغییر مییابد را میبینید:
2.1 افزودن خصوصیت به موجودیت Task
در این مرحله باید برای موجودیت Task، خصوصیتهایی تعیین کنیم تا اطلاعات زیر را ذخیره کند:
- id – به صورت یک شناسه یکتا برای هر وظیفه استفاده میشود.
- name – نامی است که کاربر به وظیفه میدهد.
- isComplete – تکمیل شدن یا نشدن وظیفه را تعیین میکند.
- dateAdded – تاریخ اضافه شدن وظیفه را نشان میدهد.
برای افزودن خصوصیتها به Task از کلید + در بخش Attributes استفاده کنید و یک نام و نوع برای خصوصیت تعیین کنید. در تصویر زیر روش انجام کار را میبینید:
این جدول هر خصوصیت و نوع متناظر با خصوصیت را تعیین میکند:
- id: UUID
- name: string
- isComplete: Bool
- dateAdded: date
فایل ProjectName.xcdatamodeld اینک باید به صورت تصویر زیر در آمده باشد:
2.2 افزودن یک فایل جدید سوئیفت
در این بخش یک فایل جدید سوئیفت اضافه میکنیم که موجب میشود Task قابل شناسایی باشد و فراخوانی فهرست وظایف آسانتر باشد. یک فایل جدید سوئیفت اضافه کرده و نام آن را Task+Extensions بگذارید. در این فایل کد زیر را اضافه کنید:
1extension Task: Identifiable {
2}
با افزودن کد فوق، کلاس Task اکنون با کلاس Identifiable سازگار است.
2.3 افزودن Core Data به ContentView.swift
در ادامه باید متغیری اضافه کنیم که به managedObjectContext در فایل ContentView.swift دسترسی داشته باشد. به این منظور فایل ContentView.swift را باز کنید و خط زیر را پیش از متغیر body اضافه کنید:
1@Environment(.\managedObjectContext) var context
به این ترتیب ContentView اینک باید به صورت زیر در آمده باشد:
1struct ContentView: View {
2 @Environment(\.managedObjectContext) var context
3 var body: some View {
4 Text("Hello world!")
5 }
6}
7struct ContentView_Previews: PreviewProvider {
8 static var previews: some View {
9 ContentView()
10 }
11}
در کد فوق context را به صورت یک متغیر محیطی اعلان میکنیم یعنی مقدار آن قرار است از محیط view بیاید. در این مورد مقدار آن از SceneDelegate.swift در خطوط 23 تا 27 ناشی میشود که context اعلان شده و سپس ()ContentView داده میشود.
3. کار روی UI
در این بخش روی رابط کاربری اپلیکیشن خود در ContentView.swift کار میکنیم.
3.1 افزودن یک TextField
کار خود را با اضافه کردن یک TextField به اپلیکیشن آغاز میکنیم. به این منظور در فایل ContentView.swift مقدار Text(HelloWorld) را به خط زیر تغییر دهید:
1TextField(title: StringProtocol, text:Binding<String>)
TextField باید دو مشخصه یکی StringProtocol و دیگری <Binding<String داشته باشد. برای StringProtocol یک مشخصه به نام "Task Name" اضافه میکنیم. زمانی که TextField خالی باشد، نام وظیفه به رنگ خاکستری روشن ظاهر میشود. سپس به یک <Binding<String نیاز داریم. این مشخصه به اندازه قبلی ساده نیست. ما باید یک متغیر برای آن اعلان کنیم. پیش از اعلان متغیر body خط زیر را اضافه کنید:
1@State private var taskName: String = ""
سپس مشخصه دوم TextField را به صورت $taskName تعیین کنید. اینک فایل ContentView.swift باید مانند زیر باشد:
1struct ContentView: View {
2 @Environment(\.managedObjectContext) var context
3 // this is the variable we added
4 @State private var taskName: String = ""
5 var body: some View {
6 // this is the TextField that we added
7 TextField("Task Name", text: $taskName)
8 }
9}
10struct ContentView_Previews: PreviewProvider {
11 static var previews: some View {
12 ContentView()
13 }
14}
از آنجا که ما از SwiftUI استفاده میکنیم، اگر از canvas استفاده کنید، میتوانید بدون الزام به اجرای اپلیکیشن در شبیهساز، UI اپلیکیشن را ببینید. در کد فوق ابتدا یک مشخصه state با استفاده از پوشش مشخصه State@ تعریف کردیم. بنابراین taskName یک مقدار binding است. یک مشخصه state باید مقدار را در taskName ذخیره کند و به ویو امکان میدهد که موارد تغییر مقادیر را مشاهده کرده و بهروزرسانی کند.
3.2 افزودن وظیفه به Core Data
ابتدا باید یک دکمه اضافه کنیم تا وقتی که کاربر، وظیفه را وارد کرد، بتواند با فشردن آن، وظیفه را در لیست خود اضافه کند. به این منظور باید TextField را در یک HStack قرار دهیم و سپس یک ()Button اضافه کنیم. زمانی که دکمه را اضافه میکنیم، اکشن آن باید ()self.addTask باشد و برچسب دکمه نیز به صورت Text("Add Task") است. کد ما در body به صورت زیر است:
1var body: some View {
2 HStack{
3 TextField("Task Name", text: $taskName)
4 Button(action: {
5 self.addTask()
6 }){
7 Text("Add Task")
8 }
9 }
10}
اکنون Xcode خطایی به صورت زیر نمایش میدهد:
1Value of type 'ContentView' has no member 'addTask',
معنای آن این است که باید تابع ()addTask را اضافه کنیم. پس از متغیر body کد زیر را اضافه کنید:
1func addTask() {
2 let newTask = Task(context: context)
3 newTask.id = UUID()
4 newTask.isComplete = false
5 newTask.name = taskName
6 newTask.dateAdded = Date()
7 do {
8 try context.save()
9 } catch {
10 print(error)
11 }
12}
در کد فوق در ()addTask یک شیء جدید Task اضافه کرده و سپس خصوصیتهای متناظر را به مقادیر newTask میدهیم. در ادامه از ()save استفاده میکنیم تا آن را به Core Data اضافه کنیم. اینک UI ما به صورت زیر در آمده است:
3.3 ایجاد لیست وظایف
اینک زمان آن رسیده است که لیست وظایف را ایجاد کنیم. ابتدا باید یک درخواست واکشی برای دریافت وظایف اضافه شده ارسال کنیم. کد زیر را به ContentView اضافه میکنیم:
1@FetchRequest(
2 entity: Task.entity(),
3 sortDescriptors: [NSSortDescriptor(keyPath: \Task.dateAdded, ascending: false)],
4 predicate: NSPredicate(format: "isComplete == %@", NSNumber(value: false))
5) var notCompletedTasks: FetchedResults<Task>
در کد فوق کارهای زیر را انجام میدهیم:
- Entity تعیین میکند که کدام موجودیت Core Data را بازیابی میکنیم.
- sortDescriptors شیوه مرتبسازی موجودیتها را توصیف میکند.
- predicate به عنوان یک فیلتر عمل میکند.
از این رو در کد فوق همه وظایفی که تکمیل نشدهاند را درخواست میکنیم. این موارد بر اساس تاریخ از جدید به قدیم مرتبسازی میشوند. سپس باید یک لیست ایجاد کنیم که وظایف را نمایش دهد. بدین منظور HStack را درون یک VStack جای میدهیم. کد آن به صورت زیر است:
1VStack {
2 HStack {
3 // TEXTFIELD CODE HERE
4 }
5}
اکنون میتوانیم یک لیست اضافه کنیم. پس از HStack کد زیر را اضافه کنید:
1List {
2 Text("Hi")
3}
بدین ترتیب یک لیست زیر TextField اضافه شده و UI به صورت زیر درمیآید:
در ادامه باید Hi را به تعداد وظایفی که داریم تکرار کنیم. به این منظور Text("Hi") را درون ForEach به صورت زیر قرار دهید:
1ForEach(notCompletedTasks){ task in
2 Text("Hi")
3}
لزومی به تعیین id برای notCompletedTasks در ForEach وجود ندارد، زیرا Task به لطف کاری که در گام 2.3 راهنما انجام دادیم با Identifiable سازگار است. اینک اگر اپلیکیشن را اجرا کنید و یک نام وظیفه وارد کنید، با زدن روی دکمه Add Task ردیف دیگری از Hi ایجاد میشود. در ادامه یک struct جدید برای نمای TaskRow ایجاد میکنیم که وظیفه موجود در ContentView.swift را میگیرد. کد زیر را بالاتر از ()ContentView اضافه کنید:
1struct TaskRow: View {
2 var task: Task
3 var body: some View {
4 Text(task.name ?? "No name given")
5 }
6}
درون Text از عملگر ادغام تهی ?? استفاده کردیم تا یک مقدار پیشفرض به آن بدهیم. دلیل این که این کار را انجام دادیم آن است که مقدار خصوصیت Task به صورت Optional است و ممکن است مقداری نداشته باشد. اکنون درون ForEach مقدار Text را با TaskRow(task) عوض میکنیم. بدین ترتیب فایل ContentView.swift اکنون باید به صورت زیر باشد:
1import SwiftUI
2struct TaskRow: View {
3 var task: Task
4 var body: some View {
5 Text(task.name ?? "No name given")
6 }
7}
8struct ContentView: View {
9 @Environment(\.managedObjectContext) var context
10 @FetchRequest(
11 entity: Task.entity(),
12 sortDescriptors: [NSSortDescriptor(keyPath: \Task.dateAdded, ascending: false)],
13 predicate: NSPredicate(format: "isComplete == %@", NSNumber(value: false))
14 ) var notCompletedTasks: FetchedResults<Task>
15 @State private var taskName: String = ""
16 var body: some View {
17 VStack {
18 HStack{
19 TextField("Task Name", text: $taskName)
20 Button(action: {
21 self.addTask()
22 }){
23 Text("Add Task")
24 }
25 }
26 List {
27 ForEach(notCompletedTasks){ task in
28 TaskRow(task: task)
29 }
30 }
31 }
32 }
33 func addTask() {
34 let newTask = Task(context: context)
35 newTask.id = UUID()
36 newTask.isComplete = false
37 newTask.name = taskName
38 newTask.dateAdded = Date()
39 do {
40 try context.save()
41 } catch {
42 print(error)
43 }
44 }
45}
46struct ContentView_Previews: PreviewProvider {
47 static var previews: some View {
48 ContentView()
49 }
50}
ظاهر اپلیکیشن نیز در این مرحله مانند زیر است:
4. علامتگذاری یک وظیفه به صورت تکمیل شده
اکنون باید وظیفهها را به صورت تکمیلشده علامتگذاری کنیم. به این ترتیب آن وظیفهها دیگر در لیست ظاهر نمیشوند. ابتدا TaskRow را درون یک Button قرار دهید و اکشن دکمه را نیز self.updateTask(task) قرار دهید تا به صورت زیر دربیاید:
1Button(action: {
2 self.updateTask(task)
3}){
4 TaskRow(task: task)
5}
سپس باید یک تابع به نام updateTask ایجاد میکنیم که بتونیم عملاً وظیفه را بهروزرسانی کرده و آن را به صورت تکمیلشده علامتگذاری کنیم. پس از addTask خط زیر را اضافه کنید:
1func updateTask(_ task: task){}
استفاده از _ نشان میدهد که میتوانیم برچسب آرگومان را در زمان فراخوانی تابع نادیده بگیریم. در ادامه تابع را به صورت زیر مینویسیم:
1let isComplete = true
2let taskID = task.id! as NSUUID
3let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Task")
4fetchRequest.predicate = NSPredicate(format: "id == %@", taskID as CVarArg)
5fetchRequest.fetchLimit = 1
6do {
7 let test = try context.fetch(fetchRequest)
8 let taskUpdate = test[0] as! NSManagedObject
9 taskUpdate.setValue(isComplete, forKey: "isComplete")
10} catch {
11 print(error)
12}
در کد فوق ابتدا یک ثابت برای مقدار جدید isComplete برای وظیفه تعیین کردیم. سپس id وظیفه را روی یک مقدار ثابت قرار دادیم تا به صورت پیشبینی مورد استفاده دهیم. در ادامه باید یک درخواست واکشی ایجاد کنیم که یک وظیفه خاص که قرار ست بهروزرسانی شود را میگیرد. در ادامه بهروزرسانی را اجرا میکنیم.
اکنون اگر اپلیکیشن را اجرا کنید، میتوانید یک وظیفه جدید اضافه کرده و سپس روی آن ضربه بزنید تا به صورت تکمیلشده علامتگذاری شود. از آنجا که در لیست تنها از وظایف تکمیلنشده استفاده میکنیم، وظایف تکمیلشده دیگر در لیست ظاهر نخواهند شد. تصویر زیر اپلیکیشن نهایی را نمایش میدهد:
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- ساخت اپلیکیشن چت برای iOS با SwiftUI — از صفر تا صد
- ساخت نمودارهای میلهای در SwiftUI — از صفر تا صد
- ساخت اپلیکیشن با Firebase و SwiftUI — از صفر تا صد
==