Skip to content

Instantly share code, notes, and snippets.

@RoyalIcing
Created October 5, 2013 05:56
Show Gist options
  • Save RoyalIcing/6837231 to your computer and use it in GitHub Desktop.
Save RoyalIcing/6837231 to your computer and use it in GitHub Desktop.
An example of how syncing tags could be made hopefully easier by separating a tag into two objects: - One that is recorded to the database, with a unique identifier. - And one that the are assigned to notes, which then points to a recorded tag.
@protocol TFFTagging
@property (readonly, nonatomic) NSString *name;
@end
// Tags in sidebar UI use TFFRecordedTag
// Tags in notes UI use TFFAttachableTag
// Same interface for what makes a tag, which is its name.
@interface TFFRecordedTag : NSObject <TFFTagging>
@property (readwrite, nonatomic) NSString *name;
@property (readwrite, nonatomic) NSUUID *uniqueID;
@end
@interface TFFAttachableTag : NSObject <TFFTagging>
@property (nonatomic) TFFRecordedTag *recordedTag;
- (instancetype)initWithRecordedTag:(TFFTag *)recordedTag;
@end
@implementation TFFRecordedTag
- (instancetype)init
{
self = [super init];
if (self) {
(self.uniqueID) = [NSUUID new];
}
return self;
}
@end
@implementation TFFAttachableTag
- (instancetype)initWithRecordedTag:(TFFTag *)recordedTag
{
self = [super init];
if (self) {
(self.recordedTag) = recordedTag;
}
return self;
}
- (NSString *)name
{
return (self.recordedTag.name);
}
@end
@interface TFFTagSystem
- (BOOL)tagName:(NSString *)tagNameA isEquivalentToTagName:(NSString *)tagNameB;
- (BOOL)tagNameIsPreferred:(NSString *)nameA toTagName:(NSString *)nameB; // e.g. Paris vs paris, which is preferred?
- (NSString *)normalizeTagName:(NSString *)inputTagName; // Used for keys in `self.recordedTags`;
@property (nonatomic) NSMutableDictionary *recordedTagsNormalizedNames; // Map of normalized tags name to TFFRecordedTag.
- (TFFRecordedTag *)recordedTagWithName:(NSString *)name creating:(BOOL)create;
- (NSSet *)acceptedTagsOfIntroducedRecordedTags:(NSSet *)introducedRecordedTags; // New tags introduced by another device.
@end
@implementation TFFTagSystem
- (BOOL)tagName:(NSString *)tagNameA isEquivalentToTagName:(NSString *)tagNameB
{
return [tagNameA compare:tagNameB options:NSCaseInsensitiveSearch] == NSOrderedSame;
}
- (BOOL)tagNameIsPreferred:(NSString *)nameA toTagName:(NSString *)nameB
{
// Capital letters are before lowercase letters in the unicode alphabet so I think this works.
return [tagNameA compare:tagNameB options:NSCaseInsensitiveSearch] != NSOrderedDescending; // Go with ascending or same.
}
- (NSString *)normalizeTagName:(NSString *)inputTagName;
{
// See also http://nshipster.com/cfstringtransform/ for some further ideas.
// Ideally would use -lowercaseStringWithLocale: but each device might have a different locale from each other!
return [inputTagName lowercaseString];
}
- (TFFRecordedTag *)recordedTagWithName:(NSString *)name creating:(BOOL)create
{
NSMutableDictionary * recordedTagsNormalizedNames = (self.recordedTagsNormalizedNames);
NSString *normalizedTagName = [self normalizeTagName:name];
TFFRecordTag *recordedTag = recordedTagsNormalizedNames[normalizedTagName];
if (!recordedTag && create) {
recordedTag = [TFFRecordedTag new];
(recordedTag.name) = name;
recordedTagsNormalizedNames[normalizedTagName] = recordedTag;
}
return recordedTag;
}
- (NSSet *)acceptedTagsOfIntroducedRecordedTags:(NSSet *)introducedRecordedTags
{
NSMutableSet *acceptedTags = [NSMutableSet set];
for (TFFRecordedTag *newRecordedTag in introducedRecordedTags) {
NSString *newTagName = (newRecordedTag.name);
TFFRecordedTag *existingTag = [self recordedTagWithName:newTagName creating:NO];
if (existingTag) {
NSString *existingTagName = (existingTag.name);
BOOL existingNameIsPreferred = [self tagNameIsPreferred:existingTagName andString:newTagName];
if (existingNameIsPreferred)
break; // Don't add it to acceptedTags.
}
[acceptedTags addObject:newRecordedTag];
}
return acceptedTags;
// Then on all notes, do [note syncIntroducingNewRecordedTags:acceptedTags];
}
@end
@interface TFFNote : NSObject
@property NSMutableArray /* TFFAttachableTag */ *attachedTags; // or ideally NSMutableOrderedSet
- (void)addTag:(TFFAttachableTag *)attachableTag;
- (void)syncIntroducingNewRecordedTags:(NSSet /* TFFRecordedTag */ *)newRecordedTags;
@end
@implementation TFFNote
// - (void)addTag:(TFFAttachableTag *)attachableTag; unimplemented
- (void)syncIntroducingNewRecordedTags:(NSSet /* TFFRecordedTag */ *)newRecordedTags
{
NSMutableArray *attachedTags = (self.attachedTags);
// The number of tags is likely going to be pretty low, so having a double loop should be ok.
// But this could be probably optimised by using a NSDictionary or similar somehow instead.
for (TFFRecordedTag *newRecordedTag in newRecordedTags) {
NSString *recordedTagName = (newRecordedTag.name);
for (TFFAttachableTag *attachableTag in attachedTags) {
if ([TFFTagSystem tagName:recordedTagName isEquivalentToTagName:(attachableTag.name)]) {
// Reassign the recorded tag that our tag is connected to.
(attachableTag.recordedTag) = newRecordedTag;
break;
}
}
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment