From fcca36399eeceaf3a5a4471d9ec1f910e1602775 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 9 Sep 2025 14:21:27 +0800 Subject: [PATCH 1/8] chore(ios): update iOS demo app --- .../NewPage.imageset/Contents.json | 22 ++++++ .../NewPage.imageset}/newpage@2x.png | Bin .../NewPage.imageset}/newpage@3x.png | Bin .../NewPageSelected.imageset/Contents.json | 22 ++++++ .../newpage_highlight@2x.png | Bin .../newpage_highlight@3x.png | Bin .../Setting.imageset/Contents.json | 22 ++++++ .../Setting.imageset}/setting@2x.png | Bin .../Setting.imageset}/setting@3x.png | Bin .../SettingSelected.imageset/Contents.json | 22 ++++++ .../setting_highlight@3x.png | Bin .../setting_hightlight@2x.png | Bin .../TipsImage.imageset/Contents.json | 22 ++++++ .../TipsImage.imageset}/tips@2x.png | Bin .../TipsImage.imageset}/tips@3x.png | Bin .../HippyDemo/HomePage/HomePageView.xib | 71 +++++++----------- .../HomePage/HomePageViewController.h | 9 --- .../HomePage/HomePageViewController.mm | 33 +++++--- 18 files changed, 159 insertions(+), 64 deletions(-) create mode 100644 framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/Contents.json rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/NewPage.imageset}/newpage@2x.png (100%) rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/NewPage.imageset}/newpage@3x.png (100%) create mode 100644 framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/Contents.json rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/NewPageSelected.imageset}/newpage_highlight@2x.png (100%) rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/NewPageSelected.imageset}/newpage_highlight@3x.png (100%) create mode 100644 framework/examples/ios-demo/Assets.xcassets/Setting.imageset/Contents.json rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/Setting.imageset}/setting@2x.png (100%) rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/Setting.imageset}/setting@3x.png (100%) create mode 100644 framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/Contents.json rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/SettingSelected.imageset}/setting_highlight@3x.png (100%) rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/SettingSelected.imageset}/setting_hightlight@2x.png (100%) create mode 100644 framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/Contents.json rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/TipsImage.imageset}/tips@2x.png (100%) rename framework/examples/ios-demo/{res/image_icons => Assets.xcassets/TipsImage.imageset}/tips@3x.png (100%) diff --git a/framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/Contents.json b/framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/Contents.json new file mode 100644 index 00000000000..4a282b6b481 --- /dev/null +++ b/framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "newpage@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "newpage@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/framework/examples/ios-demo/res/image_icons/newpage@2x.png b/framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/newpage@2x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/newpage@2x.png rename to framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/newpage@2x.png diff --git a/framework/examples/ios-demo/res/image_icons/newpage@3x.png b/framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/newpage@3x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/newpage@3x.png rename to framework/examples/ios-demo/Assets.xcassets/NewPage.imageset/newpage@3x.png diff --git a/framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/Contents.json b/framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/Contents.json new file mode 100644 index 00000000000..95bb357cd02 --- /dev/null +++ b/framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "newpage_highlight@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "newpage_highlight@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/framework/examples/ios-demo/res/image_icons/newpage_highlight@2x.png b/framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/newpage_highlight@2x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/newpage_highlight@2x.png rename to framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/newpage_highlight@2x.png diff --git a/framework/examples/ios-demo/res/image_icons/newpage_highlight@3x.png b/framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/newpage_highlight@3x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/newpage_highlight@3x.png rename to framework/examples/ios-demo/Assets.xcassets/NewPageSelected.imageset/newpage_highlight@3x.png diff --git a/framework/examples/ios-demo/Assets.xcassets/Setting.imageset/Contents.json b/framework/examples/ios-demo/Assets.xcassets/Setting.imageset/Contents.json new file mode 100644 index 00000000000..892e786b341 --- /dev/null +++ b/framework/examples/ios-demo/Assets.xcassets/Setting.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "setting@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "setting@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/framework/examples/ios-demo/res/image_icons/setting@2x.png b/framework/examples/ios-demo/Assets.xcassets/Setting.imageset/setting@2x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/setting@2x.png rename to framework/examples/ios-demo/Assets.xcassets/Setting.imageset/setting@2x.png diff --git a/framework/examples/ios-demo/res/image_icons/setting@3x.png b/framework/examples/ios-demo/Assets.xcassets/Setting.imageset/setting@3x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/setting@3x.png rename to framework/examples/ios-demo/Assets.xcassets/Setting.imageset/setting@3x.png diff --git a/framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/Contents.json b/framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/Contents.json new file mode 100644 index 00000000000..4b4a9802773 --- /dev/null +++ b/framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "setting_hightlight@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "setting_highlight@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/framework/examples/ios-demo/res/image_icons/setting_highlight@3x.png b/framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/setting_highlight@3x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/setting_highlight@3x.png rename to framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/setting_highlight@3x.png diff --git a/framework/examples/ios-demo/res/image_icons/setting_hightlight@2x.png b/framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/setting_hightlight@2x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/setting_hightlight@2x.png rename to framework/examples/ios-demo/Assets.xcassets/SettingSelected.imageset/setting_hightlight@2x.png diff --git a/framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/Contents.json b/framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/Contents.json new file mode 100644 index 00000000000..60fbeeffb0f --- /dev/null +++ b/framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "tips@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "tips@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/framework/examples/ios-demo/res/image_icons/tips@2x.png b/framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/tips@2x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/tips@2x.png rename to framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/tips@2x.png diff --git a/framework/examples/ios-demo/res/image_icons/tips@3x.png b/framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/tips@3x.png similarity index 100% rename from framework/examples/ios-demo/res/image_icons/tips@3x.png rename to framework/examples/ios-demo/Assets.xcassets/TipsImage.imageset/tips@3x.png diff --git a/framework/examples/ios-demo/HippyDemo/HomePage/HomePageView.xib b/framework/examples/ios-demo/HippyDemo/HomePage/HomePageView.xib index 5a2e5f1457e..b1621ad505a 100644 --- a/framework/examples/ios-demo/HippyDemo/HomePage/HomePageView.xib +++ b/framework/examples/ios-demo/HippyDemo/HomePage/HomePageView.xib @@ -1,19 +1,19 @@ - + - + - - - + + + @@ -25,7 +25,7 @@ - + @@ -35,7 +35,7 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -123,4 +99,11 @@ + + + + + + + diff --git a/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.h b/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.h index 9a3e184a5ee..9da14637197 100644 --- a/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.h +++ b/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.h @@ -32,15 +32,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) IBOutlet UIImageView *tipsImageView; -@property (nonatomic, strong) IBOutlet UIButton *pageButton; - -@property (nonatomic, strong) IBOutlet UIButton *settingButton; - -@property (nonatomic, strong) IBOutlet UIView *buttonView; - -- (IBAction)toNewPage; -- (IBAction)toSetting; - @end NS_ASSUME_NONNULL_END diff --git a/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.mm b/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.mm index 4a5f3900393..5ea3e2073d8 100644 --- a/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.mm +++ b/framework/examples/ios-demo/HippyDemo/HomePage/HomePageViewController.mm @@ -27,7 +27,17 @@ #import "PageManagerViewController.h" #import "UIViewController+Title.h" -@interface HomePageViewController () +@interface HomePageViewController () + +@property (weak, nonatomic) IBOutlet UITabBar *tabbar; +/// the New Page tabbar item +@property (weak, nonatomic) IBOutlet UITabBarItem *pageItem; +/// the setting tabbar item +@property (weak, nonatomic) IBOutlet UITabBarItem *settingItem; + +- (IBAction)toNewPage; +- (IBAction)toSetting; + @end @implementation HomePageViewController @@ -47,16 +57,7 @@ - (void)viewDidLoad { self.verLabel.text = [NSString stringWithFormat:@"Ver:%@", _HippySDKVersion]; - self.buttonView.layer.shadowColor = [UIColor grayColor].CGColor; - self.buttonView.layer.shadowOffset = CGSizeMake(0, -1); - self.buttonView.layer.shadowRadius = 4; - self.buttonView.layer.shadowOpacity = .3f; - - [self.pageButton setImage:[UIImage imageFromIconName:@"newpage"] forState:UIControlStateNormal]; - [self.pageButton setImage:[UIImage imageFromIconName:@"newpage_highlight"] forState:UIControlStateHighlighted]; - - [self.settingButton setImage:[UIImage imageFromIconName:@"setting"] forState:UIControlStateNormal]; - [self.settingButton setImage:[UIImage imageFromIconName:@"setting_highlight"] forState:UIControlStateHighlighted]; + self.tabbar.delegate = self; self.tipsImageView.image = [UIImage imageFromIconName:@"tips"]; } @@ -82,4 +83,14 @@ - (IBAction)toSetting { [self.navigationController pushViewController:[[SettingsViewController alloc] init] animated:YES]; } +#pragma - UITabBarDelegate + +- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item { + if (item == self.pageItem) { + [self toNewPage]; + } else if (item == self.settingItem) { + [self toSetting]; + } +} + @end From a47e3cf984ffb2ccbf0a7228192d7c6249a092f9 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 9 Sep 2025 14:35:35 +0800 Subject: [PATCH 2/8] fix(ios): update touch handler process of finding next view --- .../ios/renderer/touch_handler/HippyTouchHandler.mm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm index 6508c4df6af..12f85a76dcd 100644 --- a/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm +++ b/renderer/native/ios/renderer/touch_handler/HippyTouchHandler.mm @@ -618,11 +618,13 @@ - (UIView *)rootView:(UIView *)view { NSNumber *innerTag = [targetView hippyTagAtPoint:point]; if (innerTag && ![targetView.hippyTag isEqual:innerTag]) { UIView *innerView = [_bridge.uiManager viewForHippyTag:innerTag onRootTag:targetView.rootTag]; - point = [targetView convertPoint:point toView:innerView]; - NSDictionary *innerResult = [self nextResponseViewForAction:actions inView:innerView atPoint:point]; - NSMutableDictionary *mergedResult = [result mutableCopy]; - [mergedResult addEntriesFromDictionary:innerResult]; - return mergedResult; + if (innerView) { + point = [targetView convertPoint:point toView:innerView]; + NSDictionary *innerResult = [self nextResponseViewForAction:actions inView:innerView atPoint:point]; + NSMutableDictionary *mergedResult = [result mutableCopy]; + [mergedResult addEntriesFromDictionary:innerResult]; + return mergedResult; + } } return result; } From 6fa6c1998f4a641a19468b37d06a07fd700f3ae9 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 9 Sep 2025 16:46:58 +0800 Subject: [PATCH 3/8] feat(ios): iOS Liquid Glass basic support --- .../src/components/LiquidGlassiOS/index.jsx | 239 ++++++++++++++++++ .../hippy-react-demo/src/components/index.js | 1 + .../examples/hippy-react-demo/src/routes.js | 8 + .../ios/renderer/component/view/HippyView.h | 11 + .../ios/renderer/component/view/HippyView.m | 191 ++++++++++++++ .../component/view/HippyViewManager.mm | 6 + 6 files changed, 456 insertions(+) create mode 100644 driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx diff --git a/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx b/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx new file mode 100644 index 00000000000..0da9e16535b --- /dev/null +++ b/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx @@ -0,0 +1,239 @@ +import React, { useState } from 'react'; +import { + View, + Text, + StyleSheet, + Platform, + ScrollView, + Image, +} from '@hippy/react'; + +import defaultSource from '../Image/defaultSource.jpg'; + +const LiquidGlassDemo = () => { + const [glassEffectEnabled, setGlassEffectEnabled] = useState(true); + const [glassEffectColor, setGlassEffectColor] = useState('#feca57'); + const [glassEffectInteractive, setGlassEffectInteractive] = useState(true); + const [glassEffectContainerSpacing, setGlassEffectContainerSpacing] = useState(10); + const [glassEffectStyle, setGlassEffectStyle] = useState('regular'); // 'regular' or 'clear' + + const toggleGlassEffect = () => { + setGlassEffectEnabled(!glassEffectEnabled); + }; + + const generateRandomColor = () => { + const colors = [ + '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57', + '#ff9ff3', '#54a0ff', '#5f27cd', '#00d2d3', '#ff9f43', + '#10ac84', '#ee5a24', '#0984e3', '#6c5ce7', '#a29bfe', + '#fd79a8', '#fdcb6e', '#e17055', '#81ecec', '#74b9ff', + ]; + const randomIndex = Math.floor(Math.random() * colors.length); + setGlassEffectColor(colors[randomIndex]); + }; + + const toggleInteractive = () => { + setGlassEffectInteractive(!glassEffectInteractive); + }; + + const changeSpacing = () => { + const spacings = [5, 10, 15, 20, 25]; + const currentIndex = spacings.indexOf(glassEffectContainerSpacing); + const nextIndex = (currentIndex + 1) % spacings.length; + setGlassEffectContainerSpacing(spacings[nextIndex]); + }; + + const toggleGlassStyle = () => { + setGlassEffectStyle(glassEffectStyle === 'regular' ? 'clear' : 'regular'); + }; + + // 液态玻璃效果样式 + const liquidGlassStyles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f5f5f7', + }, + scrollContainer: { + flex: 1, + }, + backgroundImage: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, + contentContainer: { + padding: 20, + }, + controlsContainer: { + justifyContent: 'space-around', + marginBottom: 30, + }, + glassButton: { + height: 50, + marginHorizontal: 20, + marginBottom: 15, + borderRadius: 18, + glassEffectEnabled, + glassEffectStyle, + }, + fusionContainer: { + flex: 1, + flexDirection: 'row', + glassEffectContainerSpacing, + }, + fusionItem: { + flex: 1, + height: 60, + borderRadius: 30, + glassEffectEnabled: true, + glassEffectInteractive: true, + glassEffectStyle, + }, + buttonText: { + textAlign: 'center', + lineHeight: 50, + }, + propertyInfo: { + backgroundColor: '#ffffff', + borderRadius: 16, + padding: 20, + marginTop: 40, + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.05, + shadowRadius: 4, + }, + propertyItem: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 12, + }, + propertyLabel: { + color: '#1d1d1f', + fontSize: 14, + fontWeight: '500', + }, + propertyValue: { + color: '#007aff', + fontSize: 14, + fontWeight: '600', + }, + colorPreview: { + width: 20, + height: 20, + borderRadius: 10, + borderWidth: 1, + borderColor: 'rgba(0, 0, 0, 0.1)', + }, + platformNote: { + marginTop: 300, + backgroundColor: 'rgba(255, 149, 0, 0.8)', + borderRadius: 12, + padding: 16, + }, + platformNoteText: { + fontSize: 12, + color: '#fff', + textAlign: 'center', + lineHeight: 18, + }, + }); + + return ( + + {/* 全局背景图 */} + + + + + + {/* 液态玻璃交互控件演示 */} + + + + {glassEffectEnabled ? '关闭' : '开启'} + + + + 随机颜色 + + + + {glassEffectInteractive ? '关闭交互' : '开启交互'} + + + + 调整间距 + + + + {glassEffectStyle === 'regular' ? 'Clear样式' : 'Regular样式'} + + + + + {/* 融合效果演示 */} + + + + + + + {/* 属性信息展示 */} + + + glassEffectEnabled: + {glassEffectEnabled ? 'true' : 'false'} + + + glassEffectColor: + + + {glassEffectColor} + + + + glassEffectInteractive: + {glassEffectInteractive ? 'true' : 'false'} + + + glassEffectContainerSpacing: + {glassEffectContainerSpacing} + + + glassEffectStyle: + + {glassEffectStyle === 'regular' ? 'Regular' : 'Clear'} + + + + + {/* 平台说明 */} + + + {Platform.OS === 'ios' + ? '仅 iOS 26+ 支持 Liquid Glass 效果' + : 'Android 平台暂不支持 Liquid Glass 效果,仅展示属性配置' + } + + + + + + ); +}; + +export default LiquidGlassDemo; + diff --git a/driver/js/examples/hippy-react-demo/src/components/index.js b/driver/js/examples/hippy-react-demo/src/components/index.js index 39258413170..0a8b17eee9c 100644 --- a/driver/js/examples/hippy-react-demo/src/components/index.js +++ b/driver/js/examples/hippy-react-demo/src/components/index.js @@ -15,3 +15,4 @@ export { default as WebView } from './WebView'; export { default as BoxShadow } from './BoxShadow'; export { default as WaterfallView } from './WaterfallView'; export { default as RippleViewAndroid } from './RippleViewAndroid'; +export { default as LiquidGlassiOS } from './LiquidGlassiOS'; diff --git a/driver/js/examples/hippy-react-demo/src/routes.js b/driver/js/examples/hippy-react-demo/src/routes.js index 610f11200cc..58272115c56 100644 --- a/driver/js/examples/hippy-react-demo/src/routes.js +++ b/driver/js/examples/hippy-react-demo/src/routes.js @@ -152,6 +152,14 @@ export default [ type: Type.COMPONENT, }, }, + { + path: '/LiquidGlassiOS', + name: ' 效果', + component: PAGE_LIST.LiquidGlassiOS, + meta: { + type: Type.COMPONENT, + }, + }, { path: '/Moduels', name: 'Modules', diff --git a/renderer/native/ios/renderer/component/view/HippyView.h b/renderer/native/ios/renderer/component/view/HippyView.h index a28a4834252..5f11517091f 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.h +++ b/renderer/native/ios/renderer/component/view/HippyView.h @@ -43,6 +43,17 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL isUseNewShadow; @property (nonatomic, strong) HippyViewInnerLayer *innerShadowLayer; +/// iOS 26+ Liquid Glass Effect +@property (nonatomic, assign) BOOL glassEffectEnabled; +/// iOS 26+ Liquid Glass Effect Interactive +@property (nonatomic, assign) BOOL glassEffectInteractive; +/// iOS 26+ Liquid Glass Effect Color +@property (nonatomic, strong) UIColor *glassEffectTintColor; +/// iOS 26+ Liquid Glass Effect Container Spacing +@property (nonatomic, strong) NSNumber *glassEffectContainerSpacing; +/// iOS 26+ Liquid Glass Effect Style (Regular/Clear) +@property (nonatomic, strong) NSString *glassEffectStyle; + /** * get content for layer * return YES if getting content synchronized,else return NO diff --git a/renderer/native/ios/renderer/component/view/HippyView.m b/renderer/native/ios/renderer/component/view/HippyView.m index f4761de945b..2f6765662bb 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.m +++ b/renderer/native/ios/renderer/component/view/HippyView.m @@ -71,6 +71,8 @@ static CGSize makeSizeConstrainWithType(CGSize originSize, CGSize constrainSize, @implementation HippyView { UIColor *_backgroundColor; + // iOS 26+ Liquid Glass EffectView + UIVisualEffectView *_effectView; } @synthesize hippyZIndex = _hippyZIndex; @@ -108,6 +110,13 @@ - (NSString *)description { return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement]; } +#pragma mark - Hippy Lifecycle override + +- (void)didUpdateHippySubviews { + [super didUpdateHippySubviews]; + [self moveSubviewsToEffectView]; +} + #pragma mark - Borders - (UIColor *)backgroundColor { @@ -178,6 +187,21 @@ - (void)hippySetFrame:(CGRect)frame { - (void)setFrame:(CGRect)frame { [super setFrame:frame]; + + // Update effect view frame if it exists + if (_effectView) { + _effectView.frame = self.bounds; + } +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + // Update effect view frame and corner radius + if (_effectView) { + _effectView.frame = self.bounds; + _effectView.layer.cornerRadius = self.layer.cornerRadius; + } } - (HippyBorderColors)borderColors { @@ -306,6 +330,11 @@ - (void)displayLayer:(CALayer *)layer { layer.contents = nil; layer.needsDisplayOnBoundsChange = NO; layer.mask = nil; + + if (_effectView) { + _effectView.layer.cornerRadius = cornerRadii.topLeft; + } + return; } @@ -520,4 +549,166 @@ -(void)setBorder##side##Style : (HippyBorderStyle)style { \ setBorderStyle() + +#pragma mark - Liquid Glass Effect + +- (void)setGlassEffectEnabled:(BOOL)glassEffectEnabled { + if (_glassEffectEnabled == glassEffectEnabled) { + return; + } + _glassEffectEnabled = glassEffectEnabled; + + if (@available(iOS 26.0, *)) { + if (glassEffectEnabled) { + [self setupGlassEffect]; + } else { + [self removeGlassEffect]; + } + } +} + +- (void)setGlassEffectTintColor:(UIColor *)glassEffectTintColor { + if ([_glassEffectTintColor isEqual:glassEffectTintColor]) { + return; + } + _glassEffectTintColor = glassEffectTintColor; + + if (@available(iOS 26.0, *)) { + if (_glassEffectEnabled && _effectView) { + UIGlassEffectStyle style = [self glassEffectStyleFromString:_glassEffectStyle]; + UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:style]; + glassEffect.tintColor = glassEffectTintColor; + glassEffect.interactive = _glassEffectInteractive; + _effectView.effect = glassEffect; + } + } +} + +- (void)setGlassEffectInteractive:(BOOL)glassEffectInteractive { + if (_glassEffectInteractive == glassEffectInteractive) { + return; + } + _glassEffectInteractive = glassEffectInteractive; + + if (@available(iOS 26.0, *)) { + if (_glassEffectEnabled && _effectView) { + UIGlassEffectStyle style = [self glassEffectStyleFromString:_glassEffectStyle]; + UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:style]; + glassEffect.tintColor = _glassEffectTintColor; + glassEffect.interactive = glassEffectInteractive; + _effectView.effect = glassEffect; + } + } +} + +- (void)setGlassEffectContainerSpacing:(NSNumber *)glassEffectContainerSpacing { + if ([_glassEffectContainerSpacing isEqual:glassEffectContainerSpacing]) { + return; + } + _glassEffectContainerSpacing = glassEffectContainerSpacing; + + if (@available(iOS 26.0, *)) { + if (glassEffectContainerSpacing && glassEffectContainerSpacing.doubleValue > 0) { + [self setupGlassContainerEffect]; + } else { + [self removeGlassEffect]; + } + } +} + +- (void)setGlassEffectStyle:(NSString *)glassEffectStyle { + if ([_glassEffectStyle isEqualToString:glassEffectStyle]) { + return; + } + _glassEffectStyle = glassEffectStyle; + + if (@available(iOS 26.0, *)) { + if (_glassEffectEnabled && _effectView) { + [self setupGlassEffect]; + } + } +} + +#pragma mark - Private Liquid Glass Methods + +- (UIGlassEffectStyle)glassEffectStyleFromString:(NSString *)styleString { + if (@available(iOS 26.0, *)) { + if ([styleString isEqualToString:@"clear"]) { + return UIGlassEffectStyleClear; + } + } + return UIGlassEffectStyleRegular; // Default to Regular +} + +- (void)setupGlassEffect { + if (@available(iOS 26.0, *)) { + [self removeGlassEffect]; + + // Create glass effect with specified style + UIGlassEffectStyle style = [self glassEffectStyleFromString:_glassEffectStyle]; + UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:style]; + glassEffect.tintColor = _glassEffectTintColor; + glassEffect.interactive = _glassEffectInteractive; + + _effectView = [[UIVisualEffectView alloc] initWithEffect:glassEffect]; + _effectView.frame = self.bounds; + _effectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _effectView.layer.cornerRadius = self.layer.cornerRadius; + + [self addSubview:_effectView]; + [self sendSubviewToBack:_effectView]; + } +} + +- (void)setupGlassContainerEffect { + if (@available(iOS 26.0, *)) { + [self removeGlassEffect]; + + UIGlassContainerEffect *glassContainerEffect = [[UIGlassContainerEffect alloc] init]; + glassContainerEffect.spacing = _glassEffectContainerSpacing.doubleValue; + + _effectView = [[UIVisualEffectView alloc] initWithEffect:glassContainerEffect]; + _effectView.frame = self.bounds; + _effectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _effectView.layer.cornerRadius = self.layer.cornerRadius; + + [self addSubview:_effectView]; + [self sendSubviewToBack:_effectView]; + + // Move existing subviews to the effect view's content view + [self moveSubviewsToEffectView]; + } +} + +- (void)removeGlassEffect { + if (_effectView) { + // Move subviews back to self before removing effect view + [self moveSubviewsFromEffectView]; + [_effectView removeFromSuperview]; + _effectView = nil; + } +} + +- (void)moveSubviewsToEffectView { + if (_effectView && _effectView.contentView) { + NSArray *subviews = [self.subviews copy]; + for (UIView *subview in subviews) { + if (subview != _effectView && subview.superview != _effectView.contentView) { + [subview removeFromSuperview]; + [_effectView.contentView addSubview:subview]; + } + } + } +} + +- (void)moveSubviewsFromEffectView { + if (_effectView && _effectView.contentView) { + NSArray *subviews = [_effectView.contentView.subviews copy]; + for (UIView *subview in subviews) { + [subview removeFromSuperview]; + [self addSubview:subview]; + } + } +} + @end diff --git a/renderer/native/ios/renderer/component/view/HippyViewManager.mm b/renderer/native/ios/renderer/component/view/HippyViewManager.mm index b56105681de..4653b1c52c0 100644 --- a/renderer/native/ios/renderer/component/view/HippyViewManager.mm +++ b/renderer/native/ios/renderer/component/view/HippyViewManager.mm @@ -258,6 +258,12 @@ - (void)measureInAppWindow:(NSNumber *)componentTag #pragma mark - View properties +HIPPY_EXPORT_VIEW_PROPERTY(glassEffectEnabled, BOOL) +HIPPY_EXPORT_VIEW_PROPERTY(glassEffectStyle, NSString) +HIPPY_EXPORT_VIEW_PROPERTY(glassEffectInteractive, BOOL) +HIPPY_EXPORT_VIEW_PROPERTY(glassEffectTintColor, UIColor) +HIPPY_EXPORT_VIEW_PROPERTY(glassEffectContainerSpacing, NSNumber) + HIPPY_EXPORT_VIEW_PROPERTY(accessibilityLabel, NSString) HIPPY_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor) HIPPY_EXPORT_VIEW_PROPERTY(shadowSpread, CGFloat) From 9477a9b87190cc04cef7fdd2778dbf71ee7149fa Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 9 Sep 2025 17:12:53 +0800 Subject: [PATCH 4/8] feat(ios): add compile macro to liquid glass --- .../src/components/LiquidGlassiOS/index.jsx | 12 ++++++------ .../ios/renderer/component/view/HippyView.h | 2 ++ .../ios/renderer/component/view/HippyView.m | 18 +++++++++++++++++- .../component/view/HippyViewManager.mm | 2 ++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx b/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx index 0da9e16535b..67dec5d8cc3 100644 --- a/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx @@ -12,7 +12,7 @@ import defaultSource from '../Image/defaultSource.jpg'; const LiquidGlassDemo = () => { const [glassEffectEnabled, setGlassEffectEnabled] = useState(true); - const [glassEffectColor, setGlassEffectColor] = useState('#feca57'); + const [glassEffectTintColor, setGlassEffectTintColor] = useState('#feca57'); const [glassEffectInteractive, setGlassEffectInteractive] = useState(true); const [glassEffectContainerSpacing, setGlassEffectContainerSpacing] = useState(10); const [glassEffectStyle, setGlassEffectStyle] = useState('regular'); // 'regular' or 'clear' @@ -29,7 +29,7 @@ const LiquidGlassDemo = () => { '#fd79a8', '#fdcb6e', '#e17055', '#81ecec', '#74b9ff', ]; const randomIndex = Math.floor(Math.random() * colors.length); - setGlassEffectColor(colors[randomIndex]); + setGlassEffectTintColor(colors[randomIndex]); }; const toggleInteractive = () => { @@ -162,7 +162,7 @@ const LiquidGlassDemo = () => { {glassEffectEnabled ? '关闭' : '开启'} - 随机颜色 @@ -198,10 +198,10 @@ const LiquidGlassDemo = () => { {glassEffectEnabled ? 'true' : 'false'} - glassEffectColor: + glassEffectTintColor: - - {glassEffectColor} + + {glassEffectTintColor} diff --git a/renderer/native/ios/renderer/component/view/HippyView.h b/renderer/native/ios/renderer/component/view/HippyView.h index 5f11517091f..ce232809216 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.h +++ b/renderer/native/ios/renderer/component/view/HippyView.h @@ -43,6 +43,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL isUseNewShadow; @property (nonatomic, strong) HippyViewInnerLayer *innerShadowLayer; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 /// iOS 26+ Liquid Glass Effect @property (nonatomic, assign) BOOL glassEffectEnabled; /// iOS 26+ Liquid Glass Effect Interactive @@ -53,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) NSNumber *glassEffectContainerSpacing; /// iOS 26+ Liquid Glass Effect Style (Regular/Clear) @property (nonatomic, strong) NSString *glassEffectStyle; +#endif /** * get content for layer diff --git a/renderer/native/ios/renderer/component/view/HippyView.m b/renderer/native/ios/renderer/component/view/HippyView.m index 2f6765662bb..6101ffe9698 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.m +++ b/renderer/native/ios/renderer/component/view/HippyView.m @@ -71,8 +71,10 @@ static CGSize makeSizeConstrainWithType(CGSize originSize, CGSize constrainSize, @implementation HippyView { UIColor *_backgroundColor; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 // iOS 26+ Liquid Glass EffectView UIVisualEffectView *_effectView; +#endif } @synthesize hippyZIndex = _hippyZIndex; @@ -189,19 +191,23 @@ - (void)setFrame:(CGRect)frame { [super setFrame:frame]; // Update effect view frame if it exists +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 if (_effectView) { _effectView.frame = self.bounds; } +#endif } - (void)layoutSubviews { [super layoutSubviews]; // Update effect view frame and corner radius +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 if (_effectView) { _effectView.frame = self.bounds; _effectView.layer.cornerRadius = self.layer.cornerRadius; } +#endif } - (HippyBorderColors)borderColors { @@ -331,9 +337,11 @@ - (void)displayLayer:(CALayer *)layer { layer.needsDisplayOnBoundsChange = NO; layer.mask = nil; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 if (_effectView) { _effectView.layer.cornerRadius = cornerRadii.topLeft; } +#endif return; } @@ -552,6 +560,8 @@ -(void)setBorder##side##Style : (HippyBorderStyle)style { \ #pragma mark - Liquid Glass Effect +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 + - (void)setGlassEffectEnabled:(BOOL)glassEffectEnabled { if (_glassEffectEnabled == glassEffectEnabled) { return; @@ -629,9 +639,13 @@ - (void)setGlassEffectStyle:(NSString *)glassEffectStyle { } } +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 + #pragma mark - Private Liquid Glass Methods -- (UIGlassEffectStyle)glassEffectStyleFromString:(NSString *)styleString { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 + +- (UIGlassEffectStyle)glassEffectStyleFromString:(NSString *)styleString API_AVAILABLE(ios(26.0)) { if (@available(iOS 26.0, *)) { if ([styleString isEqualToString:@"clear"]) { return UIGlassEffectStyleClear; @@ -711,4 +725,6 @@ - (void)moveSubviewsFromEffectView { } } +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 + @end diff --git a/renderer/native/ios/renderer/component/view/HippyViewManager.mm b/renderer/native/ios/renderer/component/view/HippyViewManager.mm index 4653b1c52c0..475932d14f3 100644 --- a/renderer/native/ios/renderer/component/view/HippyViewManager.mm +++ b/renderer/native/ios/renderer/component/view/HippyViewManager.mm @@ -258,11 +258,13 @@ - (void)measureInAppWindow:(NSNumber *)componentTag #pragma mark - View properties +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 HIPPY_EXPORT_VIEW_PROPERTY(glassEffectEnabled, BOOL) HIPPY_EXPORT_VIEW_PROPERTY(glassEffectStyle, NSString) HIPPY_EXPORT_VIEW_PROPERTY(glassEffectInteractive, BOOL) HIPPY_EXPORT_VIEW_PROPERTY(glassEffectTintColor, UIColor) HIPPY_EXPORT_VIEW_PROPERTY(glassEffectContainerSpacing, NSNumber) +#endif HIPPY_EXPORT_VIEW_PROPERTY(accessibilityLabel, NSString) HIPPY_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor) From 78cd0224808e3922120a2760819ff482bef65c72 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 9 Sep 2025 17:13:59 +0800 Subject: [PATCH 5/8] feat(ios): set interactive as default for liquid glass --- renderer/native/ios/renderer/component/view/HippyView.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/renderer/native/ios/renderer/component/view/HippyView.m b/renderer/native/ios/renderer/component/view/HippyView.m index 6101ffe9698..1b157ff8db4 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.m +++ b/renderer/native/ios/renderer/component/view/HippyView.m @@ -94,6 +94,9 @@ - (instancetype)initWithFrame:(CGRect)frame { _backgroundColor = super.backgroundColor; self.layer.shadowOffset = CGSizeZero; self.layer.shadowRadius = 0.f; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 + _glassEffectInteractive = YES; +#endif } return self; } @@ -602,6 +605,7 @@ - (void)setGlassEffectInteractive:(BOOL)glassEffectInteractive { if (@available(iOS 26.0, *)) { if (_glassEffectEnabled && _effectView) { + _effectView.effect = nil; UIGlassEffectStyle style = [self glassEffectStyleFromString:_glassEffectStyle]; UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:style]; glassEffect.tintColor = _glassEffectTintColor; From 908dd8cb8895646de7638349e07a845009f3cddb Mon Sep 17 00:00:00 2001 From: wwwcg Date: Wed, 24 Sep 2025 11:41:46 +0800 Subject: [PATCH 6/8] feat(ios): update glass effect imp --- .../src/components/LiquidGlassiOS/index.jsx | 6 +++--- renderer/native/ios/renderer/component/view/HippyView.m | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx b/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx index 67dec5d8cc3..d1be4b1e806 100644 --- a/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/components/LiquidGlassiOS/index.jsx @@ -87,8 +87,8 @@ const LiquidGlassDemo = () => { flex: 1, height: 60, borderRadius: 30, - glassEffectEnabled: true, - glassEffectInteractive: true, + glassEffectEnabled, + glassEffectInteractive, glassEffectStyle, }, buttonText: { @@ -166,7 +166,7 @@ const LiquidGlassDemo = () => { onClick={generateRandomColor}> 随机颜色 - {glassEffectInteractive ? '关闭交互' : '开启交互'} diff --git a/renderer/native/ios/renderer/component/view/HippyView.m b/renderer/native/ios/renderer/component/view/HippyView.m index 1b157ff8db4..f51be55436a 100644 --- a/renderer/native/ios/renderer/component/view/HippyView.m +++ b/renderer/native/ios/renderer/component/view/HippyView.m @@ -605,12 +605,7 @@ - (void)setGlassEffectInteractive:(BOOL)glassEffectInteractive { if (@available(iOS 26.0, *)) { if (_glassEffectEnabled && _effectView) { - _effectView.effect = nil; - UIGlassEffectStyle style = [self glassEffectStyleFromString:_glassEffectStyle]; - UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:style]; - glassEffect.tintColor = _glassEffectTintColor; - glassEffect.interactive = glassEffectInteractive; - _effectView.effect = glassEffect; + [self setupGlassEffect]; } } } From c8ee371c9d7915d1333dc112b0606be7f3ccf636 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Wed, 24 Sep 2025 16:50:04 +0800 Subject: [PATCH 7/8] docs(ios): update api docs of liquid glass --- docs/api/hippy-react/components.md | 7 +++++++ docs/api/hippy-vue/components.md | 9 +++++++++ .../hippy-vue-demo/src/components/demos/demo-button.vue | 2 ++ 3 files changed, 18 insertions(+) diff --git a/docs/api/hippy-react/components.md b/docs/api/hippy-react/components.md index 3fe1f6e1617..a16621b6afa 100644 --- a/docs/api/hippy-react/components.md +++ b/docs/api/hippy-react/components.md @@ -468,6 +468,8 @@ import icon from './qb_icon_new.png'; 最基础的容器组件,它是一个支持Flexbox布局、样式、一些触摸处理、和一些无障碍功能的容器,并且它可以放到其它的视图里,也可以有任意多个任意类型的子视图。不论在什么平台上,`View` 都会直接对应一个平台的原生视图。 +> **新特性**:现已支持 iOS 26+ Liquid Glass(液态玻璃)效果,详见`glassEffect` 相关API说明。 + !> Android 具有节点优化的特性,请注意 `collapsable` 属性的使用 ## 属性 @@ -488,6 +490,11 @@ import icon from './qb_icon_new.png'; | onTouchEnd | 当触屏操作结束,用户在该控件上抬起手指时,此函数将被回调,event参数也会通知当前的触屏点信息;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | onTouchCancel | 当用户触屏过程中,某个系统事件中断了触屏,例如电话呼入、组件变化(如设置为hidden)、其他组件的滑动手势,此函数会收到回调,触屏点信息也会通过event参数告知前端;参数为 `nativeEvent: { name, page_x, page_y, id }`,`page_x` 和 `page_y` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | pointerEvents | 用于控制视图是否可以成为触摸事件的目标。 | `enum('box-none', 'none', 'box-only', 'auto')` | `iOS` | +| glassEffectEnabled | 启用或禁用 iOS 26 Liquid Glass(液态玻璃)效果。当设置为 `true` 时,视图将应用液态玻璃视觉效果。 | `boolean` | `iOS 26+` | +| glassEffectStyle | 设置液态玻璃效果的样式。可选值为 `'clear'` 和 `'regular'`,默认为 `'regular'`。 | `string` | `iOS 26+` | +| glassEffectInteractive | 控制液态玻璃效果是否响应用户交互。当设置为 `true` 时,玻璃效果会根据用户触摸产生动态变化。 | `boolean` | `iOS 26+` | +| glassEffectTintColor | 设置液态玻璃效果的着色颜色。可以使用任何有效的颜色值来调整玻璃效果的色调。 | `Color` | `iOS 26+` | +| glassEffectContainerSpacing | 设置液态玻璃容器效果的间距值。当设置此属性后,视图将成为一个液态玻璃组合容器,其内嵌的液态玻璃组件之间将产生融合效果。 | `number` | `iOS 26+` | * pointerEvents 的参数含义: * `auto`(默认值) - 视图可以是触摸事件的目标; diff --git a/docs/api/hippy-vue/components.md b/docs/api/hippy-vue/components.md index d0265cc8ca0..2426123611c 100644 --- a/docs/api/hippy-vue/components.md +++ b/docs/api/hippy-vue/components.md @@ -27,6 +27,8 @@ 该组件映射到 View 组件,容器里面可以放图片、也可以放文本。但是因为 View 不能包裹文本,所以需要在 `