diff --git a/packages/react-native/React/CoreModules/RCTRedBox.mm b/packages/react-native/React/CoreModules/RCTRedBox.mm index dc58594a80702c..ae1e99573b3306 100644 --- a/packages/react-native/React/CoreModules/RCTRedBox.mm +++ b/packages/react-native/React/CoreModules/RCTRedBox.mm @@ -25,7 +25,7 @@ #if RCT_DEV_MENU -@class RCTRedBoxWindow; +@class RCTRedBoxController; @interface UIButton (RCTRedBox) @@ -62,41 +62,44 @@ - (void)rct_addBlock:(RCTRedBoxButtonPressHandler)handler forControlEvents:(UICo @end -@protocol RCTRedBoxWindowActionDelegate +@protocol RCTRedBoxControllerActionDelegate -- (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame; -- (void)reloadFromRedBoxWindow:(RCTRedBoxWindow *)redBoxWindow; +- (void)redBoxController:(RCTRedBoxController *)redBoxController openStackFrameInEditor:(RCTJSStackFrame *)stackFrame; +- (void)reloadFromRedBoxController:(RCTRedBoxController *)redBoxController; - (void)loadExtraDataViewController; @end -@interface RCTRedBoxWindow : NSObject -@property (nonatomic, strong) UIViewController *rootViewController; -@property (nonatomic, weak) id actionDelegate; +@interface RCTRedBoxController : UIViewController +@property (nonatomic, weak) id actionDelegate; @end -@implementation RCTRedBoxWindow { +@implementation RCTRedBoxController { UITableView *_stackTraceTableView; NSString *_lastErrorMessage; NSArray *_lastStackTrace; + NSArray *_customButtonTitles; + NSArray *_customButtonHandlers; int _lastErrorCookie; } -- (instancetype)initWithFrame:(CGRect)frame - customButtonTitles:(NSArray *)customButtonTitles - customButtonHandlers:(NSArray *)customButtonHandlers -{ - if (self = [super init]) { - _lastErrorCookie = -1; +- (instancetype) initWithCustomButtonTitles:(NSArray *)customButtonTitles + customButtonHandlers:(NSArray *)customButtonHandlers { + if (self = [super init]) { + _lastErrorCookie = -1; + _customButtonTitles = customButtonTitles; + _customButtonHandlers = customButtonHandlers; + } + + return self; +} - _rootViewController = [UIViewController new]; - UIView *rootView = _rootViewController.view; - rootView.frame = frame; - rootView.backgroundColor = [UIColor blackColor]; +- (void)viewDidLoad { + self.view.backgroundColor = [UIColor blackColor]; const CGFloat buttonHeight = 60; - CGRect detailsFrame = rootView.bounds; + CGRect detailsFrame = self.view.bounds; detailsFrame.size.height -= buttonHeight + (double)[self bottomSafeViewHeight]; _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain]; @@ -107,7 +110,7 @@ - (instancetype)initWithFrame:(CGRect)frame _stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3]; _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone; _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; - [rootView addSubview:_stackTraceTableView]; + [self.view addSubview:_stackTraceTableView]; #if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST NSString *reloadText = @"Reload\n(\u2318R)"; @@ -137,45 +140,56 @@ - (instancetype)initWithFrame:(CGRect)frame accessibilityIdentifier:@"redbox-extra" selector:@selector(showExtraDataViewController) block:nil]; - - CGFloat buttonWidth = frame.size.width / (CGFloat)(4 + [customButtonTitles count]); - CGFloat bottomButtonHeight = frame.size.height - buttonHeight - (CGFloat)[self bottomSafeViewHeight]; - dismissButton.frame = CGRectMake(0, bottomButtonHeight, buttonWidth, buttonHeight); - reloadButton.frame = CGRectMake(buttonWidth, bottomButtonHeight, buttonWidth, buttonHeight); - copyButton.frame = CGRectMake(buttonWidth * 2, bottomButtonHeight, buttonWidth, buttonHeight); - extraButton.frame = CGRectMake(buttonWidth * 3, bottomButtonHeight, buttonWidth, buttonHeight); - - [rootView addSubview:dismissButton]; - [rootView addSubview:reloadButton]; - [rootView addSubview:copyButton]; - [rootView addSubview:extraButton]; - - for (NSUInteger i = 0; i < [customButtonTitles count]; i++) { - UIButton *button = [self redBoxButton:customButtonTitles[i] + + [dismissButton.heightAnchor constraintEqualToConstant:buttonHeight].active = YES; + [reloadButton.heightAnchor constraintEqualToConstant:buttonHeight].active = YES; + [copyButton.heightAnchor constraintEqualToConstant:buttonHeight].active = YES; + [extraButton.heightAnchor constraintEqualToConstant:buttonHeight].active = YES; + + UIStackView *buttonStackView = [[UIStackView alloc] init]; + buttonStackView.translatesAutoresizingMaskIntoConstraints = NO; + buttonStackView.axis = UILayoutConstraintAxisHorizontal; + buttonStackView.distribution = UIStackViewDistributionFillEqually; + buttonStackView.alignment = UIStackViewAlignmentTop; + + [buttonStackView.heightAnchor constraintEqualToConstant:buttonHeight+[self bottomSafeViewHeight]].active = YES; + buttonStackView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1]; + + [buttonStackView addArrangedSubview:dismissButton]; + [buttonStackView addArrangedSubview:reloadButton]; + [buttonStackView addArrangedSubview:copyButton]; + [buttonStackView addArrangedSubview:extraButton]; + + [self.view addSubview:buttonStackView]; + + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:buttonStackView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]]; + + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:buttonStackView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0]]; + + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:buttonStackView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; + + + for (NSUInteger i = 0; i < [_customButtonTitles count]; i++) { + UIButton *button = [self redBoxButton:_customButtonTitles[i] accessibilityIdentifier:@"" selector:nil - block:customButtonHandlers[i]]; - button.frame = CGRectMake(buttonWidth * (double)(4 + i), bottomButtonHeight, buttonWidth, buttonHeight); - [rootView addSubview:button]; + block:_customButtonHandlers[i]]; + [button.heightAnchor constraintEqualToConstant:buttonHeight].active = YES; + [buttonStackView addArrangedSubview:button]; } - UIView *topBorder = - [[UIView alloc] initWithFrame:CGRectMake(0, bottomButtonHeight + 1, rootView.frame.size.width, 1)]; + UIView *topBorder = [[UIView alloc] init]; + topBorder.translatesAutoresizingMaskIntoConstraints = NO; topBorder.backgroundColor = [UIColor colorWithRed:0.70 green:0.70 blue:0.70 alpha:1.0]; + [topBorder.heightAnchor constraintEqualToConstant:1].active = YES; - [rootView addSubview:topBorder]; - - UIView *bottomSafeView = [UIView new]; - bottomSafeView.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1]; - bottomSafeView.frame = CGRectMake( - 0, - frame.size.height - (CGFloat)[self bottomSafeViewHeight], - frame.size.width, - (CGFloat)[self bottomSafeViewHeight]); + [self.view addSubview:topBorder]; + + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:topBorder attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]]; - [rootView addSubview:bottomSafeView]; - } - return self; + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:topBorder attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:0]]; + + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:topBorder attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:buttonStackView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; } - (UIButton *)redBoxButton:(NSString *)title @@ -226,7 +240,7 @@ - (void)showErrorMessage:(NSString *)message // Remove ANSI color codes from the message NSString *messageWithoutAnsi = [self stripAnsi:message]; - BOOL isRootViewControllerPresented = self.rootViewController.presentingViewController != nil; + BOOL isRootViewControllerPresented = self.presentingViewController != nil; // Show if this is a new message, or if we're updating the previous message BOOL isNew = !isRootViewControllerPresented && !isUpdate; BOOL isUpdateForSameMessage = !isNew && @@ -246,19 +260,19 @@ - (void)showErrorMessage:(NSString *)message [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; - [RCTKeyWindow().rootViewController presentViewController:self.rootViewController animated:YES completion:nil]; + [RCTKeyWindow().rootViewController presentViewController:self animated:YES completion:nil]; } } } - (void)dismiss { - [self.rootViewController dismissViewControllerAnimated:YES completion:nil]; + [self dismissViewControllerAnimated:YES completion:nil]; } - (void)reload { - [_actionDelegate reloadFromRedBoxWindow:self]; + [_actionDelegate reloadFromRedBoxController:self]; } - (void)showExtraDataViewController @@ -396,7 +410,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath if (indexPath.section == 1) { NSUInteger row = indexPath.row; RCTJSStackFrame *stackFrame = _lastStackTrace[row]; - [_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame]; + [_actionDelegate redBoxController:self openStackFrameInEditor:stackFrame]; } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @@ -438,13 +452,13 @@ - (BOOL)canBecomeFirstResponder @interface RCTRedBox () < RCTInvalidating, - RCTRedBoxWindowActionDelegate, + RCTRedBoxControllerActionDelegate, RCTRedBoxExtraDataActionDelegate, NativeRedBoxSpec> @end @implementation RCTRedBox { - RCTRedBoxWindow *_window; + RCTRedBoxController *_controller; NSMutableArray> *_errorCustomizers; RCTRedBoxExtraDataViewController *_extraDataViewController; NSMutableArray *_customButtonTitles; @@ -592,17 +606,14 @@ - (void)showErrorMessage:(NSString *)message [[self->_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"collectRedBoxExtraData" body:nil]; #pragma clang diagnostic pop - - if (!self->_window) { - self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds - customButtonTitles:self->_customButtonTitles - customButtonHandlers:self->_customButtonHandlers]; - self->_window.actionDelegate = self; + if (!self->_controller) { + self->_controller = [[RCTRedBoxController alloc] initWithCustomButtonTitles:self->_customButtonTitles customButtonHandlers:self->_customButtonHandlers]; + self->_controller.actionDelegate = self; } RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message stack:stack]; errorInfo = [self _customizeError:errorInfo]; - [self->_window showErrorMessage:errorInfo.errorMessage + [self->_controller showErrorMessage:errorInfo.errorMessage withStack:errorInfo.stack isUpdate:isUpdate errorCookie:errorCookie]; @@ -613,8 +624,8 @@ - (void)loadExtraDataViewController { dispatch_async(dispatch_get_main_queue(), ^{ // Make sure the CMD+E shortcut doesn't call this twice - if (self->_extraDataViewController != nil && ![self->_window.rootViewController presentedViewController]) { - [self->_window.rootViewController presentViewController:self->_extraDataViewController + if (self->_extraDataViewController != nil && ![self->_controller presentedViewController]) { + [self->_controller presentViewController:self->_extraDataViewController animated:YES completion:nil]; } @@ -629,7 +640,7 @@ - (void)loadExtraDataViewController RCT_EXPORT_METHOD(dismiss) { dispatch_async(dispatch_get_main_queue(), ^{ - [self->_window dismiss]; + [self->_controller dismiss]; }); } @@ -638,7 +649,7 @@ - (void)invalidate [self dismiss]; } -- (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame +- (void)redBoxController:(__unused RCTRedBoxController *)redBoxController openStackFrameInEditor:(RCTJSStackFrame *)stackFrame { NSURL *const bundleURL = _overrideBundleURL ?: _bundleManager.bundleURL; if (![bundleURL.scheme hasPrefix:@"http"]) { @@ -661,10 +672,10 @@ - (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEd - (void)reload { // Window is not used and can be nil - [self reloadFromRedBoxWindow:nil]; + [self reloadFromRedBoxController:nil]; } -- (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow +- (void)reloadFromRedBoxController:(__unused RCTRedBoxController *)redBoxController { if (_overrideReloadAction) { _overrideReloadAction();