当我的代码从其他 swift 包导入控件或值时,我在 Swift 包中的视图中遇到 SwiftUI 预览问题。
import Foundation
import SwiftUI
import Common
struct AppointmentListItem: View {
var appointment: Appointment
var body: some View {
VStack{
HStack(spacing: 10){
//Client Info
Image(self.appointment.client.profilePicture)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 35, height: 35)
.clipShape(Circle())
.shadow(radius: 10)
.overlay(Circle().stroke(Color.white, lineWidth: 1.5))
Text(self.appointment.client.fullName)
.font(.system(size: 18))
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
Text(self.appointment.getHourAndMinutes()).bold()
//Detail info
Button(action: {
withAnimation{
print("Go to details")
}
}){
Image(systemName: "ellipsis")
.font(.system(size: 18))
.frame(width: 20, height: 20)
.rotationEffect(Angle.init(degrees: 90))
}
}
.padding()
}
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 20)
.fill(Color.hippoPrimary)// <- this color is part of Common package
)
}
}
如果我删除或更改
.fill(Color.hippoPrimary)
,预览可用。
Xcode提供的错误如下:
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
这是我的 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: "TodayAppointments",
platforms: [
.iOS(.v13)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "TodayAppointments",
targets: ["TodayAppointments"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(path: "../Common")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "TodayAppointments",
dependencies: ["Common"]),
.testTarget(
name: "TodayAppointmentsTests",
dependencies: ["TodayAppointments"]),
]
)
在 Common Package 中,颜色是这样定义的:
public extension Color {
static let hippoPrimary = Color("Primary", bundle: .module)
static let progressBarBackground = Color("ProgressBarBackground", bundle: .module)
static let textBackground = Color("TextBackground", bundle: .module)
static let textColor = Color("TextColor", bundle: .module)
static let appleSignInBackground = Color("AppleSignInBackground", bundle: .module)
static let buttonActionText = Color("Text", bundle: .module)
}
构建没有错误,所以我知道依赖关系没问题,听起来像一个 IDE。
提前致谢。
适用于 iOS 和 macOS 的解决方法(未使用 Catalyst 进行测试):
extension Foundation.Bundle {
static var swiftUIPreviewsCompatibleModule: Bundle {
final class CurrentBundleFinder {}
/* The name of your local package, prepended by "LocalPackages_" for iOS and "PackageName_" for macOS. You may have same PackageName and TargetName*/
let bundleNameIOS = "LocalPackages_TargetName"
let bundleNameMacOs = "PackageName_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,
/* 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")
}
}
然后将对
Bundle.module
的呼叫替换为 Bundle.swiftUIPreviewsCompatibleModule
。
更新1:
抱歉我的上一篇文章不准确。这是 Xcode 中的一个错误,无法解决。刚刚向 Apple 提交了一份错误报告 (FB8880328)。另外,在here发布了带有示例代码和重现步骤的详细信息。直接链接到 GitHub 项目:https://github.com/ryanholden8/SwiftUI-Preview-Failing-Test-Project
旧帖子:
在做同样的事情时遇到了这个确切的错误,将颜色放入单独的包中。 这篇文章帮助我了解了真相。我删除了颜色包中生成的默认类。但是,我没有删除基于该默认类的单元测试。
简而言之:删除
Common
包中自动生成的单元测试。或者确保所有单元测试都通过。
这在 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
}
我尝试使用这一行作为上面提到的答案,但它不起作用,因为它找不到捆绑包:
/* The name of your local package, prepended by "LocalPackages_" for iOS and "PackageName_" for macOS. You may have same PackageName and TargetName*/
let bundleNameIOS = "LocalPackages_TargetName"
我的包叫做“Networking”。经过一番调试后,我按如下方式更改了包名称,它运行得很好!
let bundleNameIOS = "Networking_Networking"
也许这对任何人都有帮助!
这是我针对 Xcode 15.3 的解决方案,它结合了我在此处和其他地方找到的一些不同技术。
适用于 iOS 和 Mac Catalyst 模拟器设备。
import Foundation
public extension Bundle {
static let myPackage: Bundle = .swiftUIPreviewsCompatibleModule
}
private extension Bundle {
private static let packageName = "my-package"
private static let moduleName = "MyModule"
static var swiftUIPreviewsCompatibleModule: Bundle {
final class CurrentBundleFinder {}
let isPreview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
guard isPreview else {
// Not a preview, so use regular ol' Bundle.module
return .module
}
let bundleName = "\(packageName)_\(moduleName).bundle"
let simulatorProductsDirectory = Bundle(for: CurrentBundleFinder.self)
.resourceURL?
.deletingLastPathComponents(upToPathComponentWithPrefix: "Debug-")
.appendingPathComponent(bundleName)
if let bundleURL = simulatorProductsDirectory,
let bundle = Bundle(url: bundleURL)
{
return bundle
}
// Welp. This will likely crash.
return .module
}
}
private extension URL {
func deletingLastPathComponents(upToPathComponentWithPrefix prefix: String) -> URL {
var currentURL = self
while let lastPathComponent = currentURL.pathComponents.last, !lastPathComponent.hasPrefix(prefix) {
currentURL.deleteLastPathComponent()
}
return currentURL
}
}