Pain points

In our iOS development, UITableView is almost a UI control that all apps will use. Because of business needs, we often register multiple cells, and then

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Copy the code

It would be natural to write a bunch of code like this:

The code for handling the event looks something like this:

This doesn’t seem to be a problem, the code is clean and the logic is clear.

But after you’ve maintained a few releases, or you’ve run into a fickle product manager.

As you’ll see, this code is really dangerous to maintain and can go awry if you don’t pay attention to it. The type used here is probably better than indexPath.

If you use indexPath as a criterion, if your cell order changes, or changes, then you probably need to maintain at least one of the following:

  1. Your model array
  2. Cell Conditions for determining a dequeue
  3. Conditions for event processing
  4. .

The more things you maintain, the more likely you are to make mistakes.

Are there any good ways to handle this kind of code?

Analysis of the

And if you think about it, no matter how complex a UITableView is, all that corresponds to it is an array of models.

So if we maintain the model array, does that mean we maintain all the cells in the UITableView? That’s obvious.

If we have N cell styles in our UITableView, there must be N models in our model array.

That is to say, each cell is paired with each model one by one, and the conventional model and cell binding is like the above idea.

The above idea, obviously, is not what we want, maintenance is too inconvenient, and the coupling is also relatively large.

Think about the process of presenting a UITableView

  1. Initiate a network request
  2. JSON to Model, construct the Model array
  3. The data

It’s basically three steps.

In fact, in the second step, when we construct the model array, can we determine the style of the UI?

If you don’t understand here, look at our analysis above, a cell style corresponds to a model, so if we know the model, do we know the cell style

If you’re still not sure, let’s move on to the field

In actual combat

Look at a simple page like this and you’ll say, “You’re kidding me, my friend. What does this have to do with UITableView?”

This interface needs a UITableView, right?

Yeah, this interface is built directly in UIViewController.

Please look at the following

Does it feel very similar, but there are many different places.

plan

  1. I’m going to write one VC at a time.

Disadvantages:

Where there is a lot of duplicate code and late changes need to be maintained, there is no high cohesion.

  1. Abstract a superclass

    Disadvantages:

    Although the three VCS seem to have a lot in common in the UI, the business processes among them are completely different

  2. Abstract a UIHelper for building the UI

    Disadvantages:

    That sounds great, but if you add a control or subtract a control from an interface, you have to redo the constraint, which is obviously not what you want.

Now let’s look at the UI that we built with UITableView

show

Code in SignInVC:

Code in PasswordSignVC:

Now look at the dequeue code for the cell

The binding of data is all dispersed to each cell.

Row. H code

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol Updatable <NSObject>

@optional
- (void)updateViewData:(id)viewData;

@end


@interface Row : NSObject

@property(nonatomic.copy.readonly) NSString *reuseIdentifier;

@property(nonatomic.strong.readonly) Class cellClass;

@property(nonatomic.strong.readonly) id model;

- (instancetype)initWithClass:(Class)cls model:(id)model;

- (instancetype)initWithClass:(Class)cls;

- (void)updateCell:(UITableViewCell *)cell;

@end

NS_ASSUME_NONNULL_END
Copy the code

Row. The m code

@interface Row(a)

@property(nonatomic.strong.readwrite) Class cellClass;

@property(nonatomic.strong.readwrite) id model;

@end

@implementation Row

- (instancetype)initWithClass:(Class)cls {
    if (self = [self initWithClass:cls model:@ ""]) {}return self;
}

- (instancetype)initWithClass:(Class)cls model:(id)model {
    if (self = [super init]) {
        self.cellClass = cls;
        self.model = model;
    }
    return self;
}

- (void)updateCell:(UITableViewCell *)cell {
    if ([cell respondsToSelector:@selector(updateViewData:)]) {
        [cell performSelector:@selector(updateViewData:) withObject:self.model]; }} - (NSString *)reuseIdentifier {
    return [NSString stringWithFormat:@ "% @".self.cellClass];
}

@end
Copy the code

The whole Row is only 100 lines of code, so all the processing is clustered together, so we can manage The UITableView very well as long as we maintain the model array

The UI is built, but there are two issues that I’m sure you’re interested in

  1. Cell height calculation
  2. A callback to an event on the Cell

Cell height calculation

Self-sizing was introduced in UITableView after iOS8, so the height of the Cell changed

        UIView *dummyView = [[UIView alloc] init];
        dummyView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.contentView insertSubview:dummyView belowSubview:self.textField];
        [dummyView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor].active = YES;
        [dummyView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor].active = YES;
        NSLayoutConstraint *constraint = [dummyView.heightAnchor constraintEqualToConstant:60];

        constraint.priority = 999;
        constraint.active = YES;
Copy the code

If you’re not familiar with this section, go ahead. If you want to Auto Layout has a improving suggestion to see Auto Layout Guide, if you want to know the role of systemLayoutSizeFittingSize, see understand Auto Layout first

A callback to an event on the Cell

Some people are going to dismiss this, but I want to say this: If you don’t use blocks, agents, observers.

How to call back the event of a button on the cell to VC (the button is not exposed to the outside)?

Let’s first look at the method that adds the Action

- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
Copy the code

These three arguments are required:

  • Target (corresponding to action)
  • Action (click on the corresponding method of the button)
  • ControlEvents (the general UIControlEventTouchUpInside)

Once we find the target, write the action to the target and the action binding is complete.

Target is actually our VC, so we just pass the VC to the Cell, but then the Cell is coupled to the VC again. It doesn’t make any difference if I use a block or delegate.

Now all we need to do is find the Cell’s VC, and we’re done.

And that’s where an important concept comes into play: the iOS Responder Chain.

I’m not going to expand it here, but you have to understand this.

Problems that the response chain can solve:

  • Expand the corresponding area
  • Beyond the superclass view the corresponding can still be passed
  • The collapse layer passes events

Find UIViewController of UIView

- (UIViewController *)viewController {
    UIResponder *responder = self;
    while(! [responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break; }}return (UIViewController *)responder;
}
Copy the code

ButtonCell event binding code:

Here we still have to use a protocol:

Pay attention to

This protocol is used primarily for code reading purposes, and is required in Swift because the method is not found at compile time.

As you can see, ButtonCell does not have such a piece of code in its code

@property (nonatomic.weak) id<ButtonCellActionable> delegate;
Copy the code

or

@property (nonatomic.strong) void (^buttonAction)(void);
Copy the code

In this way, our ButtonCell will not be coupled with VC, which is really convenient to modify

The tail

So that’s probably the idea, this is just the Detail part, but I’ll show you the List part in the demo

I’ll cover the Detail and List concepts in Section 3. Section 2 is the idea for the Swift version, which uses generics and is more elegant.

Demo