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

