为什么我的segue不等到完成处理程序完成之后?

时间:2018-09-23 21:06:45

标签: swift xcode segue

我有一个基于页面的应用程序,使用RootViewController,ModelViewController,DataViewController和SearchViewController。

在我的searchViewController中,我搜索一个项目,然后将该项目添加或删除到包含在Manager类(和UserDefaults)中的数组中,modelViewController使用该数组实例化DataViewController的实例,并使用以下方法加载正确的信息: dataObject。根据是添加还是删除Item,我使用Bool来确定使用哪个序列,即addCoin或removeCoin,以便RootViewController(PageView)将显示数组中的最后一页(添加页面时)或第一个(删除时)。

一切正常,直到遇到无法诊断的错误为止,问题是,当我添加页面时,应用程序崩溃了,给了我“解开可选值时意外地找到nil”

这似乎是问题函数,在searchViewController中'self.performSegue(withIdentifier:“ addCoin”')似乎被立即调用,即使没有调度也是如此:

@objc func addButtonAction(sender: UIButton!) {

    print("Button tapped")

    if Manager.shared.coins.contains(dataObject) {
        Duplicate()
    } else if Manager.shared.coins.count == 5 {
        max()
    } else {
        Manager.shared.addCoin(coin: dataObject)

        CGPrices.shared.getData(arr: true, completion: { (success) in
            print(Manager.shared.coins)

            DispatchQueue.main.async {
                self.performSegue(withIdentifier: "addCoin", sender: self)
            }
        })

    }

    searchBar.text = ""
}

这意味着在我的DataViewController中,此函数将找到nil:

func getIndex() {
    let index = CGPrices.shared.coinData.index(where: { $0.id == dataObject })!
    dataIndex = index
}

我不知道为什么它不等待完成。

我也遇到有关线程的错误:

[Assert] Cannot be called with asCopy = NO on non-main thread.

这就是为什么我尝试使用派遣que进行推送搜索的原因

这是我的searchViewController的完整代码:

import UIKit

class SearchViewController: UIViewController, UISearchBarDelegate {

    let selectionLabel = UILabel()
    let searchBar = UISearchBar()
    let addButton = UIButton()
    let removeButton = UIButton()

    var filteredObject: [String] = []
    var dataObject = ""

    var isSearching = false

    //Add Button Action.
    @objc func addButtonAction(sender: UIButton!) {

        print("Button tapped")

        if Manager.shared.coins.contains(dataObject) {
            Duplicate()
        } else if Manager.shared.coins.count == 5 {
            max()
        } else {
            Manager.shared.addCoin(coin: dataObject)

            CGPrices.shared.getData(arr: true, completion: { (success) in
                print(Manager.shared.coins)

                DispatchQueue.main.async {
                    self.performSegue(withIdentifier: "addCoin", sender: self)
                }
            })

        }

        searchBar.text = ""
    }

    //Remove button action.
    @objc func removeButtonActon(sender: UIButton!) {

        print("Button tapped")

        if Manager.shared.coins.contains(dataObject) {
            Duplicate()
        } else if Manager.shared.coins.count == 5 {
            max()
        } else {
            Manager.shared.removeCoin(coin: dataObject)

            self.performSegue(withIdentifier: "addCoin", sender: self)
        }

        searchBar.text = ""
    }

    //Prepare for segue, pass removeCoinSegue Bool depending on remove or addCoin.
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if segue.identifier == "addCoin" {

            if let destinationVC = segue.destination as? RootViewController {
                destinationVC.addCoinSegue = true
            }

        } else if segue.identifier == "addCoin" {

            if let destinationVC = segue.destination as? RootViewController {
                destinationVC.addCoinSegue = false
            }
        }
    }

    //Remove button action.
    @objc func removeButtonAction(sender: UIButton!) {

        if Manager.shared.coins.count == 1 {
            removeAlert()
        } else {
            Manager.shared.removeCoin(coin: dataObject)

            print(Manager.shared.coins)
            print(dataObject)

            searchBar.text = ""
            self.removeButton.isHidden = true

            DispatchQueue.main.async {
                self.performSegue(withIdentifier: "removeCoin", sender: self)
            }
        }
    }

    //Search/Filter the struct from CGNames, display both the Symbol and the Name but use the ID as dataObject.
    func filterStructForSearchText(searchText: String, scope: String = "All") {

        if !searchText.isEmpty {
            isSearching = true

            filteredObject = CGNames.shared.coinNameData.filter {

                // if you need to search key and value and include partial matches
                // $0.key.contains(searchText) || $0.value.contains(searchText)

                // if you need to search caseInsensitively key and value and include partial matches
                $0.name.range(of: searchText, options: .caseInsensitive) != nil || $0.symbol.range(of: searchText, options: .caseInsensitive) != nil
                }
                .map{ $0.id }

        } else {
            isSearching = false
            print("NoText")
        }
    }

    //Running filter function when text changes.
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

        filterStructForSearchText(searchText: searchText)

        if isSearching == true && filteredObject.count > 0 {

            addButton.isHidden = false
            dataObject = filteredObject[0]
            selectionLabel.text = dataObject

            if Manager.shared.coins.contains(dataObject) {
                removeButton.isHidden = false
                addButton.isHidden = true
            } else {
                removeButton.isHidden = true
                addButton.isHidden = false
            }

        } else {
            addButton.isHidden = true
            removeButton.isHidden = true
            selectionLabel.text = "e.g. btc/bitcoin"
        }

    }

    override func viewDidLoad() {
        super.viewDidLoad()

        //Setup the UI.
        self.view.backgroundColor = .gray
        setupView()
    }

    override func viewDidLayoutSubviews() {

    }

    //Hide keyboard
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.view.endEditing(true)
    }

    //Alerts
    func removeAlert() {
        let alertController = UIAlertController(title: "Can't Remove", message: "\(dataObject) can't be deleted, add another to delete \(dataObject)", preferredStyle: .alert)

        alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))

        self.present(alertController, animated: true, completion: nil)
    }

    func Duplicate() {
        let alertController = UIAlertController(title: "Duplicate", message: "\(dataObject) is already in your pages!", preferredStyle: .alert)

        alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))

        self.present(alertController, animated: true, completion: nil)
    }

    func max() {
        let alertController = UIAlertController(title: "Maximum Reached", message: "\(dataObject) can't be added, you have reached the maximum of 5 coins. Please delete a coin to add another.", preferredStyle: .alert)

        alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))

        self.present(alertController, animated: true, completion: nil)
    }
}

这是DataViewController

import UIKit

class DataViewController: UIViewController {

    @IBOutlet weak var dataLabel: UILabel!

    //Variables and Objects.

    //The dataObject carries the chosen cryptocurrencies ID from the CoinGecko API to use to get the correct data to load on each object.
    var dataObject = String()

    //The DefaultCurrency (gbp, eur...) chosen by the user.
    var defaultCurrency = ""

    //The Currency Unit taken from the exchange section of the API.
    var currencyUnit = CGExchange.shared.exchangeData[0].rates.gbp.unit
    var secondaryUnit = CGExchange.shared.exchangeData[0].rates.eur.unit
    var tertiaryUnit = CGExchange.shared.exchangeData[0].rates.usd.unit

    //Index of the dataObject
    var dataIndex = Int()

    //Objects
    let cryptoLabel = UILabel()
    let cryptoIconImage = UIImageView()
    let secondaryPriceLabel = UILabel()
    let mainPriceLabel = UILabel()
    let tertiaryPriceLabel = UILabel()

    //Custom Fonts.
    let customFont = UIFont(name: "AvenirNext-Heavy", size: UIFont.labelFontSize)
    let secondFont = UIFont(name: "AvenirNext-BoldItalic" , size: UIFont.labelFontSize)

    //Setup Functions

    //Get the index of the dataObject
    func getIndex() {
        let index = CGPrices.shared.coinData.index(where: { $0.id == dataObject })!
        dataIndex = index
    }

    //Label
    func setupLabels() {

        //cryptoLabel from dataObject as name.
        cryptoLabel.text = CGPrices.shared.coinData[dataIndex].name

        //Prices from btc Exchange rate.

        let btcPrice = CGPrices.shared.coinData[dataIndex].current_price!
        let dcExchangeRate = CGExchange.shared.exchangeData[0].rates.gbp.value
        let secondaryExchangeRate = CGExchange.shared.exchangeData[0].rates.eur.value
        let tertiaryExchangeRate = CGExchange.shared.exchangeData[0].rates.usd.value

        let realPrice = (btcPrice * dcExchangeRate)
        let secondaryPrice = (btcPrice * secondaryExchangeRate)
        let tertiaryPrice = (btcPrice * tertiaryExchangeRate)

        secondaryPriceLabel.text = "\(secondaryUnit)\(String((round(1000 * secondaryPrice) / 1000)))"
        mainPriceLabel.text = "\(currencyUnit)\(String((round(1000 * realPrice)  /1000)))"
        tertiaryPriceLabel.text = "\(tertiaryUnit)\(String((round(1000 * tertiaryPrice) / 1000)))"
    }

    //Image
    func getIcon() {

        let chosenImage = CGPrices.shared.coinData[dataIndex].image
        let remoteImageUrl = URL(string: chosenImage)

        guard let url = remoteImageUrl else { return }

        URLSession.shared.dataTask(with: url) { (data, response, err) in

            guard let data = data else { return }

            do {
                DispatchQueue.main.async {
                    self.cryptoIconImage.image = UIImage(data: data)
                }

            }
            }.resume()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        //        for family in UIFont.familyNames.sorted() {
        //            let names = UIFont.fontNames(forFamilyName: family)
        //            print("Family: \(family) Font names: \(names)")
        //        }
        // Do any additional setup after loading the view, typically from a nib.

        self.setupLayout()
        self.getIndex()
        self.setupLabels()
        self.getIcon()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.dataLabel!.text = dataObject
        view.backgroundColor = .lightGray
    }

}

编辑:带有getData方法的CGPrices类:

import Foundation

class CGPrices {

    struct Coins: Decodable {
        let id: String
        let name: String
        let symbol: String
        let image: String
        let current_price: Double?
        let low_24h: Double?
        //let price_change_24h: Double?
    }

    var coinData = [Coins]()

    var defaultCurrency = ""
    var coins = Manager.shared.coins
    var coinsEncoded = ""

    static let shared = CGPrices()

    func encode() {
        for i in 0..<coins.count {
            coinsEncoded += coins[i]
            if (i + 1) < coins.count { coinsEncoded += "%2C" }
        }
        print("encoded")
    }

    func getData(arr: Bool, completion: @escaping (Bool) -> ()) {

        encode()

        let urlJSON = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=\(coinsEncoded)"

        guard let url = URL(string: urlJSON) else { return }

        URLSession.shared.dataTask(with: url) { (data, response, err) in

            guard let data = data else { return }

            do {
                let coinsData = try JSONDecoder().decode([Coins].self, from: data)
                self.coinData = coinsData
                completion(arr)

            } catch let jsonErr {
                print("error serializing json: \(jsonErr)")
                print(data)
            }

            }.resume()

    }

    func refresh(completion: () -> ()) {
        defaultCurrency = UserDefaults.standard.string(forKey: "DefaultCurrency")!
        completion()
    }

}

1 个答案:

答案 0 :(得分:0)

我知道了。

问题出在我的getData方法内部,我没有更新硬币数组:

 var coinData = [Coins]()

var defaultCurrency = ""

var coins = Manager.shared.coins
var coinsEncoded = ""

static let shared = CGPrices()

func encode() {
    for i in 0..<coins.count {
        coinsEncoded += coins[i]
        if (i+1)<coins.count { coinsEncoded+="%2C" }
    }
    print("encoded")
}

我需要在getData中添加这一行:

func getData(arr: Bool, completion: @escaping (Bool) -> ()) {

//Adding this line to update the array so that the URL is appended correctly.
    coins = Manager.shared.coins

    encode()

let urlJSON = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=\(coinsEncoded)"

这将修复DataViewController中的零发现,但是应用程序仍会崩溃,无法在后台线程上更新UI元素,因为在getData方法的完成处理程序中调用了segue。为了解决这个问题,我在addButton函数的getData方法内的segue上使用了DispatchQue.Main.Async,以确保所有内容都在主线程上更新,例如:

 @objc func addButtonAction(sender: UIButton!) {
    print("Button tapped")
    if Manager.shared.coins.contains(dataObject) {
        Duplicate()
    } else if Manager.shared.coins.count == 5 {
        max()
    } else {

        Manager.shared.addCoin(coin: dataObject)

            print("starting")

        CGPrices.shared.getData(arr: true) { (arr) in
            print("complete")
            print(CGPrices.shared.coinData)
//Here making sure it is updated on main thread.
            DispatchQueue.main.async {
                 self.performSegue(withIdentifier: "addCoin", sender: self)
            }

        }

    }
    searchBar.text = ""
}

感谢所有评论,因为它们帮助我弄清楚了这一点,我从中学到了很多。希望这可以在调试时帮助其他人,因为他们会陷入一个问题的某个领域,而忘了退后一步去看看其他领域。