Problem
It appears that fonts, added to a swift package, somehow breaks my Previews/Canvas. It should be noted that I'm working with an acircular dependency graph, meaning almost everything is separated into neat packages, eg Resources, Design, Models, etc.. This may be relevant given the view that I'm getting this error for, exists in my Design package, and not at the root of my project.
Also, this builds and runs on a real device or simulator without issue and works perfectly, meaning it has 100% something to do with the way Previews loads the Fonts. I would have expected it to failover to the system font, but that is not the case.
One solution I tried was to do this however it made no effect.
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
return .system(size: size)
}
Error
== 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)
==================================
Reproducing
- Add a Font and a Font+Extension to a Swift Package
- In the Font extension, reference a statically programmed font. Eg:
public static var h1 = customFont(weight: "Bold", size: 22) - Import the new package into a project.
- Create a view, use that font, Eg:
Text("Hello, World").font(.h1) - Observe Previews breaking.
My Code
I've reduced the size of this as much as possible while retaining the entire context. There is also a UIFont version however I didn't include it here as that doesn't appear to be the cause of my issue.
public struct FontConfig: Codable {
public enum FontSet: String {
case inter = "Inter"
// Other Cases
}
public static var currentFontSet: FontSet = .nunito {
didSet {
Font.h1 = Font.customFont(weight: "Bold", size: 22)
//Other Cases
}
}
}
// swiftlint:disable identifier_name
extension Font {
public static var h1 = customFont(weight: "Bold", size: 22)
// Other Fonts
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)
}
}
I also have this FontConfig+Extension that registers, however this would never be hit in the context of 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")
}
Unfortunately, in this case you might have to register your fonts with every preview. Expose a method which does that and add it to the body of the macro.