Property Wrappers - tems
- @FocusState属性包装器
- @GestureState属性包装器
- @FetchRequest属性包装器
- @AppStorage属性包装器
- @SceneStorage属性包装器
- @ScaledMetric属性包装器
- @UIApplicationDelegateAdaptor属性包装器
概述
文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,Property Wrappers items介绍具体属性包装器,偏向理论,可以移步Github下载code -> github案例链接
1、@FocusState属性包装器
SwiftUI提供了一个特定的属性包装器来跟踪当前接受用户输入的视图,称为@FocusState
。这可以绑定到一个Bool值以控制单个字段,或绑定到一个枚举以在多个字段之间进行控制。
1.1、单焦点
Swift
//控制单个输入字段是否具有键盘焦点
struct FFPropertyWrapperFocusState: View {
@FocusState private var isUsernameFocused: Bool
@State private var username = "meta BBLv"
var body: some View {
VStack {
TextField("输入你的用户名", text: $username)
.focused($isUsernameFocused)
Button("切换焦点") {
isUsernameFocused.toggle()
}
}
}
}
1.2、多焦点
如果想在多个视图之间移动键盘焦点,应该使用可选的枚举。可以将其设置为枚举中的一个案例来激活特定的输入字段,或者将其设置为nil以使没有任何字段具有焦点。在iOS上实际上是取消键盘的显示。
因此可以创建两个文本字段来存储用户名和密码,然后使用@FocusState和onSubmit()在他们之间进行移动。
Swift
struct FFPropertyWrapperFocusStateEnum: View {
enum FocusedFieldEnum {
case username, passward
}
@FocusState private var focusedField: FocusedFieldEnum?
@State private var username = "meta BBLv"
@State private var password = "123456"
var body: some View {
VStack {
TextField("请输入用户名", text: $username)
.focused($focusedField, equals: .username)
SecureField("请输入密码", text: $password)
.focused($focusedField, equals: .passward)
}
.onSubmit {
if focusedField == .username {
focusedField = .passward
} else {
focusedField = nil
}
}
}
}
2、@GestureState属性包装器
SwiftUI为我们提供了一个专门用于跟踪手势状态的属性包装器,称为@GestureState
。尽管可以使用简单的@State属性包装器实现相同的效果。但@GestureState具有额外的功能,他在手势结束时将自动将属性设置回其初始值,而且通常比使用@State快得多。
Swift
struct FFPropertyWrapperGestureState: View {
//创建一个手势,可以拖动视图。为此,先创建一个@GestureState属性,以存储视图一移动多少
@GestureState var dragAmount = CGSize.zero
//这具有CGSize.zero的默认值,代表当手势结束时,将自动设置会.zero
var body: some View {
Image(.fullEnglish)
.offset(dragAmount)
.gesture(
DragGesture().updating($dragAmount, body: { value, state, transcation in
state = value.translation
})
)
}
}
代码分解:
- DragGesture().updating()创建了一个新的拖动手势,要求它修改存储在dragAmount中的值,这是我们的CGSize。
- 采用了一个带有三个参数的闭包:value、state和transaction
- value参数时拖动的当前数据,在哪里开始,移动了多远,预测在哪里结束等等。
- state参数是一个inout值,是我们的属性。因此,在此闭包内,我们应该修改state,而不是直接读取或写入dragAmount
- transaction参数是一个inout值,存储整个动画上下文,为此我们提供一些关于正在发生的情况的信息,比如这是否是一个连续或瞬间动画。连续动画可能是通过拖动滑块产生的,而瞬时动画可能是通过点击产生的。 为了使视图可以拖动,我所做的就是将当前的拖动翻译直接分配给state(在这种情况下,实际上是dragAmount),然后在offset()修改器中使用它来移动视图。
@GestureState的优点之一是它会在手势结束时自动将属性的值设置回初始值。在这种情况下,可以随意的拖动视图,一旦松开就会回归原位。
调试结果
3、@FetchRequest属性包装器
SwiftUI为我们提供了一个专门用于处理CoreData获取请求的属性包装器,可以将数据直接嵌入到SwiftUI视图中,而无需编写额外的逻辑。
使用@FetchRequest
至少提供一个值,即用于排列数据的排序描述符数组,还可以根据需求选择性提供参数来过滤数据。
在使用@FetchRequest之前,必须将CoreData托管对象上下文注入到环境中,
Swift
struct FFPropertyWrapperFetchRequest: View {
@Environment(\.managedObjectContext) var managedObjectContext
// SortDescriptors参数是一个数组,所以可以提供尽可能多的排序选项
@FetchRequest(
sortDescriptors: []
) var languages: FetchedResults<ProgrammingLanguage>
var body: some View {
Text("Hello, World!")
}
}
这里就不做演示了,参考前面- SwiftUI基础篇CoreData
4、@AppStorage属性包装器
SwiftUI为从UserDefaults
读取值提供了一个专门的属性包装器,当值发生更改时,它自动重新调整视图的body属性。这个属性包装器实际上会监听UserDefaults中的一个键,并在该键发生更改时刷新UI。
Swift
//监听UserDefaults的"username"key,在按下时set
struct FFPropertyWrapperAppStorage: View {
@AppStorage("username") var username: String = "meta BBLv"
//默认情况下,@AppStorage会监听UserDefaults.standard,也可以监听特定的应用程序组
@AppStorage("username", store: UserDefaults(suiteName: "group.com.metaBBLv.unwrap")) var hobby: String = "metaBBLv"
//@AppStore将数据写入UserDefaults,这不是安全的存储,因此,不可以使用@AppStore存储
//个人数据等敏感信息,非常容易提取。
var body: some View {
VStack {
Text("欢迎:\(username)同学")
Button("登陆") {
username = "@metaBBLv"
}
Button("旧的存储方式") {
//上述代码更改用户名将立即写入UserDefaults,并同时更新视图,如果使用旧的方式
UserDefaults.standard.setValue("@metaBBLv", forKey: "username")
}
}
}
}
5、@SceneStorage属性包装器
如果想为每个屏幕保存独特的数据,应该使用SwiftUI的@SceneStorage
属性包装器。这与@AppStorage有些相似,需要为它提供一个名称来保存数据以及一个默认值,但与使用UserDefaults不同,它用于状态恢复,而且它甚至在iPadOS中经常看到的复杂多场景设置中非常好用。
Swift
//如果有一个文本编辑器,并希望存储用户正在输入的内容
struct FFPropertyWrapperSceneStorage: View {
@SceneStorage("text") var text = ""
var body: some View {
NavigationStack {
TextEditor(text: $text)
}
}
}
因为使用了@SceneStorage
,SwiftUI将自动确保每个场景实例都有其自己的文本,如果同时运行多个应用程序,都可以正确保存和恢复其数据。
在使用@SceneStorage之前,有一些来自Apple的重要警告:
- 不要保存大量数据:只保存状态恢复的所需内容
- 永远不要将敏感数据存储在场景存储中,因为是不安全的。
- 如果用户转到应用程序切换器并销毁应用程序,场景存储也将被销毁(iOS17上发现未销毁)。
调试结果
6、@ScaledMetric属性包装器
SwiftUI提供了@ScaleMetric
属性包装器,用于定义根据用户的动态类型设置自动缩放的数字。
Swift
//在其基本用法中,为属性提供一个默认值,@ScaledMetric将处理其余部分。例如,根据用户的设置,
//以下代码将以不同的size绘制相同的图像
struct FFPropertyWrapperScaledMetric: View {
@ScaledMetric var imageSize = 100.0
//如果需要使缩放与特定的其他文本匹配,可以为属性包装器使用relativeTo参数,
//该参数可以指定要匹配的字体大小。例如,与大标题大小一起缩放
@ScaledMetric(relativeTo: .largeTitle) var titleSize = 100.0
var body: some View {
Image(systemName: "cloud.sun.bolt.fill")
.resizable()
.frame(width: imageSize, height: imageSize)
}
}
7、@UIApplicationDelegateAdaptor属性包装器
如果需要在SwiftuI中访问AppDelegate
的功能,要创建一个class,并且要继承NSObject和UIApplicationDelegate,并添加你想要的功能。
7.1、AppDelegate
如果想要实现旧的 didFinishLaunchingWithOptions 方法,可以使用以下代码
Swift
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("applicationDidFinishLaunching")
return true
}
}
7.2、FFModifierApp
一旦创建了这个类,您可以在主 App 中使用 ApplicationDelegateAdaptor
属性包装器,以便 SwiftUI 知道创建和管理您的应用委托类:
Swift
@main
struct FFModifierApp: App {
//在你的应用场景中,使用UIApplicationDelegateAdaptor属性包装器来告诉SwiftUI它应该使用你的AppDelegate类作为delegate
@UIApplicationDelegateAdaptor(Appdelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
FFPropertyWrapperSceneStorage()
}
}
FFPropertyWrapperSceneStorage这是我的某个视图,不受它影响,可以是任何视图。
terminal日志
terminal
applicationDidFinishLaunching