iOS Review | UIAppearance夜間主題配置

最終效果

要求:

  • Platform: iOS8.0+
  • Language: Swift3.2
  • Editor: Xcode8+
UIApplication Key Window
// 主色
UIApplication.shared.delegate?.window??.tintColor = mainColor

UINavigaitonBar

  • 導航欄的樣式barStyle有2中: defaultblack,default情況下,status bar的顏色為black,black情況下為white;
barStyle = .default
barStyle = .black
  • black樣式適用于在設置了barTintColor為非黑色的實色的導航欄上;
  • 設置導航欄統一的默認返回按鈕icon時,要注意設置其maskicon為如下樣式:
backIndicatorImage
backIndicatorTransitionMaskImage
  • mask圖組合原理:
正確的backIndicatorImage和backIndicatorTransitionMaskImage組合模式
INavigationBar.appearance().barStyle = barStyle
UINavigationBar.appearance().setBackgroundImage(navigationBarBackgroundImage, for: .default)
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed")
UITaBar
  • tab bar的barStyle和導航欄的顯示一樣;
  • 可以設置tab選中的指示圖selectionIndicatorImage
selectionIndicatorImage
UITabBar.appearance().barStyle = barStyle
UITabBar.appearance().backgroundImage = tabBarBackgroundImage
let tabIndicator = UIImage(named: "tabBarSelectionIndicator")?
    .withRenderingMode(.alwaysTemplate)
    .resizableImage(withCapInsets: .init(top: 0, left: 2, bottom: 0, right: 2))
UITabBar.appearance().selectionIndicatorImage = tabIndicator
UISegmentedControl
  • 主要是默認背景圖的和選中背景圖的設置setBackgroundImage()
  • 也可以設置某個item的圖片setImage(, forSegmentAt:)
UISegmentedControl
let controlBgImage = UIImage(named: "controlBackground")?
    .withRenderingMode(.alwaysTemplate)
    .resizableImage(withCapInsets: .init(top: 3, left: 3, bottom: 3, right: 3))
let controlSelectedBgImage = UIImage(named: "controlSelectedBackground")?
    .withRenderingMode(.alwaysTemplate)
    .resizableImage(withCapInsets: .init(top: 3, left: 3, bottom: 3, right: 3))
UISegmentedControl.appearance().setBackgroundImage(controlBgImage, for: .normal, barMetrics: .default)
UISegmentedControl.appearance().setBackgroundImage(controlSelectedBgImage, for: .selected, barMetrics: .default)
UIStepper
  • 主要可以設置normaldisabledhighlighted的背景圖setBackgroundImage()
  • 可以設置加減按鈕的圖片setIncrementImage()setDecrementImage()
UIStepper
let controlBgImage = UIImage(named: "controlBackground")?
    .withRenderingMode(.alwaysTemplate)
    .resizableImage(withCapInsets: .init(top: 3, left: 3, bottom: 3, right: 3))
UIStepper.appearance().setBackgroundImage(controlBgImage, for: .normal)
UIStepper.appearance().setBackgroundImage(controlBgImage, for: .highlighted)
UIStepper.appearance().setBackgroundImage(controlBgImage, for: .disabled)
UIStepper.appearance().setDecrementImage(UIImage.init(named: "fewerPaws")!, for: .normal)
UIStepper.appearance().setIncrementImage(UIImage.init(named: "morePaws")!, for: .normal)
UISlider
  • 主要可以設置拇指按鈕的圖片setThumbImage()
  • 主要可以設最大/最小軌道的圖片setMaximumTrackImage()setMinimumTrackImage()
UISlider
UISlider.appearance().setThumbImage(UIImage(named: "sliderThumb"), for: .normal)
UISlider.appearance().setMaximumTrackImage(UIImage(named: "maximumTrack")?
    .resizableImage(withCapInsets:UIEdgeInsets(top: 0, left: 0.0, bottom: 0, right: 6.0)), for: .normal)
UISlider.appearance().setMinimumTrackImage(UIImage(named: "minimumTrack")?
    .withRenderingMode(.alwaysTemplate)
    .resizableImage(withCapInsets:UIEdgeInsets(top: 0, left: 6.0, bottom: 0, right: 0)), for: .normal)
UIProgressView
  • 主要可以設置默認軌道的圖片trackImage和進度軌跡的圖片progressImage
UIProgressView
UIProgressView.appearance().trackImage = UIImage(named: "controlBackground")?
    .withRenderingMode(.alwaysTemplate)
    .resizableImage(withCapInsets: .init(top: 3, left: 3, bottom: 3, right: 3))
UIProgressView.appearance().progressImage = UIImage(named: "maximumTrack")?
    .withRenderingMode(.alwaysTemplate)
    .resizableImage(withCapInsets:UIEdgeInsets(top: 0, left: 0.0, bottom: 0, right: 6.0))
UISwitch
  • 主要可以開狀態的顏色onTintColor和拇指按鈕的顏色thumbTintColor
  • 也可以設置開關狀態的圖片onImageoffImage(不過設置這兩個屬性后沒什么效果,不建議使用);
UISwitch on
UISwitch.appearance().onTintColor = .red
UISwitch.appearance().thumbTintColor = mainColor
其他
  • 其實UIAppearance是一個協議,主要作用是返回一個Self類,然后通過這個代理對象設置的所有UI元素實例對象,都會保留appearance()擁有的屬性值;
  • 一旦設置,對所有Self類對象起作用,如果要過濾某些UI類的樣式不生效,可以使用appearance(whenContainedInInstancesOf: [])方法;

一個夜間主題Demo

定義一個Theme枚舉
  • 定義樣式類型;
enum Theme: Int {
    case `default`, dark, graphical
    
    private enum Keys: String{
        case selected = "ThemeSelected"
    }
}
  • 定義一個單例對象current,返回選擇的樣式;
static var current: Theme {
        return Theme.init(rawValue: UserDefaults.standard.integer(forKey: Keys.selected.rawValue)) ?? .default
    }
  • 定義mainColor、textColor等屬性;
var mainColor: UIColor{
        switch self {
        case .default:
            return .blue
        case .dark:
            return .orange
        case .graphical:
            return .darkGray
        }
    }
    
    var barStyle: UIBarStyle{
        switch self {
        case .default, .graphical:
            return .default
        case .dark:
            return .black
        }
    }
    
    var navigationBarBackgroundImage: UIImage?{
        return self == .graphical ? UIImage.init(named: "navBarBackground") : nil
    }
    
    var tabBarBackgroundImage: UIImage?{
        return self == .graphical ? UIImage.init(named: "tabBarBackground") : nil
    }
    
    var backgroundColor: UIColor{
        switch self {
        case .default, .graphical:
            return .white
        case .dark:
            return UIColor(white: 0.4, alpha: 1)
        }
    }
    
    var textColor: UIColor{
        switch self {
        case .default, .graphical:
            return .black
        case .dark:
            return .white
        }
    }
  • 定義一個實例方法apply()來讓選擇的主題生效
func apply(){
    UserDefaults.standard.set(rawValue, forKey: Keys.selected.rawValue)
    UserDefaults.standard.synchronize()
    
    updateMainColor()
    
    updateNavigationBarAppearance()
    
    updateTableViewAppearance()
    
    updateLabelAppearance()
    
    updateTabBarAppearance()
    
    updateSegmentedControlAppearance()
    
    updateStepperAppearance()
    
    updateSliderAppearance()
    
    updateProgressAppearance()
    
    updateSwitchAppearance()
    
    updateActivityIndicatorAppearance()
}
  • 設置各種UI元素的appearance(此處只舉一個導航欄例子)
private func updateNavigationBarAppearance(){
        UINavigationBar.appearance().barStyle = barStyle
        UINavigationBar.appearance().setBackgroundImage(navigationBarBackgroundImage, for: .default)
        UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
        UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed")
    }
使用主題
  • 本例中通過segmented control來切換主題
@IBAction func changeTheme(_ sender: UISegmentedControl) {
// 存儲并設置主題
    Theme(rawValue: sender.selectedSegmentIndex)!.apply()
// TODO: 讓主題立即生效
}
  • 為了讓app的window上的所有元素對主題立即生效,就務必要做到remove所有的subview,然后再addSubview,因為appearance的應用,一定是在add subview時才能起作用的
// TODO: 讓主題立即生效
let window = UIApplication.shared.delegate?.window
window??.subviews.forEach{
    view in
    view.removeFromSuperview()
    window??.addSubview(view)
}
自動設置夜間主題
  • 這里用到了一個第三方的庫Solar,它是根據設置的地點的經緯度,判斷應用啟動時是日出還是日落,如果是日落時間段,則設置主題為dark;
// 北京
private let solar = Solar(latitude: 39.9047253699, longitude: 116.4072154982)!

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
    initializeTheme()
    
    return true
}

func initializeTheme(){
    if solar.isDaytime {
        Theme.current.apply()
        scheduleThemeTimer()
    }
    else{
        Theme.dark.apply()
    }
}
  • 如果應用啟動時是白天時間,則啟動一個定時器,并加到Runloop.main隊列中,一旦判斷時間點為日落,則自動切換為夜間主題;
func scheduleThemeTimer(){
    let timer = Timer.init(fire: solar.sunset!, interval: 0, repeats: false) { [weak self] _ in
        Theme.dark.apply()
        
        self?.window?.subviews.forEach{
            view in
            view.removeFromSuperview()
            self?.window?.addSubview(view)
        }
    }
    
    RunLoop.main.add(timer, forMode: .commonModes)
}
Github

https://github.com/BackWorld/NightThemeDemo

如果對你有幫助,別忘了加個 關注 或 點個 哦??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

推薦閱讀更多精彩內容

  • 大眾創業,萬眾創新,人人都是創業者,人人也都是消費者。 近年來,隨著創業的低門檻,熱潮不減,任何一個公司或生意人,...
    推爺閱讀 272評論 0 0
  • 大后天就是除夕了,如果待在公會里不出去的話,感受不到什么年味。一來是因為天氣不冷。從小到大,過年的印象都是和...
    雪xl梨閱讀 382評論 2 1
  • 他很努力,經常加班到深夜。 噢。我還在加班啊 你經常加班,只是假裝自己態度良好 工作后,聯系同學和朋友聽得最多的一...
    維生素B2毫克閱讀 614評論 0 0
  • 如果這份感情不是我一個人的,我寧可什么也不要,一點也不要。 他心里有人,心外也有人。 我不能讓自己沉迷。
    以夢為馬的牙云云閱讀 218評論 0 0