简体   繁体   中英

How to get data in two sections with header in table view swift

I have a table view with header as title for two offers.

  1. Offers for standard routes as 1 euro offers
  2. Offers for alternative routes as nearby offers

I have a search bar to search offers from tableview and load in table rows.I want to show both offers in two sections as section 1 with standard route offer and section 2 with nearby offers. The problem is I am getting only one section with header as standard offers or nearby offers which one is being searched in tableview. How I can get two sections with title for both standard and nearby offers. Here is my code. Two array for offers as:

    struct AlternativeRoutesResponse: Codable {
        let nearbyRoutes: [AlternativeRouteOffer]
        let standardRoutes: [AlternativeRouteOffer]
        var nearbyRoutesSection: SearchSection? {
            nearbyRoutes.isEmpty ? nil : SearchSection(title: L10n.Search.nearbyOffers, offers: nearbyRoutes.map({RouteOffer(alternativeOffer: $0)}))
        }
        var standardRoutesSection: SearchSection? {
            standardRoutes.isEmpty ? nil : SearchSection(title: L10n.Search.standardOffers, offers: standardRoutes.map({RouteOffer(alternativeOffer: $0, isStandard: true)}))
        }
    }

Offers parameters:

    struct AlternativeRouteOffer: Codable {
    struct LocationOffer: Codable {
        let name: String
        let thingId: Int
    }
    
    let departure: LocationOffer
    let destination: LocationOffer
}

In search model:

    struct OffersResponse {
         let status: Bool
         let sections: [SearchSection]
         let title: String
     }
    
     enum SearchOffersDetailsEvent: Event {
         case nothingFoundAt(date: Date, offer: RouteOffer)
     }
    
     enum SearchOffersEvent: Event {
         case setAlert(departure: AddressLocation?, destination: AddressLocation?)
     }

    enum SearchOffersModelAction {
         case exchangeLocations
         case setAlert
     }
    
     protocol SearchOffersModelInput: AnyObject {
         
         var numberOfSections: Int { get }
         var isGuest: Bool { get }
         func send(action: SearchOffersModelAction)
         func load()
         func itemsIn(section: Int) -> Int
         func configureCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell
         func didSelect(indexPath: IndexPath)
         func headerIn(section: Int, for tableView: UITableView) -> UITableViewHeaderFooterView?
         func heightForHeader(in section: Int) -> CGFloat
     }

    protocol SearchOffersModelOutput: AnyObject {
         func didLoad()
         func pop()
     }
    
     final class SearchOffersModel: TextFieldEventNode {
     
     enum StateResult {
         case isFound, noResults, empty
         
         var headerItemsCount: Int {
             switch self {
             case .empty: return 2
             case .noResults: return 3
             default: return 2
             }
         }
         
         func cellReuseIdentifierFor(row: Int) -> String {
             switch self {
             default:
                 switch row {
                 case 0: return SearchRoutesHeaderTableViewCell.className
                 case 1: return SearchResultInfoTableViewCell.className
                 case 2: return SearchPlaceholderTableViewCell.className
                 default: return ""
                 }
             }
         }
     }
     
     enum SearchTableSection: Int {
         case searchHeader = 0
         case results = 1
     }
     
     weak var output: SearchOffersModelOutput!
     
     private let offersProvider = DataManager<RoutesAPI, SearchRoutesResponse>()
     private let alternativeOffersProvider = DataManager<RoutesAPI, AlternativeRoutesResponse>()
     
     private let routesDetailsProvider = DataManager<RoutesAPI, [OfferDetails]>()
     private let alternativeRoutesDetailsProvider = DataManager<RoutesAPI, [OfferDetails]>()
     
     private let searchData: SearchRoutesData
     
     private var sections: [SearchSection] {
         [data?.searchSection, alternativeData?.nearbyRoutesSection, alternativeData?.standardRoutesSection].compactMap({$0})
     }
    
     private var state = StateResult.empty
     private var data: SearchRoutesResponse?
     private var alternativeData: AlternativeRoutesResponse?
     
     init(parent: EventNode?, data: SearchRoutesData) {
         searchData = data
         super.init(parent: parent)
         
         addHandler(.onPropagate) {[weak self] (event: SearchOffersDetailsEvent) in
             switch event {
             case .nothingFoundAt(let date, let offer):
                 self?.searchData.date = date
                 self?.searchData.departure = offer.departure
                 self?.searchData.destination = offer.destination
                 self?.data = nil
                 self?.state = .noResults
                 self?.output.pop()
                 self?.output.didLoad()
                 self?.load()
             }
         }
     }
     
     override func didChange(_ textField: TextField) {
         switch textField.fieldType {
         case .departureCity:
             searchData.departure = textField.addressLocation
             updateResults()
         case .destinationCity:
             searchData.destination = textField.addressLocation
             updateResults()
         case .selectDate:
             searchData.date = textField.date
             updateResults()
         default: break
         }
     }
     
     func updateResults() {
         alternativeData = nil
         guard searchData.isEmpty == false, searchData.departure != nil || searchData.destination != nil else {
             state = .empty
             data = nil
             output.didLoad()
             return
         }
         offersProvider.load(target: .offers(parameters: searchData.apiParameters), withActivity: true) {[weak self] result in
             switch result {
             case .success(let response):
                 self?.didRecive(response)
             case .failure(let error):
                 print(error.localizedDescription)
             }
         }
     }
     
     private func didRecive(_ response: SearchRoutesResponse) {
         data = response
         
         if response.isFound {
             alternativeData = nil
             state = .isFound
             if let o = response.section.offers.first {
                loadAlternatives()
                 load(route: o)
             
             }
           
         } else {
             if searchData.isEmpty {
                 state = .empty
             } else if response.section.offers.isEmpty {
                 loadAlternatives()
             } else {
                 state = .isFound
             }
         }
         output.didLoad()
     }
     
     private func loadAlternatives() {
         
         var target: RoutesAPI!
         
         if let departure = searchData.departure?.id, let destination = searchData.destination?.id {
             target = .alternative(departure: departure, destination: destination, date: searchData.date)
         } else if let departure = searchData.departure?.id {
             target = .alternativeDeparture(departure: departure, date: searchData.date)
         } else if let destination = searchData.destination?.id {
             target = .alternativeDestination(destination: destination, date: searchData.date)
         } else {
             state = .noResults
             output.didLoad()
             return
         }
         
         alternativeOffersProvider.load(target: target, withActivity: true) {[weak self] result in
             switch result {
             case .success(let response):
                 self?.didReciveAlternative(response)
             case .failure(let error):
                 print(error)
             }
             self?.output.didLoad()
         }
     }
     
     private func didReciveAlternative(_ response: AlternativeRoutesResponse) {
         alternativeData = response
         if sections.isEmpty {
            state = .noResults
         } else {
             state = .isFound
         }
     }
 }

 

    extension SearchOffersModel: SearchOffersViewControllerOutput {
         
         var isGuest: Bool {
             isAuthorized == false
         }
     
     func send(action: SearchOffersModelAction) {
         switch action {
         case .exchangeLocations:
             let departure = searchData.departure
             searchData.departure = searchData.destination
             searchData.destination = departure
             data = nil
             alternativeData = nil
             output.didLoad()
             updateResults()
         case .setAlert:
             if isAuthorized {
                 raise(event: SearchOffersEvent.setAlert(departure: searchData.departure, destination: searchData.destination))
             } else {
                 raise(event: UserSessionEvent.setSession(nil))
             }
         }
     }
     
     var searchDataDisplayable: SearchRoutesData {
        searchData
     }
     
     private var topTitleText: String {
         switch state {
         case .noResults:
             return ""
         default:
             return searchData.isEmpty ? L10n.Search.emptySearch : data?.title ?? ""
         }
     }
     
     private var emptyPlaceholderText: String {
         if searchData.departure != nil && searchData.destination == nil {
             if isAuthorized {
                 return L10n.Home.enterDestinationCity
             } else {
                 return L10n.Home.guestEnterDestinationCity
             }
         } else if searchData.destination != nil && searchData.departure == nil {
             if isAuthorized {
                 return L10n.Home.enterDepartureCity
             } else {
                 return L10n.Home.guestEnterDepartureCity
             }
         } else {
             return L10n.Home.noResults
         }
     }
     
     func load() {
         updateResults()
     }
     
     func configureCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
         switch indexPath.section {
         case SearchTableSection.searchHeader.rawValue:
             let cell = tableView.dequeueReusableCell(withIdentifier: state.cellReuseIdentifierFor(row: indexPath.row))
             switch cell {
             case cell as? SearchRoutesHeaderTableViewCell:
                 let c = cell as? SearchRoutesHeaderTableViewCell
                 apply([c?.departureTextField, c?.destinationTextField, c?.dateTextField].compactMap({$0}))
                 c?.apply(searchData:    searchData)
                 return c!
             case cell as? SearchResultInfoTableViewCell:
                 (cell as? SearchResultInfoTableViewCell)?.apply(isFound: data?.isFound ?? false, text: topTitleText)
                 return cell!
             case cell as? SearchPlaceholderTableViewCell:
                 (cell as? SearchPlaceholderTableViewCell)?.apply(text: emptyPlaceholderText)
                 return cell!
             default:
                 return cell!
             }
         default:
             let cell = tableView.dequeueReusableCell(withIdentifier: SearchItemTableViewCell.className) as? SearchItemTableViewCell
            if !sections.isEmpty {
                cell?.apply(DisplayableRouteOffer(offer: sections[indexPath.section - 1].offers[indexPath.row]))}
             return cell!
         }
     }
     
     var numberOfSections: Int {
         sections.count + 1
       
     }
     
     func itemsIn(section: Int) -> Int {
         switch section {
         case SearchTableSection.searchHeader.rawValue: return state.headerItemsCount
         default: return sections[section - 1].offers.count
         }
     }
     
     func headerIn(section: Int, for tableView: UITableView) -> UITableViewHeaderFooterView? {
         let h = tableView.dequeueReusableHeaderFooterView(withIdentifier: SearchSectionHeaderTableViewCell.className)
        if !sections.isEmpty{
         (h as? SearchSectionHeaderTableViewCell)?.apply(title: sections[section - 1].title)
        }
         return h
     }
     
     func heightForHeader(in section: Int) -> CGFloat {
         return section == 0 ? 0.0 : 20.0
     }
     
     func didSelect(indexPath: IndexPath) {
         guard indexPath.section > 0 else { return }
         let section = sections[indexPath.section - 1]
         var offer = section.offers[indexPath.row]
         offer.selectedDate = searchData.date
         
         if offer.isStandard {
             loadStandard(route: offer)
            
         } else {
             load(route: offer)
         }
     }
     
     private func loadStandard(route: RouteOffer) {
         routesDetailsProvider.load(target: .standardRouteDetails(departure: route.departure.id, destination: route.destination.id, date: searchData.date), withActivity: true) {[weak self] result in
             switch result {
             case .success(let response):
                 self?.raise(event: PopularRoutesEvent.openDetails(details: response, offer: route))
             case .failure(let error):
                 print(error)
             }
         }
     }
     
     private func load(route: RouteOffer) {
         routesDetailsProvider.load(target: .routeDetails(departure: route.departure.id, destination: route.destination.id, date: searchData.date), withActivity: true) {[weak self] result in
             switch result {
             case .success(let response):
                 self?.raise(event: PopularRoutesEvent.openDetails(details: response, offer: route))
             case .failure(let error):
                 print(error)
             }
         }
     }
 }

The default offers are as when no offer found it should load default offers

    struct SearchRoutesResponse: Codable {
    var title: String
    var isFound: Bool
    var section: SearchSection
    var searchSection: SearchSection? {
        section.offers.isEmpty ? nil : SearchSection(title: L10n.Search.defaultOffers, offers: section.offers)

    }
    
    
    
}

    struct SearchSection: Codable {
        let title: String
        let offers: [RouteOffer]
    }

// From Route Offer it should decode data from api as

    struct DisplayableRouteOffer {
    var fromName: String
    var toName: String
    var description: String
    
    init(offer: RouteOffer) {
        fromName = offer.fromName ?? ""
        toName = offer.toName ?? ""
        description = "\(offer.distance ?? 0) km, ca. \(offer.amountOfTime ?? "")"
    }
}

    struct RouteOffer: Codable, Equatable {
        
        struct Location: Codable, Equatable {
            let id: Int
        }

// Table view method is as here called in viewcontroller

    extension SearchOffersViewController: SearchOffersViewControllerInput {
    func didLoad() {
        tableView.reloadData()
    }
    
    func pop() {
        navigationController?.popToViewController(self, animated: true)
    }
}

    extension SearchOffersViewController: UITableViewDataSource {
        func numberOfSections(in tableView: UITableView) -> Int {
            model.numberOfSections
           
        }

   

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        model.itemsIn(section: section)
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        model.configureCell(for: tableView, at: indexPath)
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        model.headerIn(section: section, for: tableView)
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        model.heightForHeader(in: section)
    }
}

    extension SearchOffersViewController: UITableViewDelegate {
        
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        model.didSelect(indexPath: indexPath)
    }
}

Simple solution is force numberOfSections to 2.

func numberOfSections(in tableView: UITableView) -> Int {
    return 2
   
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0{
        return standardRoutesSection.count
    }else{
        return nearbyRoutesSection.count
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if section == 0{
        let data = standardRoutesSection[index.row]
        return UITableViewCell()
    }else{
        let data = nearbyRoutesSection[index.row]
        return UITableViewCell()
    }
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    if section == 0{
        //standar header
    }else{
        //nearby header
    }
    return nil
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM