Created
October 5, 2013 05:56
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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