Using SwiftGen to create a nested enum for assets - swiftgen

I'm setting up SwiftGen in the project and want to create a nested enum of assets like shown in the docs...
https://github.com/SwiftGen/SwiftGen#asset-catalog
I've tried multiple ways to set up the assets catalog and different config options in the yml but I can't get the output to look like it does in the readme with multiple namespaces...
All I get is a single flat enum with all the images in it...
public enum Asset {
public static let arrowBack = ImageAsset(name: "arrow-back")
public static let arrowBottomRight = ImageAsset(name: "arrow-bottom-right")
public static let arrowClockwise = ImageAsset(name: "arrow-clockwise")
public static let arrowDouble = ImageAsset(name: "arrow-double")
public static let arrowDown = ImageAsset(name: "arrow-down")
public static let arrowTopRight = ImageAsset(name: "arrow-top-right")
etc...
How do I set up SwiftGen (or the xcassets file) to generate the nested enum as shown in the docs.
Thanks

There are two ways to have nested enums in your Assets.swift file (generated by SwiftGen with the swift5 template).
Have multiple assets catalogs.
You can give different asset catalogs in input for the same output file name.
xcassets:
inputs:
- ./Assets/Styles.xcassets
- ./Assets/Food.xcassets
outputs:
templateName: swift5
output: ./Assets.swift
params:
publicAccess: true
Here the two catalogs are in the same folder then you can simplify :
xcassets:
inputs: ./Assets
outputs:
templateName: swift5
output: ./Assets.swift
The Assets.swift content will be :
enum Asset {
enum Styles {
...
}
enum Food {
...
}
}
Provide namespaces to folders in the asset catalogs.
In addition to this, when you add a folder in your asset catalog you can check the "provides namespace" option (in Identity and type inspector - here in my Food.xcasset file).
After that you have to specify your asset "path" when you try to access to this asset :
Color("Exotic/mango")
But most importantly, Swiftgen will generate a sub-enum with the items contained in the folder.

Related

RepositoryItem from List&Label .lst file

Currently we are using a WPF application for creation/editing of List&Label Templates, but we are considering to move to the WebDesigner. Because we use project includes we need to use the repository mode.
I've been trying to import our existing templates, but I run into some issues regarding the RepositoryItemDescriptor. To create a RepositoryItem object you have to give a Descriptor in the constructor, but I cannot find any info regarding how you get it from the generated .lst file.
The data that we have at our disposal are:
TemplateType: List or Form
TemplateData: content of the .lst file (byte[])
IsMainTemplate: bool, is a "project include" or not
File name: name of the .lst file
The RepositoryItem constructor requires: string internalID, string descriptor, string type, DateTime lastModificationUTC.
What I have now is:
public class TemplateBaseModel : RepositoryItem
{
// Properties
// we have our own Ids and modification date, override RepositoryItem properties
public new InternalID => $"repository://{{{Id}}}";
public DateTime LastModificationUTC => ModifiedOn;
public TemplateBaseModel() : base($"repository://{{{Guid.NewGuid()}}}", /* ?? */, RepositoryItemType.ProjectList.Value, DateTime.Now) { }
public TemplateBaseModel(string internalID, string descriptor, string type, DateTime lastModificationUTC) : base(internalID, descriptor, type, lastModificationUTC) { }
}
In the documentation I can only find what it is (internal metadata that is serialized into a string, and can be edited with the class RepositoryItemDescriptor), but not how it's created or how you can get it, and if I try to debug the example I get (in the CreateOrUpdate() method)#2#PgUAAENoS19QYWNrZWQAeNqd1E1PE1EYxfHfmsTvMAyJEeLY8iKCtpChU5MmvAiOC2NcjDCYmqFtZkaEqF9dXThgsTVGt/fm+Z9zz3lyv3/r2HXlQiFwKVeqDI2NdIVWPdIWCuRGTo2dGRp5ryv0Suq5yKpNoUCllhk5kymMjeS6QtdyldCuHfcs6FgUiQQSqUQgEk3dJY70pF57oS8wURo7N1TIBd64Z0GgY1HfodRA6rXAqVIgdN+SK21tbZlnt4o9J41W2OjNo9Qy72Y421OcVGzvD6R9fQcNcdb7A4WhSm3FQ4GhWu7CimUrt6T5rJvJacruHcruHEosldo38PI3ykjmQi7Qk4ilYoElJ/qOvTJwoi+Z4s33daMeeGDJiyna8szs725+zf6vmz8Tf+71U5WJzGmT/5ncucxHhdoXE6VcJVe6lFsWCGdOQzsCb+ds8I3T6R2+2/qv/ZjNvit0IjcxVhmqjZWuDZpXhHfanE2rKzSQCO0o53Ceamn5rGdTrC3Ws6YtkuiJbYts2LJlXWRbbNWayIbEE7E9sZ4Na9Y91vdVR+vWx9+9pa5NmvwKhVaTzQe5U7WWQqX+R+q+TKV20PxI54ZyZ0I7LmXK5t17PkkcOnSkdKxtT6pwLNbVnava0brt6abP1txGfwD+q8AH, which doesn't help either.
Any idea how to properly create a RepositoryItem from a .lst file? or how to create/get the descriptor?
You should try and use the class RepositoryImportUtil from the combit.ListLabel23.Repository namespace. This helper class does all the hard work for you. Given an IRepositoryinterface and the lst file in place, the required code would be something like
IRepository listLabelRepository = <yourRepository>;
using (ListLabel LL = new ListLabel())
{
LL.FileRepository = listLabelRepository;
using (RepositoryImportUtil importUtil = new RepositoryImportUtil(listLabelRepository))
{
importUtil.ImportProjectFileWithDependencies(LL,
#"<PathToRootProject>");
}
}
If this method is not what your require, the helper class has a couple of other methods as well to help you importing existing projects.

'An NSManagedObject of class 'className' must have a valid NSEntityDescription.' error

I've created simple entity 'CDWorkout' with one attribute 'name' inside CDModel.xcdatamodeld. Name of container in AppDelegate is also 'CDModel'. Class Codegen for 'CDWorkout' is Category/Extension. Here is code for CDWorkout class:
class CDWorkout: NSManagedObject {
class func createWorkout(workoutInfo : Workout, in context: NSManagedObjectContext) -> CDWorkout{
let workout = CDWorkout(context: context)
workout.name = "anyName"
return workout
}
}
the createWorkout function is called from another viewController with context argument as container.viewContext but it immediately crashes with message:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An NSManagedObject of class 'Workout_Generator.CDWorkout' must have a valid NSEntityDescription.'
What did i forget about?
The issue I had was that I needed to have the Class Module set to Current Product Module for my CDWorkout Entity.
In Xcode 10 there is a drop down in the Class section of the Data Model Inspector.
I had a silly minor issue that resulted in the same error. I was initializing NSPersistentContainer with the incorrect name.
It should have the same name as the source file with the extension .xcdatamodeld. e.g. modelFileName.xcdatamodelId
let persistentContainer = NSPersistentContainer(name: "modelFileName")
In my case the same problem was in #objc modifier in auto-generated header of data-class
#objc(CachedMovie)
public class CachedMovie: NSManagedObject {
I just removed #objc(CachedMovie) and it began to work
I came across this same error message when trying to insert/add a managed object from an extension (SiriKit). It seems to have been an issue with the namespace of the extension not matching the .xcdatamodeld file, because I was creating the entity description using MyClass.entity().
The combination that worked for me was:
#objc(MyClass) at the top of each NSManagedObject subclass
Entities in the data model use the default "Global namespace", not "Current Product Module"
Create the entity description using let entity = NSEntityDescription.entity(forEntityName: "MyClass", in: context)!
In AppDelegate check persistent container name.
let container = NSPersistentContainer(name: "CoreData")
If you trying to reach nonexistent Core Data, that may manifest as this error.
Name should be exact same as .xcdatamodeld object in navigation menu.
If you are using coreData from a static library make sure you specify the module aswell
This gives you the correct entity access MyStaticLibrary.MyManagedObject
So if your receiving warnings with this dot notation, its not looking in your module
One way this could happen is by trying to rename your entities. Make sure that these are the same!
I've got the same issue. In my case I used CoreData initialised and managed in framework wich I integrated with main app via SPM.
Solution
First, in the framework provide module name for entity by hand, by following steps:
open .xcdatamodel,
select Entity which you want to edit,
open Data Model Inspector (the 4th panel in right sidebar),
at section Class element Module by default presents Current Product Module -- enter here your Module name
Repeat these steps for each entity.
Second, in the framework override description for each NSManagedObject you use in project, f.ex. you have:
public class Person: NSManagedObject {
}
override there description with String without module name, like:
public class Person: NSManagedObject {
public override var description: String {
return "Person"
}
}
Code presented above will help if you use convenience initialiser for Person (like Person(context: Context)) which uses description to specify Person.entity().
For me, I forgot to add #objc(Reminder) in this below example. I wrote the NSManagedObject class programmatically.
#objc(Reminder)
public class Reminder: NSManagedObject {
}
I encountered this issue using Swift Package Manager to import a Swift Package that had a model file included as a resource. In my case setting the Module of my entity to Current Production Module produced an incorrect value in the model file. When I used the debugger to print the model after loading the persistent container the fully qualified class name was something like Module_Module.Class instead of the expected Module.Class. I had to manually type the module name instead of using Current Production Module to resolve the issue.
in your file modeldataClass
probably name of Class is incorrect cause before you change name something in your name class
The mistake I made was to change the name of the entity and the generated "NSManagedObject Subclass" and not updating the name of the class. How to fix:
open .xcdatamodeld
click on the entity
open the right panel
Go to Data Model Inspector
Change the name of the Class text field
What fixed it for me was that I had an empty space in the name and didn't notice because of a line break. Removing it fixed the problem..
It was:
container = `NSPersistentContainer(name: "ContainerName ")`
Instead of:
container = NSPersistentContainer(name: "ContainerName")
I had similar issue in CoreData stack with NSManagedObjectModel made from Swift code. The issue was in wrong value for NSEntityDescription.managedObjectClassName attribute. The Swift module prefix was missed.
Correct setup:
let entity = NSEntityDescription()
entity.name = PostEntity.entityName // `PostEntity`
entity.managedObjectClassName = PostEntity.entityClassName // `MyFrameworkName.PostEntity`
entity.properties = [....]
Where: entityName and entityClassName defined like this.
extension NSManagedObject {
public static var entityName: String {
let className = NSStringFromClass(self) // As alternative can be used `self.description()` or `String(describing: self)`
let entityName = className.components(separatedBy: ".").last!
return entityName
}
public static var entityClassName: String {
let className = NSStringFromClass(self)
return className
}
}
I was trying out adding CoreData to existing project and I renamed things a lot. I ended up with multiple .xcdatamodeld without knowing it. The solution was removing .xcdatamodeld and generated NSManagerObject and recreating it again.
it easy to answer to this question. without removed
#objc(Workout)
the solution is on documentary Core Data Programming on "Entity Name and Class Name".
here in your Xcode before doing (Editor -> Create NSManagedObject SubClass)
must change your class Name of Entities to add "MO", CoreData can differentiate between class Name and Entitie Name. and the
#objc(Workout)
will not be created, give us this one:
class CDWorkoutMO: NSManagedObject {
class func createWorkout(workoutInfo : Workout, in context: NSManagedObjectContext) -> CDWorkoutMO {
let workout = CDWorkoutMO(context: context)
workout.name = "anyName"
return workout
}
}
like I do on my Xcode
For SwiftUI you need to also update this method from SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//This gets the context from AppDelegate and sets is as moc environment variable
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let contentView = ContentView().environment(\.managedObjectContext, context)
//...
}
In my case, I had my Core Data model inside a Swift Package and had to follow this amazing guide and finally this is what did it:
I opened the model and selected the entity and in the right panel I selected the Data Model Inspector and in the Class panel, I manually entered the Module’s name i.e. the Swift Package name.
For me this worked,
Adding
#objc('YourEntityClassName')
above the class declaration in 'YourEntityClassName+CoreDataClass' file.
Note: I am using this in an SPM package.

Umbraco 7 generic node class

With the help of other Stackoverflow users, I have gone some way to my solution but have come to a halt.
I would like to build some generic classes in an app_code .cshtml file eg one would be to return property values from documents from a function eg
public static string docFieldValue(int docID,string strPropertyName){
var umbracoHelper = new Umbraco.Web.UmbracoHelper(Umbraco.Web.UmbracoContext.Current);
var strValue = "";
try{
strValue = umbracoHelper.Content(docID).GetPropertyValue(strPropertyName).ToString();
}
catch(Exception ex){
strValue = "Error - invalid document field name (" + strPropertyName + ")";
}
var nContent = new HtmlString(strValue);
return nContent;
}
This works ok for returning one field (ie property) from a document. However, if I wanted to return 2 or more, ideally, I would store the returned node in a variable or class and then be able to fetch property values repeatedly without having to look up the document with each call
ie without calling
umbracoHelper.Content(docID).GetPropertyValue(strPropertyName).ToString();
with different strPropertyName parameters each time, as I assume that will mean multiple reads from the database).
I tried to build a class, with its properties to hold the returned node
using Umbraco.Web;
using Umbraco.Core.Models;
...
public static Umbraco.Web.UmbracoHelper umbracoHelper = new Umbraco.Web.UmbracoHelper(Umbraco.Web.UmbracoContext.Current);
public static IPublishedContent docNode;
...
docNode = umbracoHelper.Content(docID);
but this crashed the code. Can I store the node in a property on a class, and if so, what type is it?
First of all, using a .cshtml file is unnecessary, use a .cs file instead :-) CSHTML files are for Razor code and HTML and stuff, CS files are for "pure" C#. That might also explain why your last idea crashes.
Second of all, UmbracoHelper uses Umbracos own cache, which means that the database is NOT touched with every request. I would at least define the umbracoHelper object outside of the method (so it gets reused every time the method is called instead of reinitialised).
Also, beware that property values can contain all kinds of other object types than strings.
EDIT
This is an example of the entire class file - my example namespace is Umbraco7 and my example class name is Helpers:
using Umbraco.Web;
namespace Umbraco7
{
public class Helpers
{
private static UmbracoHelper umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
private static dynamic docNode;
public static string docFieldValue(int docID, string strPropertyName)
{
docNode = umbracoHelper.Content(docID);
return docNode.GetPropertyValue(strPropertyName).ToString();
}
}
}
This is an example how the function is called inside a View (.cshtml file inside Views folder):
#Helpers.docFieldValue(1076, "introduction")
Helpers, again, is the class name I chose. It can be "anything" you want. I've just tested this and it works.
I suggest you read up on general ASP.NET MVC and Razor development, since this is not very Umbraco specific.

How can I selectively apply a VSTemplate?

I am creating a custom VSTemplate for MVC 4 applications for my company that uses a wizard that is comparable to the wizard that appears when you create a new MVC4 application. I have one of two templates I would like to apply when the developer creates a new app of this type as shown here:
Both of those entries correspond to templates that are defined inside my VSIX project under a folder called ProjectTemplates:
My question is, how do I apply the correct template when the wizard runs? I know how to create a vstemplate with multiple projects (using the ProjectCollection node in the vstemplate), but that's not really what I want to do since they will never be deployed together. I see that I can add both vstemplates as Assets to my vsixmanifest file, but I'm not really sure how to apply just one template conditionally.
Thanks!
You'll need to include the files for your "optional" template(s) in sub directories of the the "root" template folder but EXCLUDE them from the TemplateContent Element of the "root" template.
Your IWizard implementation needs to keep a reference to the EnvDTE.DTE object (the first parameter of RunStarted) and use it in the ProjectFinishedGenerating to add the projects to the solution using the template(s) that match what the user selected.
public class SelectTemplatesWizard : IWizard
{
private EnvDTE.DTE _dte = null;
private string _solutionDir = null;
private string _templateDir = null;
public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
{
// Store the reference to the environment for later use
_dte = automationObject as EnvDTE.DTE;
/*
The value of the item in the replacements dictionary for the key
$destinationdirectory$ is populated with the physical location of
the directory (named based on the user entered project name) created
sibling to the solution file.
The solution directory will be this directories parent
when the Type attribute of the VSTemplate element is ProjectGroup
*/
_solutionDir = System.IO.Path.GetDirectoryName(replacementsDictionary["$destinationdirectory$"]);
// customParams[0] is a default custom param that contains the physical location of the template that is currently being applied
_templateDir = System.IO.Path.GetDirectoryName(customParams[0] as string);
}
public void ProjectFinishedGenerating(Project project)
{
int userSelected = 1;
string name= null, projectPath= null, templatePath = null;
switch (userSelected)
{
case 0:
{
name = "Angular";
projectPath = System.IO.Path.Combine(_solutionDir, "Angular");
templatePath = System.IO.Path.Combine(_templateDir , "Angular\Angular.vstemplate");
}
break;
case 1:
{
name = "MVC4";
projectPath = System.IO.Path.Combine(_solutionDir, "MVC4");
templatePath = System.IO.Path.Combine(_templateDir , "MVC4\MVC4.vstemplate");
}
break;
}
_dte.Solution.AddFromTemplate(templatePath, projectPath, name);
}
/* Other IWizard methods excluded for brevity */
}

Mvvmcross binding file url to imageview

I try to bind imageview with local image file. In android, I can use setImageUrl to set image from a file outside resource folder. I read N+1 kitten example and try to use file url instead web url for my project.
The layout of image view
<Mvx.MvxImageView
android:id="#+id/advisor_message_picture"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginLeft="8dp"
android:layout_alignParentRight="true"
local:MvxBind="ImageUrl MessageImage, Converter = Image" />
The converter use to join file name and file directory url. Android view file will set the FileDir
public class ImageConverter : MvxValueConverter<string, string>
{
public static string FileDir;
protected override string Convert(string value, Type targetType, object parameter, CultureInfo culture)
{
return FileDir + "/" + value;
}
}
Update After the answer
I first copy or download to file to the Context.FilesDir.Path and check it with SetImageUrl, the image show up.
view.FindViewById<ImageView>(Resource.Id.advisor_message_picture).SetImageURI( new FileService(_context).CopyFileFromAssetsToStorage("image.png"));
Then I set the FileUrl of converter using same path and file name
ImageConverter.FileDir = FilesDir.Path;
In ViewModel
_messageImage = "image.png";
private string _messageImage;
public string MessageImage
{
get { return _messageImage; }
set { _messageImage = value; RaisePropertyChanged(() => MessageImage); }
}
It works now. The problem is I misunderstood the binding time of viewmodel
For Asset's you can bind using AssetImagePath using the ResourceLoader plugin. However, due to a sticky-fingers editing bug, this custom binding does currently need to added to your Setup - see https://github.com/slodge/MvvmCross/issues/372 for bug details
For files stored using the file plugin (which defaults to Context.FilesDir.Path - see https://github.com/slodge/MvvmCross/blob/v3/Plugins/Cirrious/File/Cirrious.MvvmCross.Plugins.File.Droid/MvxAndroidFileStore.cs#L39), you can use path directly.
For files stored in some custom FileDir determined within your app, you'll need to provide a path relative to Context.FilesDir.Path in order for the plugin to load it.
For further debugging, you could add breakpoints or trace to https://github.com/slodge/MvvmCross/blob/v3/Plugins/Cirrious/DownloadCache/Cirrious.MvvmCross.Plugins.DownloadCache.Droid/MvxAndroidLocalFileImageLoader.cs#L28 - or you could build and register your own IMvxLocalFileImageLoader<Bitmap> implementation that knows about your file paths.