preface

In case I wasn’t particularly explicit, my scheme is not a generic implementation of static pages. My solution is for a simple static tableView like some setup interface or something like that. Seeing that many people think that such a scheme will be difficult to achieve, this may be a different focus of thinking. The focus of my thinking is the maintenance modification in the later period and the response to frequent changes in demand. Welcome to communicate ~~

Static tableView like Settings interface, personal home page and so on is almost every APP will involve a module. I’m sure everyone has their own set of tricks for how to handle these kinds of interfaces. The purpose of writing this article is to introduce jade and want to communicate with everyone.

Some common ways to write it

To start with the specific way to write, here are some ways I can think of writing.

Caveman writing

Encapsulate nothing

    if (indexPath.section == 0) {
        if (indexPath.row == 0) {
            
        }else if (indexPath.row == 1) {
            
        }
    }else if (indexPath.section == 1) {
        if (indexPath.row == 0) {
            
        }else if (indexPath.row == 1) {
            
        }
    }
Copy the code

Various nested if judge indexpa. section indexpa. row to get the corresponding cell display or jump. It’s not easy to read, it’s not easy to expand, and I don’t think anybody wrote that anymore, maybe when you first learned iOS development.

2. Pure code + enumeration

Use an enumeration for each cell. The data source uses an enumeration array, or you can use an array of objects with an enumeration property.

    self.dataArray = @[@[@(settingTypeAccount)],
                       @[@(settingTypeMessage),@(settingTypePrivacy)],
                       @[@(settingTypeHelp),@(settingTypeAboutUs)]];
Copy the code

You can get an enumeration in the proxy method and use the switch to determine it

    switch (type) {
        case settingTypeHelp:
            break;
            .
            .
            .
        default:
            break;
    }
Copy the code

Using switch to determine specific cell, first of all, the readability is much higher than if, and when adding or deleting cells, Xcode will have hints to help you not miss something. This method will have more duplicated code and is not efficient when adding, deleting, and adjusting cells.

3. Static cells for storyboards

First of all, the storyboard approach is more intuitive than just plain code because you can see the interface without having to run it. It also has an advantage in terms of speed of development. But from my own experience in development, this approach is a pain in the ass when requirements change frequently.

4. Add a middle layer

No package is added a layer of middle layer can not be solved, if there is so add a layer – Lu Xun

😁 Just kidding, let’s see how to add a layer. First of all, I think it’s important to have the concept that encapsulation does not reduce the amount of code to some extent. You still have to write the right code, but the right structure makes the code more readable and extensible.

@interface tableModel : NSObject
- (void)addASection:(tableSectionModel *)section;
@property (nonatomic,strong) NSMutableArray <tableSectionModel *> *sections;
.
.
.
@end

@interface tableSectionModel : NSObject
- (void)addARow:(tableRowModel *)row;
@property (nonatomic,strong) NSMutableArray <tableRowModel *> *rows;
.
.
.
@end

@interface tableRowModel : NSObject
@property (nonatomic,assign) NSInteger rowHeight;
.
.
.
@end
Copy the code

We have already seen how the table is displayed at the beginning, including the order in which the cells are displayed, the display style of the cells, the row height, and so on. So we can abstract all the data that describes a cell into a rowModel, and then we can abstract all the data that describes each section into a sectionModel. So we only need to generate the corresponding sectionModel array to describe a table, and then we parse the data in the model in the data source to complete the display. This way has been relatively reasonable, but there is still more room for internal encapsulation.

show you my code

I’ve seen a lot of similar three-line interface implementations, which are basically a further encapsulation of the fourth method above, implementing several common cell styles internally, using an enumeration for each specific style, and then adding a cell style attribute for each rowModel. By simply setting the cell style of the rowModel, you can get the specific cell. Of course, this approach can handle most situations, and writing interfaces is a lot of fun. But I still have a few things that I’m not satisfied with, and I need to try to resolve them.

1. How to add sections and rows

– (void)addASection:(tableSectionModel *)section; That doesn’t seem to me to be enough, because you can’t guarantee that this part of the code has to be in one place. After thinking for a while, I came up with the way to write it.

[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
    // do something        
}];
Copy the code

This way of writing it solves my problem, so the last way I want to write it is

[self.tableView zhn_addSection:^(ZHNStaticTableSection *section) { [section zhn_addRow:^(ZHNStaticTableRow *row) { }]; . ..}];Copy the code

2.dataSource delegateProblems with duplicate code

We know that the dataSource and delegate are one-to-one, so the proxy method and the dataSource method must be in one place. You might say well, let’s add another layer of Manger to manage the array of SectionModels, and then set the tableView data source and agent to manager, Then in the Manager internal implementation of the dataSource and delegate resolve sectionModel array display interface. So all we need to do is configure the sectionModel. But by doing that what if we’re on the controller and we want to listen to the slide of the tableView? After much thinking, I finally tried to solve this problem with the way of message forwarding + assertion.

Message forwarding implements a broker one-to-many

Agents are one-to-one and notifications are one-to-many.

When we first learned iOS, we’ve all heard the phrase describing the difference between agents and notifications. But we can also implement a one-to-many proxy through message forwarding.

If you don’t know anything about retweeting, check out this blog. The simple idea is that when a method is called, the system looks through the isa pointer to find the method list. If the method is not found, the system provides several additional methods to implement the method before the method is found.

Proxy one-to-many main implementation logic:

  • 1. Provide a delegate container to store proxies.

  • 2.- (BOOL)respondsToSelector:(SEL)aSelector determines the agent in the agent container. If the agent method implements the agent method, this method needs to return YES. If NO is returned, the system determines that the proxy method was not implemented, the method will not be called, and there will be NO subsequent sequence of flows.

    1. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelectorReturns the signature of the method. The message forwarding method that does not return the signature is not called.
    1. - (void)forwardInvocation:(NSInvocation *)anInvocationMethod to iterate over the delegate container and forward the method.

assertions

Assertions are commonly used in Cocoa development to check whether input parameters meet certain conditions and to “assertion” them. This is a philosophical problem in the coding world, where users of our code (either other programmers or future selves) have a hard time limiting their input without knowing the implementation details. Most of the time the compiler can help us check the type of input, but if the code needs certain input conditions to run correctly, this more detailed condition is difficult to control. Our code may not work correctly with input beyond the boundary conditions, which requires some extra work in our code implementation.

The above introduction is taken from an assertion by Meow God called Tips. NSAssert(NO, @”State method should never be called in the actual dummy class”); Similar assertions are very common. When we enter an invalid argument, the program crashes and prints the description in the assertion. So we can see at a glance that there’s something wrong with our input and what the problem is.

The reason why we use assertions here is because I implement some data sources and agents internally. Then we certainly don’t want external implementations of these implemented methods. So we definitely need to do some constraints, and assertions are obviously the best way to do that. If the external implements the implemented method, it crashes directly and prints a prompt.

3. Easy to switch

If we want to switch to an interface we’ve already written, it’s relatively expensive. We must have implemented some cells in our project, and I hope my previous cell can be seamlessly connected. In this case I added a cellClass property to the rowModel to specify the cell, and a displayCellHandle block to set some cell styles.

Take a look at the writing

    [self.tableView zhn_initializeEnvironmentWithDefaultRowHeight:44
                                                 defaultCellClass:[NormalSettingTableViewCell class]
                                             defaultSectionHeader:nil
                                              defaultHeaderHeight:20
                                             defaultSectionFooter:nil
                                              defaultFooterHeight:0
                                                 originalDelegate:self
                                               originalDatasource:self];
    
    [self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"Account number and Security";
            };
            row.selectCellHandle = ^(UITableView *tableView, NSIndexPath *indexPath) {
                NSLog(@"Accounts and Security");
            };
        }];
    }];
    
    [self.tableView zhn_addSection:^(ZHNStaticTableSection *section) {
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"New Message notification";
            };
        }];
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"Privacy";
            };
        }];
        [section zhn_addRow:^(ZHNStaticTableRow *row) {
            row.displayCellHandle = ^(UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
                cell.textLabel.text = @"General";
            };
        }];
    }];
Copy the code

conclusion

The code is here github.com/zhnnnnn/ZHN…

This is my plan, which has not been used in the actual project. I hope you can give me your advice. I’d also like to know how people write this part of the code in big, well-known projects.