#20 換算 App 加油費&回饋金計算機

Lou
彼得潘的 Swift iOS / Flutter App 開發教室
21 min readMay 9, 2023

速邁樂加油回饋金計算機

抓取中油油價公告牌價 使用XML解析

中油公告牌價

https://vipmbr.cpc.com.tw/CPCSTN/ListPriceWebService.asmx/getCPCMainProdListPrice_XML

建立CPCapiParserDelegate檔案 篩選所需要的資料

import Foundation

class CPCapiParserDelegate: NSObject, XMLParserDelegate{
var currentItem: GasInfo?
var currentElementValue: String?
var resultArray = [GasInfo]()

// 碰到標籤tag<>就會觸發 開始解析
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
if elementName == "Table"{
currentItem = GasInfo()
}else if elementName == "產品名稱"{
currentElementValue = nil
}else if elementName == "參考牌價"{
currentElementValue = nil
}else if elementName == "牌價生效時間"{
currentElementValue = nil
}
}

// 再次 碰到相同標籤tag<>就會觸發 結束這段解析
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "Table"{
if currentItem != nil{
resultArray.append(currentItem!)
currentItem = nil
}
}else if elementName == "產品名稱"{
currentItem?.name = currentElementValue
}else if elementName == "參考牌價"{
currentItem?.price = currentElementValue
}else if elementName == "牌價生效時間"{
currentItem?.effectiveDate = currentElementValue
}
currentElementValue = nil
}

// 碰到標籤的內文字母時 就會開時讀取

func parser(_ parser: XMLParser, foundCharacters string: String) {
if currentElementValue == nil{
currentElementValue = string
}else{
currentElementValue = currentElementValue! + string
}
}

func getResult() -> [GasInfo]{
return resultArray
}
}

ViewController建立下載XML資料function

struct GasInfo {
var name: String?
var price: String?
var effectiveDate: String?
}

class ViewController: UIViewController {
var downloadGasInfo = [GasInfo]()
var gasInfo = [GasInfo]()
let xmlAddress = "https://vipmbr.cpc.com.tw/CPCSTN/ListPriceWebService.asmx/getCPCMainProdListPrice_XML"

func downloadXML(withXMLAddress xmlAddress: String) {
if let url = URL(string: xmlAddress){
URLSession.shared.dataTask(with: url) { [self] (data, Response, error) in
if error != nil {
let errorCode = (error! as NSError).code
if errorCode == -1009 {
DispatchQueue.main.async {
self.showAlert(title: "目前沒有網路")
}
} else {
DispatchQueue.main.async {
self.showAlert(title: "發生錯誤")
}
}
return
}
if let loadData = data {
let parser = XMLParser(data: loadData)
let cpcapiParserDelegate = CPCapiParserDelegate()
parser.delegate = cpcapiParserDelegate
if parser.parse() == true{
self.downloadGasInfo = cpcapiParserDelegate.getResult()
DispatchQueue.main.async {
//下載資料成功 更新UI
self.updateUI()
}
} else {
DispatchQueue.main.async {
self.showAlert(title: "XML解析失敗")
}
}
}
}.resume()
}

}


func showAlert(title: String) {
let alert = UIAlertController(title: title, message: "請稍後再試!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
}

設定UI

    override func viewDidLoad() {
super.viewDidLoad()
downloadXML(withXMLAddress: xmlAddress)
getweekday()
}

func updateUI() {
gasInfo = downloadGasInfo.filter{$0.name!.contains("92")} + downloadGasInfo.filter{$0.name!.contains("95")} + downloadGasInfo.filter{$0.name!.contains("98")} + downloadGasInfo.filter{$0.name!.contains("超級柴油")}

for index in 0..<gasInfo.count {
gasItemSegmentedControl.setTitle(gasInfo[index].name, forSegmentAt: index)
}
gasPriceLabel.text = gasInfo[1].price
gasName = gasInfo[1].name!
gasPrice = Double(gasInfo[1].price!) ?? 0
calculatefillupButton.setTitle(gasName + " 加滿", for: .normal)
calculateButton.setTitle("自訂加油金額", for: .normal)
fuelMeterMaxTextField.text = String(fuelMeterMaxTextValue)
fuelMeterMinTextField.text = String(fuelMeterMinTextValue)
}

DatePicker顯示星期幾

DatePicker選擇日期 顯示星期
   @IBAction func datePicker(_ sender: Any) {
getweekday()
}

func getweekday() {
let dateComponemts = Calendar.current.dateComponents(in: TimeZone.current, from: datePicker.date)
weekday = dateComponemts.weekday!
var week = ""
switch weekday {
case 1:
week = "星期日"
case 2:
week = "星期一"
case 3:
week = "星期二"
case 4:
week = "星期三"
case 5:
week = "星期四"
case 6:
week = "星期五"
case 7:
week = "星期六"
default:
break
}
dayLabel.text = "加油日期(\(week)) :"
}

設定油箱、油表資料 顯示計算結果

計算加油金額、公升數

油品SegmentedControl、油箱TextField、油表TextField、油表Stepper 連結同一個IBAction
資料變動顯示計算結果到加油金額TextField 、加油公升數Label

連結同一個IBAction
TextField 用Editing Changed連結IBAction 內容變更才會更新計算結果
    @IBAction func fuelMeterCalculateFuelLiter(_ sender: Any) {
//SegmentedControl選擇油品 價格更新
switch gasItemSegmentedControl.selectedSegmentIndex {
case 0:
gasPriceLabel.text = gasInfo[0].price
gasName = gasInfo[0].name!
gasPrice = Double(gasInfo[0].price!) ?? 0
calculatefillupButton.setTitle(gasName + " 加滿", for: .normal)
case 1:
gasPriceLabel.text = gasInfo[1].price
gasName = gasInfo[1].name!
gasPrice = Double(gasInfo[1].price!) ?? 0
calculatefillupButton.setTitle(gasName + " 加滿", for: .normal)
case 2:
gasPriceLabel.text = gasInfo[2].price
gasName = gasInfo[2].name!
gasPrice = Double(gasInfo[2].price!) ?? 0
calculatefillupButton.setTitle(gasName + " 加滿", for: .normal)
case 3:
gasPriceLabel.text = gasInfo[3].price
gasName = gasInfo[3].name!
gasPrice = Double(gasInfo[3].price!) ?? 0
calculatefillupButton.setTitle(gasName + " 加滿", for: .normal)
default:
break
}

//設定油表變數&顯示值
fuelMeterMaxTextValue = Int(fuelMeterMaxStepper.value)
fuelMeterMaxTextField.text = String(fuelMeterMaxTextValue)
fuelMeterMinTextValue = Int(fuelMeterMinStepper.value)
fuelMeterMinTextField.text = String(fuelMeterMinTextValue)

//設定油箱&油表 計算顯示加油金額&加油公升數
if let carFuelTank = Double(carFuelTankTextField.text!),
let fuelMeterMax = Double(fuelMeterMaxTextField.text!),
let fuelMeterMin = Double(fuelMeterMinTextField.text!),
carFuelTank > 0, fuelMeterMax > 0, fuelMeterMin > 0 {
fuelLiter = carFuelTank / fuelMeterMax * (fuelMeterMax - fuelMeterMin)
fuelCost = fuelLiter * gasPrice
fuelLiterLabel.text = String(format: "%.2f", fuelLiter)
costTextField.text = String(format: "%.f", fuelCost)
}
}

設定加油金額 顯示計算結果

計算加油公升數
    @IBAction func fuelCostCalculateFuelLiter(_ sender: Any) {

if let fuelCost = Double(costTextField.text!),
let gasPrice = Double(gasPriceLabel.text!),
fuelCost > 0 {
fuelLiter = fuelCost / gasPrice
fuelLiterLabel.text = String(format: "%.2f", fuelLiter)

}
}

建立計算加油回饋金function

各種信用卡、加油日條件
func calculateRebate (cost: Int) -> Double {

//設定加油回饋條件星期、卡別
switch weekday {
case 1:
openpointRebate = 0.07
if megaIcashPaySwitch.isOn, cost >= 888 {
openpointRebate = openpointRebate + 0.05
payMessage = "支付方式:兆豐 統一7-11獅聯名卡\n綁定icash Pay支付\n"
} else {
payMessage = "支付方式: icash Pay支付 \n"
}
case 3:
openpointRebate = 0.07

if fubonIcashPaySwitch.isOn, cost >= 888 {
openpointRebate += 0.05
cashRebate = 0.055
payMessage = "支付方式: 富邦 Open Possible聯名卡\n綁定icash Pay支付\n"
} else if fubonIcashPaySwitch.isOn {
cashRebate = 0.055
payMessage = "支付方式: 富邦 Open Possible聯名卡\n綁定icash Pay支付\n"
} else {
payMessage = "支付方式: icash Pay支付\n"
}
case 7:
if fuelLiter >= 25 {
openpointRebate = 0.066
if cathayCubeSwitch.isOn {
openpointRebate += 0.07
treepointRebate = 0.03
payMessage = "支付方式: 國泰世華 CUBE卡\n綁定OPNE錢包+集精選"
} else if cathayFormosaSwitch.isOn {
openpointRebate += 0.033
payMessage = "支付方式: 國泰世華 台塑聯名卡\n"
}
} else {
openpointRebate = 0.033
}
default:
break
}

openpointback = Double(cost) * openpointRebate
cashback = Int(Double(cost) * cashRebate)
treepointback = Int(Double(cost) * treepointRebate)

let totalDiscount = openpointback + Double(treepointback) + Double(cashback)

return totalDiscount
}

加滿按鈕 計算回饋金功能

加好加滿 回饋金拿滿
@IBAction func calculateFillupFuelCostBtn(_ sender: Any) {
if let fuelTank = Double(carFuelTankTextField.text!),
let cost = Double(costTextField.text!),
fuelTank > 0, cost > 0 {
let totalDiscount = calculateRebate(cost: Int(fuelCost))
let fuelLiterMessage = "本次加油量: " + String(format: "%.2f", fuelLiter)+"公升\n"
let costMessage = "花費金額: " + String(format: "%.f", fuelCost)+"元\n"
let discountMessage = "總回饋金額(" + String(format: "%.2f",(( openpointRebate+cashRebate+treepointRebate)*100)) + "%): " + String(format: "%.2f", totalDiscount) + "元\n"
let openpointMessage = "OP點數回饋(" + String(format: "%.1f",openpointRebate*100) + "%): " + String(format: "%.2f", openpointback) + "點\n"
let cachbackMessage = "信用卡回饋金(\(cashRebate*100)%): \(cashback)元\n"
let treepointMessage = "小樹點回饋(\(treepointRebate*100)%): \(treepointback)點\n"
let message = fuelLiterMessage + costMessage + discountMessage + openpointMessage + cachbackMessage + treepointMessage + payMessage
let alertController = UIAlertController(title: "\(gasName) 加滿", message: message , preferredStyle: .alert)
let okAction = UIAlertAction(title: "確定", style: .default){ _ in
self.cashback = 0
self.openpointback = 0.0
self.treepointback = 0
self.openpointRebate = 0.01
self.cashRebate = 0.0
self.treepointRebate = 0.0
self.payMessage = "支付方式:icash Pay、icash2.0、icash2.0聯名卡"
}
alertController.addAction(okAction)
present(alertController, animated: true)
}
}

自訂加油金額按鈕 計算回饋金功能

自訂加油金額 回饋精算師
@IBAction func calculateFuelCost(_ sender: Any) {

if let cost = Double(costTextField.text!), cost > 0{
let totalDiscount = calculateRebate(cost: Int(cost))
let fuelLiterMessage = "本次加油量: " + String(format: "%.2f", fuelLiter)+"公升\n"
let costMessage = "花費金額: " + String(format: "%.f", cost)+"元\n"
let discountMessage = "總回饋金額(\((openpointRebate+cashRebate+treepointRebate)*100)%): " + String(format: "%.2f", totalDiscount) + "元\n"
let openpointMessage = "OP點數回饋(" + String(format: "%.1f",openpointRebate*100) + "%): " + String(format: "%.2f", openpointback) + "點\n"
let cachbackMessage = "信用卡回饋金(\(cashRebate*100)%): \(cashback)元\n"
let treepointMessage = "小樹點回饋(\(treepointRebate*100)%): \(treepointback)點\n"
let message = fuelLiterMessage + costMessage + discountMessage + openpointMessage + cachbackMessage + treepointMessage + payMessage

let alertController = UIAlertController(title: "\(gasName) 加\(Int(cost))元", message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "確定", style: .default){ _ in
self.cashback = 0
self.openpointback = 0.0
self.treepointback = 0
self.openpointRebate = 0.01
self.cashRebate = 0.0
self.treepointRebate = 0.0
self.payMessage = "支付方式:icash Pay、icash2.0、icash2.0聯名卡"
}
alertController.addAction(okAction)
present(alertController, animated: true)
}
}

--

--