iOS 應用中的「夜間模式」怎麼實現?


這個問題很多解決方案,要根據你自己的需求去找一個合適的。

有一些開源的解決方案,比如:GitHub - Draveness/DKNightVersion: Integrate night mode/theme into your iOS app
也有一些可以載入主題的框架,比如專註顏色方面的:GitHub - ViccAlexander/Chameleon: Flat Color Framework for iOS (Obj-C Swift)

恰好我前段時間有給自己的 app 做過一個夜間模式,更準確的說是支持了主題。思路非常的簡單。這裡我只講顏色的問題,圖片的問題大同小異。

有幾個核心的原則:
1. 所有的顏色都配置化,比如我的主題被定義成這個樣子:

{
"statusbar": 0,
"colors": {
"tint": "#e74c3c",
"theme": "#ffffff",
"light": "#ecf0f1",
"text": "#666666",
"darktext": "#95a5a6",
"separator": "#dddddd",
"placeholder": "#bdc3c7",
"red": "#e74c3c",
"gray": "#b2b2b2"
}
}

你也可以用 plist 去存,反正要把你界面裡面會出現視覺全都配在文件裡面。

2. 你每次去顏色,都去 manager 裡面取,只要是和主題相關的,你就不要出現 magic number。這個 manager 會在單例初始化的時候載入一次你設定的主題,比如 light 或者 night,載入的是上面說的配置文件。

NSMutableDictionary& *temp = [NSMutableDictionary dictionary];
[(NSDictionary *)json[@"colors"] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
temp[key] = [UIColor colorWithHex:obj];
}];
_colors = [NSDictionary dictionaryWithDictionary:temp];
_statusBarStyle = [json[@"statusbar"] integerValue];

然後你每次取顏色都是從 manager 的字典裡面去取:

+ (UIColor *)colorForKey:(NSString *)key {
return [[PINThemeManager manager] colors][key] ?: [UIColor clearColor];
}

+ (UIColor *)lightColor {
return [PINThemeManager colorForKey:@"light"];
}

+ (UIColor *)textColor {
return [PINThemeManager colorForKey:@"text"];
}

這樣的話,你就能在配置不同的時候,取到不同的顏色。而且之後要擴展也非常方便,你只要增加 key 就可以了。

3. 當用戶點擊切換主題的時候,要做幾件事情:
A. 把新主題的配置文件名更新一下,ThemeManager reload 一下
B. 當前屏幕截圖並把圖片蓋上去,界面刷新主題,然後把圖片 fadeOut,這樣會有一個兩種主題融合交替的一個效果
C. 將主題變化的消息通過你擅長的任何一種方法通知到其他的界面,完成全部界面的主題更換

其實這是一個非常簡單的思路,只要你之前的開發裡面不是經常寫 magic number,要把主題切換換上去很容易。


雖然沒做過, 但也可以猜一下是要用到JSBinding以及Hydrid開發的一些知識.
比如我們解壓軟體解壓QQ瀏覽器的ipa文件, 可以在Payload-&>mttlite右鍵顯示包內容, 可以發現該文件下赫然有WebViewJavascriptBridge.js.txt這麼一個文件, 我們大可以猜測QQ瀏覽器使用到了

WebViewJavascriptBridge這個第三方庫.
然後在/res/javascript文件夾下可以發現mtt_nightmode.css這麼一個文件, 從文件名我們可以猜測QQ瀏覽器是通過JS修改CSS實現的夜間模式.
打開該文件, 可以確認這一點:

具體怎樣實現修改,就要看你的項目是怎麼實現的了, 如果你的項目就是Web App載入一個網頁, 那麼想來用相應的API介面執行JS語句修改便是了;.
如果用到了Hybrid開發, 比如像唐巧這篇文章的說的:基於 CoreText 的排版引擎:基礎, 顯示內容的容器是用TextView做的, 那麼你還要自己再做一個解析類, 用來做主題自定義類的解析. 或者參考RayWenderlich上的這篇文章:Core Text Tutorial for iOS: Making a Magazine App, 也是用到了Coretext, 內容信息都是以XML傳輸, 只是你要講相關的排版的信息都提前寫好, 不然會崩潰....(這個圖文混排的例子切換到橫屏就會閃退)
P.S. @鍾穎Cyan , 關於這個問題 您能否放個Demo給我們學習學習?或者哪天在您的專欄講一講, 以供我等菜鳥學習?


這玩意最難的是主題無痛切換,排名第一的答案所說的發通知給所有界面然後手動一個個設置屬性的方式是可行的,但是也是繁瑣的,github上 一些開源實現的做法是swizzle will move to window方法,在外吃飯待補充詳細

=======update 20160301=======
先swizzle 所有UIView的willMoveToWindow 成你自己的 xx_willMoveToWindow, 裡面大概是 { xx_willMoveToWindow(window), applyCurrentStyle(window);}
applyCurrentStyle是你要實現的切換主題的代碼.

然後新建一個 UIView+Theme 的 category
實現 applyCurrentStyle 方法
比如 UIView 的背景在不同的currentStyle是不一樣的, 就在這裡根據currentStyle修改.
還可以針對具體控制項, 比如UILabel, UIButton, 都添加一個category實現applyCurrentStyle, 在裡面針對不同的currentStyle設置背景色啊字色啊等等, 這裡面可以使用配置文件來做, 便於管理.

最後怎麼一鍵觸發所有view的被換過的willMoveToWindow?
只需要
UIWindow* window = [UIApplication sharedApplication].keyWindow;
for (UIView* view in window.subviews) {
[view removeFromSuperview];
[window addSubview:view];
}
[window makeKeyWindow];


安利一下我自己寫的庫:
GitHub - coppercash/KnightWatson: Offer every of your objects a night version (or other theme)

這個庫最大的特點就是用到了 ObjC 的運行時,方法轉發等相關技術。所以不同主題的相關數據配置比較直觀,且支持的方法非常廣,幾乎涵蓋了所有的 `NSObject` 子類。

一個最簡單的例子,設置 `UIView` 的 `backgroundColor`:

// 本來傳入 UIColor 的地方現在傳入了一個字典,
// 字典內是不同主題下的兩種顏色。
//
UIView
*view = [[UIView alloc] init];
view.knw_themable.backgroundColor = (id)
@{@"daylight": UIColor.whiteColor,
@"night": UIColor.blackColor,};

// 切換主題!
// 之前所有用 knw_themable 後綴調用的函數都會使用新的主題對應的值,重新調用一次,
// 所以顏色就變了!
//
KNWThemeContext.defaultThemeContext.theme = @"night";

因為重度使用了動態技術,所以編譯器可能會不太高興。設置參數的時候用了 `(id)` 讓編譯器忽視了類型檢查。

其實參數不光可以傳字典,傳的只要是實現了 `KNWObjectArgument` 協議的對象就行。所以,結合這個特性,實現主題動態下發也是可以的。

更多特性請移步 Github 吧,我就不一一累述了。


那有沒有框架是直接可以在xib或者storyboard中就可以實現夜間模式的切換呢?


用UIAppearance?我看了下有這個功能的APP,變顏色的部分感覺使用appearance就可以實現


忘了在哪看到過的了。

設置→一般→輔助使用→縮放,然後往下拉,點擊「縮放過濾」,選擇「低光」。
然後測試,連續按HOME鍵三下。連按三下復原。


最簡單的,寫一個Const的Struct,裡面存兩個Theme的類,一個白天模式,一個夜間模式。每個theme裡面都包含了每一種模式的所有顏色配置。
然後再存一個夜間模式是否打開的UserDefault常數。

白天模式,則在Struct裡面返回白天模式的theme;夜間模式則返回夜間模式的theme,類似:

static var theme: Theme {
if nightModeOn {
return Theme(nightModeOn: true)
} else {
return Theme(nightModeOn: false)
}
}

其中:

Class Theme {
...
mainColor = ...
otherColro = ...
...
}

實際程序中所有用到顏色配置的地方, 都可以用類似:

AAA.color = UIConst.theme.mainColor
BBB.color = UIConst.theme.otherColor

這樣所有的view在init的時候都會載入正確的顏色。但這樣打開夜間模式的動作的時候,不會立即在當前頁面生效。最簡單的辦法就是加一個Notification,當switch被打開的時候,整個view重載就行了。


明確地提醒(告訴)用戶,連續三次三指點擊屏幕,選擇「弱光」濾鏡,就是夜間模式。


推薦閱讀:

iOS 有什麼地方讓你使用時感到尤其不爽?
為 App 命名有什麼技巧 ?
普通 iPhone 用戶應該安裝測試版 iOS 系統嗎?為什麼?
是否要升級 iOS 9?

TAG:iOS | 軟體開發 | iOS開發 | 夜間模式 | iOS開發入門 |