Mbprogresshud source code (I)

Time:2019-11-22

This post records the learning process of mbprogresshud source code. Starting from the official demo project, we can learn its code structure step by step, learn the technology it uses, and experience the author’s programming ideas.

I. structure

Let’s first look at the structure of mbprogresshud and see the definition of its class.
1. Mbprogresshud is a subclass of uiview.
2. properties:

1.
//Agent, only one method is defined: - (void) hudwashidden: (mbprogresshud *) HUD; used to perform the operation after HUD hiding
@property (weak, nonatomic) id delegate;
//Block of HUD hide operation, the purpose is the same as above
@property (copy, nullable) MBProgressHUDCompletionBlock completionBlock;
2.
//Delay time: if the task is completed before grace time, the HUD will no longer be displayed, that is to prevent HUD from being displayed for short-term tasks
@property (assign, nonatomic) NSTimeInterval graceTime;
//Minimum display time to prevent HUD from hiding too fast
@property (assign, nonatomic) NSTimeInterval minShowTime;
//Configure whether HUD is hidden and then remove it from its supervisor. Default NO
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;
3.
//Specify the style of progress bar, including chrysanthemum, pie, ring, horizontal progress bar, custom style, plain text, etc
@property (assign, nonatomic) MBProgressHUDMode mode;
//Content (label + indicator + customview) color
@property (strong, nonatomic, nullable) UIColor *contentColor;
//Animation types when displaying and hiding: fade, zoom, zoomin, zoomout
@property (assign, nonatomic) MBProgressHUDAnimation animationType;
//The offset of the content box from the center position, such as cgpointmake (0. F, mbprogressmaxoffset). The content box will be centered at the bottom
@property (assign, nonatomic) CGPoint offset;
@Property (assign, nonatomic) cgfloat margin; // the margin from each element to HUD
@Property (assign, nonatomic) cgsize minsize; // minimum size of content box
@Property (assign, nonatomic, getter = issquare) bool square; // force HUD to be square
@Property (assign, nonatomic, getter = aredefaultmotioneffectsenabled) bool defaultmotioneffectsenabled; // whether the content box (bezelview) is affected by the device accelerometer, yes by default
4.
@Property (assign, nonatomic) float progress; // progress
@Property (strong, nonatomic, nullable) nsprogress * progressobject; // progress object, used to update progress bar
5.
//Content box, that is, a rectangular box showing the actual content (text, indicator)
@property (strong, nonatomic, readonly) MBBackgroundView *bezelView;
//Background attempts, covering the entire screen
@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;
//Custom view for presentation
@property (strong, nonatomic, nullable) UIView *customView;
@Property (strong, nonatomic, readonly) uilabel * label; // text
@Property (strong, nonatomic, readonly) uilabel * detailslabel; // the detailed text below the text
@Property (strong, nonatomic, readonly) uibutton * button; // action button under the text

3. Other related categories

(1) MBBackgroundView

  • Subclass of uiview, acting asContent box (bezelview)andBackground viewTwo roles.
  • There are two styles available:MbprogresshudbackgroundstylesolidcolorandMbprogresshudbackgroundstyleblur
  • Fuzzy style is throughUIVisualEffectViewandUIBlurEffectAchieved.

(2) MBRoundProgressView

  • Subclass of uiview, which is displayed as a pie / ring progress bar.

(3) MBBarProgressView

  • Subclass of uiview, displayed as a bar like progress bar.

(4) MBProgressHUDRoundedButton

  • The subclass of uibutton, showing the round corner button, serves as the function button on the HUD.

Knowledge point: there is a button attribute in HUD as follows:

/**
 * A button that is placed below the labels. Visible only if a target / action is added. 
 */
@property (strong, nonatomic, readonly) UIButton *button;

Note its commentsVisible only if a target / action is added。 That is, the button will not display until you add an event to it. How is this done? That’s rewriting the uiview function- (CGSize)intrinsicContentSize:

- (CGSize)intrinsicContentSize {
    // Only show if we have associated control events
    if (self.allControlEvents == 0) return CGSizeZero;
    CGSize size = [super intrinsicContentSize];
    // Add some side padding
    size.width += 20.f;
    return size;
}

This function is used to set theBuilt in size。 As you can see, by judgmentallControlEventsTo determine whether there is an event on the button, add 20 to the original built-in size.

2. Code tracking

After understanding the basic structure of mbprogresshud, let’s take a look at how the specific functions are implemented. Huddemo provides 15 samples. We selectPure textLoad (chrysanthemum)Strip progress barandCustom viewFor analysis, other examples are similar to them.

1. Plain text

Let’s start with the simplest plain text. Start huddemo project, click OpenMBHudDemoViewController.mFiles, finding functions- (void)textExample{…}, which is the processing function for displaying plain text:

- (void)textExample {
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];

    // Set the text mode to show only text.
    hud.mode = MBProgressHUDModeText;
    hud.label.text = NSLocalizedString(@"Message here!", @"HUD message title");
    // Move to bottm center.
    hud.offset = CGPointMake(0.f, MBProgressMaxOffset);

    [hud hideAnimated:YES afterDelay:3.f];
}

① enter into the functionshowHUDAddedTo:animated:To view the creation process of mbprogresshud instance:

  • initWithView:->initWithFrame:->commonInit

    Useself.navigationController.viewThe HUD is initialized by the bounds of thecommonInitIt specifies the animation type (fade), HUD mode (chrysanthemum), spacing (20), and content color (black and translucent). In addition, the HUD is set to be completely transparent, and the background color is clear,Configure HUD automatic size adjustment

    //Ensure that the ratio of the upper and lower spacing is the same, and the ratio of the left and right spacing is the same, that is, to prevent HUD position error during horizontal and vertical screen switching
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    //Let each subview of HUD control its own transparency, so that it is not affected by HUD transparency
    self.layer.allowsGroupOpacity = NO;
  • [self setupViews]

    In this function, the creation of subviews is actually performed.

    • Background view

      For classMBBackgroundViewExamples.MBBackgroundViewBy default, the instance creates a white translucent blur effect, andFull screen coverage, but in this case, it will be changed after creationstylebyMBProgressHUDBackgroundStyleSolidColor, and set the background color to clear.

    • Content box (bezelview)

      Same categoryMBBackgroundViewAn example is a view (i.e. a black box in the middle) that actually displays the content, including text, indicator, progress bar, etc.bezelViewCreates a white translucent blur effect by default, butFrame is 0.。 After creation, the corner radius is set to 5.

      Knowledge point: the author added motioneffect for bezel view, that is to say, when bezel view displays, it will adjust its position according to the tilt direction of the mobile phone!

      - (void)updateBezelMotionEffects {
      #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
          MBBackgroundView *bezelView = self.bezelView;
          if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return;
      
          if (self.defaultMotionEffectsEnabled) {
              CGFloat effectOffset = 10.f;
              UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
              effectX.maximumRelativeValue = @(effectOffset);
              effectX.minimumRelativeValue = @(-effectOffset);
      
              UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
              effectY.maximumRelativeValue = @(effectOffset);
              effectY.minimumRelativeValue = @(-effectOffset);
      
              UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
              group.motionEffects = @[effectX, effectY];
      
              [bezelView addMotionEffect:group];
          } else {
              NSArray *effects = [bezelView motionEffects];
              for (UIMotionEffect *effect in effects) {
                  [bezelView removeMotionEffect:effect];
              }
          }
      #endif
      }
    • Label and detailslabel

      Set the label for the displayed text, where detailslabel allows multiple lines.

    • button

      byMBProgressHUDRoundedButtonAs a function button on the HUD, for example, a cancel button can be displayed under the progress bar.

    • Topspacer and bottomspacer

      All areUIViewThe auxiliary view used to adjust the upper and lower spacing.

    • Set the compression coefficient of label, detailslabel and button, and add them to the parent view.

      for (UIView *view in @[label, detailsLabel, button]) {
          View.translatesauautoresizingmaskintoconstraints = no; // manage constraints manually
          [view setcontentcompressionresistance priority: 998. F foraxis: uilayoutconstraintashorizontal]; // set the horizontal anti compression coefficient. The larger the value is, the less easy it is to be compressed
          [view setcontentcompressionresistance priority: 998. F foraxis: uilayoutconstraintasvertical]; // set the vertical anti compression coefficient. The larger the value, the less easy it is to be compressed
          [bezelView addSubview:view];
      }
  • [self updateIndicators]

    HUDindicatoryesUIViewIt is used to record the views displayed on the HUD. The progress bar, load icon (chrysanthemum), custom view, etc. all use HUDindicatorProperty. In function- (void)updaetIndicatorsIn, configure different indicators according to the mode value of HUD. It will call[self setNeedsUpdateConstraints]Trigger constraint update function-(void)updateConstraintsTo update the UI.

  • [self registerForNotifications]

    Register for notifications to handle screen rotation.

② after the HUD is created, the[hud showAnimated:animated];Display the HUD on the screen. In fact, although the HUD is currently on the screen, the user can’t see it because the bezel view frame is 0 when the HUD is initialized.

③ configure HUD instance properties

Hud.mode = mbprogresshudmodetext; // set HUD to display only plain text
HUD. Label. Text = nslocalizedstring (@ "message here!", @ "HUD message title"); // set the text content
HUD. Offset = cgpointmake (0. F, mbprogressmaxoffset); // sets the offset of HUD relative to the center position

It is called in the setter function of mode- (void)updateIndicatorsReconfigure the indicator according to the new value of mode, and then call it.- (void)setNeedsUpdateConstraintsTrigger –(void)updateConstraintsTo update the UI. In the setter function of offset, it will directly call- (void)setNeedsUpdateConstraintsTrigger –(void)updateConstraintsTo update the UI.

④ in function- (void)updateConstraintsUpdate layout in:

  • Remove constraints for all controls
  • adoptNSLayoutConstraintReset constraints
//1. Apply offset based on the screen center. priority = 998
CGPoint offset = self.offset;
NSMutableArray *centeringConstraints = [NSMutableArray array];
//x
[centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]];
//y
[centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]];
//Set priority for each constraints
[self applyPriority:998.f toConstraints:centeringConstraints];
[self addConstraints:centeringConstraints];

//2. Set the minimum spacing constraint, priority = 999 
NSMutableArray *sideConstraints = [NSMutableArray array];
[sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
[sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]];
[self applyPriority:999.f toConstraints:sideConstraints];
[self addConstraints:sideConstraints];

//3. Bezel's minimum size constraint priority = 997
CGSize minimumSize = self.minSize;
if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) {
  NSMutableArray *minSizeConstraints = [NSMutableArray array];
  [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]];
  [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]];
  [self applyPriority:997.f toConstraints:minSizeConstraints];
  [bezelConstraints addObjectsFromArray:minSizeConstraints];
}

//4. Square constraint priority = 997
if (self.square) {
    NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0];
    square.priority = 997.f;
    [bezelConstraints addObject:square];
}

//5. Set the spacing constraint of upper and lower spacers according to the margin
[topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];
[bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]];

[bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]];

//6. Set constraints on bezel subviews (topspacer, label, detaillabel, button, bottomspacer)
[subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
        // Center in bezel
        [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]];
        // Ensure the minimum edge margin is kept
        [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]];
        // Element spacing
        if (idx == 0) {
            // First, ensure spacing to bezel edge
            [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]];
        } else if (idx == subviews.count - 1) {
            // Last, ensure spacing to bezel edge
            [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]];
        }
        if (idx > 0) {
            // Has previous
            NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f];
            [bezelConstraints addObject:padding];
            [paddingConstraints addObject:padding];
        }
}];
[bezel addConstraints:bezelConstraints];
self.bezelConstraints = bezelConstraints;

self.paddingConstraints = [paddingConstraints copy];
[self updatepaddingconstraints]; // in this function, according to the visibility (hidden) of the subview, set the upper and lower spacing (4) of the subview

You can know the priority through the priority above:Minimum spacing constraint > bezel offset constraint > bezel minimum size constraint = square constraint。 So, if you sethud.square = YESHowever, bezel does not become square in practice, which is likely due to the conflict between the above constraints. The system adopts the high priority constraint and ignores the square constraint. If you don’t believe it, you can change the square priority to 1000 and try:)

Knowledge point: a macro appears hereNSDictionaryOfVariableBindings, which can be used to easily create nsdictionary:

UIView *view1 = [UIView new];
UIView *view2 = [UIView new];
NSDictionary *dict = NSDictionaryOfVariableBindings(view1,view2);//{@"view1":view1,@"view2":view2}

Conclusion:

So far, let’s summarize the whole process of creating and displaying plain text HUD:

  1. call[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES]Create HUD instance: configure attribute default values (animation type, HUD style, spacing, content color, etc.), initialize view (backgroundview, bezelview, label, detaillabel, button, topspacer, bottomspacer), and create an indicator by default. The HUD then appears on the screen, but is not visible to the user because the constraint is not triggered.
  2. hud.mode = MBProgressHUDModeText。 HUD will hide the indicator according to the mode value and update the constraint.
  3. hud.label.text = NSLocalizedString(@"Message here!", @"HUD message title")Set the text to display.
  4. hud.offset = CGPointMake(0.f, MBProgressMaxOffset)Set the offset property of bezelview to display at the bottom. And update constraints.
  5. [hud hideAnimated:YES afterDelay:3.f]Set a delay timer to hide HUD after 3S. After hidden, callcompletionBlockAnd agent methods- (void)hudWasHidden:(MBProgressHUD *)hud;

2. Loading (chrysanthemum)

The loading style is represented as a rotating chrysanthemum, and the bottom can also contain “loading “And so on. We include “loading “The HUD of the characters is an example to analyze its internal principle. The code is as follows:

- (void)labelExample {
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];

    // Set the label text.
    hud.label.text = NSLocalizedString(@"Loading...", @"HUD loading title");
    // You can also adjust other label properties if needed.
    // hud.label.font = [UIFont italicSystemFontOfSize:16.f];

    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
        [self doSomeWork];
        dispatch_async(dispatch_get_main_queue(), ^{
            [hud hideAnimated:YES];
        });
    });
}
  • callMBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];The process of creating HUD instance is the same as that of plain text.
  • hud.label.text = NSLocalizedString(@"Loading...", @"HUD loading title");The configuration prompt is “loading”.
  • After thatglobal_queueIt performs tasks and returns to the main thread to hide HUD after completing tasks.

By analyzing the creation process of plain text HUD, we know that when HUD is initialized, its mode defaults toMBProgressHUDModeIndeterminate, that is, simple callMBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];The created HUD is the HUD with chrysanthemum load control. What we do next is to assign a copy to its label.

3. Strip progress bar

Mbprogresshud provides three styles of progress bars: bar, pie, and ring. The pie shape is similar to the ring shape. Next, we analyze the realization principle of the bar shape progress bar:

- (void)barDeterminateExample {
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];

    // Set the bar determinate mode to show task progress.
    hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
    hud.label.text = NSLocalizedString(@"Loading...", @"HUD loading title");

    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
        // Do something useful in the background and update the HUD periodically.
        [self doSomeWorkWithProgress];
        dispatch_async(dispatch_get_main_queue(), ^{
            [hud hideAnimated:YES];
        });
    });
}
  • callMBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];The process of creating HUD instance is the same as that of plain text.
  • hud.mode = MBProgressHUDModeDeterminateHorizontalBar;Set mode to bar progress bar. It is called in the setter method of mode- (void)updateIndicatorCreate a progress bar indicator.
  • Progress bar indicator is a classMBBarProgressViewExamples. When creating, the default width is 120, the height is 20, and the content height is 20(intrinsicContentSize10. Its style is in- (void)drawRectDrawn in. In itsprogressProperty is called in the setter method.-(void)setNeedsDisplayTriggering- (void)drawRectTo update the progress.
  • hud.label.text = NSLocalizedString(@"Loading...", @"HUD loading title");Configure the HUD prompt text to “loading”.

4. Custom view

Mbprogresshud provides the ability to display custom views. In demo, a check mark is displayed.

- (void)customViewExample {
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];

    // Set the custom view mode to show any view.
    hud.mode = MBProgressHUDModeCustomView;
    // Set an image view with a checkmark.
    UIImage *image = [[UIImage imageNamed:@"Checkmark"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    hud.customView = [[UIImageView alloc] initWithImage:image];
    // Looks a bit nicer if we make it square.
    hud.square = YES;
    // Optional label text.
    hud.label.text = NSLocalizedString(@"Done", @"HUD done title");

    [hud hideAnimated:YES afterDelay:3.f];
}
  • callMBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];The process of creating HUD instance is the same as that of plain text.
  • hud.mode = MBProgressHUDModeCustomView;Set mode to custom view. Next, assign the custom view to the HUD’scustomViewAttribute. staycustomViewProperty will be called in the setter method of the- (void)updateIndicatorstakecustomViewAdd to HUD.
  • In order to make the interface beautiful, HUD is required to be displayed as a square:hud.square = YES;
  • hud.label.text = NSLocalizedString(@"Loading...", @"HUD loading title");Configure the HUD prompt text to “loading”.

So far, we have simply understood the whole code structure and use process of mbprogresshud, which is enough for us to create and use HUD that meets our needs. But in fact, the source code of mbprogresshud also contains many advanced technical details. We will analyze and learn one by one in the next article.