Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to write a UITableView

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces "how to write a UITableView". In daily operation, I believe many people have doubts about how to write a UITableView. The editor consulted all kinds of materials and sorted out simple and easy-to-use methods of operation. I hope it will be helpful to answer the doubts of "how to write a UITableView"! Next, please follow the editor to study!

If you think that a large number of methods in `UITableViewDelegate` and `UITableViewDataSource` are copied and pasted every time, the implementation is more or less the same; if you think it takes a lot of code to initiate a network request and parse the data, plus the complexity after refreshing and loading, if you want to know why the following code can meet all the above requirements:

MVC

Before we discuss decoupling, we need to understand the core of MVC: the controller (hereinafter referred to as C) is responsible for the interaction between the model (hereinafter referred to as M) and the view (hereinafter referred to as V).

M here is not usually a single class, but in many cases it is a layer made up of multiple classes. The top level is usually the class that ends with `Model`, which is held directly by C. The `Model` class can also hold two objects:

1. Item: it is the object that actually stores the data. It can be understood as a dictionary, corresponding to the attributes in V.

2. Cache: it can cache its own Item (if there are many)

Common misunderstandings:

1. In general, data will be processed in M rather than C (C only does things that cannot be reused)

two。 Decoupling is not just about taking a piece of code outside. Instead, it focuses on whether repetitive code can be merged and dragged on.

Original edition

In C, we create the `UITableView` object, and then set its data source and proxy to ourselves. That is, you manage the logic of UI and the logic of data access. Under this framework, there are mainly these problems:

1. Contrary to the MVC mode, V now holds C and M.

2. C manages all the logic and the coupling is too serious.

3. In fact, most of the UI correlation is done by Cell rather than `UITableView` itself.

To solve these problems, we first understand what the data source and agent do respectively.

Data source

It has two proxy methods that must be implemented:

-(NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section;- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath

To put it simply, as long as these two methods are implemented, a simple `UITableView` object is complete.

In addition, it is also responsible for managing the number of `section`, title, editing and moving of a certain `cell`, etc.

Agent

The agency mainly involves the following aspects:

1. Callback before and after the presentation of cell, headerView, etc.

2. Height of cell, headerView, etc., click event.

The two most commonly used methods are also:

-(CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath;- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath

Reminder: most proxy methods have a parameter `indexPath`

Optimize data sources

The simplest idea is to take out the data source as an object separately.

This writing method has a certain decoupling effect, and at the same time can effectively reduce the amount of code in C. However, the total amount of code will increase. Our goal is to reduce unnecessary code.

For example, if you get the number of rows for each `section`, its implementation logic is always highly similar. However, because the specific implementation of the data source is not unified, so each data source has to be implemented again.

SectionObject

First of all, let's consider the question: what does the Item held by a data source as an M look like? The answer is a two-dimensional array, and each element holds all the information needed for `section`. Therefore, in addition to having its own array (for cell), there is also the title of section. We name this element `SectionObject`:

@ interface KtTableViewSectionObject: the titleForHeaderInSection method in NSObject@property (nonatomic, copy) NSString * headerTitle; / / UITableDataSource protocol may be used in @ property (nonatomic, copy) NSString * footerTitle; / / UITableDataSource protocol titleForFooterInSection method may be used in @ property (nonatomic, retain) NSMutableArray * items;- (instancetype) initWithItemArray: (NSMutableArray *) items;@endItem

The `items` array should store the `Item` required by each cell. Considering the characteristics of `Cell`, the `BaseItem` of the base class can be designed as follows:

Interface KtTableViewBaseItem: NSObject@property (nonatomic, retain) NSString * itemIdentifier;@property (nonatomic, retain) UIImage * itemImage;@property (nonatomic, retain) NSString * itemTitle;@property (nonatomic, retain) NSString * itemSubtitle;@property (nonatomic, retain) UIImage * itemAccessoryImage;- (instancetype) initWithImage: (UIImage *) image Title: (NSString *) title SubTitle: (NSString *) subTitle AccessoryImage: (UIImage *) accessoryImage;@end parent implementation code

Once we have defined a uniform data storage format, we can consider completing some methods in the base class. Taking the `- (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section` method as an example, it can be implemented as follows:

-(NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section {if (self.sections.count > section) {KtTableViewSectionObject * sectionObject = [self.sections objectAtIndex:section]; return sectionObject.items.count;} return 0;}

It is difficult to create `cell`, because we do not know the type of `cell`, so we cannot call the `alloc` method. In addition, besides creating cell`, you also need to set up UI, which is something the data source should not do.

The solutions to these two problems are as follows:

1. Define a protocol. The parent class returns the base class `Cell`, and the subclass returns the appropriate type as appropriate.

two。 Add a `setObject` method for `Cell` to parse Item and update UI.

advantage

After all this trouble, the benefits are quite obvious:

1. The data source of the subclass only needs to implement the `cellClassForObject` method. The original data source method has been uniformly implemented in the parent class.

two。 Each Cell can simply write its own `setObject` method, then wait for it to be created, and then be called.

3. The subclass can quickly get the item through the `objectForRowAtIndexPath` method without rewriting.

Compare with demo (SHA-1:6475496) and feel the effect.

Optimize the agent

Let's take the two methods commonly used in the agent protocol as an example to see how to optimize and decouple.

First of all, the calculation height, this logic is not necessarily completed in C, because it involves UI, so it is up to Cell to implement. The basis for calculating height is Object, so we add a class method to the Cell of the base class:

+ (CGFloat) tableView: (UITableView*) tableView rowHeightForObject: (KtTableViewBaseItem *) object

Another kind of problem is the proxy method, which is represented by dealing with click events, and their main feature is that they all have the parameter `indexPath` to represent the location. However, in fact, in the process of processing, we do not care about the location, we are concerned about the data in this location.

Therefore, we encapsulate the proxy method so that all the methods called by C have data parameters. Because this data object can be obtained from the data source, we need to be able to get the data source object in the proxy method.

To achieve this, the best way is to inherit `UITableView`:

@ protocol KtTableViewDelegate@optional- (void) didSelectObject: (id) object atIndexPath: (NSIndexPath*) indexPath;- (UIView *) headerViewForSectionObject: (KtTableViewSectionObject *) sectionObject atSection: (NSInteger) section;// can have callbacks such as cell editing, swapping, left sliding, etc. / / this protocol inherits UITableViewDelegate, so VC still needs to implement some @ end@interface KtBaseTableView: UITableView@property (nonatomic, assign) id ktDataSource;@property (nonatomic, assign) id ktDelegate;@end.

The implementation of cell height is as follows: call the method of the data source to get the data:

-(CGFloat) tableView: (UITableView*) tableView heightForRowAtIndexPath: (NSIndexPath*) indexPath {id dataSource = (id) tableView.dataSource; KtTableViewBaseItem * object = [dataSource tableView:tableView objectForRowAtIndexPath:indexPath]; Class cls = [dataSource tableView:tableView cellClassForObject:object]; return [cls tableView:tableView rowHeightForObject:object];} advantage

By encapsulating `UITableViewDelegate` (actually mainly through `UITableView`), we have obtained the following features:

1. C Don't worry about Cell height, each Cell class is responsible for this.

two。 If the data itself exists in the data source, it can be passed to C in the agent protocol, eliminating the need for C to revisit the data source.

3. If the data does not exist in the data source, the method of the proxy protocol will be forwarded normally (because the custom proxy protocol inherits from `UITableViewDelegate`)

Compare with demo (SHA-1:ca9b261) and feel the effect.

More MVC, more concise.

In the above two packages, we changed the original proxy and data source of `UITableView` to the custom proxy and data source of `KtTableView`. And many systematic methods are implemented by default.

So far, it seems that everything has been done, but in fact there are still some areas for improvement:

1. It is still not MVC mode at present!

2. The logic and implementation of C can still be further simplified.

Based on the above considerations, we implement a subclass of `UIViewController` and encapsulate the data source and agent in C.

Interface KtTableViewController: UIViewController@property (nonatomic, strong) KtBaseTableView * tableView;@property (nonatomic, strong) KtTableViewDataSource * dataSource;@property (nonatomic, assign) UITableViewStyle tableViewStyle; / / used to create tableView- (instancetype) initWithStyle: (UITableViewStyle) style;@end

To ensure that the subclass creates a data source, we define this method in the protocol and define it as `roomd`.

Achievements and goals

Now let's sort out how to use the modified `TableView`:

1. First you need to create a view controller that inherits from `KtTableViewController` and call its `initWithStyle` method.

`objc KTMainViewController * mainVC = [[KTMainViewController alloc] initWithStyle:UITableViewStylePlain]; `

two。 Implement the `createDataSource` method in the subclass VC to bind the data source.

```objc

* (void) createDataSource {self.dataSource = [[KtMainTableViewDataSource alloc] init]; / / data source} ```is created in this step

3. In the data source, you need to specify the type of cell.

```objc

* (Class) tableView: (UITableView *) tableView cellClassForObject: (KtTableViewBaseItem *) object {return [KtMainTableViewCell class];} ```

4. In Cell, you need to update the UI and return your height by parsing the data.

Objc

* (CGFloat) tableView: (UITableView *) tableView rowHeightForObject: (KtTableViewBaseItem *) object {return 60;} / Demo follows the setObject method of the parent class. `

What else needs to be optimized?

So far, we have implemented the encapsulation of `UITableView` and related protocols and methods, making it easier to use and avoiding a lot of repetitive and meaningless code.

When using it, we need to create a controller, a data source, and a custom Cell, which happen to be based on the MVC schema. Therefore, it can be said that in terms of encapsulation and decoupling, we have done quite well, and even if we make a great effort, it is difficult to make a significant improvement.

But the discussion about `UITableView` is far from over. I have listed the following problems that need to be addressed.

1. In this design, the return of data is not convenient, such as sending messages to C by cell.

two。 How to integrate pull-down refresh and pull-up loading

3. The initiation of network requests and how to integrate parsing data

With regard to the first question, it is actually the interaction between V and C in ordinary MVC mode. You can add the weak attribute to the Cell (or other classes) for the purpose of direct holding, or you can define the protocol.

Question 2 and 3 is another big topic, network request will be realized by everyone, but how to gracefully integrate into the framework to ensure that the code is simple and extensible is a problem worthy of in-depth consideration and study. Next, we will focus on network requests.

Why create a network layer

How to design an iOS network layer framework? This is a very broad question that is beyond my ability. There are already some excellent and mature ideas and solutions in the industry. due to the limitation of ability and role, I decided to talk about how to design an ordinary and simple network layer from the perspective of an ordinary developer rather than an architect. I believe that no matter how complex the architecture is, it evolves from simple design.

For the vast majority of small applications, a network request framework such as integrating `AFNetworking` is sufficient to meet more than 99% of the requirements. However, with the expansion of the project, or taking a long-term view, directly invoking a specific network framework in VC (taking `AFNetworking` as an example), at least the following problems exist:

1. Once the maintenance of `AFNetworking` is stopped in the future, and we need to change the network framework, the cost will be unimaginable. All VC changes the code, and most of the changes are the same.

Such examples exist, for example, we still use `ASIHTTPRequest` in our project, which has long been stopped maintenance, and it can be predicted that this framework will be replaced sooner or later.

two。 The existing framework may not meet our needs. Take `ASIHTTPRequest` as an example. Its underlying layer uses `NSOperation` to represent each network request. As we all know, the cancellation of a `NSOperation` cannot simply be done by calling the `cancel` method. Without modifying the source code, once it is placed in the queue, it cannot be cancelled.

3. Sometimes all we need is to make a web request and make various custom extensions to the request. For example, we may want to count the initiation and end time of the request, so as to calculate the time spent on the network request and the step of data parsing. Sometimes we want to design a common component and support specific rules to be customized by each business unit. For example, different departments may add different headers to the HTTP request.

4. Network requests may also have other requirements that need to be added widely, such as the pop-up window when the request fails, the logging of the request, and so on.

Refer to the current code (SHA-1:a55ef42) to feel the design without any network layer.

How to design the network layer

The solution is actually very simple:

All computer problems can be solved by adding a middle layer

Readers can think for themselves why adding a middle tier can solve the above three problems.

Three modules

For a network framework, I think there are three main aspects that are worth designing:

1. How to request

two。 How to call back

3. Data parsing

A complete network request is generally composed of the above three modules. We analyze the matters needing attention when implementing each module one by one:

# initiate a request

When initiating a request, there are generally two ways of thinking. The first is to write all the parameters to be configured in the same method, and borrow [keep pace with the times, iOS network layer architecture design under HTTP/2] (the code in http://www.jianshu.com/p/a9bca62d8dab) says:

+ (void) networkTransferWithURLString: (NSString *) urlString andParameters: (NSDictionary *) parameters isPOST: (BOOL) isPost transferType: (NETWORK_TRANSFER_TYPE) transferType andSuccessHandler: (void (^) (id responseObject)) successHandler andFailureHandler: (void (^) (NSError * error)) failureHandler { / / Encapsulation AFN}

The advantage of this method is that all the parameters are clear at a glance, and they are easy to use, so you can call this method every time. But the disadvantage is also obvious, with the increase of parameters and the number of calls, the number of network request code quickly explodes.

Another set of methods is to set the API as an object and take the parameter to be passed in as the property of the object. When initiating a request, just set the relevant properties of the object and call a simple method.

Interface DRDBaseAPI: NSObject@property (nonatomic, copy, nullable) NSString * baseUrl;@property (nonatomic, copy, nullable) void (^ apiCompletionHandler) (_ Nonnull id responseObject, NSError * _ Nullable error);-(void) start;- (void) cancel;...@end

According to the concepts of Model and Item mentioned earlier, you can think of: * * this API object used to access the network is actually an attribute of Model * *.

Model is responsible for exposing necessary properties and methods, while specific network requests are completed by API objects, and Model should also hold the Item that is really used to store data.

How to call back

The return result of a network request should be a string in JSON format, which can be converted into a dictionary through a system or some open source framework.

Next we need to use runtime-related methods to convert dictionaries into Item objects.

Finally, Model needs to assign this Item to its own attribute to complete the entire network request.

From a global point of view, we also need a callback for the completion of the Model request so that VC can have a chance to process it accordingly.

Considering the advantages and disadvantages of Block and Delegate, we chose to use Block to complete the callback.

[data parsing] (https://bestswifter.com/how-to-create-an-uitableview/#)

This part mainly uses runtime to convert the dictionary into Item, and its implementation is not difficult, but how to hide the implementation details so that the upper business does not need to care too much is a problem that we need to consider.

We can define an Item of a base class and define a `parseData` function for it:

/ / KtBaseItem.m- (void) parseData: (NSDictionary *) data {/ / parses the dictionary data, assigns values to its own attributes / / see the following article} encapsulates the API object

First, we encapsulate a `KtBaseServerAPI` object, which has three main purposes:

1. Isolate the implementation details of the specific network library and provide a stable interface for the upper layer.

two。 You can customize some properties, such as the status of the network request, the returned data, etc., and make it easy to call

3. Deal with some common logic, such as network time statistics

For specific implementation, please refer to Git submission history: SHA-1:76487f7

Model and ItemBaseModel

Model is mainly responsible for initiating network requests and handling callbacks. Take a look at how the Model of the base class is defined:

@ interface KtBaseModel// request callback @ property (nonatomic,copy) KtModelBlock completionBlock;// network request @ property (nonatomic,retain) KtBaseServerAPI * serverApi;// network request parameters @ property (nonatomic,retain) NSDictionary * params;// request address needs to be initialized in the subclass init @ property (nonatomic,copy) NSString * address;//model cache @ property (retain,nonatomic) KtCache * ktCache

It completes the network request by holding the API object, and can customize its own storage logic and control the choice of request mode (long or short links, JSON or protobuf).

Model should expose a very simple calling interface to the upper layer, because assuming that a Model corresponds to a URL, you only need to set parameters for each request and you can call the appropriate method to initiate the request.

Since we can't predict when the request will end, we need to set the callback when the request completes, which also needs to be an attribute of Model.

BaseItem

The Item of the base class is mainly responsible for the mapping of property name to json path and the parsing of json data. The core implementation of the dictionary conversion model is as follows:

-(void) parseData: (NSDictionary *) data {Class cls = [self class]; while (cls! = [KtBaseItem class]) {NSDictionary * propertyList = [[KtClassHelper sharedInstance] propertyList:cls]; for (NSString* key in [propertyList allKeys]) {NSString* typeString = [propertyList objectForKey:key]; NSString* path = [self.jsonDataMap objectForKey:key]; id value = [data objectAtPath:path] [self setfieldName:key fieldClassName:typeString value:value];} cls = class_getSuperclass (cls);}}

The complete code refers to the Git submission history: SHA-1:77c6392

How to use

In practical use, the first step is to create the Modle and Item of the subclass. The Model of the subclass should hold the Item object and assign the JSON data carried in the API to the Item object when the network requests a callback.

This JSON-to-object process is implemented in the Item of the base class, and the Item of the subclass needs to specify the corresponding relationship between the attribute name and the JSON path when it is created.

For the upper layer, it needs to generate a Model object, set its path and callback. This callback is usually the VC operation when the network request returns, such as calling the `reloadData` method. At this time, the VC can determine that the data requested by the network is stored in the Item object held by the Model.

For more information, please see Git submission history: SHA-1:8981e28

Drop-down refresh

The `UITableview` of many applications have the features of pull-down refresh and pull-up loading. When implementing this feature, we mainly consider two points:

1. Hide the implementation details of the underlying layer and expose stable and easy-to-use interfaces.

2. How to implement Model and Item

The first point is already a clich é. Refer to SHA-1 61ba974 to see how to implement a simple encapsulation.

The focus is on the transformation of Model and Item.

ListItem

This Item has no other function except to define an attribute `pageNumber`, which needs to be negotiated with the server. Model will determine whether it has been fully loaded based on this property.

/ / In .h @ interface KtBaseListItem: KtBaseItem@property (nonatomic, assign) int pageNumber;@end// In .m-(id) initWithData: (NSDictionary *) data {if (self = [super initWithData:data]) {self.pageNumber = [[NSString stringWithFormat:@ "% @", [data objectForKey:@ "page_number"] intValue];} return self;}

For Server, it is very inefficient to return 'page_ number' every time, because the parameters may be different each time, and calculating the total amount of data is a very time-consuming task. Therefore, in practical use, the client can agree with Server and return the result with the field `isHasNext`. From this field, we can also determine whether or not to load to the last page.

ListModel

It holds a `ListItem` object, exposes a set of loading methods, and defines a protocol `KtBaseListModelProtocol`. The method in this protocol is the method to be executed after the request ends.

@ protocol KtBaseListModelProtocol @ required- (void) refreshRequestDidSuccess;- (void) loadRequestDidSuccess;- (void) didLoadLastPage;- (void) handleAfterRequestFinish; / / after the request ends, refresh the tableview or close the animation, etc. @ optional- (void) didLoadFirstPage;@end@interface KtBaseListModel: KtBaseModel@property (nonatomic, strong) KtBaseListItem * listItem;@property (nonatomic, weak) id delegate;@property (nonatomic, assign) BOOL isRefresh; / / if yes, refresh, otherwise load. -(void) loadPage: (int) pageNumber;- (void) loadNextPage;- (void) loadPreviousPage;@end

In fact, when data additions and deletions occur on the Server, only passing the parameter `nextPage` cannot meet the requirements. The pages acquired twice are not completely unintersected, and there is a good chance that they have repetitive elements, so Model should also take on the task of de-weighting. In order to simplify the problem, it is not fully implemented here.

RefreshTableViewController

It implements the protocols defined in `ListMode` and provides some general methods, while the specific business logic is implemented by subclasses.

# pragma-mark KtBaseListModelProtocol- (void) loadRequestDidSuccess {[self requestDidSuccess];}-(void) refreshRequestDidSuccess {[self.dataSource clearAllItems]; [self requestDidSuccess];}-(void) handleAfterRequestFinish {[self.tableView stopRefreshingAnimation]; [self.tableView reloadData];}-(void) didLoadLastPage {[self.tableView.mj_footer endRefreshingWithNoMoreData];} # pragma-mark KtTableViewDelegate- (void) pullUpToRefreshAction {[self.listModel loadNextPage];}-(void) pullDownToRefreshAction {[self.listModel refresh] } actual use

In a VC, it only needs to inherit the `ReviewTableViewController`, and then implement the `requestDidSuccess` method. Here's the complete code for VC, which is extraordinarily simple:

-(void) viewDidLoad {[super viewDidLoad]; [self createModel]; / / Do any additional setup after loading the view, typically from a nib.}-(void) createModel {self.listModel = [[KtMainTableModel alloc] initWithAddress:@ "/ mooclist.php"]; self.listModel.delegate = self;}-(void) createDataSource {self.dataSource = [[KtMainTableViewDataSource alloc] init]; / / this step creates the data source}-(void) didReceiveMemoryWarning {[super didReceiveMemoryWarning] / / Dispose of any resources that can be recreated.}-(void) requestDidSuccess {for (KtMainTableBookItem * book in ((KtMainTableModel *) self.listModel) .tableViewItem.books) {KtTableViewBaseItem * item = [[KtTableViewBaseItem alloc] init]; item.itemTitle = book.bookTitle; [self.dataSource appendItem:item];}}

Other judgments, such as closing the animation at the end of the request, no more data on the last page, and common logic such as drop-down refresh and pull-up load trigger methods have been implemented by the parent class.

At this point, the study of "how to write a UITableView" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report