NSString with fixed length - objective-c

I want an nsstring object that can only store specified lenght of character in it.
If it exceeds it should get truncated from left. For example if i set lenght to 5, and I enter value as Ileana then it should store leana.
I tried by making a category on nsstring but i am out of ideas :
-(void)setMaximumLength:(NSInteger)length;{
if ([self length]>length) {
NSLog(#"exxed");
}
}
Please suggest what should I do? I hve one thng in my mind I need to observe the string size but how to do in a category, and which notification will be called?

This sounds suspiciously like formatting for display or processing user input or something like that rather than an actual constraint you want to impose on strings in general. In that case, an NSFormatter class or a bit of code in some specific controller property setter would be a good idea.
But if you really want this to be on the string itself, you either need to provide a method like stringByTruncatingToLength: on NSString or you need to switch to using NSMutableString everywhere, because NSStrings cannot be modified at all and thus a setMaximumLength: method would be kind of meaningless.

The mechanism of truncation can be implemented like this:(for a NSString property of a custom class)
-(void)setName:(NSString*)newName
{
if([newName length]> maxLength)
{
newName = [newName substringWithRange:NSMakeRange([newName length] - maxLength, maxLength];
}
_name = newName;
}

Related

(NSString *) description -- not clear on something.... (Objective-C)

Let's say I have two array's. Let's imagine one is a NSMutableDictionary, the other is an NSMutableArray.
I also have this defined:
-(NSString *) description {
// return a human readable version of the array contents
return self.contents;
}
Then, for clarity sake, I want to print an array using something like this:
self.descriptionOfLastFlip = [NSString stringWithFormat:#"Array %#",[cardsFaceUp componentsJoinedByString:#", "]];
Then, of course, using that self.descriptionOfLastFlip to print something to the screen.
Ok, stupid question time... How would I define two separate description methods for dealing with the array and the dictionary in a different way? Obviously I'd probably want to access the info slightly differently, but, self.description, while it might work for the array, wouldn't work for the dictionary..
I'd love some insight on how to deal with creating a description method for multiple array's/dictionary's (or, how can you 'target' a description to one type of array, etc..?)
I tend to do things like this:
- (NSString *)description {
return [NSString stringWithFormat:#"MyClass { array = %#, dictionary = %# }", someArray, someDictionary];
}
Replace someArray and someDictionary with whatever properties or ivars you wish to include.

NSKeyedArchiver and AttributedString [duplicate]

I am trying to store an NSAttributedString to a Core Data SQL store.
I have the property set as a "transformable", it is optional and it is NOT transient or indexed and the value transformer name is set to default "NSKeyedUnarchiveFromData". In the .xcdatamodel and generated the managed object class which has this in the .h:
#property (nonatomic, retain) id Text; (I have tried changing id to NSAttributedString *Text)
and this in the .m:
#dynamic Text;
I look through and set the ".text" property of my NSManagedObject to the attributed string then when completed I do:
NSError *error = nil;
[managedObjectContext save:&error];
This through is causing this error in the output:
[NSCFType encodeWithCoder:]:
unrecognized selector sent to instance
0xc04edb0 Terminating app due to
uncaught exception
'NSInvalidArgumentException', reason:
'* -[NSCFType encodeWithCoder:]:
unrecognized selector sent to instance
0xc04edb0'
I have checked the class of what I am storing to the property and it is NSAttributedString also I check responsesToSelector #selector(:) and this returns true so very confused as this is contrary to the error message?
Please advise.
Thanks
James
For anyone experiencing this problem I found the easiest solution:
In Core Data add an attribute, let's call it attributedText. Then define its type as Transformable. After you create the .h file, change the data type of attributedText from NSDictionary to NSAttributedString.
Now you can save the NSAttributedString in Core Data with no modification needed.
Recalling it is as easy as going:
myObject.attributedText
which will return your NSAttributedString!
Hope this helps someone.
I was checking the Apple Developer Forums and found a thread almost exactly the same as this question, one person had done this but unfortunately did not share the code. All they said was the following:
"In Core Data i have an transformable in the database and i us my own NSVauleTransformer. This is a subclass of NSValueTransformer and creates an attributed string from the data object and back.
Therefore i created a class called PersistableAttributedString which is NSCoding compliant. This class has a string and an array of attributes and builds the attributed string. I also created a class for the possible text attributes which is NSCoding compliant. Together the string and the attributes are all NSCoding compliant.
The class NSAttributedString is also NSCoding compliant, but the attributes are not, that's the problem."
Hope that might help.
In Xcode 10 with automatic code generation this is a lot simpler than the other suggestions.
Select the name of the Attribute and open the Data Model inspector (Command+Option+3)
Set Attribute Type to Transformable
Set Custom Class to NSAttributedString
And that's it, now you can just save your data in your Swift code as you'd expect, e.g.:
detailItem.content = textView.attributedText
Another idea would be to create a Custom Class of NSAttributedString and somewhere use enumerateAttributesInRange:options:usingBlock: to get all the attributes of the string and then save the NSDictionary with the attributes and ranges in to Core Data aswell as the attributed string stripped of it's attributes.
Then when the string is loaded again you could apply the attributes that are in the dictionary to the attributed string using initWithString:attributes:.
It's the font that's giving you grief - a CTDictionary is toll-free bridged to NSDictionary which implements NSCoding so should encode fine.
You might have to deal with the font yourself :( - here's a sucky way of doing it.
1) Instead of storing the NSAttributedString, break it down and put each of it's components into an array.
2) Go through the array - if you see font ref you must store just the information required to re-create this font - have a look at the CTFontCopyFontDescriptor function and the CTFontDescriptorCopyAttribute function should let you get font attributes as a string. Put all these into a NSDictionary which should store in core data fine.
3) Store this array in core data - hopefully all the items in the array will be NSCoding compliant so you should be fine.
...
To recreate your string, when you load from coredata, if you see an NSDctionary representing font attributes you should be able to re-create the fCTFontDescriptor and from that the font.
Then, put your string back together.
I found a way to save attributed text in Swift 4 that does not use a header file. My Core Data store consists of an entity called "AttrNote" with attributes of "title" and "notes". "title" is of type "String" but "notes" is of type "transformable". Then within the view controller where note entry/editing is done I have the following for the save button:
#IBAction func saveBtn(_ sender: Any) {
var note: AttrNote!
//other code
if let title = titleTextField.text {
note.title = title
}
if let noteText = notesTextView.attributedText {
note.notes = noteText
}
And the function that is called to load the data has the following:
func loadNoteData() {
if let note = noteToEdit {
titleTextField.text = note.title
notesTextView.attributedText = note.notes as! NSAttributedString
}
}
And I have the following in ViewDidLoad that enables the B/I/U formatting options to appear with the selection of text:
notesTextView.allowsEditingTextAttributes=true
I am able to save attributed text and then view/edit it later.
OK... Some kind of break through although not a good one...
If we NSLog the attributed string then we can see in there it has NSFont and NSParagraphStyle in. Although these are NOT NSFont and NSParagraphStyle in the code these are CTFontRef and CT Dictionaries of paragraph styles... These are not NS objects although in NSLog they output as these and therefore guess that is why we can not perform the "encodeWithCoder" selector on the object.
IF in the code we just do "NSFont;" the compiler says "NSFont undeclared" so what can we do as we only have the CT functions?
As my colleague said in the comments above if we set the ".text" property to just "NSAttrinutedString *string = [NSAttributedString alloc] initWithString:#"test"] it saves fine and if we remove all the styling from the one we WANT to save it also works!
THERE MUST BE A WAY OF STORING NSATTRIBUTED STRING WITH STYLING INTO CORE DATA... NO?
Any ideas greatly appreciated.
-JM

Issue comparing NSString literal and constant

I have an NSString, firstWord, that has the value of "First". It was defined like this:
NSString *firstWord = [[NSString alloc] init];
firstWord = [textView text];
However, when I try to check its value, like so:
if (firstWord == #"First"){}
The comparison does not return true, or YES I guess in Objective-C. I know that it does have the same value as a string. So, from this I am pretty sure that the issue is that I am comparing pointers, which are not the same here, even though the strings themselves do have the same value. So how can I directly compare the strings themselves? Or maybe more efficiently, make sure the two string objects do have the same pointer, so I don't have to the do the relatively costly string comparison?
So how can I directly compare the strings themselves?
String comparison in Cocoa is done with isEqualToString:.
Or maybe more efficiently, make sure the two string objects do have the same pointer,
This isn't possible. One is a string literal, stored in the DATA section of your app's binary; the other is on your app's heap. In certain circumstances (creating a string using initWithString:#"literal string") you'll end up with the same address, but you shouldn't rely on that.
As an aside, you don't need to -- in fact, shouldn't, because you're creating a leak -- allocate a string before assigning the text view's text to the pointer.
There are actually two errors.
Your actual question:
if (firstWord == #"First"){}
needs to be
if ([firstword compare:#"first"]==NSOrderedSame) ...
Only works if firstword isn't nil.
In addition:
NSString *firstWord = [[NSString alloc] init];
firstWord = [textView text];
This looses memory, since you are not "copying" the textview into the string (which wouldn't be possible anyway since it's not mutable), you are simply assigning another string object to the pointer. So the empty string that you've allocated is lost.
Just do
NSString *firstWord;
firstWord = [textView text];
EDIT: making sure the strings have the same pointers would be even more costly, since you would have to keep a table of string objects and lookup every string there so you can either replace your pointer with the "unique" one or add the new string to the table...
Start to learn Objective-C from Swift. Here is what I do to give different layout code per deviceModel type.
#property (nonatomic, strong) NSString *deviceModel;
NSString *iPhoneSE = #"iPhone8,4";
if ([self.deviceModel isEqualToString:iPhoneSE]) { // iPhone SE
// layout code
}

Generate #property implementations with C preprocessor (uppercase a character in the preprocessor)

I may be trying to abuse the preprocessor. I want to see if what I have in mind is even possible.
My class has #properties that all have the same bodies. I want to generate these bodies with a preprocessor macro. E.g.:
- (float) accelerometerSensitivity {
return [dict floatForSelector:_cmd or:1];
}
- (void) setAccelerometerSensitivity:(float) n {
[dict setFloat:n forSelector:_cmd];
[dict writeToFile:[self globalDataFilename] atomically:YES];
}
- (float) returnSpringTension {
return [dict floatForSelector:_cmd or:0];
}
- (void) setReturnSpringTension:(float) n {
[dict setFloat:n forSelector:_cmd];
[dict writeToFile:[self globalDataFilename] atomically:YES];
}
// set*ForSelector methods are in a category on NSMutableDictionary and depend on a function that translates selectors into strings:
// NSString* keyFromSelector(SEL selector);
The idea is that instead of using string literals (or string constants) as keys into the dictionary, I derive the string from the selector name. This way I am sure that the spelling of the key matches the property name and essentially get the benefit of compile-time validation of dictionary keys.
What I want to do is say something like SELECTOR_PROPERY(accelerometerSensitivity) and have it expand into the the getter and the setter. The main difficulty I have in implementing this as a pre-processor macro is generating the setter name from the property name. I need to uppercase the first letter of the property name, and I have no idea how to do that in the preprocessor.
Nope, you can't do that.
But, you can combine identifiers, so in theory you could define this as:
MACRO(A,a,ccelerometerSensitivity)
It's somewhat klugey, but it's more terse than the alternative.
Here's how I'd do it:
#define MACRO(_a) { \
const char *name = #_a; \
NSString *getterName = [NSString stringWithUTF8String:name]; \
NSString *setterName = [NSString stringWithFormat:#"set%c%s:", toupper(name[0]), (name+1)]; \
NSLog(#"getter name: %#", getterName); \
NSLog(#"setter name: %#", setterName); \
}
Basically, you stringify the macro parameter, then use a simple C function to uppercase the first letter, and use an offset to get everything after the first letter.
Now when you do this:
MACRO(foo);
MACRO(bar);
It logs this:
2011-07-19 21:21:24.798 EmptyFoundation[16016:903] getter name: foo
2011-07-19 21:21:24.800 EmptyFoundation[16016:903] setter name: setFoo:
2011-07-19 21:21:24.801 EmptyFoundation[16016:903] getter name: bar
2011-07-19 21:21:24.802 EmptyFoundation[16016:903] setter name: setBar:
HOWEVER, these are strings. You can't use them as method names. Sorry. :(
Actually, you probably really don't want to do this purely for architectural reasons.
You'll likely be better off if you:
separate the notion of setting state from persisting state. That you are causing I/O with every single tiny little change is horribly inefficient. It is also a mode rife with potential for problems; what happens if you move to a UI where the values track the UI continuously? ... you really don't want disk I/O for every time a dial/slider is tracked under a finger!
use #synthesize for all your #properties and don't even declare ivars. Leverage the tool's ability to generate exactly correct setters / getters for you.
that code looks an awful lot like you've re-invented NSUserDefaults? Use NSUserDefaults for any user preference kinda stuff.

iterate through object properties and change current property in a loop in objective-c

Newbie question: Can one substitue commands, properties or methods for NSStrings or char at runtime?
Lets say I do something like this:
//these are the names of my properties of an NSObject I created
char theChangingProperty[17][18] = {"name", "title", "number", "property4", "property5", "property6", "property7", "property8", "property9", "property10", "property11", "property12", "property13", "property14", "property15", "property16", "property17"};
(PS I know there is a better actual objective-c way of getting an objects properties, I even found a post with this Get an object properties list in Objective-C)
anyway, how does one iterate through a list and have the property name change? Lets say I'm using dot notation at runtime during a loop... If I wanted to set 17 (or 100!) different properties of my object all to values of some other array all at once, like this:
for (int t=1; t<17; t++) {
myObject.theChangingProperty[t] = valueOfAnotherArray[t];
}
I know objective-c is going to look for an actual property called "theChangingProperty". But I want that to be an array that will spit out the actual property that would change with each loop iteration (name, title, number, property4, etc)
Thanks for your time in answering a newbie question :)
What you want is called key-value coding. Specifically, the line you're trying to write is [myObject setValue:valueOfAnotherArray[t] forKey:[NSString stringWithUTF8String:theChangingProperty[t]]].
Dot notation is resolved at compile time, not runtime, therefore you cannot use it for this purpose. You'll have to use the setter name for this. i.e., setTheChangingProperty: and performSelector: with the appropriate object to pass in as the value.
Note that in a simple case, this will probably work just fine, however, someone could have gone in and done something like:
#property (nonatomic, retain, setter=fooBarBaz:) id blurgle;
You in the above case, would not be simply able to assume setBlurgle: is the right setter.
You can use setValue:forKey: but note that property keys need to be NSStrings, not C strings so declare your array:
NSString* theChangingProperty[17] = { #"name", ....
Then you would write:
for (int t = 0; t < 17; t++)
{
[myObject setValue: valueOfAnotherArray[t] forKey: theChangingProperty[t]];
}
Or you can use performSelector:withObject: but then your array needs to contain selectors
SEL theChangingProperty[17] = { #selector(setName:), ....
for (int t = 0; t < 17; t++)
{
[myObject performSelector: theChangingProperty[t] withObject: valueOfAnotherArray[t]];
}