Date 数据类型

Date 是一种特有的日期数据类型。包含年月日,时分秒数据。

// 声明方法:  now 代表当前日期时间
var wakeUp = Date.now

// 声明方法: distantPast 代表至少 2000 年以前
var wakeUp = Date.distantPast

Date 支持加减运算

Date 类型支持加减运算,其单位是秒。

var actualSleep = 48880.0
let sleepTime = wakeUp - actualSleep

// 2023-11-27 12:57:16 +0000
print(sleepTime)

Date 格式化器

Date 格式用文本展示时,经常需要用到格式化器。

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "MMM dd"
    formatter.locale = Locale(identifier: "en_US")
    formatter.calendar = Calendar(identifier: .gregorian)
    formatter.timeZone = TimeZone.current
    return formatter
}()

如果需要支持多语言,最好使用 setLocalizedDateFormatFromTemplate 方法

// 声明格式化器
let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.setLocalizedDateFormatFromTemplate("MMMd")
    return formatter
}()

// 不同模板的效果(以美国英语为例)
// formatter.setLocalizedDateFormatFromTemplate("MMMd")  // Oct 31
// formatter.setLocalizedDateFormatFromTemplate("MMMMd")  // October 31
// formatter.setLocalizedDateFormatFromTemplate("yMMMd")  // Oct 31, 2024
// formatter.setLocalizedDateFormatFromTemplate("yMMMMd") // October 31, 2024
// formatter.setLocalizedDateFormatFromTemplate("Md")     // 10/31
// formatter.setLocalizedDateFormatFromTemplate("yMd")    // 10/31/24

// 同一个模板在不同地区的显示效果
// "MMMd" 模板在不同 locale 下的显示:
// formatter.locale = Locale(identifier: "en_US")  // Oct 31
// formatter.locale = Locale(identifier: "zh_CN")  // 10月31日
// formatter.locale = Locale(identifier: "fr_FR")  // 31 oct.
// formatter.locale = Locale(identifier: "de_DE")  // 31. Okt
// formatter.locale = Locale(identifier: "ja_JP")  // 10月31日

// 使用时
Text(dateFormatter.string(from: date))
Text(date, formatter: dateFormatter)

DateComponents 对象

Swift 提供了 Date 处理日期的功能,它封装了年、月、日、时、分、秒、时区等。然而,我们不想考虑其中的大部分,我们想说“给我一个早上 8 点的起床时间,不管今天是哪一天。

为此,Swift 提供了一个类型称为 DateComponents ,它允许我们读取或写入日期的特定部分,而不是整个日期。DateComponents 会将日期所需的所有部分存储为单独的值,这意味着我们可以读取小时、分钟部分,而忽略其余部分。然后我们需要做的就是将分钟乘以 60(得到秒而不是分钟),将小时乘以 60 和 60(得到秒而不是小时)。

通过 DateComponents 生成 Date

如果想要设置一个今天上午 8 点的日期,可以通过 DateComponents 进行具体设置。

//1. 创建一个 DateComponents 对象
var components = DateComponents()

//2. 按需要设置 DateComponents 对象的具体属性
components.hour = 8
components.minute = 0

//3. 使用 Calendar 的方法,传入 DateComponents 参数,生成 Date
let date = Calendar.current.date(from: components)

//由于日期验证的困难,date(from:) 方法实际上返回一个Optional的值
//因此最好使用 nil 合并来表示“如果失败,只需将当前日期还给我”,如下所示:
let date = Calendar.current.date(from: components) ?? .now

读取 Date 的具体属性

我们还可以要求 iOS 提供特定日期的 DateComponents ,然后读出其中具体的属性。我们会返回一个实例,其中包含其所有属性的可选值。虽然我们知道小时和分钟会在那里(因为这些是我们要求的),但仍然需要解开可选值或提供默认值。

//使用 Calendar 的方法,传入 Date 类型给 from 参数,生成 dateComponents
let components = Calendar.current.dateComponents([.hour, .minute], from: someDate)

//用点语法读取 dateComponents 的属性
let hour = components.hour ?? 0
let minute = components.minute ?? 0

将日期格式化显示为文本

依靠非常有效的 format 参数,可以读取我们想要显示日期的任何部分。

//例如,如果我们只想要一个Date的时间,可以这样写:
Text(Date.now, format: .dateTime.hour().minute())

//或者,如果我们想要日、月和年,我们会这样写:
Text(Date.now, format: .dateTime.day().month().year())

//您可能想知道程序如何适应处理不同的日期格式——例如,在英国使用日/月/年,但在其他一些国家/地区,使用月/日/年。神奇的是,我们不需要担心这一点:当我们编写 day().month().year() 时,我们要求的是该数据,而不是排列它,iOS 将使用用户的偏好自动格式化该数据。
//作为替代方案,我们可以直接在日期上使用该 formatted() 方法,传入配置选项,用于格式化日期和时间,如下所示:
Text(Date.now.formatted(date: .long, time: .shortened))

DatePicker 日历选择器

DatePicker 日历选择器 是一个收缩型视图。

参数:label 标签

//1. 直接使用字符串作为 label
DatePicker(
	"Label", 
	selection: $nextFullMoonDate
)

//2. 使用 label 视图
DatePicker(
	selection: $nextFullMoonDate, 
	label: { 
		Text("Label") 
	}
)

**隐藏 label :**如果直接将 DatePicker 的 label 位置的字符串,设置为空。虽然能起到隐藏 label 的作用,但是有两个问题

因此更好的替代方法是使用 labelsHidden() 修饰符,如下所示:

DatePicker("Please enter a date", selection: $wakeUp)
    .labelsHidden()

参数:selection 双向绑定 Date 对象

使用 DatePicker 时,需要绑定 Date 类型的状态参数。

//需要先设定一个用于绑定的Date类型参数
@State private var nextFullMoonDate = Date()
@State private var nextFullMoonDate = Date.now

DatePicker(
	selection: $nextFullMoonDate, 
	label: { Text("Label") }
)

参数:displayedComponents 设置显示类型

通过 displayedComponents 参数,可以设置日期选择器的显示形式。

// 1.默认:如果不提供此参数,用户将看到天、小时和分钟
DatePicker("", 
		selection: $arrivalDate,
		displayedComponents: [.date, .hourAndMinute]
)

// 2.如果使用 .date ,用户会看到月、日和年
DatePicker("", 
	selection: $theDateAndTime, 
	displayedComponents: .date
)

// 3.如果使用 .hourAndMinute ,用户只能看到小时和分钟组件
DatePicker("", 
	selection: $justTime, 
	displayedComponents: .hourAndMinute
)

参数:in 设置日期范围

in 参数的工作方式与 Stepper 类似。我们可以为其提供一个日期范围,日期选择器将确保用户无法选择超出该范围的日期范围。

//支持从某一天开始选到永远
@State private var arrivalDate = Date()

let fromToday = Calendar.current.date(byAdding: .minute, value: -1, to: Date())!

DatePicker("", 
	selection: $arrivalDate, 
	in: fromToday...,
	displayedComponents: .date
)

//支持选到某一天截止
@State private var arrivalDate = Date()

DatePicker("", 
		selection: $arrivalDate, 
		in: ...Date(), 
		displayedComponents: [.date, .hourAndMinute]
)
//还可以制定一个具体范围,传入给 in 使用

//例如:创建今天到明天一天
func exampleDates() {
		// 根据距离第一个时间点的具体时间,创建第二个时间点(单位为秒)
    let tomorrow = Date.now.addingTimeInterval(86400)
		// 创建一个时间范围 range
    let range = Date.now...tomorrow
}

//例如:创建今天到未来30天的范围
//通过计算属性设定一个范围(高级)
var withinNext30Days: ClosedRange<Date> {
	let today = Calendar.current.date(byAdding: .minute, value: -1, to: Date())!
	let next30Days = Calendar.current.date(byAdding: .day, value: 30, to: Date())!
	return today...next30Days
}

DatePicker("", 
		selection: $nextFullMoonDate,
    in: withinNext30Days
)

修饰符:datePickerStyle

从 iOS 14 开始,您可以使用新的 GraphicalDatePickerStyle() 修饰符来获得高级日期选择器,该选择器显示日历以及用于输入精确时间的空格:

struct ContentView: View {
    @State private var date = Date.now

    var body: some View {
        VStack {
            Text("Enter your birthday")
                .font(.largeTitle)
            DatePicker("Enter your birthday", selection: $date)
                .datePickerStyle(GraphicalDatePickerStyle())
                .frame(maxHeight: 400)
        }
    }
}

MultiDatePicker 选择多个日期

MultiDatePicker 显示一个日历视图,用户可以在其中同时选择多个日期,无论是从任何可能的日期还是从您选择的日期范围。

// 在最简单的形式中,您只需要某种状态来跟踪他们选择的日期,然后将其绑定到您的选择器:
struct ContentView: View {
    @State var dates: Set<DateComponents> = []
    var body: some View {
        MultiDatePicker("Select your preferred dates", selection: $dates)
    }
}

但是,您很可能想要将这些日期组件转换为实际日期,在这种情况下,您需要从环境中读取用户的日历并根据需要转换数据:

struct ContentView: View {
    @Environment(\\.calendar) var calendar
    @State var dates: Set<DateComponents> = []
    
    var body: some View {
        VStack {
            MultiDatePicker("Select your preferred dates", selection: $dates)
            Text(summary)
        }
        .padding()
    }

		// 把选择的日期集合,转换成字符串
    var summary: String {
        dates.compactMap { components in
            calendar.date(from: components)?.formatted(date: .long, time: .omitted)
        }.formatted()
    }
    
}

默认情况下,用户可以选择他们喜欢的任何日期,但您也可以将他们的选择限制在您选择的范围内。例如,此代码允许他们选择从今天开始的任何日期,但不能选择更早的日期:

MultiDatePicker("Select your preferred dates", selection: $dates, in: Date.now...)