UICollectionView layoutAttributesForElementsInRect not returning reusableViews - layout

I am trying to implement a UICollectionView using supplementaryViews.
I use a Nib file to create my supplementary view. Here is the method in the layout to add the supplementaryView:
NSMutableArray *attributesInRect = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
NSLog(#"%#", attributesInRect);
if ([self.collectionView numberOfItemsInSection:0] > 1 && !self.selectedItem)
{
if ([self.musicList count] > 0)
{
UICollectionViewLayoutAttributes *musicViewAttributes = [self layoutAttributesForSupplementaryViewOfKind:#"MusicView" atIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
musicViewAttributes.size = CGSizeMake(CGRectGetWidth([[UIScreen mainScreen] bounds]), 150);
musicViewAttributes.center = CGPointMake(musicViewAttributes.size.width/2, musicViewAttributes.size.height/2);
musicViewAttributes.zIndex = 0;
[attributesInRect addObject:musicViewAttributes];
}
}
return attributesInRect;
Don't pay attention to the conditions here, only on the array of attributes (attributesInRect). When I do that, my supplementary is properly added to the CollectionView.
My problem is to retrieve an added supplementaryView. On the NSLog, it seams that my supplementaryView is not listed in the array. In this case I can't check it's existence before adding a new one.
I don't really understand why as the documentation specify:
Return Value
An array of UICollectionViewLayoutAttributes objects representing the layout information for the cells and views. The default implementation returns nil.
According to that my supplementaryView should be in that array.
Any idea about that ?

Related

How to get fields (attributes) out of a single CoreData record without using [index]?

I have one CoreData record that contains all of the app's settings. When I read that single record (using MagicalRecord), I get an array back. My question is: can I get addressabiltiy to the individual fields in the record without using "[0]" (field index), but rather using [#"shopOpens"]?
I was thinking something like this, but I don't think it's right:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"aMostRecentFlag == 1"]; // find old records
preferenceData = [PreferenceData MR_findAllWithPredicate:predicate inContext:defaultContext]; // source
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *preferencesDict = [[userDefaults dictionaryForKey:#"preferencesDictionary"] mutableCopy]; // target
// start filling the userDefaults from the last Preferences record
/*
Printing description of preferencesDict: {
apptInterval = 15;
colorScheme = Saori;
servicesType = 1;
shopCloses = 2000;
shopOpens = 900;
showServices = 0;
syncToiCloud = 0;
timeFormat = 12;
}
*/
[preferencesDict setObject: preferenceData.colorScheme forKey:#"shopOpens"];
UPDATE
This is how I finally figured it out, for those who have a similar question:
NSPredicate *filter = [NSPredicate predicateWithFormat:#"aMostRecentFlag == 0"]; // find old records
NSFetchRequest *freqest = [PreferenceData MR_requestAllWithPredicate: filter];
[freqest setResultType: NSDictionaryResultType];
NSDictionary *perferenceData = [PreferenceData MR_executeFetchRequest:freqest];
Disclaimer: I've never used magical record, so the very first part is just an educated guess.
I imagine that preferenceData is an instance of NSArray firstly because the method name uses findAll which indicates that it will return multiple instances. Secondly, a normal core data fetch returns an array, and there is no obvious reason for that find method to return anything different. Thirdly, you referenced using an index operation in your question.
So, preferenceData is most likely an array of all objects in the store that match the specified predicate. You indicated that there is only one such object, which means you can just grab the first one.
PreferenceData *preferenceData = [[PreferenceData
MR_findAllWithPredicate:predicate inContext:defaultContext] firstObject];
Now, unless it is nil, you have the object from the core data store.
You should be able to reference it in any way you like to access its attributes.
Note, however, that you can fetch objects from core data as dictionary using NSDictionaryResultType, which may be a better alternative for you.
Also, you can send dictionaryWithValuesForKeys: to a managed object to get a dictionary of specific attributes.

Styling NSOutlineView Rows

I have a Document based Core Data app with an NSTreeController supplying the content to a view based NSOutlineView. I am "styling" (setting text colour, background colour etc.) the rows based on persistent "transformable" NSColor and NSFont attributes in my data model which the end use can modify. When a new row is popped up, it displays things with the colours/fonts set in the data model. Here is the delegate/datasource code that sets the row background colour:
- (void) outlineView:(NSOutlineView *)outlineView
didAddRowView:(NSTableRowView *)rowView
forRow:(NSInteger)row
{
// Get the relevant nodeType which contains the attributes
QVItem *aNode = [[outlineView itemAtRow:row] representedObject];
if (aNode.backColor)
{
rowView.backgroundColor = aNode.backColor;
}
}
However when the style attributes change I want the associated visible rows to be redrawn with the new style values. Each time a "style" attribute is changed, I am using NSNotificationCenter to send a notification to the Outline view delegate, with the model object whose row needs to be redrawn with the changed style. This is the code in the delegate that receives the notification.
-(void) styleHasChanged: (NSNotification *)aNotification
{
NSTreeNode *aTreeNode = [myTreeController treeNodeForModelObject:aNotification.object];
[myOutlineView reloadItem:aTreeNode];
}
My assumption here is that I can navigate the tree controller to find the tree node which is representing my model object and then ask the outline view to redraw the row for that tree node. This is the "additions" code in the tree controller which walks the tree to find the object - not super efficient, but I don't think there is another way.
#implementation NSTreeController (QVAdditions)
- (NSTreeNode *)treeNodeForModelObject:(id)aModelObject
{
return [self treeNodeForModelObject:aModelObject inNodes:[[self arrangedObjects] childNodes]];
}
- (NSTreeNode *)treeNodeForModelObject:(id)aModelObject inNodes:(NSArray*)nodes
{
for(NSTreeNode* node in nodes)
{
if([node representedObject] == aModelObject)
return node;
if([[node childNodes] count])
{
NSTreeNode * treeNode = [self treeNodeForModelObject:aModelObject inNodes:[node childNodes]];
return treeNode;
}
}
return nil;
}
So sometimes this works and the row redraws, and sometimes it doesn't. The delegate method "styleHasChanged:" is always called, and the tree controller always returns a corresponding tree node (Actually of a subclass of NSTreeNode). But more often than not the outline view does not recognise the tree node, and the row is not redrawn. Its like the tree controller has given back a different tree node object to the one it gave the outline view in the past. But weirdly sometimes it does work and the right row is redrawn with the new background colour. If I collapse the row out of view and pop it open again, it is redrawn correctly.
Anyone any idea why it works sometimes and not other times?
It would be nice to be able to bind the colour/font attributes to the row and columns in some way, so that the outline view did this styling automatically with KVO, but I don't think that is possible - is it?
You spend hours/days trying to work out what you've done wrong; You write the question out; Post it; Sleep on it; and think how stupid can you be.
So I asked the NSTableRowView to redraw itself, but I had not set the new background colour. So here is the new improved (and works) version of styleHasChanged:
-(void) styleHasChanged: (NSNotification *)aNotification
{
QVItem *modelItem = aNotification.object;
NSTreeNode *aTreeNode = [myTreeController treeNodeForModelObject:modelItem];
NSInteger rowIndex = [myOutlineView rowForItem:aTreeNode];
if !(rowIndex == -1)
{
NSTableRowView *rowViewToBeUpdated = [myOutlineView rowViewAtRow:rowIndex makeIfNecessary:YES];
rowViewToBeUpdated.backgroundColor = modelItem.backColor;
}
}
Duh!

Cocoa Core Data: Using a value transformer on an array controller's arrangedObjects to provide a filtered count

As per the title really. I have an entity which has a property "idNumber". Just as I can bind a text box to the array controller's arrangedObjects with Model Key Path "#count" to provide a count of all the objects in the array, I would like to be able to bind a text field to the array controller's arrangedObjects with a value transformer to return a count of a filtered subset of the array (those objects with an idNumber >5).
I'm assuming this is possible??
My attempt is:
I have bound the text box to the array controller, Controller Key "arrangedObjects" Model Key Path "" Value Transformer "AllToSomeTransformer".
The code for the AllToSomeTransformer is:
-(id)transformedValue:(id)value {
NSArray *arrayOfAllCars;
if (value == nil) return nil;
if ([value respondsToSelector: #selector(count)]) {
arrayOfAllCars = [NSArray arrayWithArray:value];
} else {
[NSException raise: NSInternalInconsistencyException
format: #"Value (%#) does not respond to -count.",
[value class]];
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idNumber > %#", [NSNumber numberWithInt:5]];
NSArray *arrayOfBlueCars = [arrayOfAllCars filteredArrayUsingPredicate:predicate];
return [NSNumber numberWithInt:[arrayOfBlueCars count]];
}
I believe my value transformer is correctly registered etc. By way of trying to figure out what's going on I added some NSLog outputs through to above code. It appears the above method is only called once, on app startup, and not again when new objects are added to the array. Could this be why the text field is not being updated with values??
Thanks, Oli
Since the transformer is called and does work but only once, that suggest there is something wrong with the bindings such that the transformer is not observing the changes in arrangedObjects. I'm not sure what that would be.

Core Data uniqueness

Is there any way I can validate a value updated in a Core Data entity's property against values of the property in other entities in the collection?
At the moment I create an entity with some default values, add it to arrangedObjects, then get the user to modify the various property values. However, I would like to check a particular property and make sure there're no other entities in the array with the same value for that property. What's the best way to do this?
Many thanks,
Dany.
Manually checking is only a few lines of code with a fast enumeration loop:
BOOL unique = YES;
for (NSManagedObject *obj in collection) {
if (obj.property == value) {
unique = NO;
break;
}
}

Getting Attributes of Keychain Items

I'm trying to get the attributes of a keychain item. This code should look up all the available attributes, then print off their tags and contents.
According to the docs I should be seeing tags like 'cdat', but instead they just look like an index (i.e., the first tag is 0, next is 1). This makes it pretty useless since I can't tell which attribute is the one I'm looking for.
SecItemClass itemClass;
SecKeychainItemCopyAttributesAndData(itemRef, NULL, &itemClass, NULL, NULL, NULL);
SecKeychainRef keychainRef;
SecKeychainItemCopyKeychain(itemRef, &keychainRef);
SecKeychainAttributeInfo *attrInfo;
SecKeychainAttributeInfoForItemID(keychainRef, itemClass, &attrInfo);
SecKeychainAttributeList *attributes;
SecKeychainItemCopyAttributesAndData(itemRef, attrInfo, NULL, &attributes, 0, NULL);
for (int i = 0; i < attributes->count; i ++)
{
SecKeychainAttribute attr = attributes->attr[i];
NSLog(#"%08x %#", attr.tag, [NSData dataWithBytes:attr.data length:attr.length]);
}
SecKeychainFreeAttributeInfo(attrInfo);
SecKeychainItemFreeAttributesAndData(attributes, NULL);
CFRelease(itemRef);
CFRelease(keychainRef);
There are two things you should be doing here. Firstly, you need to handle "generic" itemClasses before the call to SecKeychainAttributeInfoForItemID...
switch (itemClass)
{
case kSecInternetPasswordItemClass:
itemClass = CSSM_DL_DB_RECORD_INTERNET_PASSWORD;
break;
case kSecGenericPasswordItemClass:
itemClass = CSSM_DL_DB_RECORD_GENERIC_PASSWORD;
break;
case kSecAppleSharePasswordItemClass:
itemClass = CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD;
break;
default:
// No action required
}
Second, you need to convert the attr.tag from a FourCharCode to a string, i.e.
NSLog(#"%c%c%c%c %#",
((char *)&attr.tag)[3],
((char *)&attr.tag)[2],
((char *)&attr.tag)[1],
((char *)&attr.tag)[0],
[[[NSString alloc]
initWithData:[NSData dataWithBytes:attr.data length:attr.length]
encoding:NSUTF8StringEncoding]
autorelease]]);
Notice that I've also output the data as a string -- it almost always is UTF8 encoded data.
I think the documentation leads to a bit of confusion.
The numbers I'm seeing appear to be keychain item attribute constants for keys.
However, SecKeychainItemCopyAttributesAndData returns a SecKeychainAttributeList struct, which contains an array of SecKeychainAttributes. From TFD:
tag
A 4-byte attribute tag. See “Keychain Item Attribute Constants” for valid attribute types.
The attribute constants (of the non-"for keys" variety) are the 4-char values I expected to see.

Resources