【问题标题】:Xcode 12b1 & Swift Packages: Custom FontsXcode 12b1 和 Swift 包:自定义字体
【发布时间】:2020-07-01 16:24:57
【问题描述】:

我已经能够使用 Xcode 12b1 和 Swift 5.3 成功地在 Swift 包中发布一些图像和资产目录。在 Swift 包中使用自定义 .ttf 文件时我运气不佳。

我正在清单中加载一个 .ttf 文件,如下所示:

.target(
  name: "BestPackage",
  dependencies: [],
  resources: [
    .copy("Resources/Fonts/CustomFont.ttf"),
    .process("Resources/Colors.xcassets")
  ]
),

我注意到 SwiftUI 中的 Font 类型没有初始化器来包含来自模块的资产。例如,这有效:

static var PrimaryButtonBackgroundColor: SwiftUI.Color {
  Color("Components/Button/Background", bundle: .module)
}

但是,无法指定字体的来源。我希望将其加载到模块中会将其发送到目标中以供使用,但没有这样的运气:

static var PrimaryButtonFont: Font {
  Font.custom("CustomFont", size: 34)
}

这不会按预期加载字体。我正在研究使用 CoreText api 来尝试欺骗它进行加载,但我觉得应该有一种更简单的方法。有什么建议吗?

更新

仍然没有成功,但我能够证明字体确实在模块内

我编写了一个从模块中获取可用字体 URL 的方法,如下所示:

  static func fontNames() -> [URL] {
    let bundle = Bundle.module
    let filenames = ["CustomFont"]
    return filenames.map { bundle.url(forResource: $0, withExtension: "ttf")! }
  }

在运行时调用此方法并打印结果:

font names: [file:///Users/davidokun/Library/Developer/CoreSimulator/Devices/AFE4ADA0-83A7-46AE-9116-7870B883DBD3/data/Containers/Bundle/Application/800AE766-FB60-4AFD-B57A-0E9F3EACCDB2/BestPackageTesting.app/BestPackage_BestPackage.bundle/CustomFont.ttf]

然后我尝试使用以下方法注册字体以在运行时使用:

extension UIFont {
  static func register(from url: URL) {
    guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
      print("could not get reference to font data provider")
      return
    }
    guard let font = CGFont(fontDataProvider) else {
      print("could not get font from coregraphics")
      return
    }
    var error: Unmanaged<CFError>?
    guard CTFontManagerRegisterGraphicsFont(font, &error) else {
      print("error registering font: \(error.debugDescription)")
      return
    }
  }
}

当我这样称呼它时:

fontNames().forEach { UIFont.register(from: $0) }

我收到此错误:

error registering font: Optional(Swift.Unmanaged<__C.CFErrorRef>(_value: Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=105 "Could not register the CGFont '<CGFont (0x600000627a00): CustomFont>'" UserInfo={NSDescription=Could not register the CGFont '<CGFont (0x600000627a00): CustomFont>', CTFailedCGFont=<CGFont (0x600000627a00): CustomFont>}))

欢迎提出更多想法。

【问题讨论】:

    标签: ios swift swiftui


    【解决方案1】:

    我设法使用 SPM 导入自定义字体,使用这个 SO 答案来帮助https://stackoverflow.com/a/36871032/5508175

    这就是我所做的。创建你的包并添加你的字体。这是我的Package.swift

    // swift-tools-version:5.3
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "MyFonts",
        products: [
            .library(
                name: "MyFonts",
                targets: ["MyFonts"]),
        ],
        dependencies: [
        ],
        targets: [
    
            .target(
                name: "MyFonts",
                dependencies: [],
                resources: [.process("Fonts")]),
            .testTarget(
                name: "MyFontsTests",
                dependencies: ["MyFonts"]),
        ]
    )
    

    这是我的文件夹结构。我的所有字体都包含在一个名为 Fonts 的文件夹中。

    MyFonts.swift 内部,我执行以下操作:

    import Foundation // This is important remember to import Foundation
    
    public let fontBundle = Bundle.module
    

    这允许我访问包外的 Bundle。

    接下来我将包添加到我的项目中。这是一个带有 AppDelegate 的 SwiftUI 项目。

    • 导入我的字体
    • didFinishLaunchingWithOptions 中检查字体文件是否可用(可选)
    • 使用 UIFont 的扩展名添加字体。
    • 打印字体以检查它们是否已安装(可选)

    这是我的 AppDelegate:

    import UIKit
    import MyFonts
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
            // This prints out the files that are stored in the MyFont bundle
            // Just doing this to check that the fonts are actually in the bundle
            if let files = try? FileManager.default.contentsOfDirectory(atPath: fontBundle.bundlePath ){
                for file in files {
                    print(file)
                }
            }
    
            // This registers the fonts
            _ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Medium", fontExtension: "ttf")
            _ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Bold", fontExtension: "ttf")
            _ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Light", fontExtension: "ttf")
            _ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Regular", fontExtension: "ttf")
            _ = UIFont.registerFont(bundle: fontBundle, fontName: "FiraCode-Retina", fontExtension: "ttf")
    
            // This prints out all the fonts available you should notice that your custom font appears in this list
            for family in UIFont.familyNames.sorted() {
                let names = UIFont.fontNames(forFamilyName: family)
                print("Family: \(family) Font names: \(names)")
            }
    
            return true
        }
    
        // MARK: UISceneSession Lifecycle
    
        func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
            return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
        }
    
        func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}
    }
    
    // This extension is taken from this SO answer https://stackoverflow.com/a/36871032/5508175
    extension UIFont {
        static func registerFont(bundle: Bundle, fontName: String, fontExtension: String) -> Bool {
    
            guard let fontURL = bundle.url(forResource: fontName, withExtension: fontExtension) else {
                fatalError("Couldn't find font \(fontName)")
            }
    
            guard let fontDataProvider = CGDataProvider(url: fontURL as CFURL) else {
                fatalError("Couldn't load data from the font \(fontName)")
            }
    
            guard let font = CGFont(fontDataProvider) else {
                fatalError("Couldn't create font from data")
            }
    
            var error: Unmanaged<CFError>?
            let success = CTFontManagerRegisterGraphicsFont(font, &error)
            guard success else {
                print("Error registering font: maybe it was already registered.")
                return false
            }
    
            return true
        }
    }
    

    那么在你ContentView 中你可以做这样的事情:

    import SwiftUI
    
    struct ContentView: View {
        var body: some View {
            VStack(spacing: 20) {
                Text("Hello San Francisco")
                Text("Hello FiraCode Medium").font(Font.custom("FiraCode-Medium", size: 16))
                Text("Hello FiraCode Bold").font(Font.custom("FiraCode-Bold", size: 16))
                Text("Hello FiraCode Light").font(Font.custom("FiraCode-Light", size: 16))
                Text("Hello FiraCode Regular").font(Font.custom("FiraCode-Regular", size: 16))
                Text("Hello FiraCode Retina").font(Font.custom("FiraCode-Retina", size: 16))
            }
        }
    }
    

    结果如下:


    注意事项

    我还没有在完整的 SwiftUI 应用程序中尝试过这个,但是如果你没有 AppDelegate,你可以按照here 显示的教程来了解如何添加 AppDelegate。

    显然fontBundle 中文件的打印和安装的字体是可选的。它们只是用于调试和确保您拥有正确的字体名称文件名可能与您必须用于显示字体的字体名称有很大不同。请参阅我的 SO post 关于添加自定义字体:


    更新

    我想知道是否可以创建一个包含在包中的函数并调用它来加载字体。显然是的。

    我将MyFonts.swift更新为以下内容:

    import Foundation
    import UIKit
    
    public func registerFonts() {
        _ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Medium", fontExtension: "ttf")
        _ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Bold", fontExtension: "ttf")
        _ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Light", fontExtension: "ttf")
        _ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Regular", fontExtension: "ttf")
        _ = UIFont.registerFont(bundle: .module, fontName: "FiraCode-Retina", fontExtension: "ttf")
    }
    
    extension UIFont {
        static func registerFont(bundle: Bundle, fontName: String, fontExtension: String) -> Bool {
    
            guard let fontURL = bundle.url(forResource: fontName, withExtension: fontExtension) else {
                fatalError("Couldn't find font \(fontName)")
            }
    
            guard let fontDataProvider = CGDataProvider(url: fontURL as CFURL) else {
                fatalError("Couldn't load data from the font \(fontName)")
            }
    
            guard let font = CGFont(fontDataProvider) else {
                fatalError("Couldn't create font from data")
            }
    
            var error: Unmanaged<CFError>?
            let success = CTFontManagerRegisterGraphicsFont(font, &error)
            guard success else {
                print("Error registering font: maybe it was already registered.")
                return false
            }
    
            return true
        }
    }
    

    这意味着我可以从 AppDelegate 中删除扩展名,并且不必像调用 registerFonts() 之前那样在 AppDelegate 中注册每种字体

    所以我的didFinishLaunchingWithOptions 现在看起来像这样:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
        // This registers the fonts
        registerFonts()
    
        return true
    }
    

    记住你仍然需要导入你的包。

    【讨论】:

    • 我试过你注册字体的方式,但它不起作用。您可以链接示例项目的 GitHub 链接吗?提前致谢!
    • 这是一个 github 链接,指向包含自定义字体的 Swift 包。我已经检查过它是否适用于 UIKit 和 SwiftUI。如果您仍然有困难,那么我建议您发布自己的 repo,以便我可以检查它有什么问题。
    • 如何在包内的视图控制器中使用包内的字体?
    【解决方案2】:

    这是@Andrew 答案的简化版本。我在 iOS 和 macOS 上的 100% SwiftUI 应用程序中对此进行了测试;它不需要 UIKit。以这种方式注册的字体可以从其他依赖包中访问。

    func registerFont(_ name: String, fileExtension: String) {
        guard let fontURL = Bundle.module.url(forResource: name, withExtension: fileExtension) else {
            print("No font named \(name).\(fileExtension) was found in the module bundle")
            return
        }
    
        var error: Unmanaged<CFError>?
        CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, &error)
        print(error ?? "Successfully registered font: \(name)")
    }
    

    你应该像这样将你的字体和颜色资源加载到你的包中:

    .target(
      name: "BestPackage",
      dependencies: [],
      resources: [
        .process("Resources")
      ]
    ),
    

    来自the docs

    如果给定的路径代表一个目录,Xcode 会递归地将进程规则应用到目录中的每个文件。

    如果可能,请使用此规则而不是 copy(_:)。

    【讨论】:

    • 这应该是公认的答案,不再需要 UIKit。例如,除了上面的答案之外,您还需要在 App 类 init 中调用 registerFont 函数。如果我们可以在加载 SPM 时使用类似 @main SPM 可执行注释的东西来执行一些代码,而不是从应用程序调用 registerFonts 函数,那就太好了
    猜你喜欢
    • 2015-10-03
    • 1970-01-01
    • 2012-03-14
    • 2014-10-07
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    • 2021-04-03
    • 2012-05-19
    相关资源
    最近更新 更多