IOS MJRefresh Li Mingjie source code analysis

IOS MJRefresh Li Mingjie source code analysis

MJRefresh is the famous developer and trainer Li Mingjie teacher’s work, and now there are more than 10000 star in GitHub, it is really excellent iOS drop-down refresh (also supports more pull loading more) controls. The main source of this article is the article on iOS developer J_Knight, who is learning hybrid, and the usage of Li Mingjie on GitHub (mainly the next part of this article).

This paper is divided into two parts introduction to MJRefresh part from the frame level were summarized, it is the experience in the learning and research in the process of understanding; the lower part is the detailed analysis of the specific implementation of each class code. It is therefore suggested that if we already have some understanding of the MJRefresh students can look on the part, if just began to use this framework to students can skip the first part directly looks from the code, so as not to be the author may not be misled by mature opinion. The level is limited, but we also hope generous with your criticism.

Part one

The frame design is very clear, the use of a base class MJRefreshComponent to do some basic settings, and then by way of inheritance to MJRefreshHeader and MJRefreshFooter respectively with pull-down refresh and pull loading function. From the view of inheritance mechanism can be divided into three layers, the concrete can be seen in the below picture:

Hierarchical sketch of IOS MJRefresh Li Mingjie source code analysis
The structure diagram given by IOS MJRefresh Li Mingjie source code analysis

One of the inheritance chains is like this

MJRefreshNormalHeader-> MJRefreshStateHeader-> MJRefreshHeader-> MJRefreshComponent

Developers generally use scenarios like this:

TableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector (refresh:)]; tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{/ refresh}];

1. add member properties at run time

In the category UIScrollView+MJRefresh, the author uses runtime Association attribute functions objc_setAssociatedObject and objc_getAssociatedObject to add attributes such as mj_header, mj_footer, and so on to the UIScrollView view control

2. factory methods and polymorphic applications

MJRefreshNormalHeader is the lowest sub class, and headerWithRefreshingTarget and headerWithRefreshingBlock are the static shortcut construction methods exposed by the top-level base class MJRefreshComponent. The creation of objects is delegated to subclasses, where factory methods are clearly used. Compile time type mj_header, is, classof, MJRefreshHeader, runtime type MJRefreshNormalHeader is its subclass, which is typical of polymorphic applications.

3., the principle of inversion of dependency and the idea of hierarchical implementation

That is, coding for interfaces rather than coding for implementation. The author from the highest level base class MJRefreshComponent exposes the interface

#pragma mark - to sub class to achieve initialization (void) / * * * / - prepare NS_REQUIRES_SUPER; / * * * / - placed child control frame (void) placeSubviews NS_REQUIRES_SUPER; / * * when scrollView contentOffset changed the call * / - (void) scrollViewContentOffsetDidChange: (NSDictionary *) change NS_REQUIRES_SUPER; / * * when scrollView contentSize is changed call * / - (void) scrollViewContentSizeDidChange: (NSDictionary *) change NS_REQUIRES_SUPER; / * * when scrollView pulled change drag call * / (void) - scrollViewPanStateDidChange: (NSDictionary *) change NS_REQUIRES_SUPER;

Concrete realization:

layer Realization function
MJRefreshComponent SetState Interface, callback, status
MJRefreshHeader SetState prepare placeSubviews scrollViewContentOffsetDidChange Sets default values to handle gestures, drop down, and listen callback
MJRefreshStateHeader SetState prepare placeSubviews Set status, date, text
MJRefreshNormalHeader SetState prepare placeSubviews Set status arrow, chrysanthemum logo

The base class makes the interface uniformly, and the client is easy to use. The different sub classes cover the functions of their own layer respectively, and make the logic clear, independent of function and easy to expand the code.

4., multi-level structure to facilitate the control of different levels of custom implementation

  • A custom left arrow, chrysanthemum animation
    inherited from MJRefreshGifHeader, override the prepare method to set the picture you will. Eating baozi animation is as follows:
#pragma - mark - (void) prepare{rewriting method [super prepare]; / / set the common state of NSMutableArray *idleImages [NSMutableArray array] animation picture (NSUInteger = for; I = 1; i< =60; i++) {UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@ dropdown_anim__000%zd, i]] [idleImages; addObject:image] [self setImages:idleImages forState:MJRefreshStateIdle];}; / / set will refresh the animated picture (a release will refresh state) NSMutableArray * refreshingImages [NSMutableArray for (NSUInteger = array]; I = 1; i< =3; i++) {UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@ "dropdown_loading_0%zd" i]]; [refreshingImages addObject:im Age] [self setImages:refreshingImages forState:MJRefreshStatePulling];}; / / set is the animated picture [self setImages:refreshingImages refresh forState:MJRefreshStateRefreshing];}

Obviously, if you want to customize the refresh time and the text display of the refresh state, you need to do an article on the MJRefreshStateHeader layer

  • The custom view
    in accordance with the inheritance chain, MJRefreshHeader began to deal with the presentation layer, is the successor to the MJRefreshHeader, the MJRefreshComponent interface can be covered according to specific implementation, can refer to the source MJDIYHeader

Read the source code, so we can not only understand the author’s function, we use the convenient function, encountered in the use of fast fix bug, more important is the experience of design and architecture design in reading the best authors source, the technology will soon become obsolete, excellent design ideas will give. Our future encoding with great reference and enlightenment. Pay tribute to the best open source!

Second part

For specific implementation, first look at the base class of the control: MJRefreshComponent:


This class, as the base class of the control, covers the components of the base class: status, callback, block, and so on, roughly divided into the following 5 functions:

What functions do you have?

  1. Declare all States of the control.
  2. Declare the callback function of the control.
  3. Add monitor.
  4. Provides refresh and stops refresh interfaces.
  5. Provides methods that subclasses need to implement.

How are functions implemented?

1. declare all States of the control

Refresh control state / * * * / typedef NS_ENUM (NSInteger, MJRefreshState) {/ * * * MJRefreshStateIdle = 1 ordinary idle, you can refresh the release / * * * / MJRefreshStatePulling / * * state, refreshing state in MJRefreshStateRefreshing / * * /, * * / MJRefreshStateWillRefresh will refresh the state, all the / * * data loading, no more data. MJRefreshStateNoMoreData};

2. declare the callback function of the control

To refresh the status of the callback / * * * typedef void (^MJRefreshComponentRefreshingBlock) (); / * * start refresh after callback (to refresh the status after the callback) / typedef (^MJRefreshComponentbeginRefreshingCompletionBlock) (void); / * * end of new brush after void / typedef callback (^MJRefreshComponentEndRefreshingCompletionBlock) ();

3. add monitor

Listening statements: offset, size, state

#pragma mark - KVO - (void) addObservers{NSKeyValueObservingOptions monitor options = NSKeyValueObservingOptionNew NSKeyValueObservingOptionOld [self.scrollView addObserver:self |; forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil]; [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil]; self.pan = self.scrollView.panGestureRecognizer; [self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil];}

Handling of listening:

- (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (ID) object change: (NSDictionary * Change) context: (void * context{) / / in these cases directly return if (self.userInteractionEnabled! Return); / / this even could not see the need to deal with if ([keyPath isEqualToString:MJRefreshKeyPathContentSize]) {[self scrollViewContentSizeDidChange:change];} / / invisible if (self.hidden) return; if ([keyPath isEqualToString:MJRefreshKeyPathContentOffset]) {[self scrollViewContentOffsetDidChange:change]}; else if ([keyPath isEqualToString:MJRefreshKeyPathPanState]) {[self}} scrollViewPanStateDidChange:change]; #pragma mark- was following monitoring response method are in this layer is not solid Now, instead of giving them different subclasses, different implementations of the same method are implemented, taking full advantage of the polymorphic properties of object-oriented languages. - - (void) scrollViewContentOffsetDidChange: (NSDictionary *), change{} - (void) scrollViewContentSizeDidChange: (NSDictionary *), change{} - (void) scrollViewPanStateDidChange: (NSDictionary *) change{}

4. provides refresh and stops the refresh interface

#pragma mark (void) - to refresh the status of beginRefreshing [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{{self.alpha = 1; self.pullingPercent = 1; / /}]; as long as refreshing, fully display if (self.window) {self.state = MJRefreshStateRefreshing;} else {/ / prevention is being refreshed when this method is called the header inset if self.state (failure back home! = MJRefreshStateRefreshing) {self.state = MJRefreshStateWillRefresh; / / refresh (prevention from another control back to the controller, back to refresh) [self setNeedsDisplay];}}} - (void) beginRefreshingWithCompletionBlock: (void) (^) (completionB) Lock {self.beginRefreshingCompletionBlock = completionBlock; [self beginRefreshing];} #pragma mark (void) - end refresh endRefreshing {self.state = MJRefreshStateIdle;} - (void) endRefreshingWithCompletionBlock: (void (^) (completionBlock)) {self.endRefreshingCompletionBlock = completionBlock; [self endRefreshing];}

5. provide the methods that subclasses need to implement

#pragma mark - to sub class to achieve initialization (void) / * * * / - prepare NS_REQUIRES_SUPER; / * * * / - placed child control frame (void) placeSubviews NS_REQUIRES_SUPER; / * * when scrollView contentOffset changed the call * / - (void) scrollViewContentOffsetDidChange: (NSDictionary *) change NS_REQUIRES_SUPER; / * * when scrollView contentSize is changed call * / - (void) scrollViewContentSizeDidChange: (NSDictionary *) change NS_REQUIRES_SUPER; / * * when scrollView pulled change drag call * / (void) - scrollViewPanStateDidChange: (NSDictionary *) change NS_REQUIRES_SUPER;

As can be seen from the diagram above, the next base class is divided into MJRefreshHeader and MJRefreshFooter, which follow the branch of MJRefreshHeader:


MJRefreshHeader inherits from MJRefreshComponent, and it does these things:

What functions do you have?

  1. Initialization。
  2. Set header height.
  3. Readjust the Y value.
  4. According to the change of contentOffset, to switch the state (the default state, the state that can be refreshed, the state being refreshed), the implementation method is: scrollViewContentOffsetDidChange.
  5. When switching, the corresponding operation is performed. The implementation method is: setState.

How are functions implemented?

1. initialization

There are two ways to initialize: the action target /block block function, and the callback

#pragma - mark + headerWithRefreshingBlock: construction method (instancetype) (MJRefreshComponentRefreshingBlock) refreshingBlock *cmp alloc] init] {MJRefreshHeader = [[self; cmp.refreshingBlock = refreshingBlock; return CMP;} + (instancetype) headerWithRefreshingTarget: (ID) target refreshingAction: (SEL) action *cmp alloc] init] {MJRefreshHeader = [[self; [cmp setRefreshingTarget:target refreshingAction:action]; return CMP;}

2. set the header height

Set the height of the header by overriding the prepare method:

#pragma mark - covered the superclass - (void) prepare{[super prepare]; / / set for NSUserDefaults in the storage time of key self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey; / / set the height of the header self.mj_h = MJRefreshHeaderHeight;}

3. re adjust the y value

To reset the y value by overriding the placeSubviews method:

#pragma mark - covered the superclass - (void) placeSubviews{[super placeSubviews]; / / set the value of Y (when their height changed, must re adjust the Y value, so to set the value of Y in the placeSubviews method) - self.mj_h - self.mj_y = self.ignoredScrollViewContentInsetTop;}

4. status switching code:

#pragma mark - covered the superclass - (void) scrollViewContentOffsetDidChange: (NSDictionary * Change) {[super scrollViewContentOffsetDidChange:change]; / / in the refreshing state if refresh (self.state = = MJRefreshStateRefreshing) {if (self.window = = Nil) return sectionheader CGFloat insetT; / / stay solve = - self.scrollView.mj_offsetY > - self.scrollView.mj_offsetY:; insetT = insetT > self.mj_h + self.mj_h + insetT; self.scrollView.mj_insetT = insetT; self.insetTDelta = - insetT; return;} / / jump to the next A controller, contentInset may become _scrollViewOriginalInset = self.scrollView.contentInset; / / CGFloat offsetY current contentOffset = self.scrollView.mj_offsetY; / / offsetY CGFloat head controls just appear happenOffsetY = -; / / if it is to scroll to see the head of control, direct return / / > = -> if; > (offsetY > happenOffsetY return); / / CGFloat = happenOffsetY - normal2pullingOffsetY critical point and common will refresh the self.mj_h; CGFloat pullingPercent = (happenOffsetY - offsetY) / if (self.mj_h; self.scrollView.isDragging) {/ / if you are dragging self.pullingPercent = pullingPercent; if (self.state = = MJRefres HStateIdle & & offsetY; < normal2pullingOffsetY) {/ / turn will refresh state self.state = MJRefreshStatePulling; else} if (self.state = = MJRefreshStatePulling & & offsetY = > normal2pullingOffsetY) {/ / into ordinary state self.state = MJRefreshStateIdle;}} else if (self.state = MJRefreshStatePulling) {// will refresh & & hand / / to refresh the [self beginRefreshing] else (pullingPercent < if;}; 1) {self.pullingPercent = pullingPercent;}}

Three points need to be noted: there are three states: the default state (MJRefreshStateIdle), the state of being refreshed (MJRefreshStatePulling), and the state of being refreshed (MJRefreshStateRefreshing). There are two factors for the state switch: one is whether the distance from the drop exceeds the critical value, and the other is whether the finger leaves the screen. The state that can be refreshed is different from the state being refreshed. Because the finger is still attached to the screen, it can not be refreshed. So even if the drop distance exceeds the critical distance (status bar + navigation bar + header height), if no finger off the screen, then it can not immediately refresh, but switch state: can refresh. Once the finger leaves the screen, the state is immediately switched to being refreshed.

Here is a diagram to show the difference between the three states:

IOS MJRefresh Li Mingjie source code analysis,
, 3 states

5., the corresponding operation when the state is switched:

- (void) setState: (MJRefreshState state MJRefreshCheckState) {/ / according to the state to do if (state = = MJRefreshStateIdle) {if (oldState! = MJRefreshStateRefreshing return); / / [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] to save the refresh time of forKey:self.lastUpdatedTimeKey]; [[NSUserDefaults standardUserDefaults] synchronize]; / / offset [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{inset and self.scrollView.mj_insetT = self.insetTDelta; / / if (automatic adjustment of transparency self.isAutomaticallyChangeAlpha) self.alpha = 0;} completion:^ (BOOL finished) {self.pullingPercent = 0; If (self.endRefreshingCompletionBlock) {self.endRefreshingCompletionBlock}}); (}]; else if (state = = MJRefreshStateRefreshing) {dispatch_async (dispatch_get_main_queue), [UIView (animateWithDuration: MJRefreshFastAnimationDuration animations:^{CGFloat ^{top = + self.mj_h; / / top = top self.scrollView.mj_insetT increase the scroll area; / / set the scroll position (0, -top) [self.scrollView setContentOffset:CGPointMake animated:NO] completion:^;} (BOOL finished) {[self executeRefreshingCallback];}}});}];

There are two things to note here: there are two main types of switching in this state: the default state and the refresh state. That is, the two switching points are refreshed for the start and end refresh. When the refresh state is switched to the default state (end refresh), you need to record the end of the refresh. Because there is a default label in header that is used to display the last refresh time.


This class is a subclass of the MJRefreshHeader class, and it does two things:

What functions do you have?

  1. Simple layout of stateLabel and lastUpdatedTimeLabel.
  2. Toggle the text of the two label displays according to the toggle of the control state (default state, refresh state).

To give a map, so that we intuitively feel these two controls:

IOS MJRefresh Li Mingjie source code analysis
status and refresh time 2 label

How are functions implemented?

This class implements the above two implementations by overriding three methods of the parent class:

Method 1:prepare method

Here, the prompt text corresponding to each state is put into a dictionary, and key is the NSNumber form of the state

#pragma mark - covered the superclass - (void) prepare{[super prepare]; / / initialize the spacing of self.labelLeftInset = MJRefreshLabelLeftInset; / / [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderIdleText] forState: MJRefreshStateIdle] to initialize the text; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderPullingText] forState:MJRefreshStatePulling]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderRefreshingText] forState:MJRefreshStateRefreshing];}
#pragma mark - (void) - public methods (NSString * setTitle:) Title forState: (MJRefreshState state) {if (title = = Nil) return; self.stateTitles[@ (state)] = title; self.stateLabel.text = self.stateTitles[@ (self.state)];}

Method 2:placeSubviews method

Here is the layout of lastUpdatedTimeLabel and stateLabel. Pay attention to the lastUpdatedTimeLabel hidden situation.

#pragma mark - covered the superclass - (void) placeSubviews {[super placeSubviews]; if (self.stateLabel.hidden) return; BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count = 0; if (self.lastUpdatedTimeLabel.hidden) {/ / state of if (noConstrainsOnStatusLabel) self.stateLabel.frame = self.bounds;} else {CGFloat stateLabelH = self.mj_h * 0.5; / / if (noConstrainsOnStatusLabel) {self.stateLabel.mj_x = 0; self.stateLabel.mj_y = 0; self.stateLabel.mj_w = self.mj_w; self.stateLabel.mj_h = stateLabelH;} / / update time if (self.lastUpdatedTimeLabel.constraints.count = = 0) {sel F.lastUpdatedTimeLabel.mj_x = 0; self.lastUpdatedTimeLabel.mj_y = stateLabelH; self.lastUpdatedTimeLabel.mj_w = self.mj_w; self.lastUpdatedTimeLabel.mj_h = self.mj_h - self.lastUpdatedTimeLabel.mj_y;}}}

Method 3: setState: method

Here, depending on the incoming state, the corresponding text is changed in stateLabel and lastUpdatedTimeLabel.

  • The text in the stateLabel can be extracted directly from the stateTitles dictionary.
  • The text in lastUpdatedTimeLabel needs to be removed by a method:
- (void) setState: (MJRefreshState state) {MJRefreshCheckState self.stateLabel.text = self.stateTitles[@ / / set the status word (state)]; / / reset key (re display time) self.lastUpdatedTimeKey = self.lastUpdatedTimeKey;}
#pragma mark- key:lastUpdatedTimeKey (void) - setLastUpdatedTimeKey: (NSString * lastUpdatedTimeKey) {[super setLastUpdatedTimeKey:lastUpdatedTimeKey]; / / if label is hidden, you're not going to deal with if (self.lastUpdatedTimeLabel.hidden) return; NSDate *lastUpdatedTime = [[NSUserDefaults standardUserDefaults] objectForKey:lastUpdatedTimeKey]; / / if block if (self.lastUpdatedTimeText) {self.lastUpdatedTimeLabel.text = self.lastUpdatedTimeText (lastUpdatedTime) if (lastUpdatedTime; return;} 1.) {/ / *calendar = [self date NSCalendar currentCalendar]; NSUInteger unitFlags = NSCalendarUnitYear| NSCalendarUnitMonth NSCalendarUnitDay |NSCalendarUn | ItHour |NSCalendarUnitMinute; NSDateComponents *cmp1 = [calendar components:unitFlags fromDate:lastUpdatedTime]; NSDateComponents *cmp2 = [calendar components:unitFlags fromDate:[NSDate date]]; NSDateFormatter *formatter = 2. / / date format "NSDateFormatter alloc] init] BOOL; isToday = NO; if ([cmp1 day] = [cmp2 day]) {/ / formatter.dateFormat @ today =" HH:mm "; isToday = YES; else} if ([cmp1 = = year] [cmp2 formatter.dateFormat = year]) {/ / this year @" MM-dd HH:mm ";} else {formatter.dateFormat = @" yyyy-MM-dd HH:mm ";} NSString *time = [formatter stringFromDate:lastUpdatedTime]; / / the 3. date Self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@, [NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText], "%@%@%@" isToday? [NSBundle "," mj_localizedStringForKey:MJRefreshHeaderDateTodayText]: @ time];} else {self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@ [NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText], "%@%@", [NSBundle mj_localizedStringForKey:MJRefreshHeaderNoneLastDateText]];}}

Here, pay attention to two points: the author uses block to let the user define the actual format of the date himself. If the user does not have a custom, the author uses the default format provided by the author. In the default format setting, you determine whether it’s today or not this year. Later, when you design the time label, you can use it for reference.


What functions do you have?

MJRefreshNormalHeader inherits from MJRefreshStateHeader, and it does two main things:

  1. It added _arrowView and loadingView to MJRefreshStateHeader.
  2. Configure the two view and change the styles of the two view when the status of the Refresh control is switched.

To give a map to intuitively feel the two view:

IOS MJRefresh Li Mingjie source code analysis
status and refresh time 2 label.png

How are functions implemented?

Like MJRefreshStateHeader, there are three ways to rewrite a parent class:

Method 1:prepare

#pragma mark - Method of rewriting parent class - (void) prepare{[super prepare]; self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;}

Method 2:placeSubviews

- (void) placeSubviews{[super placeSubviews]; / / center arrows CGFloat = self.mj_w * arrowCenterX 0.5; if (! Self.stateLabel.hidden) {CGFloat stateWidth CGFloat = self.stateLabel.mj_textWith; timeWidth = 0; if (self.lastUpdatedTimeLabel.hidden! = self.lastUpdatedTimeLabel.mj_textWith) {timeWidth}; CGFloat textWidth = MAX (stateWidth, timeWidth); arrowCenterX = textWidth + self.labelLeftInset / 2 CGFloat arrowCenterY = self.mj_h;} * 0.5; CGPoint arrowCenter = CGPointMake (arrowCenterX, arrowCenterY); / / arrow if (self.arrowView.constraints.count = = 0) {self.arrowView.mj_size = self.arrowView.image.size; self.arrowView .center = arrowCenter;} / / circle if (self.loadingView.constraints.count = = 0) {} = arrowCenter; self.arrowView.tintColor = self.stateLabel.textColor;}

Note here: because stateLabel and lastUpdatedTimeLabel are on the distribution side, while arrowView or loadingView is in the two left, so in order to avoid the two groups overlap, when calculating arrowView or loadingView center, stateLabel and lastUpdatedTimeLabel need to get two the width of the control and compare the size, will be larger as a two label “wide distance”, then calculate center, so that you’re not a coincidence.
, and for how to calculate the width, the author gives a program that we can use in future practice:

- (CGFloat) mj_textWith CGFloat CGSize {stringWidth = 0; size = CGSizeMake (MAXFLOAT, MAXFLOAT); if (self.text.length > 0) {#if defined (__IPHONE_OS_VERSION_MAX_ALLOWED) & & __IPHONE_OS_VERSION_MAX_ALLOWED > =[self.text boundingRectWithSize:size = 70000 stringWidth options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.font} context:nil].size.width; #else stringWidth = [self.text sizeWithFont:self.font constrainedToSize:size lineBreakMode:NSLineBreakByCharWrapping].width #endif return stringWidth;}};

Method 3: setState:

- (void) setState: (MJRefreshState state) {MJRefreshCheckState / / according to if status updates and loadingView (state arrowView = = MJRefreshStateIdle) {/ / 1. is set to the default state of if (oldState = = MJRefreshStateRefreshing) {/ / 1.1 is switched over from refresh state self.arrowView.transform = CGAffineTransformIdentity; [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{/ / hide chrysanthemum self.loadingView.alpha = 0;} completion:^ (BOOL finished) {/ / if not executed animation idle, directly back into the other state if (self.state! = MJRefreshStateIdle) return; Chrysanthemum / stop rotating self.loadingView.alpha = 1; [self.loadingView stopAnimating] self.arrowView.hidden; / / arrow = NO;} else {/ /}]; 1.2 from other state switched [self.loadingView stopAnimating]; / / display the arrow and set as the initial state of the self.arrowView.hidden [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{= NO; self.arrowView.transform = CGAffineTransformIdentity;}}}]; else if (state = = MJRefreshStatePulling 2.) {/ / set can refresh the status [self.loadingView stopAnimating]; self. ArrowView.hidden = NO; [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{/ / self.arrowView.transform = CGAffineTransformMakeRotation inverted arrow (0.000001 - M_PI);}]; else} if (state = = MJRefreshStateRefreshing) {/ / 3. is set to refresh the status of self.loadingView.alpha = 1; / / refreshing -> to prevent idle animation; action is not performed after rotating [self.loadingView startAnimating] / / / / chrysanthemum; hidden arrowView self.arrowView.hidden = YES;}}

So far, we’ve looked through the implementation of MJRefreshComponent to MJRefreshNormalHeader. As you can see, the author uses prepare, placeSubviews, and setState as methods for the base class, so that the following subclasses go up one layer at a time.

And each layer of the sub class, according to their own responsibilities, respectively, in accordance with their own way to achieve these three methods:

  • MJRefreshHeader: is responsible for the height of the header and adjusts the external location of the header itself.
  • MJRefreshStateHeader: is responsible for the layout of the stateLabel and header within the lastUpdatedTimeLabel and the display of the internal text in different states.
  • MJRefreshNormalHeader: is responsible for the layout of loadingView within header and the layout of arrowView and display in different states.

The advantage is that if you want to add some kind of header, you can just do it on a certain level. For example, the MJRefreshGifHeader in the framework, which belongs to the same class as MJRefreshNormalHeader, is inherited from MJRefreshStateHeader. Since the two have the same form of stateLabel and lastUpdatedTimeLabel, the only difference is the left:

  • MJRefreshNormalHeader is the arrow on the left.
  • On the left of the MJRefreshGifHeader is a GIF animation.

Or to provide a map to intuitively feel:

IOS MJRefresh Li Mingjie source code analysis,
, normalHeader, and gifHeader.png

Now let’s see how it works:


It provides two interfaces that are used to set the array of pictures used in different states:

- (void) setImages: (NSArray *) images duration: (NSTimeInterval) duration forState: (MJRefreshState) state (images = = Nil) {if return; / / set the picture group under different conditions and duration of self.stateImages[@ (state)] = images; self.stateDurations[@ (state)] = @ (duration); / * set the controls according to the pictures. The height of *image / UIImage = [images firstObject]; if (image.size.height > self.mj_h) {self.mj_h = image.size.height;}} - (void) setImages: (NSArray *) images forState: (MJRefreshState state) {/ / if no incoming duration, according to the number of images to calculate [self setImages:images duration:images.count * 0.1 forState:state];}

What functions do you have?

Like MJRefreshNormalHeader, it also rewrites the three methods provided by the base class to implement the function of displaying gif images.

How are functions implemented?

1. initialization and label spacing

- (void) prepare {[super prepare]; / / initialize the spacing of self.labelLeftInset = 20;}

2. set the position of GIF based on the width of label and the presence or absence

- (void) placeSubviews {[super placeSubviews]; / / if constraints exist, immediately return to if (self.gifView.constraints.count) return; self.gifView.frame = self.bounds; if (self.stateLabel.hidden & & self.lastUpdatedTimeLabel.hidden) {/ / if stateLabel and lastUpdatedTimeLabel are in a hidden state, the GIF will display self.gifView.contentMode = UIViewContentModeCenter;} else {/ / if at least one exists stateLabel and lastUpdatedTimeLabel, according to the width of the label set the location of the GIF self.gifView.contentMode CGFloat stateWidth = UIViewContentModeRight; CGFloat = self.stateLabel.mj_textWith; timeWidth = 0; if {(self.lastUpdatedTimeLabel.hidden!) TimeWidth = self.lastUpdatedTimeLabel.mj_textWith;} CGFloat textWidth = MAX (stateWidth, timeWidth); self.gifView.mj_w = self.mj_w * textWidth * 0.5 - 0.5 - self.labelLeftInset;}}

3. set animations according to the different incoming States

- (void) setState: (MJRefreshState) state MJRefreshCheckState if (state = = MJRefreshStatePulling {|| state = = MJRefreshStateRefreshing) {//1. if the incoming state can refresh and refreshing NSArray *images = self.stateImages[@ (state)] (images.count = = 0); if return; [self.gifView stopAnimating]; if (images.count = = 1) {//1.1 single picture self.gifView.image [images = lastObject];} else {//1.2 picture self.gifView.animationImages = images; self.gifView.animationDuration = [self.stateDurations[@ (state)] doubleValue] [self.gifView startAnimating]; else if;}} (state = = MJRefreshStateIdle {//2. if the incoming state is the default state, [self.gifView stopAnimating];}}

The Footer class is used to deal with load pull, realization principle and the pull-down refresh is very similar, here first introduced to
in general, the framework is designed to be very neat: through a base class to define some state and some need to subclass implementation interface. Through a layer of a layer of inheritance, so that each layer of the sub class to carry out their duties, only completed its own task, improve the framework can be customized, but also for the expansion of the function and bug tracking is also very helpful, very worthy of our reference.