Apple框架中带自身静态属性的结构体用法及复刻疑问
Apple 使用如下结构体实现 PresentationBackgroundInteraction:
public struct PresentationBackgroundInteraction : Sendable { public static var automatic: PresentationBackgroundInteraction { get } public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteraction ... }
在视图修饰符中的使用方式:
.presentationBackgroundInteraction(.enabled(upThrough(.medium)))
技术疑问
- 这种实现的原理是什么?每次调用都会实例化结构体,但如何获取在其他位置赋值的实例?我们需要在某处持有该实例的引用。
- Apple 是如何确定该引用的存储位置并加以使用的?
- 为何选择结构体而非枚举来实现?
复刻时遇到的问题
直接让静态函数返回自身会导致无限递归:
public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteraction { .enabled(upThrough: detent) }
用内部变量赋值缓解后,作为视图修饰符使用时,始终返回默认结构体实例而非传入的指定值,无法根据传入值调整逻辑:
结构体代码:
public struct PresentationBackgroundInteractionRecreate : Sendable { public static var automatic: PresentationBackgroundInteractionRecreate = .automatic }
视图代码:
EmptyView() .presentationBackgroundInteractionRecreate(.automatic)
视图修饰符代码:
extension View { public func presentationBackgroundInteractionRecreate( _ interaction: PresentationBackgroundInteractionRecreate ) -> some View { // 此处始终返回 PresentationBackgroundInteractionRecreate() // 如何在此处获取 `automatic` 的值? print(interaction) return EmptyView() } }
1. 实现原理与实例引用问题
PresentationBackgroundInteraction的静态成员(automatic、enabled(upThrough:)返回的实例)本质是携带具体配置信息的结构体实例,并非无意义的空实例。
内部会通过私有存储来区分不同配置,示例实现逻辑如下:
public struct PresentationBackgroundInteraction : Sendable { private enum Mode { case automatic case enabled(upThrough: PresentationDetent) } private let mode: Mode public static var automatic: PresentationBackgroundInteraction { PresentationBackgroundInteraction(mode: .automatic) } public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteraction { PresentationBackgroundInteraction(mode: .enabled(upThrough: detent)) } }
调用静态方法/属性时,会生成对应配置的实例。视图修饰符接收实例后,SwiftUI框架会自动将配置存储到视图环境或关联的presentation配置项中,后续处理背景交互时,直接从这些存储位置读取实例的配置逻辑即可,无需开发者手动持有引用。
2. Apple如何确定引用的存储位置
SwiftUI的视图修饰符会将配置信息存储到**视图环境(Environment)或关联偏好设置(Preference)**中,部分弹窗相关配置会直接绑定到presentation的专属配置集合里。
当你调用.presentationBackgroundInteraction(_:)时,框架会把传入的实例注入到对应存储位置;当系统需要处理背景交互逻辑时,会从这些位置取出实例,解析内部配置(比如是自动模式还是启用至某个detent),再执行对应行为。
3. 选择结构体而非枚举的原因
- 扩展性更强:后续新增配置模式时,只需添加静态方法和内部存储逻辑,无需修改原有API结构,避免破坏兼容性。比如新增
.disabled()模式,仅需补充对应静态方法即可。 - 细节封装性好:结构体可以隐藏内部实现(比如私有枚举、计算属性),对外只暴露简洁的API;而枚举的case是公开的,无法隐藏配置细节。
- 符合SwiftUI设计风格:SwiftUI中
Font、Color等配置类都采用结构体+静态成员的模式,保持API一致性,降低开发者学习成本。
复刻问题的解决方法
你的复刻代码存在两个核心问题:一是静态属性automatic的赋值形成递归初始化,二是结构体未存储任何配置信息,导致无法区分不同实例。正确的复刻实现如下:
import SwiftUI public struct PresentationBackgroundInteractionRecreate : Sendable, Equatable { // 私有枚举存储配置模式 private enum Mode: Sendable, Equatable { case automatic case enabled(upThrough: PresentationDetent) } private let mode: Mode // 静态成员返回携带对应配置的实例 public static var automatic: PresentationBackgroundInteractionRecreate { PresentationBackgroundInteractionRecreate(mode: .automatic) } public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteractionRecreate { PresentationBackgroundInteractionRecreate(mode: .enabled(upThrough: detent)) } // 对外暴露配置判断方法,方便逻辑处理 public var isAutomatic: Bool { switch mode { case .automatic: return true default: return false } } public func getEnabledDetent() -> PresentationDetent? { switch mode { case .enabled(let detent): return detent default: return nil } } } // 视图修饰符实现 extension View { public func presentationBackgroundInteractionRecreate( _ interaction: PresentationBackgroundInteractionRecreate ) -> some View { // 根据配置执行对应逻辑 if interaction.isAutomatic { print("使用自动模式") } else if let detent = interaction.getEnabledDetent() { print("启用背景交互,直到detent:\(detent)") } // 将配置注入环境,供后续逻辑读取 return self.environment(\.customBackgroundInteraction, interaction) } } // 自定义环境键,用于存储配置 private struct CustomBackgroundInteractionKey: EnvironmentKey { static let defaultValue: PresentationBackgroundInteractionRecreate = .automatic } extension EnvironmentValues { var customBackgroundInteraction: PresentationBackgroundInteractionRecreate { get { self[CustomBackgroundInteractionKey.self] } set { self[CustomBackgroundInteractionKey.self] = newValue } } }
实现后,调用.presentationBackgroundInteractionRecreate(.automatic)或.presentationBackgroundInteractionRecreate(.enabled(upThrough: .medium))时,视图修饰符就能正确识别不同配置,执行对应逻辑。
内容的提问来源于stack exchange,提问作者Wouter

