So the new data assets class allows reading 'data', but there appears to be some content restrictions? I have a file like so test.csv (collapsed here for viewing as a single line but actually 1-line per string):
½f ⅛m 1½f ¼m 2½f ⅜m 3½f ½m 4½f ⅝m 5½f ¾m 6½f ⅞m 7½f 1m 1m ½f 1⅛m 1m 1½f 1¼m 1m 2½f 1⅜m 1m 3½f 1½m 1m 4½f 1⅝m 1m 5½f 1¾m 1m 6½f 1⅞m 1m 7½f 2m
The file I maintain via TextEdit, and read such like so (NSData category but various methods can return different inherent - to the 'type', data):
+ (id)assetWithName:(NSString *)name
{
NSDataAsset * asset = [[[NSDataAsset alloc] initWithName:name] autorelease];
NSData * data = [[[NSData alloc] initWithData:asset.data] autorelease];
NSAssert(data.length > 0, #"'%#' has zero data.length ", name);// Yoink
NSString * string = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSString * type = asset.typeIdentifier;
// default to Ascii when UTF8 doesn't work
if (!string.length)
{
string = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
}
if ([#"public.xml" isEqualToString:type])
{
return [self assetBook:string];
}
else
if ([#"public.comma-separated-values-text" isEqualToString:type])
{
return [self assetCSVs:string];
}
else
{
NSLog(#"'%#' has unknown asset type %#",name,type);
return [self assetCSVs:string];
}
}
All was dandy, until I altered the text.
I hated having the fractions like "½" stored as "1/2" so I took to replacing these by the single character equivalents.
However, once doing so, the assert fires, so the class appears to not like my edits. I've taken to in-lining the file as a single string (above) - yuck, which I pull apart (-componentsSeparatedByString:) to an array, but perhaps someone else can tell me what's wrong with the approach?
Overall I favor assets' data obfuscation but it appears to have limits.
Not sure why, but naming my text files with a ".dat" type works.
The asset type reported is "dyn.ah62d4rv4ge80k2py" which I'm guessing is like the old Finder first few file bytes contents but this type was the same for several .dat files of differing content so it's probably related the the method used.
Related
I'm using NSDrawNinePartImage() to draw a stretchable control. Since this of course requires nine separate images to draw the parts (plus some extras that are drawn over that), I have a directory full of files like top-left.png and top-left#2x.png. I include this directory in my app bundle as a folder reference.
Unfortunately, the usual image-loading APIs like -[NSImage imageNamed:] and -[NSBundle imageForResource:] don't seem to support subdirectories, even if you put a slash in the name. Instead, I'm loading the images with this method:
- (NSImage*)popoverImage:(NSString*)name {
NSURL * url = [[NSBundle bundleForClass:self.class] URLForResource:name withExtension:#"png" subdirectory:#"popover"];
return [[NSImage alloc] initWithContentsOfURL:url];
}
This works fine for normal displays, but it ignores the 2x images for retina displays. How can I get it to load the retina images as well? Is there a better way than loading the two reps separately and combining them by hand? I'd rather not use TIFFs as my source files for these resources, because I use Acorn as my image editor and last time I checked, it doesn't really understand compound image formats like that very well.
The simple answer is to use TIFF's, your concern is misplaced. In Xcode set the project preference "Combine High Resolution Artwork" and continue to produce your two PNG's with Acorn as you do now. During build Xcode will combine those two PNG's into a single TIFF and store that in your bundle.
I ended up biting the bullet and combining the images manually at runtime. I did so by adding this method in a category on NSImage, and then using it in place of -initWithContentsOfURL::
- (id)initRetinaImageWithContentsOfURL:(NSURL *)url {
if((self = [self initWithContentsOfURL:url])) {
NSURL * baseURL = url.URLByDeletingLastPathComponent;
NSString * baseName = url.lastPathComponent.stringByDeletingPathExtension;
NSString * extension = url.lastPathComponent.pathExtension;
NSString * retinaBaseName = [NSString stringWithFormat:#"%##2x", baseName];
NSURL * retinaURL = [baseURL URLByAppendingPathComponent:[retinaBaseName stringByAppendingPathExtension:extension]];
if([retinaURL checkResourceIsReachableAndReturnError:NULL]) {
NSData * data = [[NSData alloc] initWithContentsOfURL:retinaURL];
NSBitmapImageRep * rep = [[NSBitmapImageRep alloc] initWithData:data];
rep.size = self.size;
[self addRepresentation:rep];
}
}
return self;
}
This is what I do:
NSArray *paths = [NSBundle.mainBundle pathsForResourcesOfType: #"icns" inDirectory: #"Collections/1"];
for (NSString *path in paths) {
NSString *fileName = [[path lastPathComponent] stringByDeletingPathExtension];
NSImage *image = [[NSImage alloc] initWithContentsOfFile: path];
image.name = fileName;
[iconCollectionController addObject: #{#"icon": image}];
}
but as CRD already pointed out you need to combine your artwork (either as tiff or icns files). But it frees you from manually selecting an image resolution (which also requires you to listen to backing store changes).
I'm trying to make a label to show the temperature, with a maximum of 3 digits for temperatures over a 100, but I don't want any decimals...
NSString *longtempstring = [tmp description];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setMaximumFractionDigits:3];
[formatter setMinimumFractionDigits:0];
NSString *shorttempstring = [formatter stringForObjectValue:[NSString stringWithString:longtempstring]];
The code above always returns (null)
Any ideas?
You are getting nil because the object you are passing to stringForObjectValue: is not of the type expected.
First of all, don't use stringForObjectValue:. That is a member of the parent class, NSFormatter. NSNumberFormatter has more specific methods that avoid confusion of object types like numberFromString: and stringFromNumber:.
Second, NSNumberFormatter is used to go from number to formatted string or formatted string to number. Not directly from formatted string to formatted string. You will need to use one number formatter to read your original string and produce a number and another to produce a new shorter formatted string from that number.
Of course, you might be able to make the first step (from long string to number) easier by using NSScanner or by taking a substring of your long string (cutting out everything except for the number itself) and then using the NSString method integerValue or doubleValue. A regular expression could also be used to extract the number from the first (longer) string.
The long and short of it is, this is a two step process. The first step (getting a number) can be accomplished any number of ways and a NSNumberFormatter might not be the easiest way. The second step (getting a new shorter string) is what NSNumberFormatter is perfect for.
I fixed it by converting the NSString to an NSNumber and only pulling the integer value from the string, thus removing any decimals.
NSString *longtempstring = [tmp description];
NSNumber *tempintegervalue = [NSNumber numberWithInteger:[longtempstring integerValue]];
NSString *shorttempstring = [tempintegervalue stringValue];
NSString *longtempstring = [tmp description];
What is the value of longtempstring here? Is it even a number?
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setMaximumFractionDigits:3];
[formatter setMinimumFractionDigits:0];
Why do you set maximum fraction digits to 3 if you say that you don't want any fraction digits? Fraction digits are what is "after the dot" and you say you don't want any decimals. If you don't want any decimals, minimum and maximum must be set to 0.
NSString *shorttempstring = [formatter stringForObjectValue:[NSString stringWithString:longtempstring]];
First of all, why are you copying the string? [NSString stringWithString:...] creates a new string that is a copy of the string you provide as argument. Strings are immutable, there is no need to copy them, they won't be modified. If you are afraid that a NSString may in fact be a NSMutableString, after all that is a subclass of NSString and you want to copy it, just copy it by calling [aString copy]. copy is a very smart method here. If the NSString is in fact a mutable string, it will really copy it, so you get an immutable copy. If it is not a mutable string, though, copy just returns the same string with a retain count increased by one (so in that case copy behaves exactly like retain). However, in your case copying makes no sense whatsoever.
And second, what makes you believe, that you can feed a string into a NSNumberFormater? It is called NSNumberFormater because it formats objects of type NSNumber. How is a NSString a NSNumber? It is not called NSStringFormater. If you feed an object of the wrong class to a formater, it will return nil, that's how this method is documented.
If your longtempstring contains a number with fraction, e.g. 12.345, then the correct code would look like this:
NSString * longtempstring = [tmp description];
NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
[formatter setMaximumFractionDigits:0];
[formatter setMinimumFractionDigits:0];
NSNumber * tempnumber = [NSNumber numberWithDouble:[longtempstring doubleValue]];
NSString * shorttempstring = [formatter stringForObjectValue:tempnumber];
However, if all you want to do is cut off the decimals, that is a horrible way to do it. This can be done much more effective:
double d;
NSString * longtempstring;
NSString * shorttempstring;
longtempstring = #"12.345";
d = [longtempstring doubleValue];
shorttempstring = [NSString stringWithFormat:#"%d", (int)d];
This is much shorter, needs less memory, and is at least three times faster. If you want to round the number (that means everything with a fraction .5 and above is rounded to the next higher value), just round it:
d = round([longtempstring doubleValue]);
I'm having a very serious problem. The application is live, but unfortunately it's fails on iOS 5, and I need to post an update.
The thing is the ID column of few entities is in Integer 16, but I need to be changed to Integer 32.
It was clearly my mistake, the model was created very long time ago, and it was only being reused. To my surprise (now) on iOS 4, Integer 16 in Core Data could easily keep number as big as 500 000 (bug?), but it doesn't work like that now - it gives me invalid numbers.
Application is live, has it success and Core Data is also used to keep the users scores, achievements and so on, what I don't want to remove, forcing them to reinstall the application. What is the best approach to simply change about ten of properties in different entities from Integer 16 to Integer 32?
Of course I know the names and entities for those properties.
If I just change the Type column for those properties in the xcdatamodeld file it will work, for new user, but what about existing users, that already have sqlite file in their Documents folder. I believe I need to change the persistent store coordinator somehow.
And also what do you thing about the performance, there are about 10 properties that news to be changed from 16 to 32, but Core Data have in usual cases more than 100 000 objects inside.
Regards
Background
Previous version of app set attribute as 16 bit in Core Data.
This was too small to hold large values greater than approx 32768.
int 16 uses 1 bit to represent sign, so maximum value = 2^15 = 32768
In iOS 5, these values overflowed into negative numbers.
34318 became -31218
36745 became -28791
To repair these negative values, add 2^16 = 65536
Note this solution works only if the original value was less than 65536.
Add a new model
In file navigator, select MyApp.xcdatamodeld
Choose menu Editor/Add Model Version
Version name: proposes "MyApp 2" but you can change e.g. to MyAppVersion2
Based on model: MyApp
In new MyAppVersion2.xcdatamodel change attribute type from integer 16 to integer 64.
In file navigator, select directory MyApp.xcdatamodeld
Open right pane inspector, Versioned Core Data Model Current change from MyApp to MyAppVersion2.
In left pane file navigator, green check mark moves from MyApp.xcdatamodel to MyAppVersion2.xcdatamodel.
In MyAppAppDelegate managedObjectModel do not change resource name from #"MyApp"
In Xcode select folder ModelClasses.
File/Add Core Data Mapping Model.
Choose source data model MyApp.xcdatamodel
Choose target data model MyAppVersion2.xcdatamodel
Save As MyAppToMyAppVersion2.xcmappingmodel
Add to target MyApp.
In MyAppAppDelegate persistentStoreCoordinator turn on CoreData manual migration
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created
// and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"MyApp.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
// Set Core Data migration options
// For automatic lightweight migration set NSInferMappingModelAutomaticallyOption to YES
// For manual migration using a mapping model set NSInferMappingModelAutomaticallyOption to NO
NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:NO],
NSInferMappingModelAutomaticallyOption,
nil];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:optionsDictionary
error:&error])
{
// handle the error
NSString *message = [[NSString alloc]
initWithFormat:#"%#, %#", error, [error userInfo]];
UIAlertViewAutoDismiss *alertView = [[UIAlertViewAutoDismiss alloc]
initWithTitle:NSLocalizedString(#"Sorry, Persistent Store Error. Please Quit.", #"")
message:message
delegate: nil
cancelButtonTitle:NSLocalizedString(#"OK", #"")
otherButtonTitles:nil];
[message release];
[alertView show];
[alertView release];
}
return persistentStoreCoordinator_;
}
Add a migration policy
MyAppToMyAppVersion2MigrationPolicy
The following example converts one entity "Environment" with an integer attribute "FeedID" and a string attribute "title".
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)aSource
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)migrationManager
error:(NSError **)error {
NSEntityDescription *aSourceEntityDescription = [aSource entity];
NSString *aSourceName = [aSourceEntityDescription valueForKey:#"name"];
NSManagedObjectContext *destinationMOC = [migrationManager destinationContext];
NSManagedObject *destEnvironment;
NSString *destEntityName = [mapping destinationEntityName];
if ([aSourceName isEqualToString:kEnvironment])
{
destEnvironment = [NSEntityDescription
insertNewObjectForEntityForName:destEntityName
inManagedObjectContext:destinationMOC];
// attribute feedID
NSNumber *sourceFeedID = [aSource valueForKey:kFeedID];
if (!sourceFeedID)
{
// Defensive programming.
// In the source model version, feedID was required to have a value
// so excecution should never get here.
[destEnvironment setValue:[NSNumber numberWithInteger:0] forKey:kFeedID];
}
else
{
NSInteger sourceFeedIDInteger = [sourceFeedID intValue];
if (sourceFeedIDInteger < 0)
{
// To correct previous negative feedIDs, add 2^16 = 65536
NSInteger kInt16RolloverOffset = 65536;
NSInteger destFeedIDInteger = (sourceFeedIDInteger + kInt16RolloverOffset);
NSNumber *destFeedID = [NSNumber numberWithInteger:destFeedIDInteger];
[destEnvironment setValue:destFeedID forKey:kFeedID];
} else
{
// attribute feedID previous value is not negative so use it as is
[destEnvironment setValue:sourceFeedID forKey:kFeedID];
}
}
// attribute title (don't change this attribute)
NSString *sourceTitle = [aSource valueForKey:kTitle];
if (!sourceTitle)
{
// no previous value, set blank
[destEnvironment setValue:#"" forKey:kTitle];
} else
{
[destEnvironment setValue:sourceTitle forKey:kTitle];
}
[migrationManager associateSourceInstance:aSource
withDestinationInstance:destEnvironment
forEntityMapping:mapping];
return YES;
} else
{
// don't remap any other entities
return NO;
}
}
In file navigator select MyAppToMyAppVersion2.xcmappingmodel
In window, show right side utilities pane.
In window, select Entity Mappings EnvironmentToEnvironment
In right side Entity Mapping, choose Custom Policy enter MyAppToMyAppVersion2MigrationPolicy.
Save file.
References:
Zarra, Core Data Chapter 5 p 87 http://pragprog.com/book/mzcd/core-data
http://www.informit.com/articles/article.aspx?p=1178181&seqNum=7
http://www.timisted.net/blog/archive/core-data-migration/
http://www.cocoabuilder.com/archive/cocoa/286529-core-data-versioning-non-trivial-value-expressions.html
http://www.seattle-ipa.org/2011/09/11/coredata-and-integer-width-in-ios-5/
Privat, Pro Core Data for iOS Ch 8 p273
Turn on NSMigratePersistentStoresAutomaticallyOption' and 'NSInferMappingModelAutomaticallyOption in your NSPersistentStore and then create a second version of your model with the changes. Only do the integer changes to keep the migration simple. That will allow users who install your upgrade to migrate from the broken model to the corrected model.
NOTE: This must be an automatic migration; a manual migration with a mapping model will not work.
Just wants to confirm Marcus S. Zarra's answer with a small addition. It works good for us to some extent. We made the exact same error in our model. But it has a problem. Values over what seems to be 2^24 is converted to 16-bit values during the automatic migration, but saved as 32-bit but with wrong value.
For example:
17 479 261 becomes 18 851
(17 479 261 mod (2^16)) - (2^16) = -18 851
We downloaded the DB from the phone and looked in the database, and the number is changed in the DB.
We have not yet solved this problem.
I often use Transformable for Core Data attributes, so I can change them later.
However, it seems like, if I want to use NSPredicate to find a NSManagedObject, using "uniqueKey == %#", or "uniqueKey MATCHES[cd] %#", it's not working as it should.
It always misses matching objects, until I change the attributes of the uniqueKey of the matching object to have specific class like NSString, or NSNumber.
Can someone explain the limitation of using NSPredicate with Transformable attributes?
Note: I'm not sure when/if this has changed since 5/2011 (from Scott Ahten's accepted answer), but you can absolutely search with NSPredicate on transformable attributes. Scott correctly explained why your assumptions were broken, but if Can someone explain the limitation of using NSPredicate with Transformable attributes? was your question, he implied that it is not possible, and that is incorrect.
Since the is the first google hit for "Core Data transformable value search nspredicate" (what I searched for trying to find inspiration), I wanted to add my working answer.
How to use NSPredicate with transformable properties
Short, heady answer: you need to be smart about your data transformers. You need to transfrom the value to NSData that contains what I'll call "primitive identifying information", i.e. the smallest, most identifying set of bytes that can be used to reconstruct your object. Long answer, ...
Foremost, consider:
Did you actual mean to use a transformable attribute? If any supported data type -- even binary data -- will suffice, use it.
Do you understand what transformable attributes actually are? How they pack and unpack data to and from the store? Review Non-Standard Persistent Attributes in Apple's documentation.
After reading the above, ask: does custom code that hides a supported type "backing attribute" work for you? Possibly use that technique.
Now, past those considerations, transformable attributes are rather slick. Frankly, writing an NSValueTransformer "FooToData" for Foo instances to NSData seemed cleaner than writing a lot of adhoc custom code. I haven't found a case where Core Data doesn't know it needs to transform the data using the registered NSValueTransformer.
To proceed simply address these concerns:
Did you tell Core Data what transformer to use? Open the Core Data model in table view, click the entity, click the attribute, load the Data Model Inspector pane. Under "Attribute Type: Transformable", set "Name" to your transformer.
Use a default transformer (again, see the previous Apple docs) or write your own transformer -- transformedValue: must return NSData.
NSKeyedUnarchiveFromDataTransformerName is the default transformer and may not suffice, or may draw in somewhat-transient instance data that can make two similar objects be different when they are equal.
The transformed value should contain only -- what I'll call -- "primitive identifying information". The store is going to be comparing bytes, so every byte counts.
You may also register your transformer globally. I have to do this since I actually reuse them elsewhere in the app -- e.g. NSString *name = #"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];
You probably don't want to use transforms heavily queried data operations - e.g. a large import where the primary key information uses transformers - yikes!
And then in the end, I simply use this to test for equality for high-level object attributes on models with NSPredicates -- e.g. "%K == %#" -- and it works fine. I haven't tried some of the various matching terms, but I wouldn't be surprised if they worked sometimes, and others not.
Here's an example of an NSURL to NSData transformer. Why not just store the string? Yeah, that's fine -- that's a good example of custom code masking the stored attribute. This example illustrates that an extra byte is added to the stringified URL to record if it was a file URL or not -- allowing us to know what constructors to use when the object is unpacked.
// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
#interface URLToDataTransformer : NSValueTransformer
#end
...
// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = #"URLToDataTransformer";
#implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }
- (id)transformedValue:(id)value
{
if (![value isKindOfClass:[NSURL class]])
{
// Log error ...
return nil;
}
NSMutableData *data;
char fileType = 0;
if ([value isFileURL])
{
fileType = 1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
}
else
{
fileType = -1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
}
return data;
}
- (id)reverseTransformedValue:(id)value
{
if (![value isKindOfClass:[NSData class]])
{
// Log error ...
return nil;
}
NSURL *url = nil;
NSData *data = (NSData *)value;
char fileType = 0;
NSRange range = NSMakeRange(1, [data length]-1);
[data getBytes:&fileType length:1];
if (1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL fileURLWithPath:str];
}
else if (-1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL URLWithString:str];
}
else
{
// Log error ...
return nil;
}
return url;
}
#end
Transformable attributes are usually persisted as archived binary data. As such, you are attempting to compare an instance of NSData with an instance of NSString or NSNumber.
Since these classes interpret the same data in different ways, they are not considered a match.
you can try this way
NSExpression *exprPath = [NSExpression expressionForKeyPath:#"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
So I have this piece of code :
if ([receivedPage hasPrefix:[NSString stringWithUTF8String:"\xC3\xAF\xC2\xBB\xC2\xBF"]]) // UTF-8 BOM 'EF BB BF' as UTF-16 chars
{
//DebugLog(#"converting calls list to UTF8");
receivedPage = [[[NSString alloc] initWithData:[receivedPage dataUsingEncoding:NSISOLatin1StringEncoding] encoding:NSUTF8StringEncoding] autorelease];
}
However sometimes when the if is true the receivedPage becomes null. why would this happen?
The received page is the returned value of this function:
NSURLResponse * response;
NSData * result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error];
if ([result length] > 0)
return [[[NSString alloc] initWithBytes: (const void*)[result bytes] length:[result length] encoding: encoding] autorelease];
else
{
if (error && *error)
DebugLog(#"URL request got error: %#",*error);
return nil;
}
The encoding here is NSISOLatin1StringEncoding (don't know why ,I'm debugging someone else's code).
Any idea why this would happen?
It looks like you're trying to treat strings (objects containing characters) as data (objects containing bytes). Keep the data you received from the connection, and check for the UTF-8 BOM (the proper three-byte version) in it, then use either NSUTF8StringEncoding or NSISOLatin1StringEncoding based on whether you find it.
Or, just use UTF-8 conditionally, if you can fix the server to do that as well.
Also, you should probably switch this code to use the NSURLConnection asynchronously. If the user's internet connection is slow, you're hanging your app here. Doing it asynchronously lets you keep the UI running, display progress if appropriate, and enable the user to cancel.