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

Swift Package中添加字体导致Previews崩溃的原因是什么?

Swift Package字体导致Previews崩溃问题解决方案

问题描述

将字体添加到Swift Package后,Previews/Canvas出现崩溃。项目采用无循环依赖的模块化结构,所有模块拆分到独立Package(如Resources、Design、Models等),出错视图位于Design包而非项目根目录,这可能是诱因之一。

真机和模拟器上构建运行完全正常,说明问题100%出在Previews加载字体的逻辑上。原本预期会自动 fallback 到系统字体,但实际并未生效。

尝试过以下方案,但无效果:

if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
    return .system(size: size)
}

错误信息

== PREVIEW UPDATE ERROR:

    LinkDylibError: Failed to build TextButtonStyle.swift
    
    Linking failed: linker command failed with exit code 1 (use -v to see invocation)
    
    ld: warning: search path '/Applications/Xcode.app/Contents/SharedFrameworks-iphonesimulator' not found
    ld: Undefined symbols:
      (extension in AppResources):SwiftUI.Font.h4.unsafeMutableAddressor : SwiftUI.Font, referenced from:
          (extension in Design_PreviewReplacement_TextButtonStyle_1):Design.TextButtonStyle.(__preview__makeBody in _8A299D844C46E27E8BBB012BB76349C4)(configuration: SwiftUI.ButtonStyleConfiguration) -> some in TextButtonStyle.1.preview-thunk.o
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    
    
    ==================================

复现步骤

  • 在Swift Package中添加字体及Font扩展
  • 在Font扩展里定义静态字体,例如:public static var h1 = customFont(weight: "Bold", size: 22)
  • 将该Package导入项目
  • 创建视图并使用该字体,例如:Text("Hello, World").font(.h1)
  • 观察到Previews崩溃

相关代码

已精简代码保留核心逻辑,另有UIFont版本实现,但并非问题根源,故未粘贴。

public struct FontConfig: Codable {
    public enum FontSet: String {
        case inter = "Inter"
        // 其他枚举值
    }

    public static var currentFontSet: FontSet = .nunito {
        didSet {
            Font.h1 = Font.customFont(weight: "Bold", size: 22)
            // 其他字体设置
        }
    }
}

// swiftlint:disable identifier_name
extension Font {
    public static var h1 = customFont(weight: "Bold", size: 22)
    // 其他静态字体属性

    public static func customFont(weight: String, size: CGFloat) -> Font {
        if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
            return .system(size: size)
        }
        
        print(">> Font Name: \(FontConfig.currentFontSet.rawValue)-\(weight)")
        return Font.custom("\(FontConfig.currentFontSet.rawValue)-\(weight)", size: size)
    }
}

还有一个用于注册字体的FontConfig+Extension,但Previews场景下不会执行到这段代码:

private static func registerFont(fontName: String) {
    let bundle = Bundle.module
    let pathForResourceString = bundle.path(forResource: fontName, ofType: "ttf")
    
    guard let path = pathForResourceString else {
        print("\(fontName) font not found")
        return
    }
    
    guard let fontData = NSData(contentsOfFile: path),
          let dataProvider = CGDataProvider(data: fontData),
          let fontRef = CGFont(dataProvider) else {
        print("\(fontName) can't be loaded")
        return
    }
    
    var errorRef: Unmanaged<CFError>? = nil
    if CTFontManagerRegisterGraphicsFont(fontRef, &errorRef) == false {
        print("Error registering font")
    }
    
    print("\(fontName) font loaded")
}

解决方案

1. 修正静态字体属性的初始化逻辑

当前静态属性h1在类加载阶段就调用customFont初始化,而Previews环境变量此时可能未被正确注入,导致判断失效。将静态属性改为计算属性,延迟到使用时再执行判断逻辑:

extension Font {
    public static var h1: Font { customFont(weight: "Bold", size: 22) }
    // 其他字体属性也改为计算属性
}

这样每次访问h1时都会重新检查Previews环境变量,而非在类初始化阶段固定值。

2. 确保Package资源正确配置

在Swift Package的Package.swift中,需确保字体资源被正确声明并允许其他模块访问:

let package = Package(
    name: "AppResources",
    products: [
        .library(
            name: "AppResources",
            targets: ["AppResources"]),
    ],
    targets: [
        .target(
            name: "AppResources",
            resources: [
                .process("Fonts/Inter-Bold.ttf"),
                // 其他字体文件,路径需与实际位置匹配
            ]),
    ]
)

3. 强制Previews环境使用系统字体的兜底方案

若计算属性修改仍未解决问题,可直接在Font扩展中为Previews环境单独做分支,避免引用未注册的字体名称:

extension Font {
    public static var h1: Font {
        #if DEBUG
        if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
            return .system(size: 22, weight: .bold)
        }
        #endif
        return Font.custom("\(FontConfig.currentFontSet.rawValue)-Bold", size: 22)
    }
}

4. 手动在Previews中注册字体(可选)

若需要在Previews中显示自定义字体,可在PreviewProvider中手动调用字体注册方法:

struct TextButtonStyle_Previews: PreviewProvider {
    static var previews: some View {
        TextButtonStyleView()
            .onAppear {
                FontConfig.registerFont(fontName: "Inter-Bold")
                // 注册其他需要用到的字体
            }
    }
}

问题根源

之前的判断逻辑放在customFont函数中,但静态属性h1在模块加载时就已初始化,此时Previews的环境变量尚未被Xcode设置到位,导致判断条件不成立,仍尝试加载未注册的自定义字体,最终触发链接错误。

内容的提问来源于stack exchange,提问作者xTwisteDx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 15:34:50