diff --git a/ios/RNNButtonBuilder.mm b/ios/RNNButtonBuilder.mm index 0731948747..a6e22a7903 100644 --- a/ios/RNNButtonBuilder.mm +++ b/ios/RNNButtonBuilder.mm @@ -30,6 +30,7 @@ - (RNNUIBarButtonItem *)build:(RNNButtonOptions *)button parentComponentId:parentComponentId componentType:RNNComponentTypeTopBarButton reactViewReadyBlock:nil]; + view.buttonBackgroundColor = [button.backgroundColor withDefault:nil]; return [[RNNUIBarButtonItem alloc] initWithCustomView:view buttonOptions:button onPress:onPress]; diff --git a/ios/RNNButtonOptions.h b/ios/RNNButtonOptions.h index 753cc49f95..cebe7a4e8f 100644 --- a/ios/RNNButtonOptions.h +++ b/ios/RNNButtonOptions.h @@ -24,6 +24,7 @@ @property(nonatomic, strong) RNNIconBackgroundOptions *iconBackground; @property(nonatomic, strong) Bool *disableIconTint; @property(nonatomic, strong) Bool *hideSharedBackground; +@property(nonatomic, strong) Color *backgroundColor; - (RNNButtonOptions *)withDefault:(RNNButtonOptions *)defaultOptions; diff --git a/ios/RNNButtonOptions.mm b/ios/RNNButtonOptions.mm index 23b2d267a0..c9681fda49 100644 --- a/ios/RNNButtonOptions.mm +++ b/ios/RNNButtonOptions.mm @@ -26,6 +26,7 @@ - (instancetype)initWithDict:(NSDictionary *)dict { self.systemItem = [TextParser parse:dict key:@"systemItem"]; self.disableIconTint = [BoolParser parse:dict key:@"disableIconTint"]; self.hideSharedBackground = [BoolParser parse:dict key:@"hideSharedBackground"]; + self.backgroundColor = [ColorParser parse:dict key:@"backgroundColor"]; return self; } @@ -51,6 +52,7 @@ - (RNNButtonOptions *)copy { newOptions.systemItem = self.systemItem.copy; newOptions.disableIconTint = self.disableIconTint.copy; newOptions.hideSharedBackground = self.hideSharedBackground.copy; + newOptions.backgroundColor = self.backgroundColor.copy; return newOptions; } @@ -92,6 +94,8 @@ - (void)mergeOptions:(RNNButtonOptions *)options { self.disableIconTint = options.disableIconTint; if (options.hideSharedBackground.hasValue) self.hideSharedBackground = options.hideSharedBackground; + if (options.backgroundColor.hasValue) + self.backgroundColor = options.backgroundColor; } - (BOOL)shouldCreateCustomView { diff --git a/ios/RNNReactButtonView.h b/ios/RNNReactButtonView.h index 45103d942b..a331673854 100644 --- a/ios/RNNReactButtonView.h +++ b/ios/RNNReactButtonView.h @@ -6,6 +6,7 @@ @interface RNNReactButtonView : RNNComponentView +@property(nonatomic, strong) UIColor *buttonBackgroundColor; @property(nonatomic, copy) void (^intrinsicSizeDidChangeHandler)(CGSize intrinsicSize); @end diff --git a/ios/RNNReactButtonView.mm b/ios/RNNReactButtonView.mm index af802d3874..fd1f22a7f2 100644 --- a/ios/RNNReactButtonView.mm +++ b/ios/RNNReactButtonView.mm @@ -181,6 +181,15 @@ - (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView { } #endif +- (void)didMoveToWindow { + [super didMoveToWindow]; + if (@available(iOS 26.0, *)) { + if (![self designRequiresCompatibility] && self.window) { + [self syncButtonBackground]; + } + } +} + - (void)layoutSubviews { [super layoutSubviews]; if (@available(iOS 26.0, *)) { @@ -200,9 +209,21 @@ - (void)layoutSubviews { self.layer.affineTransform = CGAffineTransformMakeTranslation(tx, transform.ty); } } + [self syncButtonBackground]; } } +- (void)syncButtonBackground { + if (!_buttonBackgroundColor) return; + + UIView *target = self.superview.superview.superview; + if (!target || target.bounds.size.height <= 0) return; + + target.backgroundColor = _buttonBackgroundColor; + target.layer.cornerRadius = target.bounds.size.height / 2.0; + target.clipsToBounds = YES; +} + - (NSString *)componentType { return ComponentTypeButton; } diff --git a/playground/ios/NavigationTests/RNNButtonOptionsTest.mm b/playground/ios/NavigationTests/RNNButtonOptionsTest.mm new file mode 100644 index 0000000000..96a99354df --- /dev/null +++ b/playground/ios/NavigationTests/RNNButtonOptionsTest.mm @@ -0,0 +1,84 @@ +#import +#import + +@interface RNNButtonOptionsTest : XCTestCase + +@end + +@implementation RNNButtonOptionsTest + +- (void)testInitWithDict_shouldParseBackgroundColor { + RNNButtonOptions *options = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + @"backgroundColor" : @(0xffffff00), + }]; + + XCTAssertTrue(options.backgroundColor.hasValue); + XCTAssertTrue([options.backgroundColor.get isEqual:UIColor.yellowColor]); +} + +- (void)testInitWithDict_shouldNotRequireBackgroundColor { + RNNButtonOptions *options = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + @"text" : @"title", + }]; + + XCTAssertFalse(options.backgroundColor.hasValue); +} + +- (void)testCopy_shouldCopyBackgroundColor { + RNNButtonOptions *options = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + @"backgroundColor" : @(0xffffff00), + }]; + + RNNButtonOptions *copied = [options copy]; + + XCTAssertTrue(copied.backgroundColor.hasValue); + XCTAssertTrue([copied.backgroundColor.get isEqual:UIColor.yellowColor]); +} + +- (void)testMergeOptions_shouldMergeBackgroundColor { + RNNButtonOptions *options = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + @"backgroundColor" : @(0xffffff00), + }]; + RNNButtonOptions *mergeOptions = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + @"backgroundColor" : @(0xffff0000), + }]; + + [options mergeOptions:mergeOptions]; + + XCTAssertTrue([options.backgroundColor.get isEqual:UIColor.redColor]); +} + +- (void)testMergeOptions_shouldNotOverwriteBackgroundColorWhenAbsent { + RNNButtonOptions *options = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + @"backgroundColor" : @(0xffffff00), + }]; + RNNButtonOptions *mergeOptions = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + }]; + + [options mergeOptions:mergeOptions]; + + XCTAssertTrue([options.backgroundColor.get isEqual:UIColor.yellowColor]); +} + +- (void)testWithDefault_shouldFallBackToDefaultBackgroundColor { + RNNButtonOptions *options = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + }]; + RNNButtonOptions *defaults = [[RNNButtonOptions alloc] initWithDict:@{ + @"id" : @"buttonId", + @"backgroundColor" : @(0xff00ff00), + }]; + + RNNButtonOptions *result = [options withDefault:defaults]; + + XCTAssertTrue([result.backgroundColor.get isEqual:UIColor.greenColor]); +} + +@end diff --git a/playground/ios/playground.xcodeproj/project.pbxproj b/playground/ios/playground.xcodeproj/project.pbxproj index 4e76a4d4b3..8d212028f6 100644 --- a/playground/ios/playground.xcodeproj/project.pbxproj +++ b/playground/ios/playground.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 50C9A8D4240FB9D000BD699F /* RNNComponentViewController+Utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50C9A8D3240FB9D000BD699F /* RNNComponentViewController+Utils.mm */; }; 50CF233D240695B10098042D /* RNNBottomTabsController+Helpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50CF233C240695B10098042D /* RNNBottomTabsController+Helpers.mm */; }; 50FDEFBC258F5C5D008C9C3C /* RNNSearchBarOptionsTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 50FDEFBB258F5C5D008C9C3C /* RNNSearchBarOptionsTest.mm */; }; + A1B2C3D4E5F60718A1B2C3D5 /* RNNButtonOptionsTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60718A1B2C3D4 /* RNNButtonOptionsTest.mm */; }; 6B102251DCC578519C2DC6A4 /* libPods-NavigationIOS12Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C10F72071A488F801E1F1116 /* libPods-NavigationIOS12Tests.a */; }; 8EB60A6CB93C527CC6A870A2 /* libPods-SnapshotTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E8B4CFA18A5ACE953124E129 /* libPods-SnapshotTests.a */; }; 9F9A3A9625260DA900AAAF37 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9F9A3A9525260DA900AAAF37 /* LaunchScreen.storyboard */; }; @@ -166,6 +167,7 @@ 50CF233C240695B10098042D /* RNNBottomTabsController+Helpers.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "RNNBottomTabsController+Helpers.mm"; sourceTree = ""; }; 50E4888A2427DA4800B11A8E /* StackOptionsTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StackOptionsTest.mm; sourceTree = ""; }; 50FDEFBB258F5C5D008C9C3C /* RNNSearchBarOptionsTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNNSearchBarOptionsTest.mm; sourceTree = ""; }; + A1B2C3D4E5F60718A1B2C3D4 /* RNNButtonOptionsTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNNButtonOptionsTest.mm; sourceTree = ""; }; 60BCFCC02B7F812F00FCDB38 /* libLLVM.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libLLVM.dylib; path = usr/lib/libLLVM.dylib; sourceTree = SDKROOT; }; 60BCFCCA2B7F817400FCDB38 /* libReactNativeNavigation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReactNativeNavigation.a; sourceTree = BUILT_PRODUCTS_DIR; }; 7F8E255E2E08F6ECE7DF6FE3 /* Pods-playground.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-playground.release.xcconfig"; path = "Target Support Files/Pods-playground/Pods-playground.release.xcconfig"; sourceTree = ""; }; @@ -416,6 +418,7 @@ E58D26322385888B003F36BA /* RNNNavigationOptionsTest.mm */, E58D263C2385888C003F36BA /* RNNNavigationStackManagerTest.mm */, 50FDEFBB258F5C5D008C9C3C /* RNNSearchBarOptionsTest.mm */, + A1B2C3D4E5F60718A1B2C3D4 /* RNNButtonOptionsTest.mm */, 502734AE24F3E9110022163C /* ColorParserTest.mm */, E58D262C2385888B003F36BA /* RNNOptionsTest.h */, E58D262D2385888B003F36BA /* RNNOverlayManagerTest.mm */, @@ -968,6 +971,7 @@ 50C9A8D4240FB9D000BD699F /* RNNComponentViewController+Utils.mm in Sources */, 50647FE323E3196800B92025 /* RNNExternalViewControllerTests.mm in Sources */, 50FDEFBC258F5C5D008C9C3C /* RNNSearchBarOptionsTest.mm in Sources */, + A1B2C3D4E5F60718A1B2C3D5 /* RNNButtonOptionsTest.mm in Sources */, E58D264D2385888C003F36BA /* RNNOverlayManagerTest.mm in Sources */, 501C86B9239FE9C400E0B631 /* UIImage+Utils.mm in Sources */, E58D265F2385888C003F36BA /* RNNBasePresenterTest.mm in Sources */, diff --git a/playground/src/screens/ButtonsScreen.tsx b/playground/src/screens/ButtonsScreen.tsx index d00d8434dd..095e900891 100644 --- a/playground/src/screens/ButtonsScreen.tsx +++ b/playground/src/screens/ButtonsScreen.tsx @@ -27,6 +27,7 @@ const { RESET_BUTTONS, CHANGE_BUTTON_PROPS, CHANGE_LEFT_RIGHT_COLORS, + SET_BUTTON_BACKGROUND_COLOR, } = testIDs; export default class ButtonOptions extends NavigationComponent { @@ -131,6 +132,11 @@ export default class ButtonOptions extends NavigationComponent { label="Set leftButtons default Color" onPress={this.changeButtonsColor} /> +