xcode 从嵌套Swift包访问资源时SwiftUI预览崩溃

wyyhbhjk  于 2023-01-10  发布在  Swift
关注(0)|答案(2)|浏览(175)

A模块

我有一个模块(A),它有两个结构体。

  • 一种是直接在代码中启动的颜色。
  • 一个具有从资源加载的颜色。
public struct CodeColor {
    public init() { }
    public let value = SwiftUI.Color(#colorLiteral(red: 0.8549019694, green: 0.250980407, blue: 0.4784313738, alpha: 1))
}

public struct AssetColor {
    public init() { }
    public let value = SwiftUI.Color("Legacy/Material/Gold", bundle: .module)
}

预览效果非常好:

B模块

第二个模块(B)应该使用前一个模块(A)作为从以下位置加载颜色的依赖项:

import A

public struct CodeColor {
    public init() { }
    public var value: SwiftUI.Color { A.CodeColor().value }
}

public struct AssetColor {
    public init() { }
    public var value: SwiftUI.Color { A.AssetColor().value }
}

但一旦它触及模块(A)中的资源,预览就会崩溃:

🛑#错误:

无法在此文件中预览。无法更新预览。

RemoteHumanReadableError: Failed to update preview.

The preview process appears to have crashed.

Error encountered when sending 'previewInstances' message to agent.

==================================

|  RemoteHumanReadableError: The operation couldn’t be completed. (BSServiceConnectionErrorDomain error 3.)
|  
|  BSServiceConnectionErrorDomain (3):
|  ==BSErrorCodeDescription: OperationFailed

那是为什么呢?

**注意:**奇怪的是,如果B预览代码是在实际的应用程序(而不是另一个软件包)中,它就可以工作

Here is the full code on github

xt0899hw

xt0899hw1#

我在开发者论坛找到了一个解决方案:

    • 链接**

https://developer.apple.com/forums/thread/664295?login=true#reply-to-this-question

    • 注-本地**

此解决方案适用于本地包。您定义

let bundleNameIOS = "LocalPackages_TargetName"
let bundleNameMacOs = "PackageName_TargetName"
    • 远程(例如来自GitHub的包)**

如果你在GitHub上有你的软件包并远程获取它,你不能定义本地软件包,你必须稍微修改它:

let bundleNameIOS = "TargetName_TargetName"
let bundleNameMacOs = "TargetName_TargetName"
    • 示例**

下面是一个完整的实现示例,您必须将其与资源一起放入包中:

// Inside your package with the resources:
// Extend Bundle to access it in other packages
extension Bundle {

    // public static let assets = Bundle.module
    // Updated with workaround
    public static let assets = Bundle.myModule
    
}
    • 定义捆绑包**
private class CurrentBundleFinder {}
extension Foundation.Bundle {
    static var myModule: Bundle = {
        /* The name of your package. You may have same PackageName and TargetName*/
        let bundleNameIOS = "TargetName_TargetName"
        let bundleNameMacOs = "TargetName_TargetName"
        
        let candidates = [
            /* Bundle should be present here when the package is linked into an App. */
            Bundle.main.resourceURL,
            /* Bundle should be present here when the package is linked into a framework. */
            Bundle(for: CurrentBundleFinder.self).resourceURL,
            // -> Optional UI Tests
            /* Bundle should be present here when the package is used in UI Tests. */
            Bundle(for: CurrentBundleFinder.self).resourceURL?.deletingLastPathComponent(),
            /* For command-line tools. */
            Bundle.main.bundleURL,
            /* Bundle should be present here when running previews from a different package (this is the path to "…/Debug-iphonesimulator/"). */
            Bundle(for: CurrentBundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent(),
            Bundle(for: CurrentBundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent(),
        ]
        
        for candidate in candidates {
            let bundlePathiOS = candidate?.appendingPathComponent(bundleNameIOS + ".bundle")
            let bundlePathMacOS = candidate?.appendingPathComponent(bundleNameMacOs + ".bundle")
            if let bundle = bundlePathiOS.flatMap(Bundle.init(url:)) {
                return bundle
            } else if let bundle = bundlePathMacOS.flatMap(Bundle.init(url:)) {
                return bundle
            }
        }
        fatalError("unable to find bundle")
    }()
}
    • 访问程序包B中的程序包A资源**

现在,您可以从包A访问包B中的资源,如下所示:

let example = UIImage(named: "Exampe", in: Bundle.assets, with: nil)!
c3frrgcw

c3frrgcw2#

这个变通方案在Xcode 14.2中对我很有效

private extension Bundle {
    private static let packageName = "PACKAGE_NAME"
    private static let moduleName = "MODULE_NAME"
    
    #if targetEnvironment(simulator)
    static var swiftUIPreviewsCompatibleModule: Bundle {
        final class CurrentBundleFinder {}

        let isPreview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
        
        guard isPreview else {
            return Bundle.module
        }
        
        // This is a workaround for SwiftUI previews
        // previews crash when accessing other package view using assets from Bundle.module
        
        let bundleName = "\(packageName)_\(moduleName).bundle"
        
        func bundle(stepsBack: Int) -> Bundle? {
            var bundleURL = Bundle(for: CurrentBundleFinder.self).bundleURL
            for _ in 1...stepsBack { bundleURL.deleteLastPathComponent() }
            bundleURL.appendPathComponent(moduleName)
            bundleURL.appendPathComponent("Products")
            bundleURL.appendPathComponent("Debug-iphonesimulator")
            bundleURL.appendPathComponent("PackageFrameworks")
            
            let directories: [String]
            do {
                directories = try FileManager.default.contentsOfDirectory(atPath: bundleURL.path)
            } catch {
                return nil
            }
            
            guard let matchingDir = directories.first(where: { $0.hasSuffix(".framework") }) else {
                return nil
            }
            
            bundleURL.appendPathComponent(matchingDir)
            bundleURL.appendPathComponent(bundleName)
            
            return Bundle(url: bundleURL)
        }
        
        // Steps back 5 is a workaround for crashes
        // when another module is importing this module
        return bundle(stepsBack: 5) ?? .module
    }
    #else
    static var swiftUIPreviewsCompatibleModule: Bundle { .module }
    #endif
}

相关问题