iOS開發:UILabel字號根據螢幕縮放
阿新 • • 發佈:2018-11-09
場景:
假設我們有這樣一個需求,iPhone 6(螢幕寬度為375pt)上的設計圖上的字號為17pt,iPhone 6 Plus上的字號根據螢幕寬度縮放,即字號為(17pt x 414pt / 375pt)= 18.768pt
解決方案:
如果一個一個設定太麻煩,容易遺漏,這時候我們採用 runtime 的替換方法來實現,如果嫌替換方法太麻煩,我們可以用第三方庫 Aspects 來輔助我們解決。
步驟:
-
新增pod
pod 'Aspects', '~> 1.4.1'
- 新建UILabel Category,命名為UILabel+AspectsScaling
以下為檔案內容
UILabel+AspectsScaling.h 檔案
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UILabel (AspectsScaling)
@end
NS_ASSUME_NONNULL_END
UILabel+AspectsScaling.m 檔案
#import "UILabel+AspectsScaling.h" #import "Aspects.h" @implementation UILabel (AspectsScaling) + (void)load { NSError * error = nil; [self aspect_hookSelector:@selector(initWithCoder:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, NSCoder * coder) { [info.instance scaleFont]; } error:&error]; [self aspect_hookSelector:@selector(initWithFrame:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, CGRect frame) { [info.instance scaleFont]; } error:&error]; //以下是log方法,可以不要 #if DEBUG [self aspect_hookSelector:@selector(scaleFont) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info) { UILabel * label = info.instance; NSLog(@"UILabel: Before Scaling font size: %f", label.font.pointSize); } error:&error]; [self aspect_hookSelector:@selector(scaleFont) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info) { UILabel * label = info.instance; NSLog(@"UILabel: After Scaling font size: %f", label.font.pointSize); } error:&error]; #endif } - (void)scaleFont { CGFloat ratio = CGRectGetWidth(UIScreen.mainScreen.bounds) / (CGFloat)375; self.font = [UIFont fontWithDescriptor:self.font.fontDescriptor size:self.font.pointSize * ratio]; } @end
解釋:
- 顯然,這是縮放字型的方法
- (void)scaleFont;
- 這個方法是在原來的initWithCoder: 方法後面執行一個 block ,這是 Aspects 庫的方法,利用的是 runtime,可以自行了解原始碼
[self aspect_hookSelector:@selector(initWithCoder:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, NSCoder * coder)...
- 再看 log 方法,這個 log 方法利用 Aspects ,在替換字型前後 NSLog 字型的字號,這個區別在引數 AspectPositionBefore 和 AspectPositionAfter
[self aspect_hookSelector:@selector(scaleFont) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info) ... [self aspect_hookSelector:@selector(scaleFont) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info) ...
- 我們看看 Aspects 的Aspects.h檔案:
裡面提供兩個方法,- 一個是類方法(修改類的所有例項的方法),
- 一個是例項方法(修改單個例項的方法),
- 返回值是一個id<AspectToken>可以儲存以後取消修改,
- usingBlock:(id)block 裡面的型別id一般情況下可以寫成^(id<AspectInfo> info, ...) ...是要修改的方法的所有引數,如@selector(initWithFrame:) ,block 型別^(id<AspectInfo> info, CGRect frame)
...
typedef NS_OPTIONS(NSUInteger, AspectOptions) {
AspectPositionAfter = 0, /// Called after the original implementation (default)
AspectPositionInstead = 1, /// Will replace the original implementation.
AspectPositionBefore = 2, /// Called before the original implementation.
AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
};
...
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
...
總結
Aspects 是 iOS Aspect-oriented programming (AOP) 的一種實現,
滿足以下幾點就可以使用(但不是必須滿足才能使用)
- 原來要有例項方法實現
- 頻繁呼叫,一個一個修改太麻煩
- 在原來的例項方法的前面和後面可以插入程式碼完成需求
- 最最常用的是log,以後可以一步註釋
[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];
Aspects 不是萬能的,GitHub專案主頁有Compatibility and Limitations,一般情況下不會遇到