You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apple框架中带自身静态属性的结构体用法及复刻疑问

关于PresentationBackgroundInteraction结构体实现的疑问与复刻问题

Apple 使用如下结构体实现 PresentationBackgroundInteraction:

public struct PresentationBackgroundInteraction : Sendable {
    public static var automatic: PresentationBackgroundInteraction { get }
    public static func enabled(upThrough detent: PresentationDetent) -> PresentationBackgroundInteraction
    ...
}

在视图修饰符中的使用方式:

.presentationBackgroundInteraction(.enabled(upThrough(.medium)))

技术疑问

  1. 这种实现的原理是什么?每次调用都会实例化结构体,但如何获取在其他位置赋值的实例?我们需要在某处持有该实例的引用。
  2. Apple 是如何确定该引用的存储位置并加以使用的?
  3. 为何选择结构体而非枚举来实现?

复刻时遇到的问题

直接让静态函数返回自身会导致无限递归:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 04:32:18