iOS development: UILabel size based on screen zooming

Keywords: iOS github Programming

Scene:

Suppose we have a requirement that the number on the design diagram of the iPhone 6 (375pt screen width) be 17pt, and the number on the 6Plus of the iPhone be scaled according to the screen width, i.e. (17pt x 414pt/375pt) = 18.768pt.

Solution:

If the settings one by one are too cumbersome and easy to miss, then we use the runtime replacement method to achieve it. If the replacement method is too cumbersome, we can use the third-party library. Aspects To help us solve.

Steps:

  1. Add pod

    pod 'Aspects', '~> 1.4.1'
  2. New UILabel Category named UILabel + Aspects Scaling
    Following is the file content
    UILabel+AspectsScaling.h file
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UILabel (AspectsScaling)
@end
NS_ASSUME_NONNULL_END

UILabel+AspectsScaling.m file

#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];
  //The following is the log method, which you can avoid
#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

Interpretation:

  1. Obviously, this is the way to scale fonts.
    - (void)scaleFont;
  2. This method is to execute a block after the original initWithCoder: method, which is the method of Aspects library, using runtime, which can be understood by itself. Source code
    [self aspect_hookSelector:@selector(initWithCoder:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info, NSCoder * coder)...
  3. Look at the log method, which uses Aspects to replace the NSLog font size before and after the font. The difference is in the parameters AspectPositionBefore and AspectPositionAfter.
    [self aspect_hookSelector:@selector(scaleFont) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info) ...
    [self aspect_hookSelector:@selector(scaleFont) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info) ...
  4. Let's look at Aspects.h file:
    There are two methods.
    • One is the class method, which modifies all instances of the class.
    • One is the instance method (the method of modifying a single instance).
    • The return value is an ID < AspectToken > that can be saved and unchanged later.
    • The type ID in using Block:(id) Block can generally be written as ^ (id < AspectInfo > info,...) all the parameters of the method to be modified, such as @selector(initWithFrame:), block type ^ (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;
...

summary

Aspects is an implementation of iOS Aspect-oriented programming (AOP).
You can use it if you satisfy the following points (but you don't have to satisfy them to use it)

  • There should be an example method to realize it.
  • Frequent calls, one by one modification is too cumbersome
  • You can insert code before and after the original instance method to fulfill the requirements
  • The most commonly used is log, which can be annotated one step later.
[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 is not omnipotent. The GitHub project home page has Compatibility and Limitations Normally, it won't happen

Posted by 90Nz0 on Tue, 29 Jan 2019 05:30:15 -0800