SwiftUI 如何構(gòu)建日歷App (grid網(wǎng)格化顯示)

SwiftUI 2.0最令人期待的功能之一是可以替代UICollectionView的SwiftUI。UICollectionView為我們提供了一種構(gòu)建超級自定義界面(如日歷或照片網(wǎng)格)的簡便方法。但是今天僅使用純SwiftUI來創(chuàng)建個的日歷視圖。

本文價值與收獲

看完本文后,您將能夠作出下面的界面

Jietu20200512-064329@2x.jpg
Jietu20200512-064349.gif

需求

首先我們先表述一下日歷視圖需求。日歷視圖是一個容器視圖,它使用基于日歷的網(wǎng)格顯示其子視圖。這些是我對日歷視圖的要求

  • 它應(yīng)該垂直滾動數(shù)月。
  • 它應(yīng)考慮用戶在設(shè)備上具有的日歷設(shè)置。
  • 它應(yīng)該提供一個不錯的API來構(gòu)建自定義日間單元。

CalendarView代碼

好的,現(xiàn)在我們有了組件的要求列表。我們可以開始編碼了。

struct CalendarView<DateView>: View where DateView: View {
    let interval: DateInterval
    let content: (Date) -> DateView

    init(
        interval: DateInterval,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.interval = interval
        self.content = content
    }
}

在這里,我們定義了CalendarView結(jié)構(gòu),該結(jié)構(gòu)接受需要在其中顯示日期的日期間隔和用于構(gòu)建日單元格的@ViewBuilder閉包。

struct CalendarView<DateView>: View where DateView: View {
    @Environment(\.calendar) var calendar

    let interval: DateInterval
    let content: (Date) -> DateView

    init(
        interval: DateInterval,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.interval = interval
        self.content = content
    }

    private var months: [Date] {
        calendar.generateDates(
            inside: interval,
            matching: DateComponents(day: 1, hour: 0, minute: 0, second: 0)
        )
    }

    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            VStack {
                ForEach(months, id: \.self) { month in
                    MonthView(month: month, content: self.content)
                }
            }
        }
    }
}

現(xiàn)在,我們可以顯示一個以垂直堆棧為根視圖的滾動視圖。我們使用日歷來生成用戶提供給我們的日期間隔中的所有月份。如您所見,我們使用SwiftUI放入環(huán)境中的系統(tǒng)日歷。用戶在系統(tǒng)設(shè)置中更改日歷后,SwiftUI還將更新視圖。

創(chuàng)建MonthView

如您所見,我決定創(chuàng)建單獨的MonthView結(jié)構(gòu),該結(jié)構(gòu)在我們的日歷視圖中顯示一個月。 SwiftUI允許我們組合多個視圖以構(gòu)建出色的視圖層次結(jié)構(gòu)。我想指出,我在應(yīng)用程序的其他部分重用了MonthView來呈現(xiàn)日歷預(yù)覽。

struct MonthView<DateView>: View where DateView: View {
    @Environment(\.calendar) var calendar

    let month: Date
    let content: (Date) -> DateView

    init(
        month: Date,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.month = month
        self.content = content
    }

    private var weeks: [Date] {
        guard
            let monthInterval = calendar.dateInterval(of: .month, for: month)
            else { return [] }
        return calendar.generateDates(
            inside: monthInterval,
            matching: DateComponents(hour: 0, minute: 0, second: 0, weekday: 1)
        )
    }

    var body: some View {
        VStack {
            ForEach(weeks, id: \.self) { week in
                WeekView(week: week, content: self.content)
            }
        }
    }
}

如您在上面的代碼示例中所看到的,MonthView結(jié)構(gòu)是一個純視圖,使用系統(tǒng)提供的日歷生成周,并使用具有周視圖集合的垂直堆棧呈現(xiàn)周。

創(chuàng)建周視圖

struct WeekView<DateView>: View where DateView: View {
    @Environment(\.calendar) var calendar

    let week: Date
    let content: (Date) -> DateView

    init(
        week: Date,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.week = week
        self.content = content
    }

    private var days: [Date] {
        guard
            let weekInterval = calendar.dateInterval(of: .weekOfYear, for: week)
            else { return [] }
        return calendar.generateDates(
            inside: weekInterval,
            matching: DateComponents(hour: 0, minute: 0, second: 0)
        )
    }

    var body: some View {
        HStack {
            ForEach(days, id: \.self) { date in
                HStack {
                    if self.calendar.isDate(self.week, equalTo: date, toGranularity: .month) {
                        self.content(date)
                    } else {
                        self.content(date).hidden()
                    }
                }
            }
        }
    }
}

周視圖是我的日歷視圖的最新部分。它還使用系統(tǒng)提供的日歷在給定的一周內(nèi)生成日期,并通過應(yīng)用傳遞的@ViewBuilder閉包來每天構(gòu)建視圖,從而使用水平堆棧進行渲染.

主視圖

struct RootView: View {
    @Environment(\.calendar) var calendar

    private var year: DateInterval {
        calendar.dateInterval(of: .year, for: Date())!
    }

    var body: some View {
        CalendarView(interval: year) { date in
            Text("30")
                .hidden()
                .padding(8)
                .background(Color.blue)
                .clipShape(Circle())
                .padding(.vertical, 4)
                .overlay(
                    Text(String(self.calendar.component(.day, from: date)))
                )
        }
    }
}

在上面的示例中,您將看到我們?nèi)绾问褂萌諝v視圖。我希望您注意構(gòu)建日視圖的方式。我稱之為模板視圖。我創(chuàng)建具有最大寬度的模板值的隱藏文本。然后,我將實際內(nèi)容顯示為模板視圖的疊加層。這種方法使我可以看到相同大小的日視圖。我們應(yīng)避免frame修飾符,因為通過frame限制空間將會破壞動態(tài)類型支持。

總結(jié)

SwiftUI具有如此友好的布局系統(tǒng),我們可以用來構(gòu)建出色的視圖。建議大家能用原生就優(yōu)先原生。

項目源碼

還有 11% 的精彩內(nèi)容
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
支付 ¥1.59 繼續(xù)閱讀

友情鏈接更多精彩內(nèi)容