Calling NSString method on a String in Swift - nsstring

Apple's Swift documentation states that
If you are working with the Foundation framework in Cocoa or Cocoa Touch, the entire NSString API is available to call on any String value you create
If I have a String object eg
var newString: String = "this is a string"
How do I perform NSString operations like containsString on my String var?

After doing some research, it looks like containsString is not a String function, but can be accessed by bridging to an NSString.
Under Apple's Documentation on Using Swift with Cocoa and Objective-C, it says that
Swift automatically bridges between the String type and the NSString
class. This means that anywhere you use an NSString object, you can
use a Swift String type instead and gain the benefits of both types
But it appears that only some of NSString's functions are accessible without explicitly bridging. To bridge to an NSString and use any of its functions, the following methods work:
//Example Swift String var
var newString:String = "this is a string"
//Bridging to NSString
//1
(newString as NSString).containsString("string")
//2
newString.bridgeToObjectiveC().containsString("string")
//3
NSString(string: newString).containsString("string")
All three of these work.
It's interesting to see that only some NSString methods are available to Strings and others need explicit bridging. This may be something that is built upon as Swift develops.

The methods are the same, but they just use swift's syntax instead. For example:
var newString: String = "this is a string"
newString = newString.stringByAppendingString(" that is now longer")
println(newString)
instead of:
NSString* newString = #"this is a string";
newString = [newString stringByAppendingString:#" that is now longer"];
NSLog(newString);
Note:
Not all of NSString's methods can be called on String. Some you have to bridge to NSString first like so:
newString.bridgeToObjectiveC().containsString("string")

So what you actually have to do is this
var str:NSString = "Checkcontains"
str.containsString("ckc")
Notice how the declaration of an explicit NSString is put there. Then you can use the containsString method. If you only use var and do not set it as an NSString then you have problems.

You can call any NSString method on a Swift String object; under the hood, the two objects are 'toll-free bridged'. Thus, you can just do:
var newString = "this is a string"
if newString.containsString("string") {
//do your stuff
}

Related

Validate Smart Format object against the token available in the string

I am trying to validate the smart format object against the token present in the string.
Sample:-
object obj = {"Id": "1", "RegNo": "REG123"}
Smart.Format("Your Id - {Id} for registration - {RegNo}", obj);
If I do not pass RegNo property/value in the object then smart format throws an error. Instead do we have any proper validation method to validate the tokens required against the object provided.
Any help would be appreciated.
SmartFormat provides various extensions which allow to format a string depending on the provided arguments. Here is the example code for a missing "RegNo". Still, this is not "validation", but rather "conditional formatting".
// Create a formatter with necessary extensions, including NewtonsoftJsonSource
var smart = new SmartFormatter(settings ?? new SmartSettings())
.AddExtensions(new NewtonsoftJsonSource(), new DefaultSource())
.AddExtensions(new NullFormatter(), new DefaultFormatter());
// Parse the JSON input
var jObject = JObject.Parse(#"{""Id"": ""1"", ""RegNo"": null}");
var result = smart.Format("Id: {Id:isnull:Value is Null|{}} - RegNo: {RegNo:isnull:Value is Null|{}}", jObject);
// Result: "Id: 1 - RegNo: Value is Null"

Xamarin.Forms (iOS 14.1) Observables against NSUserDefaults using a key with dots don't get triggered

Am I doing something wrong or is this a bug?
This code works (the observable gets called)
NSUserDefaults.StandardUserDefaults.AddObserver(this, "sectionname_mykey", NSKeyValueObservingOptions.New, IntPtr.Zero);
NSUserDefaults.StandardUserDefaults.SetString("test", "sectionname_mykey");
But this one doesn't
NSUserDefaults.StandardUserDefaults.AddObserver(this, "sectionname.mykey", NSKeyValueObservingOptions.New, IntPtr.Zero);
NSUserDefaults.StandardUserDefaults.SetString("test", "sectionname.mykey");
The tooltip for the keyPath reads "Key-path to use to perform the value lookup. The keypath consists of a series of lowercase ASCII-strings with no spaces in them separated by dot characters" but the observables don't get called if I add a dot.
Because NSObject does not support using this way to set a key in iOS, and the inner error will show something like the following:
Foundation.MonoTouchException: 'Objective-C exception thrown. Name: NSUnknownKeyException Reason: [<IOSSplashScreen_Item 0x600002fc1510> addObserver:<ViewController 0x7fa6f35204b0> forKeyPath:#"sectionname.mykey" options:1 context:0x0] was sent to an object that is not KVC-compliant for the "sectionname" property.
If you need to an observer for an attribute of sectionname, you can use the following code:
// Sectionname should be a NSobject object and have observer notification
Sectionname sectionname = new sectionname();
sectionname.AddObserver(this, "smykey", NSKeyValueObservingOptions.New, IntPtr.Zero);

Newtonsoft Json JsonSerializationException with simple string

My datasource creates the JSON representing an array of integers as "1,2,3,4,5". I can't do anything about this (Like changing it to [1,2,3,4,5]), it is an enterprise CMS that we have to just deal with.
I'm trying to read up on how the newtonsoft ToObject method handles the following code:
JValue theValue = new JValue("1,2,3")
List<int> x = theValue.ToObject<List<int>>();
I get a Newtonsoft.Json.JsonSerializationException. Could not cast or convert from System.String to System.Collections.Generic.List`1[System.String]. I understand this fully, but I'd like to know if the Newtonsoft JSON libraries have a built in way to convert from a comma delimited string to a List.
I'd like to think there's a better way than trying to check if the variable is a comma delimited list or not and then converting it to a List<> manually, or maybe a JArray, but I've been wrong before !
EDIT
I wanted to share my solution:
dynamic theValue = new JValue("1,2,3,4"); /// This is just passed in, i'm not doing this on purpose. Its to demo.
if (info.PropertyType == typeof (List<int>))
{
if (info.CanWrite)
{
if (theValue.GetType() == typeof (JValue) && theValue.Value is string)
{
theValue = JArray.Parse("[" + theValue.Value + "]");
}
info.SetValue(this, theValue.ToObject<List<int>>());
}
} else {
// do other things
You have three problems from what I can see:
You should be using JArray not JValue. You are intending this to be an array of things, so you need to use the equivalent class in Newtonsoft to represent an array. (A JValue, as best I can tell, represents a simple type--e.g. string, number, Date, etc.)
You should use the Parse method versus using the constructor. Parse will read the content of the string as an array, however...
...in order for it to do that, you will need to surround the data that you get with the square brackets or JArray can't correctly the parse the data. There is no need to fiddle with the CMS; just do a string concat before you parse.
e.g.
JArray theValue = JArray.Parse("[" + "1,2,3" + "]");

NVelocity not finding the template

I'm having some difficulty with using NVelocity in an ASP.NET MVC application. I'm using it as a way of generating emails.
As far as I can make out the details I'm passing are all correct, but it fails to load the template.
Here is the code:
private const string defaultTemplatePath = "Views\\EmailTemplates\\";
...
velocityEngine = new VelocityEngine();
basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, defaultTemplatePath);
ExtendedProperties properties = new ExtendedProperties();
properties.Add(RuntimeConstants.RESOURCE_LOADER, "file");
properties.Add(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, basePath);
velocityEngine.Init(properties);
The basePath is the correct directory, I've pasted the value into explorer to ensure it is correct.
if (!velocityEngine.TemplateExists(name))
throw new InvalidOperationException(string.Format("Could not find a template named '{0}'", name));
Template result = velocityEngine.GetTemplate(name);
'name' above is a valid filename in the folder defined as basePath above. However, TemplateExists returns false. If I comment that conditional out and let it fail on the GetTemplate method call the stack trace looks like this:
at NVelocity.Runtime.Resource.ResourceManagerImpl.LoadResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.Resource.ResourceManagerImpl.GetResource(String resourceName, ResourceType resourceType, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name, String encoding)
at NVelocity.Runtime.RuntimeInstance.GetTemplate(String name)
at NVelocity.App.VelocityEngine.GetTemplate(String name)
...
I'm now at a bit of an impasse. I feel that the answer is blindingly obvious, but I just can't seem to see it at the moment.
Have you considered using Castle's NVelocityTemplateEngine?
Download from the "TemplateEngine Component 1.1 - September 29th, 2009" section and reference the following assemblies:
using Castle.Components.Common.TemplateEngine.NVelocityTemplateEngine;
using Castle.Components.Common.TemplateEngine;
Then you can simply call:
using (var writer = new StringWriter())
{
_templateEngine.Process(data, string.Empty, writer, _templateContents);
return writer.ToString();
}
Where:
_templateEngine is your NVelocityTemplateEngine
data is your Dictionary of information (I'm using a Dictionary to enable me to access objects by a key ($objectKeyName) in my template.
_templateContents is the actual template string itself.
I hope this is of help to you!
Just to add, you'll want to put that into a static method returning a string of course!
Had this issue recently - NVelocity needs to be initialised with the location of the template files. In this case mergeValues is an anonymous type so in my template I can just refer to $Values.SomeItem:
private string Merge(Object mergeValues)
{
var velocity = new VelocityEngine();
var props = new ExtendedProperties();
props.AddProperty("file.resource.loader.path", #"D:\Path\To\Templates");
velocity.Init(props);
var template = velocity.GetTemplate("MailTemplate.vm");
var context = new VelocityContext();
context.Put("Values", mergeValues);
using (var writer = new StringWriter())
{
template.Merge(context, writer);
return writer.ToString();
}
}
Try setting the file.resource.loader.path
http://weblogs.asp.net/george_v_reilly/archive/2007/03/06/img-srchttpwwwcodegenerationnetlogosnveloc.aspx
Okay - So I'm managed to get something working but it is a bit of a hack and isn't anywhere near a solution that I want, but it got something working.
Basically, I manually load in the template into a string then pass that string to the velocityEngine.Evaluate() method which writes the result into the the given StringWriter. The side effect of this is that the #parse instructions in the template don't work because it still cannot find the files.
using (StringWriter writer = new StringWriter())
{
velocityEngine.Evaluate(context, writer, templateName, template);
return writer.ToString();
}
In the code above templateName is irrelevant as it isn't used. template is the string that contains the entire template that has been pre-loaded from disk.
I'd still appreciate any better solutions as I really don't like this.
The tests are the ultimate authority:
http://fisheye2.atlassian.com/browse/castleproject/NVelocity/trunk/src/NVelocity.Tests/Test/ParserTest.cs?r=6005#l122
Or you could use the TemplateEngine component which is a thin wrapper around NVelocity that makes things easier.

converting a string to a constant string

I have the following code...
Const ToAddress As String = username.Text & "#gmail.com"
which sets to ToAddress to be used in on my Net.Mail.MailMessage
that is to be created with the following constructor
Dim mm As New Net.Mail.MailMessage(username.Text, ToAddress)
which takes in a string and a constant string. But I get an error here
Const ToAddress As String = **username.Text** & "#gmail.com"
that says : constant expression required
how to use mailmessage
username.text is variable, you wont be able to create a const
you can always create a function that would do validation on the username.text
public function ToAddress(byval _username as string) as MailAddress
'
'validation here for _username
'
return new MailAddress(_username & "#gmail.com")
end function
That is not how a Const should be used. See the first line here. "a constant is a special kind of variable whose value cannot be altered during program execution"
Edit: See Fredou's answer for the correct syntax.
VB.NET and C# are not C. Constant modifier is just used for constants whose values are known at the time of compilation.
Remember that a constant is a value that is compiled as a literal into the assembly by the compiler. This is why you cannot define constants as an expression because the compiler cannot evaluate the expression at compilation time.
When you define a constant the compiler takes the value of that constant and replaces all uses of the constant in your source with the literal value. In your example I don't see any reason why you would need to define your string as Const since the method receiving the string will have no way of determining if the string was a Const at compilation time.
Rather than using a Const, specifically, if you just want the "constant" behavior for the life of an instance of your class, try:
ReadOnly ToAddress As String = username.Text & "#gmail.com"
Thank you for your assistance. I closed visual studio and restarted it and now it's sending mail works fine. Seems like VS has a bug...

Resources