为什么searchBar“textDidChange”获取服务器请求并获得错误和崩溃

时间:2017-11-17 07:37:16

标签: ios swift uitableview search

对不起,我是swift的初学者 我有一个searchBar和tableView显示数据 我还在func“textDidChange”中将Api称为服务器 但是当我很快输入或删除文本时,我的应用程序崩溃了,我收到了如下行的错误信息 我该如何防止这种情况发生? 感谢。

  

致命错误:索引超出范围

override func viewDidLoad() {

  self.subscribe = contacts.notifySubject.subscribe({ json in
        self.tableView.reloadData()
    })
}

func doSearch() {
    if let word = searchBar.text {
        if word.isEmpty == false {
            contacts.searchFriend(word)
        }
    } else {
        tableView.reloadData()
    }
}

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

    if searchText.isEmpty == true {

        contacts.friends.removeAll(keepingCapacity: false)
        tableView.reloadData()
        return
    }else{

        doSearch()
    }
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    if contacts.friends.isEmpty == false {
        return contacts.friends.count
    }
    return 0
}

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


    if contacts.friends.isEmpty == false {

        guard contacts.friends.count != 0 else { return UITableViewCell() } // don't prevent crash

        let cell: SearchTableViewCell = SearchTableViewCell(style: .default, reuseIdentifier: "SearchTableViewCell")
        let user:User = contacts.friends[indexPath.row]
        cell.labelName.text = user.name
        return cell
    }

    let cell = UITableViewCell()

    return cell
}


class Contacts:Model {

   var friends:[User] = [User]()

   func searchFriend(_ word:String) {
    if word.isEmpty {
        return
    }
    self.friends.removeAll(keepingCapacity: false)
    var params:[String:Any] = [String:Any]()
    params["value"] = keyword
    api.searchUser.get( params, { json in

        json.forEach({ (index,data) in

            let user = User(data)
            if user.isExist {
                self.friends.append(user)
            }
        })
        self.notifySubject.onNext(json)
    })
}

} 

1 个答案:

答案 0 :(得分:0)

如果您在服务器上搜索文本更改意味着您正在连续调用API。在第一个请求完成之前,您正在进行下一个请所以当你得到第一个请求的响应并同时重新加载tableview时,另一个请求再次重新加载表视图调用。这里你在表视图重新加载过程中从数组中删除对象,因此你的应用程序崩溃了。 您应该在制作新的api之前取消之前的api通话。 使用NSOperation& NSOperationQueue实现此功能。

修改

这是我用于从服务器搜索Bus Stop的示例 使用AFNetworking进行API调用

 func getStops(sessionManager:AFHTTPSessionManager, parameters:[AnyHashable:Any],completionHandler:@escaping (_ status:Bool, _ responseObject:Any)->()){

        if !ReachabilityManager.shared.isReachable {
            let error = WSError()
            error.errorTitle = "Network error"
            error.errorDescription = "Unable to connect, please check your internet connectivity."
            completionHandler(false,error)
            return
        }

        self.showNetworkActivity()

        if let deviceId = SSKeychain.uniqueDeviceId(){
            sessionManager.requestSerializer.setValue(deviceId, forHTTPHeaderField: X_DEVICE_ID)
        }

        if let accessToken = DBManager.logged?.accessToken{
            sessionManager.requestSerializer.setValue(accessToken, forHTTPHeaderField: X_ACCESS_TOKEN)
        }

        if let simulatedContextId = self.simulatedContextId {
            sessionManager.requestSerializer.setValue(simulatedContextId, forHTTPHeaderField: X_SIMULATE_CONTEXT)
        }
        if let languageCode = NSLocale.current.languageCode{
            sessionManager.requestSerializer.setValue(languageCode, forHTTPHeaderField: X_LOCALE_ID)
        }
        let urlPath =  WSApi.apiVersion + "stops"

        sessionManager.get(urlPath, parameters: parameters, progress: { progress in

        }, success: {[unowned self] task, responseObject in
            self.hideNetworkActivity()
            self.getCurretnContext(task: task)

            var stops = [Stop]()

            if let array = responseObject as? [[AnyHashable:Any]]{
                stops.append(contentsOf: Stop.GetSports(array: array))
            }

            completionHandler(true,stops)

            }, failure: {[unowned self] operation, err in
                self.hideNetworkActivity()
                self.getCurretnContext(task: operation)
                let error = WSError(error: err as NSError)
                error.getStatusCode(operation: operation)
                if error.isUnauthorised{
                    AppDelegate.shared.handleUnAuthorised(error: error)
                    return
                }
                completionHandler(false,error)

        })
    }

ViewController实现

 protocol StopSearchViewControllerDelegate : NSObjectProtocol {

    /**
     * Called when a place has been selected from the available autocomplete predictions.
     * @param viewController The |StopSearchViewController| that generated the event.
     * @param stop The |Stop| that was returned.
     */
    func viewController(_ viewController: StopSearchViewController, didAutocompleteWith stop: Stop)

    /**
     * Called when a non-retryable error occurred when retrieving autocomplete predictions or place
     * details. A non-retryable error is defined as one that is unlikely to be fixed by immediately
     * retrying the operation.
     * All other error codes are non-retryable.
     * @param viewController The |StopSearchViewController| that generated the event.
     * @param error The |WSError| that was returned.
     */
    func viewController(_ viewController: StopSearchViewController, didFailAutocompleteWithWSError error: WSError)


    /**
     * Called when the user taps the Cancel button in a |StopSearchViewController|.
     * @param viewController The |StopSearchViewController| that generated the event.
     */
    func autocompleteWasCancelled(_ viewController: StopSearchViewController)
}

class StopSearchViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var searchBar: UISearchBar!

    weak var delegate:StopSearchViewControllerDelegate?

    fileprivate var stops = [Stop]()

    fileprivate var isNavigationBarHidden = false

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.tableView.tableHeaderView = UIView()
        self.searchBar.becomeFirstResponder()

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.isNavigationBarHidden = self.navigationController?.isNavigationBarHidden ?? false
        self.navigationController?.isNavigationBarHidden = true
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.navigationController?.isNavigationBarHidden = isNavigationBarHidden
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    fileprivate func loadStops(parameters:[AnyHashable:Any]){
        WSApi.sharedManager.operationQueue.cancelAllOperations()
        WSApi.shared.getStops(sessionManager: WSApi.sharedManager, parameters: parameters) {[weak self] (status, responseObject) in
            if let strongSelf = self {
                strongSelf.stops.removeAll()
                if let stops = responseObject as? [Stop]{
                    if !strongSelf.searchBar.text!.isEmpty{
                        strongSelf.stops.append(contentsOf: stops)
                    }
                }
                strongSelf.tableView.reloadData()
            }
        }
    }

    fileprivate func closeViewController(){
        if let navigationController = self.navigationController{
            navigationController.popViewController(animated: true)
        }else{
            self.dismiss(animated: true, completion: nil)
        }
    }

    override var preferredStatusBarStyle: UIStatusBarStyle{
        return .lightContent
    }

    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{
        return .none
    }

    override var prefersStatusBarHidden: Bool{
        return false
    }

}

extension StopSearchViewController :UITableViewDataSource{

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

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return stops.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: StopSearchCell.reuseIdentifier, for: indexPath) as! StopSearchCell
        let stop = stops[indexPath.row]
        cell.configureCell(stop: stop)
        return cell
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 0
    }

    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 20
    }

    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let attrs = [NSFontAttributeName : UIFont(name: "Helvetica", size: 14)!, NSForegroundColorAttributeName:UIColor.lightGray]
        let attributedString = NSMutableAttributedString(string:"powered by ", attributes:attrs)
        let bAttrs = [NSFontAttributeName : UIFont(name: "Helvetica", size: 15)!, NSForegroundColorAttributeName:UIColor.darkGray]
        let boldString = NSMutableAttributedString(string:"Change Transit", attributes:bAttrs)
        attributedString.append(boldString)
        let frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44)
        let label = UILabel(frame: frame)
        label.textAlignment = .center
        label.attributedText = attributedString
        return label
    }


}

extension StopSearchViewController :UITableViewDelegate{

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let stop = stops[indexPath.row]
        self.delegate?.viewController(self, didAutocompleteWith: stop)
       // self.closeViewController()
    }

}

extension StopSearchViewController :UISearchBarDelegate{


    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//        guard !searchText.isEmpty else {
//            WSApi.sharedManager.operationQueue.cancelAllOperations()
//            self.stops.removeAll()
//            self.tableView.reloadData()
//            return
//        }
        let parameters = ["data_set_id":WSApi.shared.currentContextId ?? "","search_text": searchText]
        self.loadStops(parameters: parameters)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.delegate?.autocompleteWasCancelled(self)
       // self.closeViewController()
    }

}