Skip to content

Commit 0852f62

Browse files
committed
Display once per session IAM with redisplay
* Add no trigger check, display once per session * Add displayed param to IAM * Add test for cold start and init OneSignal
1 parent dafd372 commit 0852f62

12 files changed

+193
-85
lines changed

iOS_SDK/OneSignalSDK/Source/OSInAppMessage.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
4444
@property (nonatomic) OSInAppMessageDisplayStats *displayStats;
4545
@property (nonatomic) BOOL actionTaken;
4646
@property (nonatomic) BOOL isPreview;
47+
@property (nonatomic) BOOL isDisplayedInSession;
4748
@property (nonatomic) BOOL isTriggerChanged;
4849
@property (nonatomic) NSNumber *height;
4950

iOS_SDK/OneSignalSDK/Source/OSInAppMessage.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ + (instancetype)instanceWithJson:(NSDictionary * _Nonnull)json {
127127
else
128128
return nil;
129129

130+
if (json[@"displayed_in_session"]) {
131+
message.isDisplayedInSession = json[@"displayed_in_session"];
132+
}
130133
return message;
131134
}
132135

@@ -160,6 +163,8 @@ -(NSDictionary *)jsonRepresentation {
160163
json[@"redisplay"] = [_displayStats jsonRepresentation];
161164
}
162165

166+
json[@"displayed_in_session"] = @(_isDisplayedInSession);
167+
163168
return json;
164169
}
165170

iOS_SDK/OneSignalSDK/Source/OSInAppMessageDisplayStats.m

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ + (instancetype)instanceWithJson:(NSDictionary * _Nonnull)json {
9090

9191
- (BOOL)isDelayTimeSatisfied:(NSTimeInterval)date {
9292
if (_lastDisplayTime < 0) {
93-
_lastDisplayTime = date;
9493
return true;
9594
}
9695
//Calculate gap between display times

iOS_SDK/OneSignalSDK/Source/OSInAppMessagingDefines.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ typedef NS_ENUM(NSUInteger, OSTriggerOperatorType) {
7373
#define OS_IAM_SEEN_SET_KEY @"OS_IAM_SEEN_SET"
7474
#define OS_IAM_CLICKED_SET_KEY @"OS_IAM_CLICKED_SET"
7575
#define OS_IAM_IMPRESSIONED_SET_KEY @"OS_IAM_IMPRESSIONED_SET"
76-
#define OS_IAM_SEEN_WITH_REDISPLAY_DICTIONARY_KEY @"OS_IAM_SEEN_WITH_REDISPLAY_DICTIONARY_KEY"
76+
#define OS_IAM_REDISPLAY_DICTIONARY @"OS_IAM_REDISPLAY_DICTIONARY"
7777

7878
// Dynamic trigger kind types
7979
#define OS_DYNAMIC_TRIGGER_KIND_CUSTOM @"custom"

iOS_SDK/OneSignalSDK/Source/OSMessagingController.m

Lines changed: 50 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ @interface OSMessagingController ()
4545
@property (strong, nonatomic, nonnull) NSMutableSet <NSString *> *seenInAppMessages;
4646

4747
// Tracking IAMs with redisplay, used to enable showing an IAM more than once after it has been dismissed
48-
@property (strong, nonatomic, nonnull) NSMutableDictionary <NSString *, OSInAppMessage *> *redisplayInAppMessages;
48+
@property (strong, nonatomic, nonnull) NSMutableDictionary <NSString *, OSInAppMessage *> *redisplayedInAppMessages;
4949

5050
// Tracking for click ids wihtin IAMs so that body, button, and image are only tracked on the dashboard once
5151
@property (strong, nonatomic, nonnull) NSMutableSet <NSString *> *clickedClickIds;
@@ -64,6 +64,7 @@ @interface OSMessagingController ()
6464

6565
@implementation OSMessagingController
6666

67+
static long SIX_MONTHS_TIME_SECONDS = 6 * 30 * 24 * 60 * 60;
6768
static OSMessagingController *sharedInstance = nil;
6869
static dispatch_once_t once;
6970
+ (OSMessagingController *)sharedInstance {
@@ -118,7 +119,7 @@ - (instancetype)init {
118119

119120
// Get all cached IAM data from NSUserDefaults for shown, impressions, and clicks
120121
self.seenInAppMessages = [[NSMutableSet alloc] initWithSet:[standardUserDefaults getSavedSetForKey:OS_IAM_SEEN_SET_KEY defaultValue:nil]];
121-
self.redisplayInAppMessages = [[NSMutableDictionary alloc] initWithDictionary:[standardUserDefaults getSavedDictionaryForKey:OS_IAM_SEEN_WITH_REDISPLAY_DICTIONARY_KEY defaultValue:[NSMutableDictionary new]]];
122+
self.redisplayedInAppMessages = [[NSMutableDictionary alloc] initWithDictionary:[standardUserDefaults getSavedDictionaryForKey:OS_IAM_REDISPLAY_DICTIONARY defaultValue:[NSMutableDictionary new]]];
122123
self.clickedClickIds = [[NSMutableSet alloc] initWithSet:[standardUserDefaults getSavedSetForKey:OS_IAM_CLICKED_SET_KEY defaultValue:nil]];
123124
self.impressionedInAppMessages = [[NSMutableSet alloc] initWithSet:[standardUserDefaults getSavedSetForKey:OS_IAM_IMPRESSIONED_SET_KEY defaultValue:nil]];
124125

@@ -131,32 +132,39 @@ - (instancetype)init {
131132

132133
- (void)didUpdateMessagesForSession:(NSArray<OSInAppMessage *> *)newMessages {
133134
self.messages = newMessages;
134-
135-
[self removeMessagesWithRedisplayFromCache:newMessages];
135+
136+
[self resetRedisplayMessagesBySession];
136137
[self evaluateMessages];
138+
[self deleteOldRedisplayedInAppMessages];
139+
}
140+
141+
- (void)resetRedisplayMessagesBySession {
142+
for (NSString *messageId in _redisplayedInAppMessages) {
143+
[_redisplayedInAppMessages objectForKey:messageId].isDisplayedInSession = false;
144+
}
137145
}
138146

139147
/*
140148
Part of redisplay logic
141-
Remove messages that aren't sent by the backend and keep IAM updated
149+
Remove IAMs that the last display time was six month ago
142150
*/
143-
- (void)removeMessagesWithRedisplayFromCache:(NSArray<OSInAppMessage *> *)newMessages {
151+
- (void)deleteOldRedisplayedInAppMessages {
144152
NSMutableSet <NSString *> * messagesIdToRemove = [NSMutableSet new];
145-
NSMutableDictionary <NSString *, OSInAppMessage *> * newRedisplayDictionary = [_redisplayInAppMessages mutableCopy];
146153

147-
for (NSString *messageId in newRedisplayDictionary) {
148-
if (![newMessages containsObject:[newRedisplayDictionary objectForKey:messageId]]) {
154+
let sixMonthsAgo = self.dateGenerator() - SIX_MONTHS_TIME_SECONDS;
155+
for (NSString *messageId in _redisplayedInAppMessages) {
156+
if ([_redisplayedInAppMessages objectForKey:messageId].displayStats.lastDisplayTime < sixMonthsAgo) {
149157
[messagesIdToRemove addObject:messageId];
150158
}
151159
}
152160

153161
if ([messagesIdToRemove count] > 0) {
162+
NSMutableDictionary <NSString *, OSInAppMessage *> * newRedisplayDictionary = [_redisplayedInAppMessages mutableCopy];
154163
for (NSString * messageId in messagesIdToRemove) {
155164
[newRedisplayDictionary removeObjectForKey:messageId];
156165
}
157-
158-
_redisplayInAppMessages = newRedisplayDictionary;
159-
[OneSignalUserDefaults.initStandard saveDictionaryForKey:OS_IAM_SEEN_WITH_REDISPLAY_DICTIONARY_KEY withValue:newRedisplayDictionary];
166+
167+
[OneSignalUserDefaults.initStandard saveDictionaryForKey:OS_IAM_REDISPLAY_DICTIONARY withValue:newRedisplayDictionary];
160168
}
161169
}
162170

@@ -285,13 +293,13 @@ - (void)evaluateMessages {
285293
/*
286294
Part of redisplay logic
287295
288-
In order to an IAM to be re display, the following conditions need to be satisfied
289-
- IAM has redisplay property
290-
- Gap between displays is satisfied
291-
- Quantity of displays is available
292-
- Some IAM Trigger was fired
293-
294-
For re display, the message need to be removed from the arrays that track the display/impression
296+
In order to redisplay an IAM, the following conditions must be satisfied:
297+
1. IAM has redisplay property
298+
2. Time delay between redisplay satisfied
299+
3. Has more redisplays
300+
4. An IAM trigger was satisfied
301+
302+
For redisplay, the message need to be removed from the arrays that track the display/impression
295303
For click counting, every message has it click id array
296304
*/
297305
- (void)setDataForRedisplay:(OSInAppMessage *)message {
@@ -300,15 +308,17 @@ - (void)setDataForRedisplay:(OSInAppMessage *)message {
300308
}
301309

302310
BOOL messageDismissed = [_seenInAppMessages containsObject:message.messageId];
303-
let redisplayMessageSavedData = [_redisplayInAppMessages objectForKey:message.messageId];
311+
let redisplayMessageSavedData = [_redisplayedInAppMessages objectForKey:message.messageId];
304312

305313
if (messageDismissed && redisplayMessageSavedData != nil) {
306314
[OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"setDataForRedisplay with message: %@", message]];
307315
message.displayStats.displayQuantity = redisplayMessageSavedData.displayStats.displayQuantity;
308316
message.displayStats.lastDisplayTime = redisplayMessageSavedData.displayStats.lastDisplayTime;
309317

318+
// Message that don't have triggers should display only once per session
319+
BOOL triggerHasChanged = message.isTriggerChanged || (!redisplayMessageSavedData.isDisplayedInSession && [message.triggers count] == 0);
310320
// Check if conditions are correct for redisplay
311-
if ([message isTriggerChanged] &&
321+
if (triggerHasChanged &&
312322
[message.displayStats isDelayTimeSatisfied:self.dateGenerator()] &&
313323
[message.displayStats shouldDisplayAgain]) {
314324
[OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"setDataForRedisplay clear arrays"];
@@ -348,46 +358,27 @@ - (void)handleMessageActionWithURL:(OSInAppMessageAction *)action {
348358
/*
349359
* Part of redisplay logic
350360
*
351-
* If trigger key is part of message triggers, then return true, otherwise false
352-
*/
353-
- (BOOL)checkOnMessage:(OSInAppMessage *)message newTriggersKeys:(NSArray<NSString *> *)newTriggersKeys {
354-
for (NSString *triggerKey in newTriggersKeys) {
355-
for (NSArray <OSTrigger *> *andConditions in message.triggers) {
356-
for (OSTrigger *trigger in andConditions) {
357-
if ([triggerKey isEqual:trigger.property]) {
358-
// At least one trigger has changed
359-
return YES;
360-
}
361-
}
362-
}
363-
}
364-
return NO;
365-
}
366-
367-
/*
368-
* Part of redisplay logic
369-
*
370-
* Make all messages with redisplay enable available for redisplay if:
361+
* Make all messages with redisplay available if:
371362
* - Already displayed
372-
* - Trigger changed
363+
* - At least one Trigger has changed
373364
*/
374-
- (void)checkTriggerChanges:(NSArray<NSString *> *)newTriggersKeys {
365+
- (void)makeRedisplayedMessagesAvailableWithTriggers:(NSArray<NSString *> *)newTriggersKeys {
375366
for (OSInAppMessage *message in _messages) {
376-
if ([_redisplayInAppMessages objectForKey:message.messageId] &&
377-
[self checkOnMessage:message newTriggersKeys:newTriggersKeys]) {
367+
if ([_redisplayedInAppMessages objectForKey:message.messageId] &&
368+
[self.triggerController isTriggerOnMessage:message newTriggersKeys:newTriggersKeys]) {
378369
message.isTriggerChanged = true;
379370
}
380371
}
381372
}
382373

383374
#pragma mark Trigger Methods
384375
- (void)addTriggers:(NSDictionary<NSString *, id> *)triggers {
385-
[self checkTriggerChanges:triggers.allKeys];
376+
[self makeRedisplayedMessagesAvailableWithTriggers:triggers.allKeys];
386377
[self.triggerController addTriggers:triggers];
387378
}
388379

389380
- (void)removeTriggersForKeys:(NSArray<NSString *> *)keys {
390-
[self checkTriggerChanges:keys];
381+
[self makeRedisplayedMessagesAvailableWithTriggers:keys];
391382
[self.triggerController removeTriggersForKeys:keys];
392383
}
393384

@@ -411,7 +402,7 @@ - (void)messageViewControllerWasDismissed {
411402
[OneSignalUserDefaults.initStandard saveSetForKey:OS_IAM_SEEN_SET_KEY withValue:self.seenInAppMessages];
412403
// Remove dismissed IAM from messageDisplayQueue
413404
[self.messageDisplayQueue removeObjectAtIndex:0];
414-
[self persisIAMessageForReDisplay:showingIAM];
405+
[self persistInAppMessageForRedisplay:showingIAM];
415406
}
416407

417408
// Reset the IAM viewController to prepare for next IAM if one exists
@@ -441,24 +432,25 @@ - (void)hideWindow {
441432
[UIApplication.sharedApplication.delegate.window makeKeyWindow];
442433
}
443434

444-
- (void)persisIAMessageForReDisplay:(OSInAppMessage *)message {
445-
//If the IAM doesn't have the re display prop there is no need to save it
435+
- (void)persistInAppMessageForRedisplay:(OSInAppMessage *)message {
436+
// If the IAM doesn't have the re display prop there is no need to save it
446437
if (![message.displayStats isRedisplayEnabled])
447438
return;
448439

449440
let displayTimeSeconds = self.dateGenerator();
450441
message.displayStats.lastDisplayTime = displayTimeSeconds;
451442
[message.displayStats incrementDisplayQuantity];
452443
message.isTriggerChanged = false;
444+
message.isDisplayedInSession = true;
453445

454-
[OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"redisplayInAppMessages: %@", [_redisplayInAppMessages description]]];
446+
[OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"redisplayedInAppMessages: %@", [_redisplayedInAppMessages description]]];
455447

456-
//Update the data to enable future re displays
457-
//Avoid calling the userdefault data again
458-
[_redisplayInAppMessages setObject:message forKey:message.messageId];
448+
// Update the data to enable future re displays
449+
// Avoid calling the userdefault data again
450+
[_redisplayedInAppMessages setObject:message forKey:message.messageId];
459451

460-
[OneSignalUserDefaults.initStandard saveDictionaryForKey:OS_IAM_SEEN_WITH_REDISPLAY_DICTIONARY_KEY withValue:self.redisplayInAppMessages];
461-
[OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"persisIAMessageForReDisplay: %@ \nredisplayInAppMessages: %@", [message description], [_redisplayInAppMessages description]]];
452+
[OneSignalUserDefaults.initStandard saveDictionaryForKey:OS_IAM_REDISPLAY_DICTIONARY withValue:self.redisplayedInAppMessages];
453+
[OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"persistInAppMessageForRedisplay: %@ \nredisplayedInAppMessages: %@", [message description], [_redisplayedInAppMessages description]]];
462454
}
463455

464456
- (void)messageViewDidSelectAction:(OSInAppMessage *)message withAction:(OSInAppMessageAction *)action {
@@ -472,6 +464,7 @@ - (void)messageViewDidSelectAction:(OSInAppMessage *)message withAction:(OSInApp
472464
self.actionClickBlock(action);
473465

474466
let clickId = action.clickId;
467+
// If IAM has redisplay the clickId may be available
475468
BOOL clickAvailableByRedisplay = [message.displayStats isRedisplayEnabled] && [message isClickAvailable:clickId];
476469

477470
// Make sure no click tracking is performed for IAM previews
@@ -483,6 +476,7 @@ - (void)messageViewDidSelectAction:(OSInAppMessage *)message withAction:(OSInApp
483476

484477
// Add clickId to clickedClickIds
485478
[self.clickedClickIds addObject:clickId];
479+
// Track clickId per IAM
486480
[message addClickId:clickId];
487481

488482
let metricsRequest = [OSRequestInAppMessageClicked withAppId:OneSignal.app_id

iOS_SDK/OneSignalSDK/Source/OSTriggerController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ NS_ASSUME_NONNULL_BEGIN
4747
@property (weak, nonatomic) id<OSTriggerControllerDelegate> delegate;
4848

4949
- (BOOL)messageMatchesTriggers:(OSInAppMessage *)message;
50+
- (BOOL)isTriggerOnMessage:(OSInAppMessage *)message newTriggersKeys:(NSArray<NSString *> *)newTriggersKeys;
5051
- (void)addTriggers:(NSDictionary<NSString *, id> *)triggers;
5152
- (void)removeTriggersForKeys:(NSArray<NSString *> *)keys;
5253
- (NSDictionary<NSString *, id> *)getTriggers;

iOS_SDK/OneSignalSDK/Source/OSTriggerController.m

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,25 @@ - (id)getTriggerValueForKey:(NSString *)key {
7676
}
7777
}
7878

79+
/*
80+
* Part of redisplay logic
81+
*
82+
* If trigger key is part of message triggers, then return true, otherwise false
83+
*/
84+
- (BOOL)isTriggerOnMessage:(OSInAppMessage *)message newTriggersKeys:(NSArray<NSString *> *)newTriggersKeys {
85+
for (NSString *triggerKey in newTriggersKeys) {
86+
for (NSArray <OSTrigger *> *andConditions in message.triggers) {
87+
for (OSTrigger *trigger in andConditions) {
88+
if ([triggerKey isEqual:trigger.property]) {
89+
// At least one trigger has changed
90+
return YES;
91+
}
92+
}
93+
}
94+
}
95+
return NO;
96+
}
97+
7998
#pragma mark Private Methods
8099

81100
- (void)timeSinceLastMessage:(NSDate *)date {

0 commit comments

Comments
 (0)