preface

Hello, everyone. Recently, I have been learning to code with SWIFT. As many of my previous projects were implemented with OC, I am still in the stage of learning about SWIFT. In order to improve my learning efficiency, I always set a short-term goal for myself every time. Take this time for example. In order to speed up my use of Swift, I set a goal for myself to complete a netease cloud music App of Swift version. How do you improve your learning efficiency when you learn a new language? Let us know in the comments section.

The research analysis

First, analyze the homepage of netease cloud music App on iOS terminal, as shown in the figure:

After reading, the first difficulty in front of my eyes is how to get these data! My first thought, of course, was to go to GitHub to find out if there was an open source API. Once I found it, I was very satisfied. I had already had an API for netease cloud music provided by a big guy:

Among them, there are “home page discovery” and “home page discovery – circular icon entry list” API, without the need for us to call multiple interfaces and data source stitching, you can get all the home page data! When analyzing the format of the returned JSON data, I gave an issue to the big guy, and the big guy replied quickly, worshiping the big guy again.

{
    "code": 200,
    "data": {
        "cursor": null,
        "blocks": [
            {
                "blockCode": "HOMEPAGE_BANNER",
                "showType": "BANNER",
                "extInfo": {
                    "banners": [
                        {
                            "adLocation": null,
                            "monitorImpress": null,
                            "bannerId": "1622653251261138",
                            "extMonitor": null,
                            "pid": null,
                            "pic": "http://p1.music.126.net/gWmqDS3Os7FWFkJ3s8Wotw==/109951166052270907.jpg",
                            "program": null,
                            "video": null,
                            "adurlV2": null,
                            "adDispatchJson": null,
                            "dynamicVideoData": null,
                            "monitorType": null,
                            "adid": null,
                            "titleColor": "red",
                            "requestId": "",
                            "exclusive": false,
                            "scm": "1.music-homepage.homepage_banner_force.banner.2941964.-1777659412.null",
                            "event": null,
                            "alg": null,
                            "song": {

                ......
}

After solving the problem of data source, the next step is to solve the problem of how to visualize the data. According to the effect analysis of the display on the homepage of netease cloud music, the overall view supports scrolling up and down, and the view of a single Cell supports horizontal scrolling. So the UITableView nested in the UICollectionView is probably the most appropriate way to do it.

The rest are some third-party libraries that need to be used. Here we use the third party libraries as follows:

  • Alamofire
  • Kingfisher
  • SnapKit

Functions that need to be implemented

As a developer, it’s important to have a learning environment and a community. Here’s one of my iOS development communities:
710 558 675, no matter you are small white or Daniel are welcome to enter, let us progress together, common development! (The group will provide some free learning books collected by group manager and hundreds of interview questions and answer files for free!)

Its homepage content can be roughly divided into the following parts:

  1. Top search view
  2. Banner
  3. Circular menu button
  4. Recommend the playlist
  5. recommendation
  6. Selected Music Videos
  7. Radar playlist
  8. Popular podcasts
  9. Exclusive scene playlist
  10. New songs, new albums, digital albums
  11. Music calendar
  12. 24-Hour Podcasts
  13. Video collection

Light Mode and Dark Mode themes are supported

Here are the renderings I finally achieved:

The specific implementation details will be elaborated in two articles. The functions will be implemented one by one in accordance with the order of functions listed above. Without further ado, let’s continue to talk about the following.

Building the App Framework

First, open our Xcode to create an APP project based on the Swift programming language, and name it.

By observing the style of netease cloud music App, it can be seen from the TabBar at the bottom that its overall UI framework is composed of UITabBarController and UIViewController. So we can build the whole UI architecture of our App through storyboards; Some people might say I don’t know how to use StoryBoard, can I build it in pure code? The answer is yes, of course, because my development habit is to drag and drop simple UIs in storyboards and write complex UIs in code. It’s a personal habit to do whatever suits me.

The image built using Storyboard is as follows:

Build the front page discovery view

The page we need to build looks like this:

From the page shown above, we can find that the content of the home page of netease cloud music is very rich in data, including search bar, regular scrolling Banner, horizontal scrolling card view, and support for pull-up refresh and pull-down refresh, so our home page can use UITableview as the container. Then build corresponding sub-views on the Cell, such as Banner, UICollectionView, etc., to realize the front page of the table view.

Usually when we load data with UITableView, the data type is single and similar, so when we build cells, we always reuse the same Cell, just like a phone address book. But the netease cloud music home page is not such a matter, each Cell of it presents the content type is different, which makes it impossible for us to use the way of Cell to present the data, then how to build a correct view!

First, let’s identify the problem.

You may often see code like this in other projects, configuring the UITableView cell according to its index:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

   if indexPath.row == 0 {
        //configure cell type 1
   } else if indexPath.row == 1 {
        //configure cell type 2
   }
   ....
}

The same logic is used in the proxy method didSelectRowAt:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

if indexPath.row == 0 {
        //configure action when tap cell 1
   } else if indexPath.row == 1 {
        //configure action when tap cell 1
   }
   ....
}

So what’s wrong with that?

If your table view is static and there is no reordering or adding or removing cells in the table view, then this is fine. Until you try to do this with the table view, the table view’s structure will be broken, requiring you to manually update all the indexes in cellForRowAt and didSelectRowAt methods.

Is there a better way?

I will try my best to share with you how to solve this problem in the following sections.

MVVM

In this project, we will use the MVVM pattern, which stands for Model-View-ViewModel. The advantage of this pattern is that the View can be separated from the Model and the coupling can be reduced, thus reducing the size of the Controller.

Model

In the last article, we identified the interface to get the data source. Now, how do you request the data?

The network request library I used here is a third-party open source library: Alamofire. Simply encapsulate its request interface, and the code is as follows:

import UIKit import Alamofire enum MethodType { case get case post } enum NetworkError: Error { case invalidResponse case nilResponse } class NetworkManager<T: Codable> {// network request static func requestData(_ type: methodType, urlString: String, parameters: [String: Any]? , completion: @escaping (Result<T, NetworkError>) -> Void) { let method = type == .get ? HTTPMethod.get : HTTPMethod.post AF.request(URLString, method: method, parameters: parameters, encoding: URLEncoding.httpBody) .validate() .responseDecodable(of: T.self) { response in if let value = response.value { completion(.success(value)) return } if let error = response.error  { completion(.failure(.invalidResponse)) return } completion(.failure(.nilResponse)) } } }

The request returns JSON data in the following format:

{ "code": 200, "data": { "cursor": null, "blocks": [ { "blockCode": "HOMEPAGE_BANNER", "showType": "BANNER", "extInfo": { "banners": [ { "adLocation": null, "monitorImpress": null, "bannerId": "1622653251261138", "extMonitor": null, "pid": null, "pic": "http://p1.music.126.net/gWmqDS3Os7FWFkJ3s8Wotw==/109951166052270907.jpg", "program": null, "video": null, "adurlV2": null, "adDispatchJson": null, "dynamicVideoData": null, "monitorType": null, "adid": null, "titleColor": "red", "requestId": "", "exclusive": false, "scm": "1.music-homepage.homepage_banner_force.banner.2941964.-1777659412.null", "event": null, "alg": null, "song": {...... (omitted)}

Now, we need to create a Model to map the JSON we requested to the Model we created. There are many ways to parse JSON in SWIFT, including SwiftJSON, HandyJSON, etc. In this project, you can use SwiftJSON, SwiftJSON, HandyJSON, etc. I stick with native Codable for the JSON/Model crossover.

When creating a Model, we can also use some external tools to quickly create the Model. For example, I would like to recommend a tool here: QuickType, which generates the corresponding Model based on the provided JSON string, can greatly save us the time of manually coding the Model.

The Model created is as follows:

// MARK: - Welcome struct HomePage: Codable { let code: Int let data: DataClass let message: String } // MARK: - DataClass struct DataClass: Codable { let cursor: JSONNull? let blocks: [Block] let hasMore: Bool let blockUUIDs: JSONNull? let pageConfig: PageConfig let guideToast: GuideToast } // MARK: - Block struct Block: Codable { let blockCode, showType: String let extInfo: EXTInfoUnion? let canClose: Bool let action: String? let actionType: ActionType? let uiElement: BlockUIElement? let creatives: [Creative]? } enum ActionType: String, Codable { case clientCustomized = "client_customized" case orpheus = "orpheus" } // MARK: - Creative struct Creative: Codable { let creativeType: String let creativeID, action: String? let actionType: ActionType? let uiElement: CreativeUIElement? let resources: [ResourceElement]? let alg: String? let position: Int let code: String? let logInfo: String? = "" let creativeEXTInfoVO: CreativeEXTInfoVO? let source: String? enum CodingKeys: String, CodingKey { case creativeType case creativeID = "creativeId" case action, actionType, uiElement, resources, alg, position, code case creativeEXTInfoVO = "creativeExtInfoVO" case source } } // MARK: - CreativeEXTInfoVO struct CreativeEXTInfoVO: Codable { let playCount: Int } // MARK: - ResourceElement struct ResourceElement: Codable { let uiElement: ResourceUIElement let resourceType: String let resourceID: String let resourceURL: String? let resourceEXTInfo: ResourceEXTInfo? let action: String let actionType: ActionType let valid: Bool let alg: String? let logInfo: String? = "" enum CodingKeys: String, CodingKey { case uiElement, resourceType case resourceID = "resourceId" case resourceURL = "resourceUrl" case resourceEXTInfo = "resourceExtInfo" case action, actionType, valid, alg } } ........ Copy the code, omitting it because it is too long

Next, we start mapping JSON into the Model, and since the Alamofire library is already available in Cogdable, we just need to process its return value:

    NetworkManager<Menus>.requestData(.get, URLString: NeteaseURL.Menu.urlString, parameters: nil) { result in
        switch result {
        case .success(let response):
            let data: [Datum] = response.data
            let model: MenusModel = MenusModel(data: data)
        case .failure(let error):
           print(error.localizedDescription)
        }
    }

ViewModel

The Model is ready, so next we need to create the ViewModel, which is responsible for feeding the data to our TableView TableView.

We will create 12 different Sections, which are:

  • Banner
  • Radio buttons
  • Recommend the playlist
  • recommendation
  • Selected Music Videos
  • Radar playlist
  • Music calendar
  • Exclusive scene playlist
  • Cloud bei new song
  • Podcasts collection
  • 24-Hour Podcasts
  • Video collection

Since none of the data we get is in the same format, we need to use a different UITableViewCell for each type of data, so we need to use the correct ViewModel structure.

First, we must differentiate the data type so that we can use the correct Cell. How do you tell the difference? If, else, or enum! Of course there are multiple types in Swift and you can easily switch between them, the best way is to use an enumeration, so let’s start building the ViewModel!

// Type enum homeviewModelSectionType {case BANNER // BANNER case Menus // case PLAYLIST_RCMD // Recommend playlist case STYLE_RCMD // case MUSIC_MLOG // case MGC_PLAYLIST // case MUSIC_CALENDAR OFFICIAL_PLAYLIST // case ALBUM_NEW_SONG // case new VOICELIST_RCMD // Podcast collection case PODCAST24 // 24 hour podcast case VIDEO_PLAYLIST // Video_playlist

Each enum case represents a different data type required by the TableViewCell. However, since we want to use the same type of data in all of our table views, we need to abstract away all of these cases and define a single common class that will determine all the properties. Here, we can do this by using a protocol that will provide attribute evaluations for our Item:

protocol HomeViewModelSection {
    ...
}

First, we need to know the type of Item, so we need to create a type attribute for the protocol and specify whether this attribute is getTable or setTable. In our example, the type will be HomeViewModelSection:

protocol HomeViewModelSection {
    var type: HomeViewModelSectionType { get }
}

The next property we need is rowCount. It will tell us how many rows each section has:

protocol HomeViewModelSection {
    var type: HomeViewModelSectionType { get }
    var rowCount: Int { get }
}

We also need to add two properties to the protocol, rowHeight and frame. They will define the height and size of the Section:

protocol HomeViewModelSection {
    var type: HomeViewModelSectionType { get }
    var rowCount: Int { get }
    var rowHeight: CGFloat { get }
    var frame: CGRect { get set }
}

We are now ready to create the ViewModelItem for each data type. Each Item needs to follow the previously defined protocol. But before we get started, let’s take one more step toward a clean and orderly project: provide some default values for our protocol. In Swift, we can use the protocol extension to provide default values for the protocol so that we don’t have to assign a value to the rowCount of each item, saving some redundant code:

extension HomeViewModelSection {
    var rowCount: Int {
        return 1
    }
}

First create a ViewModeItem for the Banner Cell:

import Foundation import UIKit class BannerModel: HomeViewModelSection { var frame: CGRect var type: HomeViewModelSectionType { return .BANNER } var rowCount: Int{ return 1 } var rowHeight:CGFloat var banners: [Banner]! init(banners: [Banner]) { self.banners = banners self.frame = BannerModel.caculateFrame() self.rowHeight = self.frame.size.height } // View frame class func caculateFrame() -> cGrect {let height: CGFloat = sectionD_height * CGFloat(scaleW) let width: CGFloat = CGFloat(kScreenWidth) return CGRect(x: 0, y: 0, width: width, height: height) } }

Then we can create the remaining 11 ViewModeItems:

class MenusModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .MENUS
    }

    var rowCount: Int{
        return 1
    }

    var data: [Datum]!

    init(data: [Datum]) {
        self.data = data
        self.frame = MenusModel.caculateFrame()
        self.rowHeight = self.frame.size.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionC_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class MgcPlaylistModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .MGC_PLAYLIST
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = MgcPlaylistModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class StyleRcmdModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .STYLE_RCMD
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = StyleRcmdModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionE_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class PlaylistRcmdModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .PLAYLIST_RCMD
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = PlaylistRcmdModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class MusicMLOGModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .MUSIC_MLOG
    }

    var rowCount: Int{
        return 1
    }

    var uiElement: BlockUIElement?
    var mLog: [EXTInfoElement]!

    init(mLog: [EXTInfoElement], ui elements: BlockUIElement) {
        self.mLog = mLog
        self.uiElement = elements
        self.frame = MusicMLOGModel.caculateFrame()
        self.rowHeight = self.frame.size.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class OfficialPlaylistModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .OFFICIAL_PLAYLIST
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = OfficialPlaylistModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class MusicCalendarModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .MUSIC_CALENDAR
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = MusicCalendarModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionB_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class AlbumNewSongModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .ALBUM_NEW_SONG
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = AlbumNewSongModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class Podcast24Model: HomeViewModelSection
{
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .PODCAST24
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = Podcast24Model.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class VoiceListRcmdModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .VOICELIST_RCMD
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = VoiceListRcmdModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

class VideoPlaylistModel: HomeViewModelSection {
    var rowHeight: CGFloat

    var frame: CGRect

    var type: HomeViewModelSectionType {
        return .VIDEO_PLAYLIST
    }

    var rowCount: Int{
        return 1
    }

    var creatives: [Creative]!
    var uiElement: BlockUIElement?

    init(creatives: [Creative], ui elements: BlockUIElement) {
        self.creatives = creatives
        self.uiElement = elements
        self.frame = VideoPlaylistModel.caculateFrame()
        self.rowHeight = self.frame.height
    }

    /// 根据模型计算 View frame
    class func caculateFrame() -> CGRect {
        let height: CGFloat = sectionA_height * CGFloat(scaleW)
        let width: CGFloat = CGFloat(kScreenWidth)
        return CGRect(x: 0, y: 0, width: width, height: height)
    }
}

That’s all you need for a data item.

The final step is to create the ViewModel class. This class can be used by any ViewController, which is one of the key ideas behind the MVVM structure: your ViewModel doesn’t know anything about the View, but it provides all the data that the View might need.

The only property the ViewModel has is the item array, which corresponds to the section array contained in the UITableView:

Var sections = [HomeviewModelSection]()}

First, we initialize the ViewModel to store the retrieved data in an array:

ViewModel class HomeviewModel: NSObject {var sections = [HomeviewModelSection]() weak var delegate: HomeViewModelDelegate? Override init() {super.init() FetchData ()} // FetchData () {func FetchData () {// 1. Create task group let QueueGroup = DispatchGroup() // 2. QueueGroup.enter () // RequestData < / HomePage> networkManager <HomePage>.requestData(.get, URLString: NeteaseURL.Home.urlString, parameters: nil) { result in switch result { case .success(let response): // Sections = self.splitData(data: response.data.blocks) queueGroup.leave() case.failure (let error): print(error.localizedDescription) self.delegate?.onFetchFailed(with: error.localizedDescription) queueGroup.leave() } } // 3\. Asynchronous get round button queueGroup home page. Enter () NetworkManager < Menus >. The requestData (. Get, URLString: NeteaseURL. Menu. URLString, the parameters: Nil) {result in switch result {case.success (let response): // Split the data model to each block let data: [Datum] = response.data let model: MenusModel = MenusModel(data: data) if self.sections.count > 0 { self.sections.insert(model, at: 1) } queueGroup.leave() case .failure(let error): print(error.localizedDescription) self.delegate?.onFetchFailed(with: error.localizedDescription) queueGroup.leave() } } // 4\. QueueGroup.notify (qos:.default, flags: [], queue: .main) {// call back to view, finish loading, and load self.delegate?.onFetchComplete()}}}

Then, based on the property type of the ViewModelItem, you configure the ViewModel you want to display.

// -parameter data: func splitData(data: [Block]) -> [HomeViewModelSection]{var array: [HomeViewModelSection] = [HomeViewModelSection]() for item in data { if item.blockCode == "HOMEPAGE_BANNER" || item.blockCode == "HOMEPAGE_MUSIC_MLOG"{ switch item.extInfo { case .extInfoElementArray(let result): Let model: musicMlogModel = musicMlogModel (mLog: result, UI: item.uiElement!) array.append(model) break case .purpleEXTInfo(let result): // BANNER let banner: [Banner] = result.banners let model: BannerModel = BannerModel(banners: banner) array.append(model) break case .none: Break}} else if item.blockCode == "Homepage_block_playlist_rcmd" {// Let UI: blockUElement = item.uiElement! let creatives: [Creative] = item.creatives! let model: PlaylistRcmdModel = PlaylistRcmdModel(creatives: creatives, ui: UI) Array. Append (model)} else if item. BlockCode == "Homepage_Block_Style_RCMD" { BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model:StyleRcmdModel = StyleRcmdModel(creatives: creatives, ui: UI) Array. append(model)} else if item.blockCode == "HOMEPAGE_BLOCK_MGC_PLAYLIST" {// let UI: BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model:MgcPlaylistModel = MgcPlaylistModel(creatives: creatives, ui: UI) Array.append (model)} else if item.BlockCode == "Homepage_Music_Calendar" {// Music Calendar let UI: BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model:MusicCalendarModel = MusicCalendarModel(creatives: creatives, ui: UI) array. Append (model)} else if item.blockCode == "HOMEPAGE_BLOCK_OFFICIAL_PLAYLIST" {// let UI: BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model:OfficialPlaylistModel = OfficialPlaylistModel(creatives: creatives, ui: UI) Array.append (model)} else if item.blockcode == "HOMEPAGE_BLOCK_NEW_ALBUM_NEW_SONG" {// Let UI: BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model: AlbumNewSongModel = AlbumNewSongModel(creatives: creatives, ui: UI) Array. append(model)} else if item.blockCode == "HOMEPAGE_VOICELIST_RCMD" {// let UI) Array. append(model)} else if item.blockCode == "HOMEPAGE_VOICELIST_RCMD" {// let UI: BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model: VoiceListRcmdModel = VoiceListRcmdModel(creatives: creatives, ui: UI) Array.append (model)} else if item.blockcode == "HOMEPAGE_PODCAST24" {// let UI: BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model: Podcast24Model = Podcast24Model(creatives: creatives, ui: UI) Array. Append (model)} else if item. BlockCode == "Homepage_Block_Video_PlayList" { BlockUIElement = item.uiElement! let creatives: [Creative] = item.creatives! let model: VideoPlaylistModel = VideoPlaylistModel(creatives: creatives, ui: ui) array.append(model) } } return array }

Now, if you want to reorder, add, or remove items, you simply modify the Item array for this ViewModel. It’s clear, right?

Next, we add the UITableViewDataSource to the ModelView:

extension DiscoveryViewController { // Mark UITableViewDataSource override func numberOfSections(in tableView: UITableView) -> Int { if homeViewModel.sections.isEmpty { return 0 } return homeViewModel.sections.count } override func  tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return homeViewModel.sections[section].rowCount } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // configure the cells here } }

At the end

As a developer, it’s important to have a learning environment and a community. Here’s one of my iOS development communities:
710 558 675, no matter you are small white or Daniel are welcome to enter, let us progress together, common development! (The group will provide some free learning books collected by group manager and hundreds of interview questions and answer files for free!)

At this point, create project project, APP UI framework, Model, ViewModel has been basically completed. Finally, to sum up, first of all, when we built the APP UI framework, we built the UI framework by using the feature that StoryBoard can quickly build views; Then, based on the JSON returned from the interface, we quickly generated the Model using the external conversion tool QuickType, mapped the JSON data onto the Model, and implemented the mapping process using native Codable. Finally, we created the ViewModel. Since each of our sections presents different data, in order to facilitate the table view to load data, we need to abstract all the section-loaded data into a common class to call, so we use the protocol to handle this.

Well, that’s the end of this article. In the next article, let’s look at how to build a View.