You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@weex.apache.org by mo...@apache.org on 2019/11/12 08:26:29 UTC

[incubator-weex] branch dark-mode-dev created (now 6bd805e)

This is an automated email from the ASF dual-hosted git repository.

moshen pushed a change to branch dark-mode-dev
in repository https://gitbox.apache.org/repos/asf/incubator-weex.git.


      at 6bd805e  Support dark mode.

This branch includes the following new commits:

     new 6bd805e  Support dark mode.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-weex] 01/01: Support dark mode.

Posted by mo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

moshen pushed a commit to branch dark-mode-dev
in repository https://gitbox.apache.org/repos/asf/incubator-weex.git

commit 6bd805e7ba3ac2045504656cfb35ffc0359b26b1
Author: qianyuan.wqy <qi...@taobao.com>
AuthorDate: Tue Nov 12 16:26:14 2019 +0800

    Support dark mode.
---
 ios/sdk/WeexSDK.xcodeproj/project.pbxproj          |  18 +++
 .../Sources/Component/RecycleList/WXJSASTParser.mm |   2 +-
 .../Sources/Component/WXComponent_internal.h       |   7 ++
 .../WeexSDK/Sources/Component/WXImageComponent.m   | 121 ++++++++++++---------
 ios/sdk/WeexSDK/Sources/Component/WXRichText.mm    |  34 +++++-
 .../WeexSDK/Sources/Component/WXTextComponent.mm   |  86 ++++++++++-----
 .../WeexSDK/Sources/Display/WXComponent+Display.m  |  89 +++++++++++++--
 ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m       |   2 +
 .../WXDarkThemeDefaultImpl.h}                      |  11 +-
 .../WXDarkThemeDefaultImpl.m}                      |  21 +++-
 .../WeexSDK/Sources/Manager/WXComponentManager.mm  |   6 +
 ios/sdk/WeexSDK/Sources/Model/WXComponent.h        |  18 +++
 ios/sdk/WeexSDK/Sources/Model/WXComponent.mm       |  30 ++++-
 ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h      |  28 +++++
 ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m      |  85 ++++++++++++++-
 .../WeexSDK/Sources/Model/WXSDKInstance_private.h  |   2 +
 ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m |  35 +++++-
 ios/sdk/WeexSDK/Sources/Module/WXTransition.mm     |  65 +++++++++--
 .../{WXDestroyProtocol.h => WXDarkThemeProtocol.h} |  23 +++-
 ios/sdk/WeexSDK/Sources/Utility/WXUtility.h        |   8 ++
 ios/sdk/WeexSDK/Sources/Utility/WXUtility.m        |  19 +++-
 .../Sources/View/WXComponent+ViewManagement.mm     |  46 +++++++-
 ios/sdk/WeexSDK/Sources/View/WXRootView.m          |  36 ++++++
 ios/sdk/WeexSDK/Sources/WeexSDK.h                  |   1 +
 24 files changed, 663 insertions(+), 130 deletions(-)

diff --git a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
index e006b27..3502f78 100644
--- a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
+++ b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
@@ -618,6 +618,12 @@
 		D735F1B222D761F800B53CDF /* log_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D735F1AC22D761F800B53CDF /* log_utils.cpp */; };
 		D735F1B322D761F800B53CDF /* log_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D735F1AC22D761F800B53CDF /* log_utils.cpp */; };
 		D77286FF22C9B22C00E1DA7D /* eagle_bridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BD9205FA223651D800EDF93D /* eagle_bridge.cpp */; };
+		D7C96CF3237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		D7C96CF4237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		D7C96CF7237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */; };
+		D7C96CF8237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */; };
+		D7C96CF9237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */; };
+		D7C96CFA237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */; };
 		DC03ADB91D508719003F76E7 /* WXTextAreaComponent.mm in Sources */ = {isa = PBXBuildFile; fileRef = DC03ADB71D508719003F76E7 /* WXTextAreaComponent.mm */; };
 		DC03ADBA1D508719003F76E7 /* WXTextAreaComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = DC03ADB81D508719003F76E7 /* WXTextAreaComponent.h */; };
 		DC15A3DB2010BC93009C8977 /* weex-main-jsfm.js in Resources */ = {isa = PBXBuildFile; fileRef = DC15A3D92010BC93009C8977 /* weex-main-jsfm.js */; };
@@ -1351,6 +1357,9 @@
 		D3FC0DF61C508B2A002B9E31 /* WXTimerModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXTimerModule.m; sourceTree = "<group>"; };
 		D735F1AB22D761F800B53CDF /* log_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = log_utils.h; sourceTree = "<group>"; };
 		D735F1AC22D761F800B53CDF /* log_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = log_utils.cpp; sourceTree = "<group>"; };
+		D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXDarkThemeProtocol.h; sourceTree = "<group>"; };
+		D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXDarkThemeDefaultImpl.h; sourceTree = "<group>"; };
+		D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXDarkThemeDefaultImpl.m; sourceTree = "<group>"; };
 		DAB176F008F516E4F9391C61 /* libPods-WeexSDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WeexSDK.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		DC03ADB71D508719003F76E7 /* WXTextAreaComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WXTextAreaComponent.mm; sourceTree = "<group>"; };
 		DC03ADB81D508719003F76E7 /* WXTextAreaComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXTextAreaComponent.h; sourceTree = "<group>"; };
@@ -1476,6 +1485,8 @@
 		59A583031CF5B2FD0081FD3E /* Handler */ = {
 			isa = PBXGroup;
 			children = (
+				D7C96CF5237AA16100A4599C /* WXDarkThemeDefaultImpl.h */,
+				D7C96CF6237AA16100A4599C /* WXDarkThemeDefaultImpl.m */,
 				33CE190C2153443000CF9670 /* WXJSFrameworkLoadDefaultImpl.h */,
 				33CE190D2153443000CF9670 /* WXJSFrameworkLoadDefaultImpl.m */,
 				59A583041CF5B2FD0081FD3E /* WXNavigationDefaultImpl.h */,
@@ -1857,6 +1868,7 @@
 		77D1611C1C02DD3C0010B15B /* Protocol */ = {
 			isa = PBXGroup;
 			children = (
+				D7C96CF2237AA13400A4599C /* WXDarkThemeProtocol.h */,
 				33CE19122153444900CF9670 /* WXJSFrameworkLoadProtocol.h */,
 				17036A5220FDE7490029AE3D /* WXApmProtocol.h */,
 				17C74F0E2072147A00AB4CAB /* WXAnalyzerProtocol.h */,
@@ -2410,6 +2422,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D7C96CF3237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */,
 				F75C591C2313C1FC002FFF94 /* WXStreamModule.h in Headers */,
 				74A4BA9E1CB3C0A100195969 /* WXHandlerFactory.h in Headers */,
 				59A583081CF5B2FD0081FD3E /* WXNavigationDefaultImpl.h in Headers */,
@@ -2497,6 +2510,7 @@
 				59CE27E81CC387DB000BE37A /* WXEmbedComponent.h in Headers */,
 				B8D66C0F21255730003960BD /* core_side_in_platform.h in Headers */,
 				B8D66C9121255730003960BD /* render_factory_interface.h in Headers */,
+				D7C96CF7237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */,
 				DCE2CF9B1F46D4220021BDC4 /* WXVoiceOverModule.h in Headers */,
 				453F3756219A76CA00A03F1D /* default_request_handler.h in Headers */,
 				74BB5FB91DFEE81A004FC3DF /* WXMetaModule.h in Headers */,
@@ -2656,6 +2670,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D7C96CF4237AA13400A4599C /* WXDarkThemeProtocol.h in Headers */,
 				DCA4461D1EFA5AAA00D0CFA8 /* WXHandlerFactory.h in Headers */,
 				DCA446101EFA5A8500D0CFA8 /* WXBridgeMethod.h in Headers */,
 				DCA4461A1EFA5AA000D0CFA8 /* WXInvocationConfig.h in Headers */,
@@ -2724,6 +2739,7 @@
 				DCA445A21EFA570100D0CFA8 /* WXScrollerComponent.h in Headers */,
 				B8D66C7621255730003960BD /* render_object.h in Headers */,
 				DCA445B71EFA579200D0CFA8 /* WXImgLoaderProtocol.h in Headers */,
+				D7C96CF8237AA16100A4599C /* WXDarkThemeDefaultImpl.h in Headers */,
 				1746EA7420E9D253007E55BD /* WXComponent_performance.h in Headers */,
 				B8D66CEB21255B2A003960BD /* WXWebSocketLoader.h in Headers */,
 				DCA445C21EFA57D700D0CFA8 /* WXBaseViewController.h in Headers */,
@@ -3324,6 +3340,7 @@
 				749DC27C1D40827B009E1C91 /* WXMonitor.m in Sources */,
 				C4B834271DE69B09007AD27E /* WXPickerModule.m in Sources */,
 				745B2D691E5A8E1E0092D38A /* WXMultiColumnLayout.m in Sources */,
+				D7C96CF9237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */,
 				77788B752229252D000D5102 /* render_page_custom.cpp in Sources */,
 				77D161391C02DE940010B15B /* WXBridgeManager.m in Sources */,
 				74BF19F91F5139BB00AEE3D7 /* WXJSASTParser.mm in Sources */,
@@ -3515,6 +3532,7 @@
 				DCA445981EFA55B300D0CFA8 /* WXSDKInstance.m in Sources */,
 				DCA445991EFA55B300D0CFA8 /* WXJSExceptionInfo.m in Sources */,
 				DCA4459A1EFA55B300D0CFA8 /* WXResourceRequest.m in Sources */,
+				D7C96CFA237AA16100A4599C /* WXDarkThemeDefaultImpl.m in Sources */,
 				77788B762229252D000D5102 /* render_page_custom.cpp in Sources */,
 				DCA4459B1EFA55B300D0CFA8 /* WXResourceRequestHandlerDefaultImpl.m in Sources */,
 				DCA4459C1EFA55B300D0CFA8 /* WXResourceResponse.m in Sources */,
diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
index cfea370..2d26ff0 100644
--- a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm
@@ -760,7 +760,7 @@ static int binaryPrecedence(WXJSToken *token)
 
 - (WXJSExpression *)parsePrimaryExpression
 {
-    int type = _lookahead->type;
+    WXJSTokenType type = _lookahead->type;
     
     if (type == WXJSTokenTypePunctuator) {
         if (_lookahead->value == "[") {
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
index a97dfcb..6dff036 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
+++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
@@ -50,7 +50,9 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate);
      *  View
      */
     UIColor *_styleBackgroundColor;
+    UIColor *_darkThemeBackgroundColor;
     NSString *_backgroundImage;
+    NSString *_darkThemeBackgroundImage;
     NSString *_clipRadius;
     WXClipType _clipToBounds;
     UIView *_view;
@@ -115,9 +117,13 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate);
     WXThreadSafeCounter *_displayCounter;
     
     UIColor *_borderTopColor;
+    UIColor *_darkThemeBorderTopColor;
     UIColor *_borderRightColor;
+    UIColor *_darkThemeBorderRightColor;
     UIColor *_borderLeftColor;
+    UIColor *_darkThemeBorderLeftColor;
     UIColor *_borderBottomColor;
+    UIColor *_darkThemeBorderBottomColor;
     
     CGFloat _borderTopWidth;
     CGFloat _borderRightWidth;
@@ -179,6 +185,7 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate);
  DO NOT use "_backgroundColor" directly. The same reason as '_transform'.
  */
 @property (atomic, strong) UIColor* styleBackgroundColor;
+@property (atomic, strong) UIColor* darkThemeBackgroundColor;
 
 ///--------------------------------------
 /// @name Package Internal Methods
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m
index 1cb8245..2a83fee 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m
+++ b/ios/sdk/WeexSDK/Sources/Component/WXImageComponent.m
@@ -53,14 +53,14 @@ static dispatch_queue_t WXImageUpdateQueue;
 
 @interface WXImageComponent ()
 {
-    NSString * _imageSrc;
-    pthread_mutex_t _imageSrcMutex;
-    pthread_mutexattr_t _propertMutexAttr;
     BOOL _shouldUpdateImage;
     BOOL _mainImageSuccess;
 }
 
+@property (atomic, strong) NSString *src;
+@property (atomic, strong) NSString *darkThemeSrc;
 @property (atomic, strong) NSString *placeholdSrc;
+@property (atomic, strong) NSString *darkThemePlaceholderSrc;
 @property (nonatomic, assign) CGFloat blurRadius;
 @property (nonatomic, assign) UIViewContentMode resizeMode;
 @property (nonatomic, assign) WXImageQuality imageQuality;
@@ -71,7 +71,7 @@ static dispatch_queue_t WXImageUpdateQueue;
 @property (nonatomic) BOOL imageLoadEvent;
 @property (nonatomic) BOOL imageDownloadFinish;
 @property (nonatomic) BOOL downloadImageWithURL;
-@property (nonatomic ,strong) NSString* preUrl;
+@property (nonatomic, strong) NSString* preUrl;
 
 @end
 
@@ -88,17 +88,15 @@ WX_EXPORT_METHOD(@selector(save:))
             WXImageUpdateQueue = dispatch_queue_create("com.taobao.weex.ImageUpdateQueue", DISPATCH_QUEUE_SERIAL);
         }
         
-        pthread_mutexattr_init(&(_propertMutexAttr));
-        pthread_mutexattr_settype(&(_propertMutexAttr), PTHREAD_MUTEX_RECURSIVE);
-        pthread_mutex_init(&(_imageSrcMutex), &(_propertMutexAttr));
-        
         if (attributes[@"src"]) {
-             pthread_mutex_lock(&(_imageSrcMutex));
-            _imageSrc = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
-             pthread_mutex_unlock(&(_imageSrcMutex));
+            self.src = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
         } else {
             WXLogWarning(@"image src is nil");
         }
+        if (attributes[@"darkThemeSrc"]) {
+            self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+        }
+        
         [self configPlaceHolder:attributes];
         
         NSString *resizeMode = attributes[@"resize"];
@@ -135,6 +133,9 @@ WX_EXPORT_METHOD(@selector(save:))
     if (attributes[@"placeHolder"] || attributes[@"placeholder"]) {
         self.placeholdSrc = [[WXConvert NSString:attributes[@"placeHolder"]?:attributes[@"placeholder"]]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
     }
+    if (attributes[@"darkThemePlaceholder"]) {
+        self.darkThemePlaceholderSrc = [[WXConvert NSString:attributes[@"darkThemePlaceholder"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+    }
 }
 
 - (void)configFilter:(NSDictionary *)styles needUpdate:(BOOL)needUpdate
@@ -258,12 +259,13 @@ WX_EXPORT_METHOD(@selector(save:))
 - (void)dealloc
 {
     [self cancelImage];
-    pthread_mutex_destroy(&(_imageSrcMutex));
-    pthread_mutexattr_destroy(&_propertMutexAttr);
 }
 
 - (void)updateAttributes:(NSDictionary *)attributes
 {
+    if (attributes[@"darkThemeSrc"]) {
+        self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+    }
     if (attributes[@"src"]) {
         [self setImageSrc:[[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
     }
@@ -293,7 +295,16 @@ WX_EXPORT_METHOD(@selector(save:))
     [self _clipsToBounds];
     
     [self updateImage];
-    
+}
+
+- (void)themeDidChange:(NSString*)theme
+{
+    [super themeDidChange:theme];
+    if (_view) {
+        if (self.darkThemeSrc || self.darkThemePlaceholderSrc) {
+            [self updateImage];
+        }
+    }
 }
 
 - (BOOL)_needsDrawBorder
@@ -358,26 +369,15 @@ WX_EXPORT_METHOD(@selector(save:))
     }
 }
 
-- (NSString *)imageSrc
-{
-    pthread_mutex_lock(&(_imageSrcMutex));
-    NSString * imageSrcCpy = [_imageSrc copy];
-    pthread_mutex_unlock(&(_imageSrcMutex));
-    
-    return imageSrcCpy;
-}
-
 - (void)setImageSrc:(NSString*)src
 {
-    if ([src isEqualToString:_imageSrc]) {
+    if ([src isEqualToString:self.src]) {
         // if image src is equal to then ignore it.
         return;
     }
-    pthread_mutex_lock(&(_imageSrcMutex));
-    _imageSrc = src;
+    self.src = src;
     _imageDownloadFinish = NO;
     ((UIImageView*)self.view).image = nil;
-    pthread_mutex_unlock(&(_imageSrcMutex));
     
     [self updateImage];
 }
@@ -397,14 +397,19 @@ WX_EXPORT_METHOD(@selector(save:))
         _shouldUpdateImage = YES;
         return;
     }
+    
+    BOOL isDarkMode = [self.weexInstance isDarkTheme];
+    NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src;
+    NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc;
+    
     __weak typeof(self) weakSelf = self;
     if (_downloadImageWithURL && [[self imageLoader] respondsToSelector:@selector(setImageViewWithURL:url:placeholderImage:options:progress:completed:)]) {
         _mainImageSuccess = NO;
         
         NSString *newURL = nil;
-        if (self.placeholdSrc) {
-            newURL = [self.placeholdSrc copy];
-            WX_REWRITE_URL([self placeholdSrc], WXResourceTypeImage, self.weexInstance)
+        if (choosedPlaceholder) {
+            newURL = [choosedPlaceholder copy];
+            WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, self.weexInstance)
             NSDictionary* extInfo = @{@"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
             [[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:extInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) {
                 /* We cannot rely on image library even if we call setImage with placeholer before calling setImage with real url.
@@ -414,8 +419,8 @@ WX_EXPORT_METHOD(@selector(save:))
                     UIImageView *imageView = (UIImageView *)strongSelf.view;
                     if (imageView && imageView.image == image && strongSelf->_mainImageSuccess) {
                         // reload image with main image url
-                        NSString* newURL = [[strongSelf imageSrc] copy];
-                        WX_REWRITE_URL([strongSelf imageSrc], WXResourceTypeImage, strongSelf.weexInstance)
+                        NSString* newURL = [choosedSrc copy];
+                        WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, strongSelf.weexInstance)
                         NSDictionary *userInfo = @{@"imageQuality":@(strongSelf.imageQuality), @"imageSharp":@(strongSelf.imageSharp),  @"blurRadius":@(strongSelf.blurRadius), @"instanceId":[strongSelf _safeInstanceId], @"pageURL": strongSelf.weexInstance.scriptURL ?: @""};
                         [[strongSelf imageLoader] setImageViewWithURL:imageView url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) {
                             WXLogInfo(@"Image re-requested because placeholder may override main image. %@", imageURL);
@@ -424,11 +429,11 @@ WX_EXPORT_METHOD(@selector(save:))
                 }
             }];
         }
-        newURL = [[self imageSrc] copy];
+        newURL = [choosedSrc copy];
         if ([newURL length] == 0) {
             return;
         }
-        WX_REWRITE_URL([self imageSrc], WXResourceTypeImage, self.weexInstance)
+        WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, self.weexInstance)
         NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp),  @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
         [[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:^(NSInteger receivedSize, NSInteger expectedSize) {
             // progress when loading image
@@ -444,9 +449,9 @@ WX_EXPORT_METHOD(@selector(save:))
                 WXLogError(@"Error downloading image: %@, detail:%@", imageURL.absoluteString, [error localizedDescription]);
                 
                 // retry set placeholder, maybe placeholer image can be downloaded
-                if (strongSelf.placeholdSrc) {
-                    NSString *newURL = [strongSelf.placeholdSrc copy];
-                    WX_REWRITE_URL([strongSelf placeholdSrc], WXResourceTypeImage, strongSelf.weexInstance)
+                if (choosedPlaceholder) {
+                    NSString *newURL = [choosedPlaceholder copy];
+                    WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, strongSelf.weexInstance)
                     [[strongSelf imageLoader] setImageViewWithURL:(UIImageView*)strongSelf.view
                                                               url:[NSURL URLWithString:newURL]
                                                  placeholderImage:nil
@@ -513,7 +518,7 @@ WX_EXPORT_METHOD(@selector(save:))
             [strongSelf updatePlaceHolderWithFailedBlock:downloadFailed];
             [strongSelf updateContentImageWithFailedBlock:downloadFailed];
 
-            if (!strongSelf.imageSrc && !strongSelf.placeholdSrc) {
+            if (!choosedSrc && !choosedPlaceholder) {
                 dispatch_async(dispatch_get_main_queue(), ^{
                     strongSelf.layer.contents = nil;
                     strongSelf.imageDownloadFinish = YES;
@@ -526,15 +531,16 @@ WX_EXPORT_METHOD(@selector(save:))
 
 - (void)updatePlaceHolderWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock
 {
-    NSString *placeholderSrc = self.placeholdSrc;
+    BOOL isDarkMode = [self.weexInstance isDarkTheme];
+    NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc;
     
-    if ([WXUtility isBlankString:placeholderSrc]) {
+    if ([WXUtility isBlankString:choosedPlaceholder]) {
         return;
     }
     
-    WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, placeholderSrc);
-    NSString *newURL = [self.placeholdSrc copy];
-    WX_REWRITE_URL(self.placeholdSrc, WXResourceTypeImage, self.weexInstance)
+    WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, choosedPlaceholder);
+    NSString *newURL = [choosedPlaceholder copy];
+    WX_REWRITE_URL(choosedPlaceholder, WXResourceTypeImage, self.weexInstance)
     
     __weak typeof(self) weakSelf = self;
     self.placeholderOperation = [[self imageLoader] downloadImageWithURL:newURL imageFrame:self.calculatedFrame
@@ -543,16 +549,21 @@ WX_EXPORT_METHOD(@selector(save:))
     {
         dispatch_async(dispatch_get_main_queue(), ^{
             __strong typeof(self) strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
             UIImage *viewImage = ((UIImageView *)strongSelf.view).image;
             if (error) {
-                downloadFailedBlock(placeholderSrc,error);
+                downloadFailedBlock(choosedPlaceholder, error);
                 if ([strongSelf isViewLoaded] && !viewImage) {
                     ((UIImageView *)(strongSelf.view)).image = nil;
                     [strongSelf readyToRender];
                 }
                 return;
             }
-            if (![placeholderSrc isEqualToString:strongSelf.placeholdSrc]) {
+            
+            NSString* currentPlaceholder = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemePlaceholderSrc ?: strongSelf.placeholdSrc) : strongSelf.placeholdSrc;
+            if (![choosedPlaceholder isEqualToString:currentPlaceholder]) {
                 return;
             }
             
@@ -570,20 +581,25 @@ WX_EXPORT_METHOD(@selector(save:))
 
 - (void)updateContentImageWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock
 {
-    NSString *imageSrc = [NSString stringWithFormat:@"%@", self.imageSrc?:@""];
-    if ([WXUtility isBlankString:imageSrc]) {
+    BOOL isDarkMode = [self.weexInstance isDarkTheme];
+    NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src;
+    
+    if ([WXUtility isBlankString:choosedSrc]) {
         WXLogError(@"image src is empty");
         return;
     }
     
-    WXLogDebug(@"Updating image:%@, component:%@", self.imageSrc, self.ref);
+    WXLogDebug(@"Updating image:%@, component:%@", choosedSrc, self.ref);
     NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp), @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
-    NSString * newURL = [imageSrc copy];
-    WX_REWRITE_URL(imageSrc, WXResourceTypeImage, self.weexInstance)
+    NSString * newURL = [choosedSrc copy];
+    WX_REWRITE_URL(choosedSrc, WXResourceTypeImage, self.weexInstance)
     __weak typeof(self) weakSelf = self;
     weakSelf.imageOperation = [[weakSelf imageLoader] downloadImageWithURL:newURL imageFrame:weakSelf.calculatedFrame userInfo:userInfo completed:^(UIImage *image, NSError *error, BOOL finished) {
         dispatch_async(dispatch_get_main_queue(), ^{
             __strong typeof(self) strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
             
             if (strongSelf.imageLoadEvent) {
                 NSMutableDictionary *sizeDict = [NSMutableDictionary new];
@@ -598,12 +614,13 @@ WX_EXPORT_METHOD(@selector(save:))
                 [strongSelf fireEvent:@"load" params:@{ @"success": error? @false : @true,@"size":sizeDict}];
             }
             if (error) {
-                downloadFailedBlock(imageSrc, error);
+                downloadFailedBlock(choosedSrc, error);
                 [strongSelf readyToRender];
                 return ;
             }
             
-            if (![imageSrc isEqualToString:strongSelf.imageSrc]) {
+            NSString* currentSrc = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemeSrc ?: strongSelf.src) : strongSelf.src;
+            if (![choosedSrc isEqualToString:currentSrc]) {
                 return ;
             }
             
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
index 8ffeaf3..8e30d76 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
@@ -27,6 +27,7 @@
 #import "WXImgLoaderProtocol.h"
 #import "WXComponentManager.h"
 #import "WXLog.h"
+#import "WXDarkThemeProtocol.h"
 #include <pthread/pthread.h>
 
 @interface WXRichNode : NSObject
@@ -35,7 +36,9 @@
 @property (nonatomic, strong) NSString  *ref;
 @property (nonatomic, strong) NSString  *text;
 @property (nonatomic, strong) UIColor   *color;
+@property (nonatomic, strong) UIColor   *darkThemeColor;
 @property (nonatomic, strong) UIColor   *backgroundColor;
+@property (nonatomic, strong) UIColor   *darkThemeBackgroundColor;
 @property (nonatomic, strong) NSString  *fontFamily;
 @property (nonatomic, assign) CGFloat   fontSize;
 @property (nonatomic, assign) CGFloat   fontWeight;
@@ -91,7 +94,9 @@ do {\
     id value = styles[@#key]; \
     if (value) { \
         node.key = [WXConvert type:value];\
-    } else if (!([@#key isEqualToString:@"backgroundColor"] || [@#key isEqualToString:@"textDecoration"]) && superNode.key ) { \
+    } else if (!([@#key isEqualToString:@"backgroundColor"] || \
+        [@#key isEqualToString:@"darkThemeBackgroundColor"] || \
+        [@#key isEqualToString:@"textDecoration"]) && superNode.key ) { \
         node.key = superNode.key; \
     } \
 } while(0);
@@ -190,7 +195,9 @@ do {\
 - (void)fillCSSStyles:(NSDictionary *)styles toNode:(WXRichNode *)node superNode:(WXRichNode *)superNode
 {
     WX_STYLE_FILL_RICHTEXT(color, UIColor)
+    WX_STYLE_FILL_RICHTEXT(darkThemeColor, UIColor)
     WX_STYLE_FILL_RICHTEXT(backgroundColor, UIColor)
+    WX_STYLE_FILL_RICHTEXT(darkThemeBackgroundColor, UIColor)
     WX_STYLE_FILL_RICHTEXT(fontFamily, NSString)
     WX_STYLE_FILL_RICHTEXT_PIXEL(fontSize)
     WX_STYLE_FILL_RICHTEXT(fontWeight, WXTextWeight)
@@ -421,6 +428,15 @@ do {\
     };
 }
 
+- (void)themeDidChange:(NSString*)theme
+{
+    [super themeDidChange:theme];
+    if ([self isViewLoaded]) {
+        // Force inner layout
+        [self innerLayout];
+    }
+}
+
 #pragma mark Text Building
 
 - (NSMutableAttributedString *)buildAttributeString
@@ -434,6 +450,16 @@ do {\
     NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] init];
     NSUInteger location;
     
+    BOOL invert = self.invertForDarkTheme;
+    
+    // Invert default background color.
+    UIColor* defaultTextColor = [UIColor blackColor];
+    UIColor* defaultBackgroundColor = _backgroundColor;
+    if (invert && [self.weexInstance isDarkTheme]) {
+        defaultTextColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:[UIColor blackColor] ofScene:[self colorSceneType] withDefault:[UIColor blackColor]];
+        defaultBackgroundColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:_backgroundColor ofScene:[self colorSceneType] withDefault:_backgroundColor];
+    }
+    
     __weak typeof(self) weakSelf = self;
     for (WXRichNode *node in array) {
         location = attrStr.length;
@@ -444,8 +470,10 @@ do {\
                 [attrStr.mutableString appendString:text];
                 
                 NSRange range = NSMakeRange(location, text.length);
-                [attrStr addAttribute:NSForegroundColorAttributeName value:node.color ?: [UIColor blackColor] range:range];
-                [attrStr addAttribute:NSBackgroundColorAttributeName value:node.backgroundColor ?: _backgroundColor range:range];
+                UIColor* textColor = [self.weexInstance chooseColor:node.color darkThemeColor:node.darkThemeColor invert:invert scene:[self colorSceneType]];
+                UIColor* bgColor = [self.weexInstance chooseColor:node.backgroundColor darkThemeColor:node.darkThemeBackgroundColor invert:invert scene:[self colorSceneType]];
+                [attrStr addAttribute:NSForegroundColorAttributeName value:textColor ?: defaultTextColor range:range];
+                [attrStr addAttribute:NSBackgroundColorAttributeName value:bgColor ?: defaultBackgroundColor range:range];
                 
                 UIFont *font = [WXUtility fontWithSize:node.fontSize textWeight:node.fontWeight textStyle:WXTextStyleNormal fontFamily:node.fontFamily scaleFactor:self.weexInstance.pixelScaleFactor];
                 if (font) {
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
index 3a95fac..3bb7948 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
@@ -120,6 +120,8 @@ static CGFloat WXTextDefaultLineThroughWidth = 1.2;
 
 @interface WXTextComponent()
 @property (atomic, strong) NSString *fontFamily;
+@property (atomic, strong) UIColor *textColor;
+@property (atomic, strong) UIColor *darkThemeTextColor;
 @end
 
 @implementation WXTextComponent
@@ -128,7 +130,6 @@ static CGFloat WXTextDefaultLineThroughWidth = 1.2;
     UIEdgeInsets _padding;
     NSTextStorage *_textStorage;
     float _textStorageWidth;
-    float _color[4];
     float _fontSize;
     float _fontWeight;
     WXTextStyle _fontStyle;
@@ -174,8 +175,6 @@ static CGFloat WXTextDefaultLineThroughWidth = 1.2;
         } else {
             _useCoreText = YES;
         }
-        
-        _color[0] = -1.0;
 
         [self fillCSSStyles:styles];
         [self fillAttributes:attributes];
@@ -261,25 +260,41 @@ do {\
     WX_STYLE_FILL_TEXT_PIXEL(letterSpacing, letterSpacing, YES) //!OCLint
     WX_STYLE_FILL_TEXT(wordWrap, wordWrap, NSString, YES); //!OCLint
 
-    UIColor* color = nil;
-    id value = styles[@"color"];
-    if (value) {
-        if([WXUtility isBlankString:value]){
-            color = [UIColor blackColor];
-        } else {
-            color = [WXConvert UIColor:value];
+    do {
+        UIColor* color = nil;
+        id value = styles[@"color"];
+        if (value) {
+            if([WXUtility isBlankString:value]){
+                color = [UIColor blackColor];
+            } else {
+                color = [WXConvert UIColor:value];
+            }
+            if (color) {
+                self.textColor = color;
+                [self setNeedsRepaint];
+            }
         }
-        if (color) {
-            [self setNeedsRepaint];
-            CGFloat red, green, blue, alpha;
-            [color getRed:&red green:&green blue:&blue alpha:&alpha];
-            _color[0] = red;
-            _color[1] = green;
-            _color[2] = blue;
-            _color[3] = alpha;
+        if (self.textColor == nil) {
+            self.textColor = [UIColor blackColor];
         }
-    }
+    } while (0);
 
+    do {
+        UIColor* color = nil;
+        id value = styles[@"darkThemeColor"];
+        if (value) {
+            if([WXUtility isBlankString:value]){
+                color = [UIColor blackColor];
+            } else {
+                color = [WXConvert UIColor:value];
+            }
+            if (color) {
+                self.darkThemeTextColor = color;
+                [self setNeedsRepaint];
+            }
+        }
+    } while (0);
+    
     if (self.fontFamily && !_observerIconfont) {
         // notification received when custom icon font file download finish
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(repaintText:) name:WX_ICONFONT_DOWNLOAD_NOTIFICATION object:nil];
@@ -371,6 +386,20 @@ do {\
     [self setNeedsRepaint];
 }
 
+- (void)themeDidChange:(NSString*)theme
+{
+    [self setNeedsRepaint];
+    [super themeDidChange:theme];
+    if (_view) {
+        [self setNeedsDisplay];
+    }
+}
+
+- (WXColorScene)colorSceneType
+{
+    return WXColorSceneText;
+}
+
 - (BOOL)needsDrawRect
 {
     return YES;
@@ -497,8 +526,9 @@ do {\
         string = @"";
     }
     NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString: string];
-    if (_color[0] >= 0) {
-        [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:_color[0] green:_color[1] blue:_color[2] alpha:_color[3]] range:NSMakeRange(0, string.length)];
+    UIColor* textColor = [self.weexInstance chooseColor:self.textColor darkThemeColor:self.darkThemeTextColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    if (textColor) {
+        [attributedString addAttribute:NSForegroundColorAttributeName value:textColor range:NSMakeRange(0, string.length)];
     }
     
     // set font
@@ -585,8 +615,9 @@ do {\
     NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
     
     // set textColor
-    if (_color[0] >= 0) {
-        [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:_color[0] green:_color[1] blue:_color[2] alpha:_color[3]] range:NSMakeRange(0, string.length)];
+    UIColor* textColor = [self.weexInstance chooseColor:self.textColor darkThemeColor:self.darkThemeTextColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    if (textColor) {
+        [attributedString addAttribute:NSForegroundColorAttributeName value:textColor range:NSMakeRange(0, string.length)];
     }
     
     // set font
@@ -1112,10 +1143,11 @@ NS_INLINE NSRange WXNSRangeFromCFRange(CFRange range) {
 {
     [super _resetCSSNodeStyles:styles];
     if ([styles containsObject:@"color"]) {
-        _color[0] = 0;
-        _color[1] = 0;
-        _color[2] = 0;
-        _color[3] = 1.0;
+        self.textColor = [UIColor blackColor];
+        [self setNeedsRepaint];
+    }
+    if ([styles containsObject:@"darkThemeColor"]) {
+        self.darkThemeTextColor = nil;
         [self setNeedsRepaint];
     }
     if ([styles containsObject:@"fontSize"]) {
diff --git a/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m b/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m
index d209068..7b513e8 100644
--- a/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m
+++ b/ios/sdk/WeexSDK/Sources/Display/WXComponent+Display.m
@@ -28,6 +28,8 @@
 #import "UIBezierPath+Weex.h"
 #import "WXRoundedRect.h"
 #import "WXSDKInstance.h"
+#import "WXDarkThemeProtocol.h"
+#import "WXHandlerFactory.h"
 
 #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
 
@@ -101,6 +103,29 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
     WXAssertMainThread();
 }
 
+- (void)themeDidChange:(NSString*)theme
+{
+    WXAssertMainThread();
+    if (_view) {
+        if (![self _needsDrawBorder]) {
+            _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
+            _layer.backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
+        }
+        else {
+            [self setNeedsDisplay];
+        }
+        
+        if (_backgroundImage) {
+            [self setGradientLayer];
+        }
+    }
+}
+
+- (WXColorScene)colorSceneType
+{
+    return WXColorSceneUnknown;
+}
+
 #pragma mark Private
 
 - (WXDisplayBlock)_displayBlock
@@ -289,7 +314,7 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
 - (void)_collectCompositingDisplayBlocks:(NSMutableArray *)displayBlocks context:(CGContextRef)context isCancelled:(BOOL(^)(void))isCancelled
 {
     // TODO: compositingChild has no chance to applyPropertiesToView, need update here?
-    UIColor *backgroundColor = self.styleBackgroundColor;
+    UIColor *backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
     BOOL clipsToBounds = _clipToBounds;
     CGRect frame = self.calculatedFrame;
     CGRect bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
@@ -349,8 +374,9 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
     
     CGContextSetAlpha(context, _opacity);
     // fill background color
-    if (self.styleBackgroundColor && CGColorGetAlpha(self.styleBackgroundColor.CGColor) > 0) {
-        CGContextSetFillColorWithColor(context, self.styleBackgroundColor.CGColor);
+    UIColor* bgColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    if (bgColor && CGColorGetAlpha(bgColor.CGColor) > 0) {
+        CGContextSetFillColorWithColor(context, bgColor.CGColor);
         UIBezierPath *bezierPath = [UIBezierPath wx_bezierPathWithRoundedRect:rect topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight];
         [bezierPath fill];
         WXPerformBlockOnMainThread(^{
@@ -367,7 +393,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderTopWidth);
-        CGContextSetStrokeColorWithColor(context, _borderTopColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, size.width-topRight, topRight, topRight-_borderTopWidth/2, -M_PI_4+(_borderRightWidth>0?0:M_PI_4), -M_PI_2, 1);
         CGContextMoveToPoint(context, size.width-topRight, _borderTopWidth/2);
         CGContextAddLineToPoint(context, topLeft, _borderTopWidth/2);
@@ -388,7 +415,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderLeftWidth);
-        CGContextSetStrokeColorWithColor(context, _borderLeftColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderLeftColor darkThemeColor:_darkThemeBorderLeftColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, topLeft, topLeft, topLeft-_borderLeftWidth/2, -M_PI, -M_PI_2-M_PI_4+(_borderTopWidth > 0?0:M_PI_4), 0);
         CGContextMoveToPoint(context, _borderLeftWidth/2, topLeft);
         CGContextAddLineToPoint(context, _borderLeftWidth/2, size.height-bottomLeft);
@@ -409,7 +437,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderBottomWidth);
-        CGContextSetStrokeColorWithColor(context, _borderBottomColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderBottomColor darkThemeColor:_darkThemeBorderBottomColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, bottomLeft, size.height-bottomLeft, bottomLeft-_borderBottomWidth/2, M_PI-M_PI_4+(_borderLeftWidth>0?0:M_PI_4), M_PI_2, 1);
         CGContextMoveToPoint(context, bottomLeft, size.height-_borderBottomWidth/2);
         CGContextAddLineToPoint(context, size.width-bottomRight, size.height-_borderBottomWidth/2);
@@ -430,7 +459,8 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
             CGContextSetLineDash(context, 0, 0, 0);
         }
         CGContextSetLineWidth(context, _borderRightWidth);
-        CGContextSetStrokeColorWithColor(context, _borderRightColor.CGColor);
+        CGContextSetStrokeColorWithColor(context,
+                                         [self.weexInstance chooseColor:_borderRightColor darkThemeColor:_darkThemeBorderRightColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor);
         CGContextAddArc(context, size.width-bottomRight, size.height-bottomRight, bottomRight-_borderRightWidth/2, M_PI_4+(_borderBottomWidth>0?0:M_PI_4), 0, 1);
         CGContextMoveToPoint(context, size.width-_borderRightWidth/2, size.height-bottomRight);
         CGContextAddLineToPoint(context, size.width-_borderRightWidth/2, topRight);
@@ -486,7 +516,17 @@ typedef NS_ENUM(NSInteger, WXComponentBorderRecord) {
     if (!radiusEqual) {
         return YES;
     }
-    BOOL colorEqual = [_borderTopColor isEqual:_borderRightColor] && [_borderRightColor isEqual:_borderBottomColor] && [_borderBottomColor isEqual:_borderLeftColor];
+    
+    BOOL invert = self.invertForDarkTheme;
+    WXColorScene scene = [self colorSceneType];
+    UIColor* usingBorderTopColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:invert scene:scene];
+    UIColor* usingBorderRightColor = [self.weexInstance chooseColor:_borderRightColor darkThemeColor:_darkThemeBorderRightColor invert:invert scene:scene];
+    UIColor* usingBorderBottomColor = [self.weexInstance chooseColor:_borderBottomColor darkThemeColor:_darkThemeBorderBottomColor invert:invert scene:scene];
+    UIColor* usingBorderLeftColor = [self.weexInstance chooseColor:_borderLeftColor darkThemeColor:_darkThemeBorderLeftColor invert:invert scene:scene];
+    
+    BOOL colorEqual = [usingBorderTopColor isEqual:usingBorderRightColor] &&
+        [usingBorderRightColor isEqual:usingBorderBottomColor] &&
+        [usingBorderBottomColor isEqual:usingBorderLeftColor];
     if (!colorEqual) {
         return YES;
     }
@@ -579,6 +619,32 @@ do {\
     
     WX_CHECK_BORDER_PROP(Style, Top, Left, Bottom, Right, WXBorderStyle)
     WX_CHECK_BORDER_PROP(Color, Top, Left, Bottom, Right, UIColor)
+    do {
+        BOOL needsDisplay = NO;
+        if (styles[@"darkThemeBorderColor"]) {
+            _darkThemeBorderTopColor = _darkThemeBorderLeftColor = _darkThemeBorderRightColor = _darkThemeBorderBottomColor = [WXConvert UIColor:styles[@"darkThemeBorderColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderTopColor"]) {
+            _darkThemeBorderTopColor = [WXConvert UIColor:styles[@"darkThemeBorderTopColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderLeftColor"]) {
+            _darkThemeBorderLeftColor = [WXConvert UIColor:styles[@"darkThemeBorderLeftColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderRightColor"]) {
+            _darkThemeBorderRightColor = [WXConvert UIColor:styles[@"darkThemeBorderRightColor"]];
+            needsDisplay = YES;
+        }
+        if (styles[@"darkThemeBorderBottomColor"]) {
+            _darkThemeBorderBottomColor = [WXConvert UIColor:styles[@"darkThemeBorderBottomColor"]];
+            needsDisplay = YES;
+        }
+        if (needsDisplay && updating) {
+            [self setNeedsDisplay];
+        }
+    } while (0);
     WX_CHECK_BORDER_PROP_PIXEL(Width, Top, Left, Bottom, Right)
     WX_CHECK_BORDER_PROP_PIXEL(Radius, TopLeft, TopRight, BottomLeft, BottomRight)
 
@@ -592,9 +658,9 @@ do {\
         } else if (!nowNeedsDrawBorder) {
             [self _resetNativeBorderRadius];
             _layer.borderWidth = _borderTopWidth;
-            _layer.borderColor = _borderTopColor.CGColor;
+            _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
             if ((_transition.transitionOptions & WXTransitionOptionsBackgroundColor) != WXTransitionOptionsBackgroundColor ) {
-                _layer.backgroundColor = self.styleBackgroundColor.CGColor;
+                _layer.backgroundColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]].CGColor;
             }
         }
     }
@@ -606,7 +672,8 @@ do {\
     WXRoundedRect *borderRect = [[WXRoundedRect alloc] initWithRect:rect topLeft:_borderTopLeftRadius topRight:_borderTopRightRadius bottomLeft:_borderBottomLeftRadius bottomRight:_borderBottomRightRadius];
     WXRadii *radii = borderRect.radii;
     BOOL hasBorderRadius = [radii hasBorderRadius];
-    return (!hasBorderRadius) && _opacity == 1.0 && CGColorGetAlpha(self.styleBackgroundColor.CGColor) == 1.0 && [self _needsDrawBorder];
+    UIColor* currentBgColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:self.invertForDarkTheme scene:[self colorSceneType]];
+    return (!hasBorderRadius) && _opacity == 1.0 && CGColorGetAlpha(currentBgColor.CGColor) == 1.0 && [self _needsDrawBorder];
 }
 
 - (CAShapeLayer *)drawBorderRadiusMaskLayer:(CGRect)rect
diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
index ad181ac..f630f02 100644
--- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
+++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
@@ -29,6 +29,7 @@
 #import "WXNavigationDefaultImpl.h"
 #import "WXURLRewriteDefaultImpl.h"
 #import "WXJSFrameworkLoadDefaultImpl.h"
+#import "WXDarkThemeDefaultImpl.h"
 
 #import "WXSDKManager.h"
 #import "WXSDKError.h"
@@ -205,6 +206,7 @@
     [self registerHandler:[WXNavigationDefaultImpl new] withProtocol:@protocol(WXNavigationProtocol)];
     [self registerHandler:[WXURLRewriteDefaultImpl new] withProtocol:@protocol(WXURLRewriteProtocol)];
     [self registerHandler:[WXJSFrameworkLoadDefaultImpl new] withProtocol:@protocol(WXJSFrameworkLoadProtocol)];
+    [self registerHandler:[WXDarkThemeDefaultImpl new] withProtocol:@protocol(WXDarkThemeProtocol)];
 }
 
 + (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol
diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h
similarity index 71%
copy from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
copy to ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h
index 1875e79..cba2971 100644
--- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
+++ b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.h
@@ -18,12 +18,15 @@
  */
 
 #import <Foundation/Foundation.h>
+#import <WeexSDK/WXDarkThemeProtocol.h>
 
-@protocol WXDestroyProtocol <NSObject>
+NS_ASSUME_NONNULL_BEGIN
 
-/**
- *  @abstract execute unload function before dealloc
+/* By default, this implementation class do basic invert of UIColor of RGBA color space.
+ You should implementation your own handler to better handle dark theme in your application.
  */
-- (void)unload;
+@interface WXDarkThemeDefaultImpl : NSObject <WXDarkThemeProtocol>
 
 @end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m
similarity index 60%
copy from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
copy to ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m
index 1875e79..0717392 100644
--- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
+++ b/ios/sdk/WeexSDK/Sources/Handler/WXDarkThemeDefaultImpl.m
@@ -17,13 +17,22 @@
  * under the License.
  */
 
-#import <Foundation/Foundation.h>
+#import "WXDarkThemeDefaultImpl.h"
 
-@protocol WXDestroyProtocol <NSObject>
+@implementation WXDarkThemeDefaultImpl
 
-/**
- *  @abstract execute unload function before dealloc
- */
-- (void)unload;
+- (void)configureView:(UIView *)view ofComponent:(WXComponent *)component
+{
+    // Nothing
+}
+
+- (UIColor *_Nullable)getInvertedColorFor:(UIColor *_Nonnull)color ofScene:(WXColorScene)scene withDefault:(UIColor *_Nullable)defaultColor
+{
+    CGFloat red, blue, green, alpha;
+    if ([color getRed:&red green:&green blue:&blue alpha:&alpha]) {
+        return [UIColor colorWithRed:1 - red green:1 - green blue:1 - blue alpha:alpha];
+    }
+    return color;
+}
 
 @end
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm
index f4b29a8..b8f205c 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.mm
@@ -260,6 +260,7 @@ static NSThread *WXComponentThread;
     WXAssert(_rootComponent == nil, @"Create body is invoked twice.");
     
     _rootComponent = [self _buildComponent:ref type:type supercomponent:nil styles:styles attributes:attributes events:events renderObject:renderObject];
+    _rootComponent.invertForDarkTheme = YES;
     
     CGSize size = _weexInstance.frame.size;
     [WXCoreBridge setDefaultDimensionIntoRoot:_weexInstance.instanceId
@@ -327,6 +328,11 @@ static NSThread *WXComponentThread;
         }
     }
     
+    // Not explicitly declare "invertForDarkTheme", inherit
+    if (attributes[@"invertForDarkTheme"] == nil) {
+        component.invertForDarkTheme = supercomponent.invertForDarkTheme;
+    }
+    
 #ifdef DEBUG
     WXLogDebug(@"flexLayout -> _recursivelyAddComponent : super:(%@,%@):[%f,%f] ,child:(%@,%@):[%f,%f],childClass:%@",
                supercomponent.type,
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXComponent.h b/ios/sdk/WeexSDK/Sources/Model/WXComponent.h
index 0528b1d..4c31a95 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXComponent.h
+++ b/ios/sdk/WeexSDK/Sources/Model/WXComponent.h
@@ -32,6 +32,12 @@ typedef enum : NSUInteger {
     WXComponentUpdateStylesCallback
 } WXComponentCallbackType;
 
+typedef enum : NSUInteger {
+    WXColorSceneBackground,
+    WXColorSceneText,
+    WXColorSceneUnknown,
+} WXColorScene;
+
 /**
  * @abstract the component callback , result can be string or dictionary.
  * @discussion callback data to js, the id of callback function will be removed to save memory.
@@ -364,6 +370,8 @@ NS_ASSUME_NONNULL_BEGIN
 /// @name Display
 ///--------------------------------------
 
+@property (nonatomic, assign) BOOL invertForDarkTheme;
+
 @property (nonatomic, assign) WXDisplayType displayType;
 
 /**
@@ -379,6 +387,16 @@ NS_ASSUME_NONNULL_BEGIN
 - (BOOL)needsDrawRect;
 
 /**
+ * @abstract Fired on instance theme did changed.
+ */
+- (void)themeDidChange:(NSString*)theme;
+
+/**
+ * @abstract Hint used for better do color invert in dark mode.
+ */
+- (WXColorScene)colorSceneType;
+
+/**
  * @abstract Draws the component’s image within the passed-in rectangle.
  * @parameter rect The rectangle which is the entire visible bounds of your component. 
  * @return A UIImage containing the contents of the current bitmap graphics context.
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm
index 3235dbd..16de3c6 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Model/WXComponent.mm
@@ -42,6 +42,7 @@
 #import "WXSDKInstance_performance.h"
 #import "WXComponent_performance.h"
 #import "WXCoreBridge.h"
+#import "WXDarkThemeProtocol.h"
 
 #pragma clang diagnostic ignored "-Wincomplete-implementation"
 #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
@@ -71,6 +72,7 @@ static BOOL bNeedRemoveEvents = YES;
 
 @synthesize transform = _transform;
 @synthesize styleBackgroundColor = _styleBackgroundColor;
+@synthesize darkThemeBackgroundColor = _darkThemeBackgroundColor;
 
 #pragma mark Life Cycle
 
@@ -104,6 +106,7 @@ static BOOL bNeedRemoveEvents = YES;
         _eventPenetrationEnabled = NO;
         _accessibilityHintContent = nil;
         _cancelsTouchesInView = YES;
+        _invertForDarkTheme = NO;
         
         _async = NO;
         
@@ -122,6 +125,10 @@ static BOOL bNeedRemoveEvents = YES;
             }
         }
         
+        if (attributes[@"invertForDarkTheme"]) {
+            _invertForDarkTheme = [WXConvert BOOL:attributes[@"invertForDarkTheme"]];
+        }
+        
         if (attributes[@"userInteractionEnabled"]) {
             _userInteractionEnabled = [WXConvert BOOL:attributes[@"userInteractionEnabled"]];
         }
@@ -380,6 +387,10 @@ static BOOL bNeedRemoveEvents = YES;
         [self viewWillLoad];
         
         _view = [self loadView];
+        
+        // Provide a chance for dark theme handler to process the view
+        [[WXSDKInstance darkThemeColorHandler] configureView:_view ofComponent:self];
+        
 #ifdef DEBUG
         WXLogDebug(@"flexLayout -> loadView:addr-(%p),componentRef-(%@)",_view,self.ref);
 #endif
@@ -388,11 +399,19 @@ static BOOL bNeedRemoveEvents = YES;
         _view.hidden = _visibility == WXVisibilityShow ? NO : YES;
         _view.clipsToBounds = _clipToBounds;
         if (![self _needsDrawBorder]) {
-            _layer.borderColor = _borderTopColor.CGColor;
+            _layer.borderColor = [self.weexInstance chooseColor:_borderTopColor darkThemeColor:_darkThemeBorderTopColor invert:_invertForDarkTheme scene:[self colorSceneType]].CGColor;
             _layer.borderWidth = _borderTopWidth;
             [self _resetNativeBorderRadius];
             _layer.opacity = _opacity;
-            _view.backgroundColor = self.styleBackgroundColor;
+            
+            /* Also set background color to view to fix that problem that system may
+             set dynamic color to UITableView. Without these codes, event if we set
+             clear color to layer, the table view could not be transparent. */
+            UIColor* choosedColor = [self.weexInstance chooseColor:self.styleBackgroundColor darkThemeColor:self.darkThemeBackgroundColor invert:_invertForDarkTheme scene:[self colorSceneType]];
+            if (choosedColor == [UIColor clearColor]) {
+                _view.backgroundColor = choosedColor;
+            }
+            _layer.backgroundColor = choosedColor.CGColor;
         }
 
         if (_backgroundImage) {
@@ -845,7 +864,11 @@ static BOOL bNeedRemoveEvents = YES;
     if (CGRectEqualToRect(self.view.frame, CGRectZero)) {
         return;
     }
-    NSDictionary * linearGradient = [WXUtility linearGradientWithBackgroundImage:_backgroundImage];
+    
+    BOOL isDark = [self.weexInstance isDarkTheme];
+    NSString* styleValue = isDark ? (_darkThemeBackgroundImage ?: _backgroundImage) : _backgroundImage;
+    
+    NSDictionary * linearGradient = [WXUtility linearGradientWithBackgroundImage:styleValue];
     if (!linearGradient) {
         return ;
     }
@@ -854,6 +877,7 @@ static BOOL bNeedRemoveEvents = YES;
     dispatch_async(dispatch_get_main_queue(), ^{
         __strong typeof(self) strongSelf = weakSelf;
         if(strongSelf) {
+            // No need to auto-invert linear-gradient colors. We only allows using 'dark-theme-background-image' style.
             UIColor * startColor = (UIColor*)linearGradient[@"startColor"];
             UIColor * endColor = (UIColor*)linearGradient[@"endColor"];
             CAGradientLayer * gradientLayer = [WXUtility gradientLayerFromColors:@[startColor, endColor] locations:nil frame:strongSelf.view.bounds gradientType:(WXGradientType)[linearGradient[@"gradientType"] integerValue]];
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h
index c6cf521..e2df9fb 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.h
@@ -26,6 +26,8 @@
 #import <WeexSDK/WXApmForInstance.h>
 #import <WeexSDK/WXComponentManager.h>
 
+@protocol WXDarkThemeProtocol;
+
 NS_ASSUME_NONNULL_BEGIN
 
 extern NSString *const bundleUrlOptionKey;
@@ -447,6 +449,32 @@ typedef enum : NSUInteger {
  */
 + (NSDictionary*)lastPageInfo;
 
+#pragma mark - Theme Support
+
+/**
+ Handler for handling color invert.
+
+ @return Handler instance.
+ */
++ (id<WXDarkThemeProtocol>)darkThemeColorHandler;
+
+/**
+ Return true if current is dark theme for this instance.
+ */
+- (BOOL)isDarkTheme;
+
+/**
+ Get/set interface style of current instance.
+ */
+- (NSString*)currentThemeName;
+- (void)setCurrentThemeName:(NSString*)name;
+
+/**
+ Choose final color between original color and dark-mode one.
+ Also considering invert.
+ */
+- (UIColor*)chooseColor:(UIColor*)originalColor darkThemeColor:(UIColor*)darkColor invert:(BOOL)invert scene:(WXColorScene)scene;
+
 /** 
  * Deprecated 
  */
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
index 7a22748..a0af7d7 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
@@ -51,7 +51,8 @@
 #import "WXPageEventNotifyEvent.h"
 #import "WXConvertUtility.h"
 #import "WXCoreBridge.h"
-#import <WeexSDK/WXDataRenderHandler.h>
+#import "WXDataRenderHandler.h"
+#import "WXDarkThemeProtocol.h"
 
 #define WEEX_LITE_URL_SUFFIX           @"wlasm"
 #define WEEX_RENDER_TYPE_PLATFORM       @"platform"
@@ -102,6 +103,7 @@ typedef enum : NSUInteger {
 {
     self = [super init];
     if (self) {
+        self.themeName = [WXUtility isSystemInDarkTheme] ? @"dark" : @"light";
         _renderType = renderType;
         _appearState = YES;
         
@@ -538,9 +540,6 @@ typedef enum : NSUInteger {
     if (!self.userInfo[@"jsMainBundleStringContentLength"]) {
         self.userInfo[@"jsMainBundleStringContentLength"] = @([mainBundleString length]);
     }
-    if (!self.userInfo[@"jsMainBundleStringContentLength"]) {
-        self.userInfo[@"jsMainBundleStringContentMd5"] = [WXUtility md5:mainBundleString];
-    }
     
     id<WXPageEventNotifyEventProtocol> pageEvent = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)];
     if ([pageEvent respondsToSelector:@selector(pageStart:)]) {
@@ -1172,6 +1171,84 @@ typedef enum : NSUInteger {
     return result;
 }
 
++ (id<WXDarkThemeProtocol>)darkThemeColorHandler
+{
+    static id<WXDarkThemeProtocol> colorHandler;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        colorHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXDarkThemeProtocol)];
+    });
+    return colorHandler;
+}
+
+- (NSString*)currentThemeName
+{
+    return self.themeName;
+}
+
+- (BOOL)isDarkTheme
+{
+    return [self.themeName isEqualToString:@"dark"];
+}
+
+- (void)setCurrentThemeName:(NSString*)name
+{
+    if (name && ![name isEqualToString:self.themeName]) {
+        self.themeName = name;
+        
+        // Recursively visit all components and notify that theme had changed.
+        __weak WXSDKInstance* weakSelf = self;
+        WXPerformBlockOnComponentThread(^{
+            __strong WXSDKInstance* strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
+            
+            if (!strongSelf->_componentManager.isValid) {
+                return;
+            }
+            
+            [strongSelf->_componentManager enumerateComponentsUsingBlock:^(WXComponent * _Nonnull component, BOOL * _Nonnull stop) {
+                __weak WXComponent* wcomp = component;
+                WXPerformBlockOnMainThread(^{
+                    __strong WXComponent* scomp = wcomp;
+                    if (scomp) {
+                        [scomp themeDidChange:name];
+                    }
+                });
+            }];
+        });
+        
+        [[WXSDKManager bridgeMgr] fireEvent:_instanceId
+                                        ref:WX_SDK_ROOT_REF
+                                       type:@"themechanged"
+                                     params:@{@"theme": self.themeName?:@"light"}
+                                 domChanges:nil];
+    }
+}
+
+- (UIColor*)chooseColor:(UIColor*)originalColor darkThemeColor:(UIColor*)darkColor invert:(BOOL)invert scene:(WXColorScene)scene
+{
+    if ([self isDarkTheme]) {
+        if (darkColor) {
+            return darkColor;
+        }
+        else if (invert) {
+            // Invert originalColor
+            if (originalColor == [UIColor clearColor]) {
+                return originalColor;
+            }
+            return [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:originalColor ofScene:scene withDefault:originalColor];
+        }
+        else {
+            return originalColor;
+        }
+    }
+    else {
+        return originalColor;
+    }
+}
+
 @end
 
 @implementation WXSDKInstance (Deprecated)
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
index 25680da..852275d 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
@@ -39,6 +39,8 @@
 @property (nonatomic, strong) NSString *createInstanceContextResult;
 @property (nonatomic, strong) NSString *executeRaxApiResult;
 
+@property (atomic, strong) NSString* themeName;
+
 - (void)addModuleEventObservers:(NSString*)event callback:(NSString*)callbackId option:(NSDictionary*)option moduleClassName:(NSString*)moduleClassName;
 - (void)_addModuleEventObserversWithModuleMethod:(WXModuleMethod*)method;
 - (void)removeModuleEventObserver:(NSString*)event moduleClassName:(NSString*)moduleClassName;
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m
index 8ee1591..f75728a 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m
+++ b/ios/sdk/WeexSDK/Sources/Module/WXAnimationModule.m
@@ -26,6 +26,7 @@
 #import "WXLength.h"
 #import "WXTransition.h"
 #import "WXComponent+Layout.h"
+#import "WXDarkThemeProtocol.h"
 
 @interface WXAnimationInfo : NSObject<NSCopying>
 
@@ -207,7 +208,30 @@ WX_EXPORT_METHOD(@selector(transition:args:callback:))
     }
     CAMediaTimingFunction *timingFunction = [WXConvert CAMediaTimingFunction:args[@"timingFunction"]];
     NSDictionary *styles = args[@"styles"];
+    NSDictionary* componentRawStyles = target.styles;
+    
+    BOOL isDarkTheme = [target.weexInstance isDarkTheme];
+    BOOL updatingDarkThemeBackgroundColor = styles[@"darkThemeBackgroundColor"] != nil;
+    
     for (NSString *property in styles) {
+        if ([property isEqualToString:@"backgroundColor"]) {
+            if (isDarkTheme && (updatingDarkThemeBackgroundColor ||
+                                componentRawStyles[@"darkThemeBackgroundColor"] != nil)) {
+                /* Updating "darkThemeBackgroundColor" in dark mode,
+                 or this component has dark bg color explicitly defined in styels.
+                 We ignore transition animation for "backgroundColor" */
+                continue;
+            }
+        }
+        else if ([property isEqualToString:@"darkThemeBackgroundColor"]) {
+            if (!isDarkTheme || componentRawStyles[@"darkThemeBackgroundColor"] == nil) {
+                /* Do not do animation for "darkThemeBackgroundColor" in light mode.
+                 Or there is no dark bg color explicitly defined in styles.
+                 */
+                continue;
+            }
+        }
+        
         WXAnimationInfo *info = [WXAnimationInfo new];
         info.duration = duration;
         info.delay = delay;
@@ -287,10 +311,17 @@ WX_EXPORT_METHOD(@selector(transition:args:callback:))
                 [infos addObject:newInfo];
             }
             target.transform = wxTransform;
-        } else if ([property isEqualToString:@"backgroundColor"]) {
+        } else if ([property isEqualToString:@"backgroundColor"] ||
+                   [property isEqualToString:@"darkThemeBackgroundColor"]) {
             info.propertyName = @"backgroundColor";
             info.fromValue = (__bridge id)(layer.backgroundColor);
-            info.toValue = (__bridge id)[WXConvert CGColor:value];
+            UIColor* toColor = [WXConvert UIColor:value];
+            if ([target.weexInstance isDarkTheme] && target.invertForDarkTheme &&
+                [property isEqualToString:@"backgroundColor"]) {
+                // Invert color
+                toColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:toColor ofScene:[target colorSceneType] withDefault:toColor];
+            }
+            info.toValue = (__bridge id)([toColor CGColor]);
             [infos addObject:info];
         } else if ([property isEqualToString:@"opacity"]) {
             info.propertyName = @"opacity";
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm b/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm
index 8bb616d..b597232 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm
+++ b/ios/sdk/WeexSDK/Sources/Module/WXTransition.mm
@@ -30,6 +30,7 @@
 #import "WXAssert.h"
 #import "WXSDKInstance_private.h"
 #import "WXLength.h"
+#import "WXDarkThemeProtocol.h"
 
 @implementation WXTransitionInfo
 @end
@@ -82,6 +83,7 @@
                                                            @"bottom": @(WXTransitionOptionsBottom),
                                                            @"top": @(WXTransitionOptionsTop),
                                                            @"backgroundColor": @(WXTransitionOptionsBackgroundColor),
+                                                           @"darkThemeBackgroundColor": @(WXTransitionOptionsBackgroundColor),
                                                            @"transform": @(WXTransitionOptionsTransform),
                                                            @"opacity": @(WXTransitionOptionsOpacity)
                                                            };
@@ -106,24 +108,55 @@
     _filterStyles = _filterStyles ?:[NSMutableDictionary new];
     _oldFilterStyles = _oldFilterStyles ?: [NSMutableDictionary new];
     NSMutableDictionary *futileStyles = [NSMutableDictionary new];
+    NSDictionary* componentRawStyles = targetComponent.styles;
+    
+    BOOL isDarkTheme = [targetComponent.weexInstance isDarkTheme];
+    BOOL updatingDarkThemeBackgroundColor = styles[@"darkThemeBackgroundColor"] != nil;
     
     for (NSString *key in styles) {
         if (self.transitionOptions & [self transitionOptionsFromString:key]) {
+            if ([key isEqualToString:@"backgroundColor"]) {
+                if (isDarkTheme && (updatingDarkThemeBackgroundColor ||
+                                    componentRawStyles[@"darkThemeBackgroundColor"] != nil)) {
+                    /* Updating "darkThemeBackgroundColor" in dark mode,
+                     or this component has dark bg color explicitly defined in styels.
+                     We ignore transition animation for "backgroundColor" */
+                    [futileStyles setObject:styles[key] forKey:key];
+                    continue;
+                }
+            }
+            else if ([key isEqualToString:@"darkThemeBackgroundColor"]) {
+                if (!isDarkTheme || componentRawStyles[@"darkThemeBackgroundColor"] == nil) {
+                    /* Do not do animation for "darkThemeBackgroundColor" in light mode.
+                     Or there is no dark bg color explicitly defined in styles.
+                     */
+                    [futileStyles setObject:styles[key] forKey:key];
+                    continue;
+                }
+            }
+            
             [_filterStyles setObject:styles[key] forKey:key];
             if (![key isEqualToString:@"transform"]) {
                 if (!isRunning) {
-                    /* style value may not be in component.styles, so we must get
-                     value from layout and convert it to style value. */
-                    id styleValue = targetComponent.styles[key];
-                    if (styleValue == nil) {
+                    // Get animation 'from' value from raw styles.
+                    id styleValue = componentRawStyles[key];
+                    if ([key isEqualToString:@"backgroundColor"] ||
+                        [key isEqualToString:@"darkThemeBackgroundColor"]) {
+                        if (styleValue == nil) {
+                            // background color is transparent by default.
+                            styleValue = @"transparent";
+                        }
+                    }
+                    else if (styleValue == nil) {
+                        /* Flex styles may not be in component.styles, so we must get
+                         value from layout and convert it to style value. */
                         styleValue = [targetComponent convertLayoutValueToStyleValue:key];
                     }
                     [_oldFilterStyles setObject:styleValue forKey:key];
                 }
             }
         }
-        else
-        {
+        else {
             [futileStyles setObject:styles[key] forKey:key];
         }
     }
@@ -131,7 +164,7 @@
 
     _targetComponent = targetComponent;
     NSMutableDictionary *componentStyles = [NSMutableDictionary dictionaryWithDictionary:styles];
-    [componentStyles addEntriesFromDictionary:targetComponent.styles];
+    [componentStyles addEntriesFromDictionary:componentRawStyles];
 
     _transitionDuration = componentStyles[kWXTransitionDuration] ? [WXConvert CGFloat:componentStyles[kWXTransitionDuration]] : 0;
     _transitionDelay = componentStyles[kWXTransitionDelay] ? [WXConvert CGFloat:componentStyles[kWXTransitionDelay]] : 0;
@@ -200,10 +233,21 @@
         if (!_propertyArray) {
             _propertyArray = [NSMutableArray new];
         }
-        if ([singleProperty isEqualToString:@"backgroundColor"]) {
+        if ([singleProperty isEqualToString:@"backgroundColor"] ||
+            [singleProperty isEqualToString:@"darkThemeBackgroundColor"]) {
+            UIColor* fromColor = [WXConvert UIColor:_oldFilterStyles[singleProperty]];
+            UIColor* toColor = [WXConvert UIColor:_filterStyles[singleProperty]];
+            if ([_targetComponent.weexInstance isDarkTheme] &&
+                _targetComponent.invertForDarkTheme &&
+                [singleProperty isEqualToString:@"backgroundColor"]) {
+                // Invert color
+                fromColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:fromColor ofScene:[_targetComponent colorSceneType] withDefault:fromColor];
+                toColor = [[WXSDKInstance darkThemeColorHandler] getInvertedColorFor:toColor ofScene:[_targetComponent colorSceneType] withDefault:toColor];
+            }
+            
             WXTransitionInfo *info = [WXTransitionInfo new];
-            info.fromValue = [self _dealWithColor:[WXConvert UIColor:_oldFilterStyles[singleProperty]]];
-            info.toValue = [self _dealWithColor:[WXConvert UIColor:_filterStyles[singleProperty]]];
+            info.fromValue = [self _dealWithColor:fromColor];
+            info.toValue = [self _dealWithColor:toColor];
             info.perValue = [self _calculatePerColorRGB1:info.toValue RGB2:info.fromValue];
             info.propertyName = singleProperty;
             [_propertyArray addObject:info];
@@ -337,6 +381,7 @@
                                @([info.fromValue[3] floatValue] + [info.perValue[3] floatValue] * per)];
             UIColor *color = [UIColor colorWithRed:[array[0] floatValue] green:[array[1] floatValue] blue:[array[2] floatValue] alpha:[array[3] floatValue]];
             WXPerformBlockOnMainThread(^{
+                // Here we do not need to consider about dark mode.
                 _targetComponent.view.backgroundColor = color;
                 [_targetComponent.view setNeedsDisplay];
             });
diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h b/ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h
similarity index 53%
rename from ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
rename to ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h
index 1875e79..c443879 100644
--- a/ios/sdk/WeexSDK/Sources/Protocol/WXDestroyProtocol.h
+++ b/ios/sdk/WeexSDK/Sources/Protocol/WXDarkThemeProtocol.h
@@ -17,13 +17,28 @@
  * under the License.
  */
 
-#import <Foundation/Foundation.h>
+#import <WeexSDK/WXModuleProtocol.h>
 
-@protocol WXDestroyProtocol <NSObject>
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol WXDarkThemeProtocol <WXModuleProtocol>
+
+/**
+After any view of Weex component is created. Callback dark theme handler to provide a
+ chance to configure the view.
+*/
+- (void)configureView:(UIView*_Nonnull)view ofComponent:(WXComponent*_Nonnull)component;
 
 /**
- *  @abstract execute unload function before dealloc
+ Get inverted color in dark mode for input color with scene hint.
+
+ @param color Input color.
+ @param scene Scene indicating the color usage.
+ @param defaultColor If no inverted one matches, return the default color.
+ @return Inverted color.
  */
-- (void)unload;
+- (UIColor *_Nullable)getInvertedColorFor:(UIColor *_Nonnull)color ofScene:(WXColorScene)scene withDefault:(UIColor *_Nullable)defaultColor;
 
 @end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
index 61eeccd..4addab0 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
@@ -131,6 +131,14 @@ _Nonnull SEL WXSwizzledSelectorForSelector(_Nonnull SEL selector);
 + (void)performBlock:(void (^_Nonnull)(void))block onThread:(NSThread *_Nonnull)thread;
 
 /**
+ * @abstract Check if system is in dark mode.
+ *
+ * @return Boolean
+ *
+ */
++ (BOOL)isSystemInDarkTheme;
+
+/**
  * @abstract Returns the environment of current application, you can get some necessary properties such as appVersion、sdkVersion、appName etc.
  *
  * @return A dictionary object which contains these properties.
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
index 6ec4223..6d7136b 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
@@ -163,6 +163,22 @@ CGFloat WXFloorPixelValue(CGFloat value)
     return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:UISemanticContentAttributeUnspecified] == UIUserInterfaceLayoutDirectionRightToLeft ? WXLayoutDirectionRTL : WXLayoutDirectionLTR;
 }
 
++ (BOOL)isSystemInDarkTheme
+{
+    if (@available(iOS 13.0, *)) {
+        __block BOOL result = NO;
+        WXPerformBlockSyncOnMainThread(^{
+#ifdef __IPHONE_13_0
+            if ([UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark) {
+                result = YES;
+            }
+#endif
+        });
+        return result;
+    }
+    return NO;
+}
+
 + (NSDictionary *)getEnvironment
 {
     NSString *platform = @"iOS";
@@ -187,7 +203,8 @@ CGFloat WXFloorPixelValue(CGFloat value)
                                     @"deviceWidth":@(deviceWidth * scale),
                                     @"deviceHeight":@(deviceHeight * scale),
                                     @"scale":@(scale),
-                                    @"layoutDirection": [self getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr"
+                                    @"layoutDirection": [self getEnvLayoutDirection] == WXLayoutDirectionRTL ? @"rtl" : @"ltr",
+                                    @"theme": [self isSystemInDarkTheme] ? @"dark" : @"light"
                                 }];
     
     if ([[[UIDevice currentDevice] systemVersion] integerValue] >= 11) {
diff --git a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm
index ce252d0..accb21a 100644
--- a/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm
+++ b/ios/sdk/WeexSDK/Sources/View/WXComponent+ViewManagement.mm
@@ -67,6 +67,14 @@ do {\
     }\
 } while(0);
 
+#define WX_BOARD_RADIUS_DARK_THEME_COLOR_RESET_ALL(key)\
+do {\
+    if (styles && [styles containsObject:@#key]) {\
+        _darkThemeBorderTopColor = _darkThemeBorderLeftColor = _darkThemeBorderRightColor = _darkThemeBorderBottomColor = [UIColor blackColor];\
+        [self setNeedsDisplay];\
+    }\
+} while(0);
+
 #define WX_BOARD_COLOR_RESET(key)\
 do {\
     if (styles && [styles containsObject:@#key]) {\
@@ -175,7 +183,11 @@ do {\
 - (void)_initViewPropertyWithStyles:(NSDictionary *)styles
 {
     self.styleBackgroundColor = styles[@"backgroundColor"] ? [WXConvert UIColor:styles[@"backgroundColor"]] : [UIColor clearColor];
+    if (styles[@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]];
+    }
     _backgroundImage = styles[@"backgroundImage"] ? [WXConvert NSString:styles[@"backgroundImage"]]: nil;
+    _darkThemeBackgroundImage = styles[@"darkThemeBackgroundImage"] ? [WXConvert NSString:styles[@"darkThemeBackgroundImage"]] : nil;
     _opacity = styles[@"opacity"] ? [WXConvert CGFloat:styles[@"opacity"]] : 1.0;
     _clipToBounds = styles[@"overflow"] ? [WXConvert WXClipType:styles[@"overflow"]] : NO;
     _visibility = styles[@"visibility"] ? [WXConvert WXVisibility:styles[@"visibility"]] : WXVisibilityShow;
@@ -195,6 +207,9 @@ do {\
     if (styles[@"backgroundColor"]) {
         self.styleBackgroundColor = [WXConvert UIColor:styles[@"backgroundColor"]];
     }
+    if (styles[@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]];
+    }
     if (styles[@"opacity"]) {
         _opacity = [WXConvert CGFloat:styles[@"opacity"]];
     }
@@ -215,9 +230,21 @@ do {\
         [self setNeedsDisplay];
     }
     
+    if (styles[@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = [WXConvert UIColor:styles[@"darkThemeBackgroundColor"]];
+        [self setNeedsDisplay];
+    }
+    
     if (styles[@"backgroundImage"]) {
-        _backgroundImage = styles[@"backgroundImage"] ? [WXConvert NSString:styles[@"backgroundImage"]]: nil;
-        if (_backgroundImage) {
+        _backgroundImage = [WXConvert NSString:styles[@"backgroundImage"]];
+    }
+    
+    if (styles[@"darkThemeBackgroundImage"]) {
+        _darkThemeBackgroundImage = [WXConvert NSString:styles[@"darkThemeBackgroundImage"]];
+    }
+    
+    if (styles[@"backgroundImage"] || styles[@"darkThemeBackgroundImage"]) {
+        if (_backgroundImage || _darkThemeBackgroundImage) {
             [self setGradientLayer];
         }
     }
@@ -302,6 +329,11 @@ do {\
     WX_BOARD_COLOR_RESET(borderLeftColor);
     WX_BOARD_COLOR_RESET(borderRightColor);
     WX_BOARD_COLOR_RESET(borderBottomColor);
+    WX_BOARD_RADIUS_DARK_THEME_COLOR_RESET_ALL(darkThemeBorderColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderTopColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderLeftColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderRightColor);
+    WX_BOARD_COLOR_RESET(darkThemeBorderBottomColor);
 }
 
 - (void)_resetStyles:(NSArray *)styles
@@ -310,6 +342,11 @@ do {\
         self.styleBackgroundColor = [UIColor clearColor];
         [self setNeedsDisplay];
     }
+    if (styles && [styles containsObject:@"darkThemeBackgroundColor"]) {
+        self.darkThemeBackgroundColor = nil;
+        [self setNeedsDisplay];
+    }
+    
     if (styles && [styles containsObject:@"boxShadow"]) {
         _lastBoxShadow = _boxShadow;
         _boxShadow = nil;
@@ -317,6 +354,11 @@ do {\
     }
     if (styles && [styles containsObject:@"backgroundImage"]) {
         _backgroundImage = nil;
+    }
+    if (styles && [styles containsObject:@"darkThemeBackgroundImage"]) {
+        _darkThemeBackgroundImage = nil;
+    }
+    if (styles && ([styles containsObject:@"backgroundImage"] || [styles containsObject:@"darkThemeBackgroundImage"])) {
         [self setGradientLayer];
     }
     
diff --git a/ios/sdk/WeexSDK/Sources/View/WXRootView.m b/ios/sdk/WeexSDK/Sources/View/WXRootView.m
index 3cc8a36..8b2414b 100644
--- a/ios/sdk/WeexSDK/Sources/View/WXRootView.m
+++ b/ios/sdk/WeexSDK/Sources/View/WXRootView.m
@@ -23,6 +23,10 @@
 #import "WXSDKEngine.h"
 
 @interface WXRootView()
+{
+    BOOL _hasFirstTraitCollectionChange;
+    BOOL _allowFirstTraitCollectionChange;
+}
 
 @property (nonatomic, assign) BOOL mHasEvent;
 
@@ -59,4 +63,36 @@
     return _mHasEvent;
 }
 
+- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
+{
+    [super traitCollectionDidChange:previousTraitCollection];
+    
+    if (@available(iOS 13.0, *)) {
+        // When entering background system may call back change twice.. We ignore the first one.
+        UIUserInterfaceStyle currentStyle = self.traitCollection.userInterfaceStyle;
+        if (currentStyle != previousTraitCollection.userInterfaceStyle) {
+            if (_hasFirstTraitCollectionChange) {
+                _allowFirstTraitCollectionChange = NO;
+                [self.instance setCurrentThemeName:currentStyle == UIUserInterfaceStyleDark ? @"dark" : @"light"];
+            }
+            else {
+                __weak WXRootView* weakSelf = self;
+                _hasFirstTraitCollectionChange = YES;
+                _allowFirstTraitCollectionChange = YES;
+                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+                    __strong WXRootView* strongSelf = weakSelf;
+                    if (strongSelf) {
+                        if (strongSelf->_allowFirstTraitCollectionChange) {
+                            if (strongSelf.instance) {
+                                [strongSelf.instance setCurrentThemeName:currentStyle == UIUserInterfaceStyleDark ? @"dark" : @"light"];
+                            }
+                        }
+                        strongSelf->_hasFirstTraitCollectionChange = NO;
+                    }
+                });
+            }
+        }
+    }
+}
+
 @end
diff --git a/ios/sdk/WeexSDK/Sources/WeexSDK.h b/ios/sdk/WeexSDK/Sources/WeexSDK.h
index 8e5f6f4..05dbe21 100644
--- a/ios/sdk/WeexSDK/Sources/WeexSDK.h
+++ b/ios/sdk/WeexSDK/Sources/WeexSDK.h
@@ -73,6 +73,7 @@ FOUNDATION_EXPORT const unsigned char WeexSDKVersionString[];
 #import <WeexSDK/WXDefine.h>
 #import <WeexSDK/WXDebugTool.h>
 #import <WeexSDK/WXDataRenderHandler.h>
+#import <WeexSDK/WXDarkThemeProtocol.h>
 #import <WeexSDK/WXConvertUtility.h>
 #import <WeexSDK/WXConvert.h>
 #import <WeexSDK/WXConfigCenterProtocol.h>