ایجاد منوی کناری لغزشی با SwiftUI — از صفر تا صد

۳۷ بازدید
آخرین به‌روزرسانی: ۱۲ مهر ۱۴۰۲
زمان مطالعه: ۴ دقیقه
ایجاد منوی کناری لغزشی با SwiftUI — از صفر تا صد

در این راهنما با شیوه ایجاد یک منوی کناری لغزشی با SwiftUI آشنا می‌شویم. اگر تاکنون هیچ منوی کناری با UIKit ایجاد نکرده باشید، ایجاد چنین منویی با استفاده از SwiftUI برایتان جذاب خواهد بود. این کار بسیار آسان است بنابراین تا انتهای این راهنما با ما همراه باشید.

چه چیزی می‌خواهیم بسازیم؟

ما قصد داریم یک منوی کناری ساده ایجاد کنیم. زمانی که روی دکمه Open کلیک کنید، منو باز شده و پس‌زمینه نیز انیمیت خواهد شد. اگر روی پس‌زمینه بزنید، منو بسته شده و پس‌زمینه محو می‌شود.

SwiftUI

گام 1: ایجاد محتوای منو

در این مرحله لیستی با 3 نمای Text ایجاد می‌کنیم.

هر نمای Text یک TapGesture خواهد داشت به طوری که می‌توانید کارکرد آن را تست کنید.

1struct MenuContent: View {
2    var body: some View {
3        List {
4            Text("My Profile").onTapGesture {
5                print("My Profile")
6            }
7            Text("Posts").onTapGesture {
8                print("Posts")
9            }
10            Text("Logout").onTapGesture {
11                print("Logout")
12            }
13        }
14    }
15}

بدین ترتیب چیزی برای افزودن به محتوای منو وجود ندارد. ما مواردی را اضافه کردیم تا صرفاً چیزی برای نمایش وجود داشته باشد.

گام 2: ایجاد منوی کناری

این گام دشوارترین مرحله این راهنما محسوب می‌شود، اما همچنان نسبتاً ساده است. ما باید یک view جدید به صورت SideMenu ایجاد کنیم. این نما دارای مشخصه‌های width ،isOpen و menuClose است.

1struct SideMenu: View {
2    let width: CGFloat
3    let isOpen: Bool
4    let menuClose: () -> Void
5    
6    var body: some View {
7        // Code here
8    }
9}

menuClose تابعی است که ارسال می‌شود. بدین ترتیب می‌توانیم حالت باز بودن منو را به صورت لوکال برای نمای ContentView حفظ کنیم. دلیل انجام این کار آن است که این یک اپلیکیشن ساده است، اما اگر یک اپلیکیشن واقعی بود، روش‌های بهتری برای انجام این کار وجود داشتند.

ایجاد نمای پس‌زمینه

پیش از آن که نمای پس‌زمینه را داشته باشیم باید یک ZStack به body خود اضافه کنیم. بدین ترتیب می‌توانیم یک منو روی پس‌زمینه اضافه کنیم. SideMenu خود را طوری به‌روزرسانی کنید که به صورت زیر درآید:

1struct ContentView: View {
2    @State var menuOpen: Bool = false
3    
4    var body: some View {
5        // Code here
6    }
7    
8    func openMenu() {
9        self.menuOpen.toggle()
10    }
11}

اینک می‌توانید یک نمای پس‌زمینه اضافه کنید. برای این نما از یک GeometryReader و یک EmptyView استفاده می‌کنیم. این احتمالاً بهترین روش برای ایجاد چنین نمایی است چون نمی‌خواهیم یک stack دیگر داشته باشیم، زیرا حتی با وجود یک stack دیگر باز به یک view نیاز داریم که در این مورد یک EmptyView است.

این نمای پس‌زمینه یک رنگ پس‌زمینه نیز خواهد داشت که دارای سطح شفافیت است، اما خود نما نیز دارای سطح شفافیت است. ما می‌خواهیم سطح شفافیت رنگ پس‌زمینه را امتحان کنیم اما احتمالاً در وهله اول کار نمی‌کند و از این رو باید به GeometryReader شفافیت بدهیم و آن را انیمیت کنیم.

همچنین یک TapGesture به این نما اضافه می‌کنیم تا زمانی که کاربر روی آن ضربه می‌زند، منو به سمت بیرون بلغزد. این همان جایی است که متد menuClose را فرا می‌خوانیم.

کد زیر را درون ZStack اضافه کنید:

1GeometryReader { _ in
2    EmptyView()
3}
4.background(Color.gray.opacity(0.3))
5.opacity(self.isOpen ? 1.0 : 0.0)
6.animation(Animation.easeIn.delay(0.25))
7.onTapGesture {
8    self.menuClose()
9}

در ادامه با طرز کار self.isOpen آشنا خواهید شد. اما اساساً یک مشخصه State در ContentView داریم که وقتی متد menuClose را فرا می‌خوانیم باز و بسته می‌شود.

افزودن نمای محتوای منو

در این مرحله قصد داریم یک HStack اضافه کنیم و در این HStack از یک نمای MenuContent که در گام 1 ساختیم استفاده می‌کنیم. همچنین از یک Spacer استفاده می‌کنیم تا منو در سمت چپ قرار گیرد. کد زیر را در ادامه GeometryReader اضافه کنید:

1HStack {
2    MenuContent()
3        .frame(width: self.width)
4        .background(Color.white)
5        .offset(x: self.isOpen ? 0 : -self.width)
6        .animation(.default)
7    Spacer()
8}

چنان که می‌بینید، ما مقادیر frame و background مربوط به MenuContent را تنظیم کردیم. کار بعدی که باید انجام دهیم تعیین offset برای x است. اگر منو بسته باشد مقدار آن را روی ‎-self.width تنظیم می‌کنیم تا از صفحه خارج شود. زمانی که self.isOpen تغییر می‌یابد، مقدار offset را روی 0 قرار می‌دهیم. پس از آن که یک انیمیشن پیش‌فرض اضافه کردیم، با افزودن این خط کد به انیمیت تغییر offset می‌پردازد.

گام 3: افزودن منو و دکمه باز کردن به ContentView

اکنون می‌توانیم SideMenu را به ContentView اضافه کنیم. ContentView یک مشخصه به نام menuOpen دارد که به منظور ردگیری باز بودن یا نبودن منو استفاده می‌شود. بر همین اساس ما Button مربوط به باز کردن منو را نمایش داده یا پنهان می‌کنیم.

همچنین باید یک متد داشته باشیم که به SideMenu ارسال کنیم، بنابراین آن را در ادامه body اضافه می‌کنیم. اینک ContentView خود را طوری به‌روزرسانی می‌کنیم که به صورت زیر درآید:

1struct ContentView: View {
2    @State var menuOpen: Bool = false
3    
4    var body: some View {
5        // Code here
6    }
7    
8    func openMenu() {
9        self.menuOpen.toggle()
10    }
11}

دلیل این که باید یک متد برای باز کردن و یا بستن منو داشته باشیم، این است که می‌توانیم مقدار menuOpen را از نمای SideMenu تغییر دهیم. از آنجا که structs از نوع مقداری هستند، غیر قابل تغییر (immutable) هستند و بنابراین نمی‌توانیم مقدار را پس از مقداردهی اولیه SideMenu تغییر دهیم. اما این وضعیت موجب می‌شود که این مزیت را داشته باشیم که تنها با یک «منبع واقعیت» (source of truth) مواجه هستیم. در ادامه محتوای body را اضافه می‌کنیم:

1ZStack {
2    if !self.menuOpen {
3        Button(action: {
4            self.openMenu()
5        }, label: {
6            Text("Open")
7        })
8    }
9    SideMenu(width: 270,
10             isOpen: self.menuOpen,
11             menuClose: self.openMenu)
12}

در بدنه، دکمه‌ای برای افزودن یا حذف نما بسته به مقدار menuOpen اضافه می‌کنیم. اکشن دکمه‌ها فراخوانی متد openMenu است و label آن را به صورت Open تنظیم می‌کنیم.

همچنین SideMenu را در ادامه اضافه می‌کنیم. این بدان معنی است که به دلیل ZStack بالاتر از دکمه رندر می‌شود. مقدار width منو را به صورت یک مقدار تصادفی تعیین می‌کنیم و حالت menuOpen را از طریق منوی آرگومان isOpen ارسال می‌کنیم در نهایت متد openMenu را ارسال می‌کنیم تا به نمای SideMenu امکان باز و بسته شدن بدهیم.

اینک باید بتوانید اپلیکیشن را بیلد کرده و اجرا کنید و در نهایت نتیجه‌ای مانند آن چه در ابتدای این مقاله دیدیم به دست بیاورید. کد نهایی اپلیکیشن به صورت زیر است:

1import SwiftUI
2struct MenuContent: View {
3    var body: some View {
4        List {
5            Text("My Profile").onTapGesture {
6                print("My Profile")
7            }
8            Text("Posts").onTapGesture {
9                print("Posts")
10            }
11            Text("Logout").onTapGesture {
12                print("Logout")
13            }
14        }
15    }
16}
17struct SideMenu: View {
18    let width: CGFloat
19    let isOpen: Bool
20    let menuClose: () -> Void
21    
22    var body: some View {
23        ZStack {
24            GeometryReader { _ in
25                EmptyView()
26            }
27            .background(Color.gray.opacity(0.3))
28            .opacity(self.isOpen ? 1.0 : 0.0)
29            .animation(Animation.easeIn.delay(0.25))
30            .onTapGesture {
31                self.menuClose()
32            }
33            
34            HStack {
35                MenuContent()
36                    .frame(width: self.width)
37                    .background(Color.white)
38                    .offset(x: self.isOpen ? 0 : -self.width)
39                    .animation(.default)
40                
41                Spacer()
42            }
43        }
44    }
45}
46struct ContentView: View {
47    @State var menuOpen: Bool = false
48    
49    var body: some View {
50        ZStack {
51            if !self.menuOpen {
52                Button(action: {
53                    self.openMenu()
54                }, label: {
55                    Text("Open")
56                })
57            }
58            
59            SideMenu(width: 270,
60                     isOpen: self.menuOpen,
61                     menuClose: self.openMenu)
62        }
63    }
64    
65    func openMenu() {
66        self.menuOpen.toggle()
67    }
68}

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
/programming-with-swift
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *