Skip to content

Commit 8d616a7

Browse files
committed
Saving and loading tutorial state
1 parent 3f15d4b commit 8d616a7

4 files changed

Lines changed: 103 additions & 7 deletions

File tree

FFXInteractiveTutorial/FFXInteractiveTutorial.h

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,38 @@
4444

4545
@end
4646

47+
48+
@class FFXInteractiveTutorial;
49+
@protocol FFXInteractiveTutorialDelegate <NSObject>
50+
51+
@optional
52+
53+
- (BOOL) tutorial:(FFXInteractiveTutorial*) tutorial shouldFulfillItem:(NSString*)requirement;
54+
55+
@end
56+
4757
@interface FFXInteractiveTutorial : NSObject
4858

4959
+ (instancetype) defaultTutorial;
5060

61+
@property (nonatomic, weak) id<FFXInteractiveTutorialDelegate> delegate;
62+
5163
@property (nonatomic, readonly) FFXInteractiveTutorialMetrics* metrics;
5264

53-
- (instancetype)initWithWindow:(UIWindow*) window items:(NSArray<FFXInteractiveTutorialItem*>*)tutorialItems;
65+
/**
66+
* Load previous tutorial state
67+
*
68+
* @param window
69+
*
70+
* @return tutorial instance or nil
71+
*/
72+
+ (instancetype) restoreTutorialInWindow:(UIWindow*) window;
73+
74+
- (instancetype) initWithWindow:(UIWindow*) window items:(NSArray<FFXInteractiveTutorialItem*>*)tutorialItems;
5475

55-
- (instancetype)initWithWindow:(UIWindow*) window file:(NSString*) path;
76+
- (instancetype) initWithWindow:(UIWindow*) window file:(NSString*) path;
5677

57-
- (void) fullfillItemWithIdentifier:(NSString*) identifier;
78+
- (void) fulfillItemWithIdentifier:(NSString*) identifier;
5879

5980
/**
6081
* Enable the tutorial and start scanning the view hierarchy for tutorial elements
@@ -66,4 +87,9 @@
6687
*/
6788
- (void) stop;
6889

90+
/**
91+
* Save the tutorial state to restore it later with restoreTutorialInWindow:
92+
*/
93+
- (void) save;
94+
6995
@end

FFXInteractiveTutorial/FFXInteractiveTutorial.m

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
#define FFX_INTERACTIVE_TUTORIAL_HIDE_ITEM_INTERVAL 60.0
1919

20+
NSString* const FFXInteractiveTutorialStorageKey = @"FFXInteractiveTutorial";
21+
2022
@implementation FFXInteractiveTutorialMetrics
2123
@end
2224

@@ -86,6 +88,28 @@ - (FFXInteractiveTutorialMetrics*) defaultMetrics{
8688

8789
static FFXInteractiveTutorial *defaultTutorial = nil;
8890

91+
+ (instancetype)restoreTutorialInWindow:(UIWindow *)window{
92+
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
93+
id data = [defaults objectForKey:FFXInteractiveTutorialStorageKey];
94+
if ([data isKindOfClass:[NSData class]]) {
95+
NSError* error = nil;
96+
id obj = [NSKeyedUnarchiver unarchiveObjectWithData:data];
97+
if([obj isKindOfClass:[NSArray class]]){
98+
NSArray* items = obj;
99+
// ensure correct class
100+
for (id item in items) {
101+
if (![item isKindOfClass:[FFXInteractiveTutorialItem class]]) {
102+
return nil;
103+
}
104+
}
105+
NSLog(@"loaded items: %@", items);
106+
return [[FFXInteractiveTutorial alloc] initWithWindow:window items:items];
107+
}
108+
}
109+
110+
return nil;
111+
}
112+
89113
+ (instancetype) defaultTutorial{
90114
return defaultTutorial;
91115
}
@@ -142,6 +166,21 @@ -(void)setWindow:(UIWindow *)window{
142166

143167
- (void)start{
144168
if (!_timer) {
169+
// revalidate tutorial items
170+
if ([self.delegate respondsToSelector:@selector(tutorial:shouldFulfillItem:)]) {
171+
for (FFXInteractiveTutorialItem* item in self.items) {
172+
FFXInteractiveTutorialItem* theItem = item;
173+
while (theItem) {
174+
BOOL fulfilled = [self.delegate tutorial:self shouldFulfillItem:theItem.identifier];
175+
if (fulfilled) {
176+
[theItem fulfill];
177+
}
178+
theItem = theItem.nextItem;
179+
}
180+
}
181+
}
182+
183+
// set up timer
145184
_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(triggerCheck) userInfo:nil repeats:YES];
146185
}
147186
}
@@ -151,6 +190,13 @@ - (void)stop{
151190
_timer = nil;
152191
}
153192

193+
- (void)save{
194+
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
195+
[defaults setValue:[NSKeyedArchiver archivedDataWithRootObject:self.items] forKey:FFXInteractiveTutorialStorageKey];
196+
NSLog(@"stored items: %@", self.items);
197+
[defaults synchronize];
198+
}
199+
154200
-(void)triggerCheck{
155201
NSMutableArray* candidates = [NSMutableArray array];
156202

@@ -212,7 +258,7 @@ -(void)triggerCheck{
212258
[self showItems:self.activeItems];
213259
}
214260

215-
- (void)fullfillItemWithIdentifier:(NSString *)identifier{
261+
- (void)fulfillItemWithIdentifier:(NSString *)identifier{
216262
NSUInteger index = [self.items indexOfObjectPassingTest:^BOOL(FFXInteractiveTutorialItem* _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
217263
return [item.identifier isEqualToString:identifier];
218264
}];

FFXInteractiveTutorial/FFXInteractiveTutorialItem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ typedef enum : NSUInteger {
3030
FFXTutorialItemFullfillmentInteractionDefault = FFXTutorialItemFullfillmentInteractionCustom,
3131
} FFXTutorialItemFullfillmentInteraction;
3232

33-
@interface FFXInteractiveTutorialItem : NSObject<NSCopying>
33+
@interface FFXInteractiveTutorialItem : NSObject<NSCopying,NSCoding>
3434

3535
+ (instancetype) itemWithIdentifier:(NSString*)identifier viewPath:(NSString*)viewPath title:(NSString*)title;
3636

FFXInteractiveTutorial/FFXInteractiveTutorialItem.m

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ - (instancetype) initWithIdentifier:(NSString *)identifier viewPath:(NSString *)
5353
}
5454

5555
- (NSString *)description{
56-
return [NSString stringWithFormat:@"<%@: %p, identifier:%@; viewPath: %@>",
57-
NSStringFromClass([self class]), self, self.identifier, self.viewPath];
56+
return [NSString stringWithFormat:@"<%@: %p, identifier:%@; viewPath: %@; active: %d;>",
57+
NSStringFromClass([self class]), self, self.identifier, self.viewPath, self.active];
5858
}
5959

6060
- (void)setValue:(id)value forKey:(NSString *)key{
@@ -126,4 +126,28 @@ - (id)copyWithZone:(NSZone *)zone{
126126
return copy;
127127
}
128128

129+
#pragma mark NSCoding
130+
131+
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
132+
self = [super init];
133+
if (self) {
134+
self.identifier = [aDecoder decodeObjectForKey:@"identifier"];
135+
self.viewPath = [aDecoder decodeObjectForKey:@"viewPath"];
136+
self.title = [aDecoder decodeObjectForKey:@"title"];
137+
self.subtitle = [aDecoder decodeObjectForKey:@"subtitle"];
138+
self.unique = [aDecoder decodeBoolForKey:@"unique"];
139+
self.highlightView = [aDecoder decodeBoolForKey:@"highlightView"];
140+
}
141+
return self;
142+
}
143+
144+
- (void)encodeWithCoder:(NSCoder *)aCoder{
145+
[aCoder encodeObject:self.identifier forKey:@"identifier"];
146+
[aCoder encodeObject:self.viewPath forKey:@"viewPath"];
147+
[aCoder encodeObject:self.title forKey:@"title"];
148+
[aCoder encodeObject:self.subtitle forKey:@"subtitle"];
149+
[aCoder encodeBool:self.unique forKey:@"unique"];
150+
[aCoder encodeBool:self.highlightView forKey:@"highlightView"];
151+
}
152+
129153
@end

0 commit comments

Comments
 (0)