Cannot access Swift Package xcassets - swift

I'm trying to use a color set from a xcassets folder that is inside a Swift Package (src). It doesn't seem to be working. I tested this out by writing a simple view that attempts to make use of the color:
Text("Hello")
.foregroundColor(Color("brandPrimary")
I got an empty view from this code; the text can't find the color.
I've done significant research around the web - reviewing WWDC videos such as Swift packages: Resources and localization, and they seem to suggest that xcassets folder are automatically included as resources. It doesn't work for me.
I tried to add process("Resources/Colors.xcassets") inside my package manifest, but that didn't help either.

From the Color documentation:
init(_ name: String, bundle: Bundle? = nil)
…
bundle
The bundle in which to search for the color resource. If you don’t indicate a bundle, the initializer looks in your app’s main bundle by default.
Your GoodPackage library's assets are not the app's main bundle, so you need to tell the Color initializer which bundle to search.
The Swift package manager's build process automatically creates a Bundle for each target/module that contains assets. Within the module, you can access that generated Bundle using the expression Bundle.module. SwiftPM actually writes Swift code to a file named resource_bundle_accessor.swift in your DerivedData to make this work. The generated source code looks like this:
import class Foundation.Bundle
import class Foundation.ProcessInfo
import struct Foundation.URL
private class BundleFinder {}
extension Foundation.Bundle {
/// Returns the resource bundle associated with the current Swift module.
static let module: Bundle = {
let bundleName = "YourPackageName_YourTargetName"
let overrides: [URL]
#if DEBUG
if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_URL"] {
overrides = [URL(fileURLWithPath: override)]
} else {
overrides = []
}
#else
overrides = []
#endif
let candidates = overrides + [
// 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: BundleFinder.self).resourceURL,
// For command-line tools.
Bundle.main.bundleURL,
]
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
return bundle
}
}
fatalError("unable to find bundle named YourPackageName_YourTargetName")
}()
}
As you can see, that static let module property has (default) internal access, so you can only use it from source code within that module. By default, there is no way to access the module's Bundle from outside the module.
One solution is to add a public accessor for the module Bundle in the GoodPackage module. For example, add this to a source file in the GoodPackage module:
import Foundation
extension Bundle {
public var GoodPackage: Bundle { Bundle.module }
}
Then, in your app:
import GoodPackage
...
Text("Hello")
.foregroundColor(Color("brandPrimary", bundle: .GoodPackage))

Related

Dynamic Swift Framework: Fatal error: unable to find bundle

I have a framework that imports several Swift Packages.
One such package uses Bundle.myModule for resources. Here is the code use for .myModule
extension Foundation.Bundle {
static var myModule: Bundle = {
let bundleNameIOS = "xxxx"
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,
// Bundle should be present here for package 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")
if let bundle = bundlePathiOS.flatMap(Bundle.init(url:)) {
return bundle
}
if let bundle = Bundle(path: "\(Bundle.main.bundlePath)/\(bundleNameIOS).bundle") {
return bundle
}
}
fatalError("unable to find bundle")
}()
}
Then we I build the framework and embed it inside another project I get the error
Fatal error: unable to find bundle
But the Package that this is failing in is inside the XCFramework
the folder path "App/Frameworks/myFramework" does not contain any bundles
I have tried all of the follow:
Swift Package: Fatal error: unable to find bundle named
https://developer.apple.com/forums/thread/650158
Swift Package Manager - Type 'Bundle' has no member “module” error
https://forums.swift.org/t/unable-to-find-bundle-in-package-target-tests-when-package-depends-on-another-package-containing-resources-accessed-via-bundle-module/43974/6
Please any help would be greatly appreciated. Thank you

Spawning a process in an app built with UIKit for macOS (Catalyst)

I'm building an application that shares most of the code between macOS and iOS versions (targeting macOS 11 and iOS 14). UIKit for Mac seems like a natural choice to help with this. Unfortunately, one of the libraries uses the Process type under the hood. Building it produces "Cannot find type Process in scope" error when a dependency on it is added and when targeting macOS. I'm fine with excluding this library for iOS, but I still need to link with it on macOS while keeping the ability to use UIKit on all platforms.
I've selected this library to be linked only for macOS in Xcode, but this has no effect and the same build error persists. Also, I'm getting this error without adding a single import SwiftLSPClient statement in the app, so I don't think conditional imports would help in this case.
What would be the best way to resolve this issue within the constraints listed above?
I created a LSPCatalyst class in my Mac Catalyst app to replace the MacOS LanguageServerProcessHost. To make that work, I replaced the process property with a processProxy that accesses the process instance in a MacOS bundle using the FoundationApp protocol as explained below.
Following #Adam's suggestion, I created a MacOS bundle to proxy for the process instance. You follow the same idea as he pointed to for AppKit access from Catalyst apps, but you just need Foundation to get access to Process. I called the bundle FoundationGlue and put everything in a FoundationGlue folder in my Xcode project. The bundle needs an Info.plist that identifies the principal class as "FoundationGlue.MacApp", and the MacApp.swift looks like:
import Foundation
class MacApp: NSObject, FoundationApp {
var process: Process!
var terminationObserver: NSObjectProtocol!
func initProcess(_ launchPath: String!, _ arguments: [String]?, _ environment: [String : String]?) {
process = Process()
process.launchPath = launchPath
process.arguments = arguments
process.environment = environment
}
func setTerminationCompletion(_ completion: (()->Void)!) {
let terminationCompletion = {
NotificationCenter.default.removeObserver(self.terminationObserver!)
completion?()
}
terminationObserver =
NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification,
object: process,
queue: nil) { notification -> Void in
terminationCompletion()
}
}
func setupProcessPipes(_ stdin: Pipe!, _ stdout: Pipe!, _ stderr: Pipe!) {
process.standardInput = stdin
process.standardOutput = stdout
process.standardError = stderr
}
func launchProcess() {
process.launch()
print("Launched process \(process.processIdentifier)")
}
func terminateProcess() {
process.terminate()
}
func isRunningProcess() -> Bool {
return process.isRunning
}
}
The corresponding header I called FoundationApp.h looks like:
#import <Foundation/Foundation.h>
#protocol FoundationApp <NSObject>
typedef void (^terminationCompletion) ();
- (void)initProcess: (NSString *) launchPath :(NSArray<NSString *> *) arguments :(NSDictionary<NSString *, NSString *> *) environment;
- (void)setTerminationCompletion: (terminationCompletion) completion;
- (void)setupProcessPipes: (NSPipe *) stdin :(NSPipe *) stdout :(NSPipe *) stderr;
- (void)launchProcess;
- (void)terminateProcess;
- (bool)isRunningProcess;
#end
And the FoundationAppGlue-Bridging-Header.h just contains:
#import "FoundationApp.h"
Once you have the bundle built for MacOS, add it as a framework to your Mac Catalyst project. I created a Catalyst.swift in that project for access to the FoundationGlue bundle functionality::
import Foundation
#available(macCatalyst 13, *)
struct Catalyst {
/// Catalyst.foundation gives access to the Foundation functionality identified in FoundationApp.h and implemented in FoundationGlue/MacApp.swift
static var foundation: FoundationApp! {
let url = Bundle.main.builtInPlugInsURL?.appendingPathComponent("FoundationGlue.bundle")
let bundle = Bundle(path: url!.path)!
bundle.load()
let cls = bundle.principalClass as! NSObject.Type
return cls.init() as? FoundationApp
}
}
Then, you use it from your app like:
let foundationApp = Catalyst.foundation!
foundationApp.initProcess("/bin/sh", ["-c", "echo 1\nsleep 1\necho 2\nsleep 1\necho 3\nsleep 1\necho 4\nsleep 1\nexit\n"], nil)
foundationApp.setTerminationCompletion({print("terminated")})
foundationApp.launchProcess()
This is a messy solution but I know it works: Add a “Mac bundle” to your Catalyst app and import the MacOS-only framework with that.
Here’s a guide to creating and loading a Mac bundle: https://medium.com/better-programming/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5
Once you have the bundle, you can add Mac-only libraries and frameworks to it. You’ll have to bridge data and method calls between the bundle and your iOS app, but it’s manageable.

Core Data & Xcode 11: Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer

Just moved to Xcode 11 and getting the following crash at launch:
CoreData: fault: One or more models in this application are using transformable properties with transformer names that are either unset, or set to NSKeyedUnarchiveFromDataTransformerName. Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead. At some point, Core Data will default to using "NSSecureUnarchiveFromData" when nil is specified, and transformable properties containing classes that do not support NSSecureCoding will become unreadable.
CoreData: warning: Property 'color' on Entity 'Group' is using nil or an insecure NSValueTransformer. Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead.
I'm creating an NSPersistentContainer at launch using the code below:
private let container: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyApp", managedObjectModel: MyAppModelVersion.current.managedObjectModel())
let storeDescription = NSPersistentStoreDescription(url: getStoreURLWithUserName())
storeDescription.shouldMigrateStoreAutomatically = true
storeDescription.shouldInferMappingModelAutomatically = true
container.persistentStoreDescriptions = [storeDescription]
return container
}()
Error occurs right after this line is executed:
let container = NSPersistentContainer(name: "MyApp", managedObjectModel: MyAppModelVersion.current.managedObjectModel())
I also have a property called 'Colorin aGroup` entity that's transformable:
#NSManaged public var color: UIColor?
#NSManaged public var hexColorValue: String?
Below is how set the property:
public var hexColor: String? {
get {
return self.hexColorValue
}
set {
self.hexColorValue = newValue
if let str = newValue {
self.color = UIColor(hex: str)
}
}
}
This is what the property looks like in Core Data:
I am not sure how to recover from this crash. This was working fine with Xcode 10
Setting Transformer property to NSSecureUnarchiveFromDataTransformer solved the warning in my case. For this select the attribute & set its transformer type to NSSecureUnarchiveFromDataTransformer & run again by pressing commond+R.
Thanks,
Ratneshwar
Swift 5.4.2
This worked for me.
EDIT Link to the article is here.
Click on the .xcdatamodeld file in the project navigator
Click on the
Entity that has a Transformable Attribute
Click on the Transformable Attribute
Click the 'Show Data Model Inspector' icon
Enter 'NSSecureUnarchiveFromDataTransformer'
in the Transformer field - EDIT Make this 'NSSecureUnarchiveFromData' as of Swift 5.5.2.
Your warnings/errors should go away. If not, try cleaning your build folder and rebuild.
This is related to a migration from NSCoding to the NSSecureCoding protocol. The default ValueTransformer adopts NSCoding, so the only solution that worked for me was to write my own Transformer that adopts the NSSecureUnarchiveFromDataTransformer protocol.
I should say that my own experience is from trying to define an Attribute with the Transformer type to persist a custom class that adopted NSCoding. I was initially met with a warning similar to the OP's error. I was able to suppress the warning by changing the Transformer field on the attribute to "NSSecureUnarchiveFromData" as others have mentioned, but I then received an error along the lines of:
Not able to save to CoreData. SQLCore dispatchRequest Object of class “ ” not among allowed top level class list... as mentioned here. The suggestion to change the Attribute to a Relationship was undesirable in my case.
More digging came up with this blog post that details the "reason" for all of this, and gives a solution that worked for me. The blog actually uses the case of UIColor in the example, but it works for any custom class as well.
Say you have a CustomClass that you want to store as a Transformable Attribute in some Entity. If you're like me, you may have adopted NSCoding and received the aforementioned error. The solution would be to adopt NSSecureCoding instead and define an NSSecureUnarchiveFromDataTransformer subclass:
#objc(CustomClassValueTransformer)
final class CustomClassValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: CustomClass.self))
// Make sure `CustomClass` is in the allowed class list,
// AND any other classes that are encoded in `CustomClass`
override static var allowedTopLevelClasses: [AnyClass] {
// for example... yours may look different
return [CustomClass.self, OtherClass.self, NSArray.self, NSValue.self]
}
/// Registers the transformer.
public static func register() {
let transformer = CustomClassValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}
Then make sure to set the Transformer field on your Attribute to "CustomClassValueTransformer" and the Custom Class field to "CustomClass" and you should be good to go.
For Objective-C and iOS 14, the following solution works for UIColor attributes.
First add a new subclass of NSSecureUnarchiveFromDataTransformer
#interface ColorValueTransformer : NSSecureUnarchiveFromDataTransformer
Add the following static method to your implementation file:
#implementation ColorValueTransformer
+ (NSArray<Class> *)allowedTopLevelClasses {
return #[UIColor.class];
}
#end
Open your data model (e.g. datamodel..xcdatamodeld)
Select the entity and the related attribute which needs the new Transformer
Open the Data Model Inspector
Add the class name (e.g. ColorValueTransformer) as Transformer to that attribute
Change the Custom Class to UIColor
Build and run…
For the transformable attribute, you need to set its type in the Custom Class field.
For instance, I have a transformable field which stores an array of numbers and its Custom Class is declared as [Int16]. This is most likely the cause of the crash. And as #vadian mentioned before, you don't need both fields.
After your crash is fixed, you can get rid of the warning by setting the Transformer field to NSSecureUnarchiveFromData (you simply type this into the field)
I received the same warning messages when updating to Xcode 11, however in my case they are just warnings, but no crash.
In order to work out the best solution, I tried creating a stripped down sample app with just a single entity containing a transformable attribute. But it seems that no matter what I tried I could not reproduce the problem. The I copied the model file from my main app to the demo app, and of course that failed.
So I got to the point where I just had two model files and a simple unit test which does nothing more than open the model and create a persistent store container:
func testDataModels() {
openDataModel(named: "samplemodel")
openDataModel(named: "appmodel")
}
func openDataModel(named name: String) {
print("Opening \(name)")
guard let url = findFile(forResource: name, withExtension: "momd"),
let managedObjectModel = NSManagedObjectModel(contentsOf: url)
else {
XCTFail("Unable to find \(name) data model")
return
}
print(url)
_ = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
}
func findFile(forResource name: String, withExtension ext: String) -> URL? {
if let url = Bundle(for: type(of: self)).url(forResource: name, withExtension: ext) {
return url
}
return Bundle.main.url(forResource: name, withExtension: ext)
}
The appmodel causes the error messsages but the sample model does not. Even when I stripped the appmodel down to a single Entity it continues to generate the errors.
Comparing the contents of the samplemodel with the appmodel (show package contents in Finder), there is a hidden file called .xccurrentversion in the samplemodel but not in the appmodel. The file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>samplemodel.xcdatamodel</string>
</dict>
</plist>
So I created a similar file for the appmodel and put it in the package folder.
Surprisingly, that silences the warning messages! Conversely, deleting the .xccurrentversion file from the samplemodel causes the error messages to be generated. This will allow testing of the problem in isolation.
So this may be a short term fix. In the meantime, I need to work out how to migrate to secure coding.
Maybe some of these answers are acceptable enough, in my case I went to this post. I hope it will be of much help.

How to get bundle for a struct?

In Swift, you can call
let bundle = NSBundle(forClass: self.dynamicType)
in any class and get the current bundle. If you NSBundle.mainBundle() this will fail getting correct bundle when for example running unit tests.
So how can you get the current bundle for a Swift struct?
The best solution here depends on what you need a bundle for.
Is it to look up resources that exist only in a specific app, framework, or extension bundle that's known to be loaded when the code you're writing runs? In that case you might want to use init(identifier:) instead of dynamically looking up the bundle that defines a certain type.
Beware of "follows the type" bundle lookups. For example, if a framework class Foo uses NSBundle(forClass: self.dynamicType) to load a resource, a subclass of Foo defined by the app loading that framework will end up looking in the app bundle instead of the framework bundle.
If you do need a "follows the type" bundle lookup for a struct (or enum), one workaround that might prove helpful is to define a class as a subtype:
struct Foo {
class Bar {}
static var fooBundle: NSBundle { return NSBundle(forClass: Foo.Bar.self) }
}
Note there's nothing dynamic here, because nothing needs to be — every Foo comes from the same type definition (because structs can't inherit), so its static type matches its dynamic type.
(Admittedly, an NSBundle(forType:) that could handle structs, enums, and protocols might make a nice feature request. Though I imagine it could be tricky to make it handle extensions and everything...)
extension Bundle {
static var current: Bundle {
class __ { }
return Bundle(for: __.self)
}
}
Updated for Swift 3.0+:
struct Foo {
class Bar {}
static var fooBundle: Bundle { return Bundle(for: Foo.Bar.self) }
}
Swift 5
struct Foo {
class Bar {}
static var fooBundle: Bundle { return Bundle(for: Foo.Bar.self) }
}
For Swift Packages, we get the Bundle where the Swift struct is declared anywhere in the same module (target) via:
Bundle.module
However, we need to import a resource in the Package to get this autogenerated.
Bundle.main also works for other projects if you don't have extension targets.
Swift 4+
You can do let bundle = InternalConstants.bundle if you add this struct to your project. A very elegant solution in my opinion.
internal struct InternalConstants {
private class EmptyClass {}
static let bundle = Bundle(for: InternalConstants.EmptyClass.self)
}
Another potential solution (less elegant):
internal struct InternalConstants {
internal static let bundle = Bundle(identifier: "com.hello.world")!
}

Private var is accessible from outside the class

This was done in Playground, just to simplify.
class MyPrivateVar
{
private var priv: String?
}
var myInstance = MyPrivateVar()
myInstance.priv = "Something"
No compiler warning. In fact auto-complete is showing priv without a problem.
My understanding is that outside the boundaries of {} of the class, I'm not supposed to be able to see a private anything, func nor var.
Am I missing something?
Access modifiers in Swift are implemented differently than other languages. There are three levels:
private: accessible only within that particular file
internal: accessible only within the module (project)
public: accessible from anywhere
Unless marked otherwise, everything you write is internal by default.
The Swift blog had a post about access control when the features were introduced in beta 4, and Apple's documentation has a chapter as well.
Note: this answer is for Swift 2
The Swift Programming Language states:
Swift provides three different access levels for entities within your
code. These access levels are relative to the source file in which an
entity is defined, and also relative to the module that source file
belongs to.
If you wan't to test private access level with Swift, the following step by step may help you.
1/ Create a new Xcode project.
2/ Create a file, MyPrivateVar.swift, and add the following code in it:
class MyPrivateVar {
private var priv: String? = nil
}
3/ Create a second file, MySecondClass.swift, and add the following code in it:
class MySecondClass {
init() {
var myPrivateVar = MyPrivateVar()
myPrivateVar.priv = "some string"
}
}
Xcode will immediatly give you a Swift compiler error message:
'MyPrivateVar' does not have a member named 'priv'
4/ Now, remove the two previous files from your project and create a single file TwoClassesInAFile.swift with the following code in it:
class MyPrivateVar {
private var priv : String? = nil
}
class MySecondClass {
init() {
var myPrivateVar = MyPrivateVar()
myPrivateVar.priv = "some string"
}
}
This time, you will get no Swift compiler error message and you will be able to access MyPrivateVar's priv private property from MySecondClass because priv and MySecondClass are in the same file (your TwoClassesInAFile.swift file).
Furthermore, access levels also work for global variables. For example, Xcode won't give any compiler error if the following code is part of the same ViewController.swift file:
import UIKit
private var globalPrivate : String? = nil
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
globalPrivate = "some string"
println(globalPrivate)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
However, if you define globalPrivate outside of ViewController.swift, Xcode will generate an error message:
Use of unresolved identifier 'globalPrivate'