skip to Main Content

I have an issue on SwiftUI preview in a view located in a Swift Package when my code imports a control or value from an other swift package.

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
        )
    }
}

If I remove or change .fill(Color.hippoPrimary) the preview is available.

The error provided by Xcode is the following:

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

This is my Package.swift file:

// 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"]),
    ]
)

In the Common Package, the Colors are defined this way:

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)
}

The build hasn’t errors so I understand that the dependencies are ok, sounds like a IDE.

Thanks in Advance.

4

Answers


  1. Update 1:

    Sorry my last post was not accurate. This is a bug in Xcode with no work around. Just submitted to Apple a bug report (FB8880328). Also, posted a details write up with example code w/ repro steps here. Direct link to GitHub project: https://github.com/ryanholden8/SwiftUI-Preview-Failing-Test-Project

    Old Post:

    Got this exact error doing the same thing, putting colors in a separate package. This post helped me get to the bottom of it. I deleted the default class that is generated in the colors package. However, I did not delete the unit test that was based on that default class.

    In short: Delete the auto-generated unit test in your Common package. Or make sure all the unit tests pass.

    Login or Signup to reply.
  2. Workaround for both iOS and macOS (not tested with 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")
        }
    }
    
    

    Then replace calls to Bundle.module with Bundle.swiftUIPreviewsCompatibleModule.

    Login or Signup to reply.
  3. This worked for me in 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
    }
    
    Login or Signup to reply.
  4. I’m using Xcode 13.4.1

    I tried to use this line as the answer above mentioned but it did not work as it can not find the bundle:

    /* 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"
    

    My package is called "Networking". After some debugging, I changed the bundle name as follows and it worked perfectly!

    let bundleNameIOS = "Networking_Networking"
    

    Maybe this helps anyone!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search