Generally speaking, some long lists of iOS interface, such as the home page, the active page, will be quite long, so the writing always feels not so elegant, so how to achieve elegant? In practice, I use the associated values of swift enumeration and custom group model methods to achieve this

  • Here is the GIF effect

As you can see, some groups are randomly arranged, and the operation side requires that they can customize the order of the groups in the background. 🥺 see my implementation below

Define a group model enumeration

  • Contains possible definitions, each enumeration associated with the data model that the current group needs to display, either as an array of objects or as an object
/// New home group cell type
enum OriginGroupCellType {
    case marquee(list: [MarqueeModel]) / / entertaining diversions
    case beltAndRoad(list: [GlobalAdModel]) // Belt and Road advertising space
    case shoppingCarnival(list: [GlobalAdModel]) // The shopping carnival
    case walletCard(smallWelfare: WelfareSmallResutlModel) // Wallet card
    case wallet(list: [HomeNavigationModel]) Cell / / purse
    case otc(list: [GlobalAdModel]) // OTC
    case hxPrefecture(list: [GlobalAdModel]) // HX merchandise area
    case middleNav(list: [HomeNavigationModel]) // Middle navigation
    case bottomNav(list: [HomeNavigationModel]) // Bottom navigation
    case broadcast(topSale: HomeNavigationModel, hot: OriginBroadcastModel, choiceness: OriginBroadcastModel) / / live broadcast of the cell
    case middleAd(list: [GlobalAdModel]) // Middle AD cell
    case localService(list: [LocalServiceModel]) // Local service cell
    case bottomFloat(headerList: [OriginBottomFloatHeaderModel]) // Hover the bottom cell
}
Copy the code
  • These enumerations may have to be complied with in consideration of drop-down refresh and so onEquatableagreement
  extension OriginGroupCellType: Equatable {
    public static func = = (lhs: OriginGroupCellType.rhs: OriginGroupCellType) -> Bool {
        switch (lhs, rhs) {
        case (.marquee, .marquee): return true
        case (.beltAndRoad, .beltAndRoad): return true
        case (.shoppingCarnival, .shoppingCarnival): return true
        case (.walletCard, .walletCard): return true
        case (.wallet, .wallet): return true
        case (.otc, .otc): return true
        case (.hxPrefecture, .hxPrefecture): return true
        case (.middleNav, .middleNav): return true
        case (.bottomNav, .bottomNav): return true
        case (.broadcast, .broadcast): return true
        case (.middleAd, .middleAd): return true
        case (.localService, .localService): return true
        case (.bottomFloat, .bottomFloat): return true
        default:
            return false}}}Copy the code

Next comes the definition of the group model

  • And I extract a protocolGroupProviderConvenient reuse
protocol GroupProvider {
    / / / placeholder
    associatedtype GroupModel where GroupModel: Equatable
    
    /// Whether to add the current group model to the group model list
    func isNeedAppend(with current: GroupModel.listMs: [GroupModel]) -> Bool
    /// Gets the subscript of the current group model in the group model list
    func index(with current: GroupModel.listMs: [GroupModel]) -> Int
}

extension GroupProvider {
    func isNeedAppend(with current: GroupModel.listMs: [GroupModel]) -> Bool {
        return !listMs.contains(current)
    }
    
    func index(with current: GroupModel.listMs: [GroupModel]) -> Int {
        return listMs.firstIndex(of: current) ?? 0}}Copy the code
  • OriginGroupModel, also compliesEquatableProtocol to prevent repeated addition
func addTo(listMs: inout [OriginGroupModel]) 
Copy the code
  • This method is convenient to replace the latest data in the drop-down refresh
public struct OriginGroupModel: GroupProvider {
    typealias GroupModel = OriginGroupModel
    
    // the type of the group model
    var cellType: OriginGroupCellType
    / / / order
    var sortIndex: Int

    // add or replace groupModel to listMs
    func addTo(listMs: inout [OriginGroupModel]) {
        if isNeedAppend(with: self, listMs: listMs) {
            listMs.append(self)}else {
            let index = self.index(with: self, listMs: listMs)
            listMs[index] = self}}}extension OriginGroupModel: Equatable {
    public static func = = (lhs: OriginGroupModel.rhs: OriginGroupModel) -> Bool {
        return lhs.cellType = = rhs.cellType
    }
}
Copy the code
  • Consider customizing the order, so you need to define a sorted entity
// MARK: - New collation model for home group model
struct OriginGroupSortModel {
    /// Search history sort
    var marqueeIndex: Int
    var beltAndRoadIndex: Int
    var shoppingCarnivalIndex: Int
    var walletCardIndex: Int
    var walletIndex: Int
    var otcIndex: Int
    var hxPrefectureIndex: Int
    var middleNavIndex: Int
    var bottomNavIndex: Int
    var broadcastIndex: Int
    var middleAdIndex: Int
    var localServiceIndex: Int
    var bottomFloatIndex: Int

    static var defaultSort: OriginGroupSortModel {
        return OriginGroupSortModel(
            marqueeIndex: 0,
            beltAndRoadIndex: 1,
            shoppingCarnivalIndex: 2,
            walletCardIndex: 3,
            walletIndex: 4,
            otcIndex: 5,
            hxPrefectureIndex: 6,
            middleNavIndex: 7,
            bottomNavIndex: 8,
            broadcastIndex: 9,
            middleAdIndex: 10,
            localServiceIndex: 11,
            bottomFloatIndex: 99)}}Copy the code

The controller defines an array of group models

  • Here’s the key code
listMs.sort(by: { return $0.sortIndex < The $1.sortIndex }) 
Copy the code
  • After all data is loaded, it will be sorted according to our custom sorting rules
    /// group model data
    public var listMs: [OriginGroupModel] = [] {
        didSet {
            listMs.sort(by: {
                return $0.sortIndex < The $1.sortIndex
            })
            collectionView.reloadData()
        }
    }
    
    // group model collation (can be returned by background configuration, here we first give a default value)
    // a dependency is required to sort the interface first and then request the data of each group
    public lazy var sortModel: OriginGroupSortModel = OriginGroupSortModel.defaultSort
Copy the code

Network request code

func loadData(_ update: Bool = false._ isUHead: Bool = false) {
        // Define a queue group
        let queue = DispatchQueue.init(label: "getOriginData")
        let group = DispatchGroup(a)// MARK: - Text running light
        group.enter()
        queue.async(group: group, execute: {
            HomeNetworkService.shared.getMarqueeList { [weak self] (state, message, data) in
                guard let `self` = self else { return }
                self.collectionView.uHead.endRefreshing()
                
                defer { group.leave() }
                let groupModel = OriginGroupModel(cellType: .marquee(list: data), sortIndex: self.sortModel.marqueeIndex)
                guard !data.isEmpty else { return }
                
                // add groupModel to listMs
                groupModel.addTo(listMs: &self.listMs)
            }
        })
        
        / / /... The other requests are omitted here

        group.notify(queue: queue) {
            // All threads in the queue are finished, refresh the UI
            DispatchQueue.main.sync { [weak self] in
                self?.collectionView.reloadData()
            }
        }
    }
Copy the code

The proxy method for collectionView

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return listMs.count
    }
    
    func collectionView(_: UICollectionView.numberOfItemsInSection section: Int) -> Int {
        let groupModel = listMs[section]
        switch groupModel.cellType {
        case .marquee, .beltAndRoad, .walletCard, .wallet, .otc, .hxPrefecture, .shoppingCarnival, .middleAd:
            return 1
        case .middleNav(let list):
            return list.count
        case .bottomNav(let list):
            return list.count
        case .broadcast:
            return 1
        case .localService(let list):
            return list.count
        case .bottomFloat:
            return 1}}Copy the code
  • In the same way, in the proxy method of collectionView, cellType is obtained first to judge, so as to achieve accurate positioning. For examplechestnuts
    / / / Cell size
    func collectionView(_ collectionView: UICollectionView.layout collectionViewLayout: UICollectionViewLayout.sizeForItemAt indexPath: IndexPath) -> CGSize {
        let groupModel = listMs[indexPath.section]
        let width = screenWidth - 2 * margin
        switch groupModel.cellType {
        case .marquee:
            return CGSize(width: screenWidth, height: 32)
        case .beltAndRoad:
            return CGSize(width: width, height: 46)
        case .walletCard:
            return CGSize(width: width, height: 85)
        case .wallet:
            return CGSize(width: width, height: OriginWalletCell.eachHeight * 2 + 10)
        case .otc, .hxPrefecture:
            return CGSize(width: width, height: 60)
        case .middleNav:
            let row: CGFloat = 5
            let totalWidth: CGFloat = 13 * (row - 1) + 2 * margin
            return CGSize(width: (screenWidth - totalWidth) / row, height: CGFloat(98.zh(80).vi(108)))
        case .bottomNav:
            let isFirstRow: Bool = indexPath.item < 2
            let row: CGFloat = isFirstRow ? 2 : 3
            let totalWidth: CGFloat = 4 * (row - 1) + 2 * margin
            let width = (screenWidth - totalWidth) / row
            return CGSize(width: floor(Double(width)), height: 70)
        case .shoppingCarnival:
            return CGSize(width: width, height: 150)
        case .broadcast:
            return CGSize(width: screenWidth - 20, height: 114)
        case .middleAd:
            return CGSize(width: width, height: 114)
        case .localService:
            let width = (82 * screenWidth) / 375
            return CGSize(width: width, height: 110)
        case .bottomFloat:
            let h = bottomCellHeight > OriginBottomH ? bottomCellHeight : OriginBottomH
            return CGSize(width: screenWidth, height: h)
        }
    }
Copy the code

Summarize the advantages of this method

  • It is easy to change the order of groups and groups, and can even be delivered by the server

  • Easy to delete groups, as long as the data add group comment out

  • Using enumerations to define each group is clearer, and thanks to swift’s associative value advantage, you don’t have to define multiple arrays in your controller

  • Considering the drop-down refresh, a protocol GroupProvider is extracted that provides two default implementations

    • Method 1: Get the subscript of the current cellType in listMs
    • Method two: Whether to add to listMs
  • What does the interface look like, all driven by data, this group of no data, the interface corresponding not to display (skin does not exist, the wool will be behind), there is data according to the pre-designed display

Source address (source content and GIF image is different, but the idea is the same)

Github.com/XYXiaoYuan/…