diff --git a/examples/demo/App/Views/Components/LogView.swift b/examples/demo/App/Views/Components/LogView.swift index 96ddb32b3..57d83e5aa 100644 --- a/examples/demo/App/Views/Components/LogView.swift +++ b/examples/demo/App/Views/Components/LogView.swift @@ -42,7 +42,7 @@ struct LogView: View { entryList } .frame(maxWidth: .infinity) - .background(OS.Color.logBackground) + .background(OS.Color.logBackground, ignoresSafeAreaEdges: .horizontal) .accessibilityElement(children: .contain) .accessibilityIdentifier("log_view") } diff --git a/examples/demo/App/Views/ContentView.swift b/examples/demo/App/Views/ContentView.swift index 4c632c930..610c5f3ab 100644 --- a/examples/demo/App/Views/ContentView.swift +++ b/examples/demo/App/Views/ContentView.swift @@ -79,7 +79,8 @@ struct ContentView: View { // Android's `scrollIntoView` `scrollableElement` param. .accessibilityIdentifier("main_scroll_view") } - .background(OS.Color.lightBackground.ignoresSafeArea()) + .background(OS.Color.lightBackground.ignoresSafeArea(edges: [.horizontal, .bottom])) + .background(OS.Color.primary.ignoresSafeArea()) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(OS.Color.primary, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m index 704316abb..52039366a 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m @@ -1382,7 +1382,7 @@ - (void)webViewContentFinishedLoading:(OSInAppMessageInternal *)message { if (!self.window) { self.window = [[UIWindow alloc] init]; self.window.windowLevel = UIWindowLevelAlert; - self.window.frame = [[UIScreen mainScreen] bounds]; + self.window.frame = [UIApplication displayBounds]; } self.window.rootViewController = _viewController; diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.h b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.h index e8d3aa0b7..f0cbd2eb4 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.h +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.h @@ -41,10 +41,12 @@ NS_ASSUME_NONNULL_BEGIN @interface OSInAppMessageView : UIView @property (weak, nonatomic, nullable) id delegate; +// Left & right space around the message, so content is laid out at the width it is displayed at +@property (nonatomic) UIEdgeInsets horizontalInsets; - (instancetype _Nonnull)initWithMessage:(OSInAppMessageInternal *)inAppMessage withScriptMessageHandler:(id)messageHandler; -- (void)resetWebViewToMaxBoundsAndResizeHeight:(void (^) (NSNumber *newHeight)) completion; -- (void)updateSafeAreaInsets; +- (void)resetWebViewToMaxBoundsAndResizeHeight:(void (^) (NSNumber * _Nullable newHeight)) completion; +- (void)updateSafeAreaInsets:(UIEdgeInsets)insets; - (void)setupWebViewConstraints; - (void)loadReplacementURL:(NSURL *)url; - (void)loadedHtmlContent:(NSString *)html withBaseURL:(NSURL *)url; diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.m index aef2b358b..83aa7576a 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageView.m @@ -30,6 +30,7 @@ #import "OSInAppMessageClickResult.h" #import #import "OSMacros.h" +#import "UIApplication+OneSignal.h" @interface OSInAppMessageView () @@ -101,7 +102,7 @@ - (void)setupWebviewWithMessageHandler:(id)handler { // WebView should use mainBounds as frame since we need to make sure it spans full possible screen size // to prevent text wrapping while obtaining true height of message from JS - CGRect mainBounds = UIScreen.mainScreen.bounds; + CGRect mainBounds = [UIApplication displayBounds]; mainBounds.size.width -= (2.0 * marginSpacing); // Setup WebView, delegates, and disable scrolling inside of the WebView @@ -132,11 +133,8 @@ - (void)setIsFullscreen:(BOOL)isFullscreen { } - (void)setWebviewFrame { - CGRect mainBounds = UIScreen.mainScreen.bounds; - if (!self.isFullscreen) { - CGFloat marginSpacing = [OneSignalCoreHelper sizeToScale:MESSAGE_MARGIN]; - mainBounds.size.width -= (2.0 * marginSpacing); - } + CGRect mainBounds = [UIApplication displayBounds]; + mainBounds.size.width -= self.horizontalInsets.left + self.horizontalInsets.right; [self.webView setFrame:mainBounds]; } @@ -145,7 +143,7 @@ - (void)setWebviewFrame { WebView will have margins accounted for on width, but height just needs to be phone height or larger The issue is that text wrapping can cause incorrect height issues so width is the real concern here */ -- (void)resetWebViewToMaxBoundsAndResizeHeight:(void (^) (NSNumber *newHeight)) completion { +- (void)resetWebViewToMaxBoundsAndResizeHeight:(void (^) (NSNumber * _Nullable newHeight)) completion { [self.webView removeConstraints:[self.webView constraints]]; @@ -157,6 +155,9 @@ - (void)resetWebViewToMaxBoundsAndResizeHeight:(void (^) (NSNumber *newHeight)) if (error) { NSString *errorMessage = [NSString stringWithFormat:@"Javascript Method: %@ Evaluated with Error: %@", OS_JS_GET_PAGE_META_DATA_METHOD, error]; [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:errorMessage]; + // Still complete so a rotation or fold re-shows the message at its previous height + [self setupWebViewConstraints]; + completion(nil); return; } NSString *successMessage = [NSString stringWithFormat:@"Javascript Method: %@ Evaluated with Success: %@", OS_JS_GET_PAGE_META_DATA_METHOD, result]; @@ -170,13 +171,8 @@ - (void)resetWebViewToMaxBoundsAndResizeHeight:(void (^) (NSNumber *newHeight)) }]; } -- (void)updateSafeAreaInsets { - UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow; - CGFloat top = keyWindow.safeAreaInsets.top; - CGFloat bottom = keyWindow.safeAreaInsets.bottom; - CGFloat right = keyWindow.safeAreaInsets.right; - CGFloat left = keyWindow.safeAreaInsets.left; - NSString *safeAreaInsetsObjectString = [NSString stringWithFormat:OS_JS_SAFE_AREA_INSETS_OBJ,top, bottom, right, left]; +- (void)updateSafeAreaInsets:(UIEdgeInsets)insets { + NSString *safeAreaInsetsObjectString = [NSString stringWithFormat:OS_JS_SAFE_AREA_INSETS_OBJ, insets.top, insets.bottom, insets.right, insets.left]; NSString *setInsetsString = [NSString stringWithFormat:OS_SET_SAFE_AREA_INSETS_METHOD, safeAreaInsetsObjectString]; [self.webView evaluateJavaScript:setInsetsString completionHandler:^(NSDictionary *result, NSError * _Nullable error) { diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m index 873e7447d..1610b5a48 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m @@ -32,6 +32,19 @@ #import #import "OSMacros.h" #import "OSSessionManager.h" +#import "UIApplication+OneSignal.h" + +// iOS 27.1 reserved region API, declared here because release binaries are built with an older SDK. +// Remove these and call the UIKit API directly once releases build with the iOS 27.1 SDK. +@protocol OSReservedRegionKindClass +- (id)occlusionRegionKind; +@end +@protocol OSReservedRegionQuerying +- (NSArray *)reservedRegionsOfKind:(id)kind; +@end +@protocol OSReservedRegion +@property (nonatomic, readonly) CGRect frame; +@end #define HIGHEST_CONSTRAINT_PRIORITY 999.0f #define HIGH_CONSTRAINT_PRIORITY 990.0f @@ -53,9 +66,20 @@ @interface OSInAppMessageViewController () // This constraint enforces an aspect ratio for the given message type (ie. banner) @property (strong, nonatomic, nonnull) NSLayoutConstraint *heightConstraint; +// These pin the message view to the left & right, updated as the safe area changes +@property (strong, nonatomic, nullable) NSLayoutConstraint *leftConstraint; +@property (strong, nonatomic, nullable) NSLayoutConstraint *rightConstraint; + +// Area the message is laid out in vertically, inset by the larger of the margin and the safe area +@property (strong, nonatomic, nullable) UILayoutGuide *verticalGuide; +@property (strong, nonatomic, nullable) NSLayoutConstraint *verticalGuideTopConstraint; +@property (strong, nonatomic, nullable) NSLayoutConstraint *verticalGuideBottomConstraint; + // This recognizer lets the user pan (swipe) the message up and down @property (weak, nonatomic, nullable) UIPanGestureRecognizer *panGestureRecognizer; +@property (nonatomic) BOOL didWarnSceneViewFallback; + // This tap recognizer lets us dismiss the message if the user taps the background @property (weak, nonatomic, nullable) UITapGestureRecognizer *tapGestureRecognizer; @@ -136,6 +160,26 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; } +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + // Resizable scenes (e.g. an unfolded display) don't re-query prefersStatusBarHidden when this window becomes key + if (self.isFullscreen) { + [self setNeedsStatusBarAppearanceUpdate]; + // The insets may have settled before this view had a window, so no change callback follows + if (self.didPageRenderingComplete) { + [self updateContentInsets]; + } + } +} + +// The HTML was given the app's insets, and a page resize doesn't always follow to correct them +- (void)viewSafeAreaInsetsDidChange { + [super viewSafeAreaInsetsDidChange]; + if (self.isFullscreen && self.didPageRenderingComplete) { + [self updateContentInsets]; + } +} + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; @@ -288,20 +332,18 @@ - (void)parseContentData:(NSDictionary *)data { if (self.isFullscreen) { self.pendingHTMLContent = [self setContentInsetsInHTML:self.pendingHTMLContent]; } + self.messageView.horizontalInsets = [self horizontalInsets]; [self.messageView setIsFullscreen:self.isFullscreen]; } - (NSString *)setContentInsetsInHTML:(NSString *)html { NSMutableString *newHTML = [[NSMutableString alloc] initWithString:html]; - UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow; - if (!keyWindow) { + UIWindow *window = [self contentInsetsWindow]; + if (!window) { return newHTML; } - CGFloat top = keyWindow.safeAreaInsets.top; - CGFloat bottom = keyWindow.safeAreaInsets.bottom; - CGFloat right = keyWindow.safeAreaInsets.right; - CGFloat left = keyWindow.safeAreaInsets.left; - NSString *safeAreaInsetsObjectString = [NSString stringWithFormat:OS_JS_SAFE_AREA_INSETS_OBJ,top, bottom, right, left]; + UIEdgeInsets insets = window.safeAreaInsets; + NSString *safeAreaInsetsObjectString = [NSString stringWithFormat:OS_JS_SAFE_AREA_INSETS_OBJ, insets.top, insets.bottom, insets.right, insets.left]; NSString *insetsString = [NSString stringWithFormat:@"\n\n\