iOS開發之主題皮膚

來源:互聯網
上載者:User

最近在開發一款【公交應用】,裡面有個模組涉及到主題設定,這篇文章主要談一下個人的做法。

大概的步驟如下:

(1):整個應用依賴於一個主旨管理員器,主旨管理員器根據當前的主題配置,載入不同主題檔案夾下的主題

(2):在應用的各個Controller中,涉及到需要更換主題圖片或顏色的地方,由原來的寫入程式碼方式改為從主旨管理員器擷取(此處可以看到,雖然.xib配置UI會比編碼渲染UI效率來得高,但在靈活性以及協同開發方面還是有些弱了)

(3):在主題設定Controller中,一旦切換主題,則需要像系統的通知中樞發送訊息(這一步的目的,主要是為了讓那些已經存在的並且UI已構建完成的對象修改他們的主題)

最終的見:

https://github.com/yanghua/iBus#version-102preview

首先來看看主題檔案夾的目錄結構:

可以看到,通常情況下主題都是預先做好的,當然了,這裡因為我沒有後端server,如果你的應用是擁有後端server的,那麼可以做得更加強大,比如不將主題檔案儲存體在Bundle中,而是儲存在應用sandBox的Document中。這樣你在應用啟動的時候就可以check server 是否有新的主題包,如果有就download下來,釋放到Document,這樣會方便許多,而不需要在升級之後才能夠使用新主題。扯遠了,這裡可以看到這些檔案夾是藍顏色的,而非黃顏色,可見它不是xcode project的Group,它們都是真實儲存的檔案夾(它們也是app的bundle的一部分,只是擷取的方式有些不同罷了,這樣做的目的就是為了方便組織各個主題的檔案、目錄結構)。

看看擷取主題檔案夾路徑的方式:

#define Bundle_Of_ThemeResource                                @"ThemeResource"//the path in the bundle#define Bundle_Path_Of_ThemeResource                                        \[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:Bundle_Of_ThemeResource]

ThemeManager:

@interface ThemeManager : NSObject@property (nonatomic,copy) NSString                 *themeName;@property (nonatomic,copy) NSString                 *themePath;@property (nonatomic,retain) UIColor                *themeColor;+ (ThemeManager*)sharedInstance;- (NSString *)changeTheme:(NSString*)themeName;- (UIImage*)themedImageWithName:(NSString*)imgName;@end

可以看到,ThemeManager對外開放了三個屬性以及兩個執行個體方法,並且ThemeManager被構建為是單例的(Single)在執行個體初始化的時候,同時執行個體化主題的相關配置:

- (id)init{    if (self=[super init]) {        [self initCurrentTheme];    }        return self;}

initCurrentTheme定義:

- (void)initCurrentTheme{    self.themeName=[ConfigItemDao get:@"主題設定"];    NSString *themeColorStr=[ConfigItemDao get:self.themeName];    self.themeColor=[UIColor parseColorFromStr:themeColorStr];    self.themePath=[Bundle_Path_Of_ThemeResource stringByAppendingPathComponent:self.themeName];        //init UI    UIImage *navBarBackgroundImg=[[self themedImageWithName:@"themeColor.png"]                                  resizableImageWithCapInsets:UIEdgeInsetsMake(0.0f, 0.0f, 1.0f, 1.0f)                                                resizingMode:UIImageResizingModeTile];        [[UINavigationBar appearance] setBackgroundImage:navBarBackgroundImg                                       forBarMetrics:UIBarMetricsDefault];}

這裡從sqlite中擷取了主題類型、佈景主題色彩,然後配置了主題屬性,同時初始化了一致的UINavigationBar。在其他Controller中,擷取圖片的方式不再是:

UIImage *img=[UIImage imageNamed:@"img.png"];

取而代之的是:

UIImage *img=[[ThemeManager sharedInstance] themedImageWithName@"img.png"];

來確保img.png是來自當前主題檔案夾下的img.png

實現如下:
- (UIImage*)themedImageWithName:(NSString*)imgName{    if (!imgName || [imgName isEqualToString:@""]) {        return nil;    }        NSString *imgPath=[self.themePath stringByAppendingPathComponent:imgName];        return [UIImage imageWithContentsOfFile:imgPath];}

到主題設定介面,選擇一個新主題,會調用changeTheme方法:

- (NSString *)changeTheme:(NSString*)themeName{    if (!themeName || [themeName isEqualToString:@""]) {        return nil;    }        NSString *themePath=[Bundle_Path_Of_ThemeResource stringByAppendingPathComponent:themeName];    if (dirExistsAtPath(themePath)) {        self.themePath=themePath;                //update to db        [ConfigItemDao set:[NSMutableDictionary dictionaryWithObjects:@[@"主題設定",themeName]                                                              forKeys:@[@"itemKey",@"itemValue"]]];                //init again        [self initCurrentTheme];    }        return themeName;}

這是一個簡單的ThemeManager,通常有可能會有更多的配置,比如: themedFontWithName:(NSString *)fontName 等,當然方式都是一樣的。上面這些都只是定義,看看它是怎麼運轉的,怎麼跟Controller組合的。BaseController中:註:BaseController是所有其他Controller的super class,任何時候,你去寫一個應用程式也好,一個app也罷,你一開始總是應該規劃好類型的繼承關係,這是非常重要的,就個人經驗而言,我總是會在建立一個應用程式之後,就立即構建一個super class,這會為你省去很多麻煩事。又廢話了,進入正題。在BaseController中,我們開放一個public method:

- (void)configUIAppearance{    NSLog(@"base config ui ");}

該類預設沒有實現,主要用於供sub class去 override。比如其中一個子controller的實現如下:

- (void)configUIAppearance{    self.appNameLbl.strokeColor=[[ThemeManager sharedInstance] themeColor];    [self.commentBtn setBackgroundImage:[[ThemeManager sharedInstance] themedImageWithName:@"aboutBtnBG.png"]                               forState:UIControlStateNormal];        [self.shareBtn setBackgroundImage:[[ThemeManager sharedInstance] themedImageWithName:@"aboutBtnBG.png"]                             forState:UIControlStateNormal];        [self.developerBtn setBackgroundImage:[[ThemeManager sharedInstance] themedImageWithName:@"aboutBtnBG.png"] forState:UIControlStateNormal];        [super configUIAppearance];}

可以看到,所有需要使用主題的UI控制項的相關設定,都抽取出來放到了configUIAppearance中。
該方法,在所有子controller中都不會顯式調用,因為在BaseController的viewDidLoad方法中以及調用了。這樣,如果子類override了它,就會調用子類的,如果沒有override,則調用BaseController的預設實現。可以看到,上面的方法中配置了一些UI的外觀,在一個view show出來的時候會被調用,還有一個調用點當然是主題改變的時候!主題改變的時候,所有應用的controller無非是兩種狀態:(1)未被執行個體化(2)已經被執行個體化,而且view已經構建完成並且可見對於第一種狀態,我們不需要太多的擔心,因為根據選擇新主題的時候,會調用ThemeManager的changeTheme方法,該方法會update資料庫的主題配置,同時init並config新的主題,所以未被執行個體化的controller在後面被執行個體化的時候,肯定會應用新的主題。但對於已經可見的主題就不是第一種情況那樣了,除非你重新show他們,否則是不會主動應用新的主題的,但將其他controller都distory之後再重新構建,根本不可能。所以,這裡應用了ios的Notification,並再次展示了需要一個BaseController的好處,因為我們不需要為已存在的每一個controller註冊訊息通知並實現處理收到通知的方法,我們在BaseController中做,就等於在所有繼承它的sub controller中做了,不是嗎?那麼我們首先要在BaseController中像Notification center註冊我們關注的Notification_For_ThemeChanged Notification:(在BaseController的ViewDidLoad中調用該註冊方法)

- (void)registerThemeChangedNotification{    [Default_Notification_Center addObserver:self                                    selector:@selector(handleThemeChangedNotification:)                                        name:Notification_For_ThemeChanged                                      object:nil];}

同時給出訊息處理的邏輯:

- (void)handleThemeChangedNotification:(NSNotification*)notification{    UIImage *navBarBackgroundImg=[[[ThemeManager sharedInstance] themedImageWithName:@"themeColor.png"]                                  resizableImageWithCapInsets:UIEdgeInsetsMake(0.0f, 0.0f, 1.0f, 1.0f)                                                                    resizingMode:UIImageResizingModeTile];        [self.navigationController.navigationBar setBackgroundImage:navBarBackgroundImg                                                  forBarMetrics:UIBarMetricsDefault];    
    [self configUIAppearance];

}
可以看到這裡又調用了configUIAppearance,由於繼承關係,這裡的self指標指向的並非BaseController執行個體(而是sub controller的執行個體),所以調用的也是sub controller的方法。所以,雖然可能還停留在“主題設定的介面”,但其他已存活的controller已經接受到了切換主題的Notification,並悄悄完成了切換。最後再看看切換主題的大致邏輯:

- (void)themeButton_touchUpIndise:(id)sender{    //unselect    UIButton *lastSelectedBtn=(UIButton*)[self.view viewWithTag:(Tag_Start_Index  + self.currentSelectedIndex)];    lastSelectedBtn.selected=NO;        //select    UIButton *selectedBtn=(UIButton*)sender;    self.currentSelectedIndex=selectedBtn.tag - Tag_Start_Index;    selectedBtn.selected=YES;        [[ThemeManager sharedInstance] changeTheme:((NSDictionary*)self.themeArr[self.currentSelectedIndex]).allKeys[0]];        //post themechanged notification    [Default_Notification_Center postNotificationName:Notification_For_ThemeChanged                                               object:nil];}

大致的流程就是這樣~如果你覺得程式碼片段過於零散,沒有關係,所有的實現代碼都放到了github上。並且整個應用都是開源的!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.