我正在尝试构建一个小型货币转换器,问题是我的completionHandler不起作用。结果,输入货币不会在函数执行后立即更改
我已经尝试实现一个completionHandler;但是,还没有成功
class CurrencyExchange: ViewController {
//Outlets
@IBOutlet weak var lblCurrency: UILabel!
@IBOutlet weak var segOutputCurrency: UISegmentedControl!
@IBOutlet weak var txtValue: UITextField!
@IBOutlet weak var segInputCurrency: UISegmentedControl!
//Variables
var inputCurrency: String!
var currencyCNY: Double!
var currencyEUR: Double!
var currencyGBP: Double!
var currencyJPY: Double!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
}
@IBAction func btnConvert(_ sender: Any) {
assignOutput()
if txtValue.text == "" {
self.lblCurrency.text = "Please insert value"
} else {
let inputValue = Double(txtValue.text!)!
if segOutputCurrency.selectedSegmentIndex == 0 {
let output = Double(inputValue * currencyCNY!)
self.lblCurrency.text = "\(output)¥"
} else if segOutputCurrency.selectedSegmentIndex == 1 {
let output = Double(inputValue * currencyEUR!)
self.lblCurrency.text = "\(output)€"
} else if segOutputCurrency.selectedSegmentIndex == 2 {
let output = Double(inputValue * currencyGBP!)
self.lblCurrency.text = "\(output)"
} else if segOutputCurrency.selectedSegmentIndex == 3 {
let output = Double(inputValue * currencyJPY!)
self.lblCurrency.text = "\(output)"
}
}
}
func assignOutput() {
let currencies = ["EUR", "JPY", "CNY", "USD"]
inputCurrency = currencies[segInputCurrency.selectedSegmentIndex]
Alamofire.request("https://api.exchangeratesapi.io/latest?base=\(inputCurrency!)").responseJSON { (response) in
let result = response.result
let jsonCurrencies = JSON(result.value!)
let dictContent = jsonCurrencies["rates"]
self.currencyCNY = dictContent["CNY"].double
self.currencyEUR = dictContent["EUR"].double
self.currencyGBP = dictContent["GBP"].double
self.currencyJPY = dictContent["JPY"].double
}
}
}
预期结果是,每次调用btnConvert函数时,都会调用assignInput和assignOutput函数,并将变量设置为正确的值。我是一个初学者,因此我们将不胜感激。
答案 0 :(得分:3)
assignOutput()
中需要一个完成处理程序,我还添加了最小错误处理以避免崩溃
//Variables
var inputCurrency = ""
var currencyCNY = 0.0
var currencyEUR = 0.0
var currencyGBP = 0.0
var currencyJPY = 0.0
@IBAction func btnConvert(_ sender: Any) {
assignOutput() { success in
if success {
if txtValue.text!.isEmpty {
self.lblCurrency.text = "Please insert value"
} else {
if let inputValue = Double(txtValue.text!) {
if segOutputCurrency.selectedSegmentIndex == 0 {
let output = Double(inputValue * currencyCNY)
self.lblCurrency.text = "\(output)¥"
} else if segOutputCurrency.selectedSegmentIndex == 1 {
let output = Double(inputValue * currencyEUR)
self.lblCurrency.text = "\(output)€"
} else if segOutputCurrency.selectedSegmentIndex == 2 {
let output = Double(inputValue * currencyGBP)
self.lblCurrency.text = "\(output)"
} else if segOutputCurrency.selectedSegmentIndex == 3 {
let output = Double(inputValue * currencyJPY)
self.lblCurrency.text = "\(output)"
}
} else {
self.lblCurrency.text = "Please enter a number"
}
}
} else {
self.lblCurrency.text = "Could not receive the exchange rates"
}
}
}
func assignOutput(completion: @escaping (Bool) -> Void) {
let currencies = ["EUR", "JPY", "CNY", "USD"]
inputCurrency = currencies[segInputCurrency.selectedSegmentIndex]
Alamofire.request("https://api.exchangeratesapi.io/latest?base=\(inputCurrency)").responseJSON { (response) in
if let result = response.result.value {
let jsonCurrencies = JSON(result)
let dictContent = jsonCurrencies["rates"]
self.currencyCNY = dictContent["CNY"].double
self.currencyEUR = dictContent["EUR"].double
self.currencyGBP = dictContent["GBP"].double
self.currencyJPY = dictContent["JPY"].double
completion(true)
} else {
completion(false)
}
}
}
答案 1 :(得分:1)
完成处理程序的基本思想是您拥有一些异步方法(即稍后完成的方法),并且您需要让调用者有机会提供它希望异步方法在完成后要执行的操作。因此,假设assignOutput
是异步方法,那么您将使用完成处理程序转义闭包来重构该方法。
我个人将配置此转义的闭包以返回Result
类型:
例如:
func assignOutput(completion: @escaping (Result<[String: Double]>) -> Void) {
let inputCurrency = ...
Alamofire.request("https://api.exchangeratesapi.io/latest?base=\(inputCurrency)").responseJSON { response in
switch response.result {
case .failure(let error):
completion(.failure(error))
case .success(let value):
let jsonCurrencies = JSON(value)
guard let dictionary = jsonCurrencies["rates"].dictionaryObject as? [String: Double] else {
completion(.failure(CurrencyExchangeError.currencyNotFound)) // this is just a custom `Error` type that I’ve defined
return
}
completion(.success(dictionary))
}
}
}
然后您可以像这样使用它:
assignOutput { result in
switch result {
case .failure(let error):
print(error)
case .success(let dictionary):
print(dictionary)
}
}
使用Result
类型,您将获得一个很好的一致模式,您可以在整个代码中检查.failure
或.success
。
话虽如此,我建议采取其他各种改进措施:
我不会从另一个视图控制器ViewController
将此视图控制器作为子类。它应该是UIViewController
的子类。
(从技术上讲,您可以将您自己的自定义视图控制器子类重新划分为子类,但这种情况很少见。坦白地说,当视图控制器子类中的子类太多时,您需要具有子类的子类,这可能是代码异味表示您的视图控制器中有太多东西。)
我将给该视图控制器一个类名,该类名明确指示对象的类型,例如CurrencyExchangeViewController
,而不仅仅是CurrencyExchange
。当您开始将这些大视图控制器分解为更易于管理的方式时,这种习惯将在将来带来回报。
您在四个不同的位置都有接受的货币列表:
segOutputCurrency
故事板中segInputCurrency
故事板中btnConvert
例程中assignOutput
例程中这会使您的代码易碎,如果更改货币顺序,添加/删除货币等,很容易出错。最好将一个货币列表放在一个位置,以编程方式更新{ {} {1}}中的1}}个出口,然后让您的例程全部返回到允许使用单个货币的数组。
您应该避免使用UISegmentedControl
强制展开运算符。例如,如果网络请求失败,然后您引用viewDidLoad
,则您的应用将崩溃。您想优雅地处理无法控制的错误。
如果要设置货币格式,请记住,除了货币符号外,还应考虑并非所有语言环境都将!
用作小数位(例如,欧洲用户可能会使用{{1} }。因此,我们通常会使用result.value!
将计算出的数字转换回字符串。
下面,我只是将.
用于输出,但是在解释用户输入时,您也确实应该使用它。但我会将其留给读者。
在处理货币时,除了货币符号之外,还有一个更微妙的地方,即结果应显示多少个小数位。 (例如,使用日元交易时,通常没有小数位,而欧元和美元则有两个小数位。)
如果需要,您可以编写自己的转换例程,但是我可以将所选的货币代码与,
标识符相关联,这样您就可以利用该符号和适用于每种货币的小数位数。然后,我将使用NumberFormatter
s格式化数字的字符串表示形式。
插座名称的约定通常是一些功能名称,后跟控件的类型。例如。您可能有NumberFormatter
或Locale
和NumberFormatter
或inputTextField
。同样,我可以将currencyTextField
重命名为outputLabel
我个人将使用SwiftyJSON,尽管使用了它的名字,但对我来说却不算太贵。我会使用convertedLabel
。
将所有这些放在一起,您可能会得到类似以下的内容:
@IBAction
如上面的注释所示,我可能会将扩展中的某些内容移到了不同的对象中,但是我怀疑即使上述更改一次也要吸收很多,所以我已经停止了在那里重构。