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

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

本文價(jià)值與收獲

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

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

需求

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

  • 它應(yīng)該垂直滾動數(shù)月是尔。
  • 它應(yīng)考慮用戶在設(shè)備上具有的日歷設(shè)置。
  • 它應(yīng)該提供一個(gè)不錯的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)在脚乡,我們可以顯示一個(gè)以垂直堆棧為根視圖的滾動視圖。我們使用日歷來生成用戶提供給我們的日期間隔中的所有月份傻粘。如您所見每窖,我們使用SwiftUI放入環(huán)境中的系統(tǒng)日歷。用戶在系統(tǒng)設(shè)置中更改日歷后弦悉,SwiftUI還將更新視圖窒典。

創(chuàng)建MonthView

如您所見,我決定創(chuàng)建單獨(dú)的MonthView結(jié)構(gòu)稽莉,該結(jié)構(gòu)在我們的日歷視圖中顯示一個(gè)月瀑志。 SwiftUI允許我們組合多個(gè)視圖以構(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)是一個(gè)純視圖烈评,使用系統(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)建視圖常侦,從而使用水平堆棧進(jìn)行渲染.

主視圖

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)建具有最大寬度的模板值的隱藏文本际乘。然后坡倔,我將實(shí)際內(nèi)容顯示為模板視圖的疊加層。這種方法使我可以看到相同大小的日視圖。我們應(yīng)避免frame修飾符罪塔,因?yàn)橥ㄟ^frame限制空間將會破壞動態(tài)類型支持投蝉。

總結(jié)

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

項(xiàng)目源碼

還有 11% 的精彩內(nèi)容
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
支付 ¥1.59 繼續(xù)閱讀
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市请契,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌夏醉,老刑警劉巖爽锥,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異畔柔,居然都是意外死亡氯夷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門靶擦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腮考,“玉大人,你說我怎么就攤上這事玄捕〔任担” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵枚粘,是天一觀的道長馅闽。 經(jīng)常有香客問我,道長馍迄,這世上最難降的妖魔是什么福也? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮攀圈,結(jié)果婚禮上暴凑,老公的妹妹穿的比我還像新娘。我一直安慰自己赘来,他們只是感情好现喳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撕捍,像睡著了一般拿穴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忧风,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天默色,我揣著相機(jī)與錄音,去河邊找鬼。 笑死腿宰,一個(gè)胖子當(dāng)著我的面吹牛呕诉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吃度,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼甩挫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了椿每?” 一聲冷哼從身側(cè)響起伊者,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎间护,沒想到半個(gè)月后亦渗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汁尺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年法精,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痴突。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搂蜓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辽装,到底是詐尸還是另有隱情帮碰,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布如迟,位于F島的核電站收毫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏殷勘。R本人自食惡果不足惜此再,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玲销。 院中可真熱鬧输拇,春花似錦、人聲如沸贤斜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘩绒。三九已至猴抹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锁荔,已是汗流浹背蟀给。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人跋理。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓择克,卻偏偏與公主長得像,于是被迫代替她去往敵國和親前普。 傳聞我的和親對象是個(gè)殘疾皇子肚邢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345