股票申购是和时间有关的 feature,所以需要有一个 DateUtility,这个类别负责所有 Date 的处理。
struct DateUtility {
static let dateFormatter = DateFormatter()
}
很多文献都会和你说,Swift 的 DateFormatter() 是个 init 很贵(expensive)的物件,连 Apple 官方文件都建议你写一个 static property 存起来,不要一直 init()。最严重的情况,是有可能卡你 UI 滑动的。
其中有一篇真的实测 init 的秒数的文章,我个人觉得很有深度。连结如下
https://sarunw.com/posts/how-expensive-is-dateformatter/
第一个 func 要写的,就是从 String 转换成 Date 型别。因为不是每个 String 都能转成 Date,所以 return 设计成 optional。
struct DateUtility {
static let dateFormatter = DateFormatter()
func getDate(from string: String, format: String = "yyyy-MM-dd") -> Date? {
DateUtility.dateFormatter.dateFormat = format
return DateUtility.dateFormatter.date(from: string)
}
}
然後看了一下日期格式…嗯…果然是政府公开资料正常发挥的民国年,不是电脑标准的西元年。不过难度上来说,Big5 处理起来,比较麻烦,民国年转西元年倒是没有那麽困难。
跟据我过去的经验,劝大家千万不要自己手动转换历法
千万不要自己手动转换历法!!
千万不要自己手动转换历法!!!!
不管是哪种语言,尽可能的使用框架中写好的方法进行转换,而且在传值的时候,使用 unix time 来传递,只有在显示前的那一刻,再转换成人类看得懂的格式。
将 dateFormatter 的 calendar property 在读取民国年的时候,用台湾的历法,就可以正确读取以民国年纪录的资料了。
struct DateUtility {
static let dateFormatter = DateFormatter()
private var isoCalendar: Calendar {
return Calendar(identifier: .iso8601)
}
private var rocCalendar: Calendar {
return Calendar(identifier: .republicOfChina)
}
func getDate(from string: String, format: String = "yyyy-MM-dd") -> Date? {
DateUtility.dateFormatter.calendar = isoCalendar
DateUtility.dateFormatter.dateFormat = format
return DateUtility.dateFormatter.date(from: string)
}
func getDateFromTwCalendar(from string: String, format: String = "yyyy/MM/dd") -> Date? {
DateUtility.dateFormatter.calendar = rocCalendar
DateUtility.dateFormatter.dateFormat = format
return DateUtility.dateFormatter.date(from: string)
}
}
而 StockSubscription 能接受 yyyy 的输入值,这个功能也归在 DateUtility
func getIntFromDate(component: Calendar.Component) -> Int {
let date = Date()
let calendar = isoCalendar
return calendar.component(component, from: date)
}
然後,在 StockSubscriptionModel 中,加上三种状态,但有可能真的遇到资料有问题,保险起见,我加上第四种 notDefined,如果真的解不出 Date,就让他进入第四种状态。当然,你也可以选择让其中一种状态成为你的预设值啦。但实务上真的,真的,真的不要对後端来的资料用 force unwrap,迟早有一天会出事的。而出事的时候,你就是要修。
extension StockSubscriptionModel {
enum SubscriptionState {
case beforeSubscription
case duringSubscription
case finishedSubscription
case notDefined
}
}
然後,再用 local time 和 StockSubscriptionInfo 来判断 indexPath 的 info 是哪个状态。当然能取 server time 是最好,但现在状况来说,我并没有後端,所以就用 local time 来当基准。
extension StockSubscriptionModel {
func getSubscriptionState(info: StockSubscriptionInfo) -> SubscriptionState {
let currentTime = Date().timeIntervalSince1970
if let startTime = info.subscriptionStart?.timeIntervalSince1970,
let endTime = info.subscriptionEnd?.timeIntervalSince1970 {
if currentTime < startTime {
return .beforeSubscription
} else if currentTime > endTime {
return .finishedSubscription
} else {
return .duringSubscription
}
}
return .notDefined
}
}
在拿取申购资料的时候,要输入欲取得的年份。但如果写死 2021,那到了 2021-12-31 的时候,你就准备一份改成 2022 的程序码,然後在跨年夜的时候更新。不然使用者的资料就会永远停在 2021 了。
所以我们加上取得客端现在手机的时间,Model 就可以在 12月31日跨到隔年 1月1日的时候,在程序上直接处理了,不用更新程序码。
private func getQueryYear() -> Int {
let dateUtility = DateUtility()
return dateUtility.getIntFromDate(component: .year)
}
func requestStockSubscription() {
let year = getQueryYear()
manager.requestStockSubscriptionInfo(year: year) { [weak self] subscriptionList, error in
// 需要去掉中央债的资料
self?.subscriptionList = self?.filterNotAvailable(subscriptionList) ?? []
self?.delegate?.didRecieveList(subscriptionList, error: error)
}
}
在确定申购状态後, ViewController 在不同的状态下,更新对应的 cell UI风格。
有四种状态,所以就是四种更新 UI 的 func。
private func setBeforeSubscriptionUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "申购未开始"
cell.stateLabel.textColor = .black
cell.stateLabel.backgroundColor = .clear
}
private func setDuringSubscriptionUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "可申购"
cell.stateLabel.textColor = .systemGreen
cell.stateLabel.backgroundColor = .clear
}
private func setFinishedSubscriptionUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "申购结束"
cell.stateLabel.textColor = .white
cell.stateLabel.backgroundColor = .systemRed
}
private func setNotDefinedUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "申购状态未定"
cell.stateLabel.textColor = .systemGray2
cell.stateLabel.backgroundColor = .clear
}
而这个 modify func,是让 VC 在 cellForRow(at:) 呼叫的,在 cellForRow(at:) 会拿到 info 也会拿到 custom tableViewCell。让这个 func 处理每个状态的 UI。
private func modify(_ cell: StockSubscriptionTableViewCell, with info: StockSubscriptionInfo) {
let state = model.getSubscriptionState(info: info)
if info.subscriptionRateString == "0" {
cell.forthSectionLabel.text = "-- %"
}
switch state {
case .beforeSubscription:
setBeforeSubscriptionUI(cell)
case .duringSubscription:
setDuringSubscriptionUI(cell)
case .finishedSubscription:
setFinishedSubscriptionUI(cell)
case .notDefined:
setNotDefinedUI(cell)
}
}
最後,就在 cellForRow(at:) 呼叫这个 func 就完成了
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: StockSubscriptionTableViewCell.identifier, for: indexPath) as? StockSubscriptionTableViewCell,
let info = model.getSubscriptionInfo(at: indexPath) else {
return UITableViewCell()
}
let state = "申购状态"
let firstSection = "\(info.stockName) - (\(info.stockCode))"
let secondSection = "申购股数: \(info.stockCountString)"
let thirdSection = "申购价: \(info.actualPrice)"
let forthSection = "中签率: \(info.subscriptionRateString) %"
cell.stateLabel.text = state
cell.firstSectionLabel.text = firstSection
cell.secondSectionLabel.text = secondSection
cell.thirdSectionLabel.text = thirdSection
cell.forthSectionLabel.text = forthSection
modify(cell, with: info)
return cell
}
整个 VC 的程序码如下
//
// StockSubscriptionViewController.swift
// ITIronMan
//
// Created by Marvin on 2021/9/4.
//
import UIKit
class StockSubscriptionViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private lazy var model: StockSubscriptionModel = {
let model = StockSubscriptionModel()
model.delegate = self
return model
}()
// MARK: - life cycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
// MARK: - private methods
private func setupUI() {
tableView.dataSource = self
tableView.delegate = self
}
// MARK: - IBAction
@IBAction func requestSubscriptionButtonDidTap(_ sender: Any) {
model.requestStockSubscription()
}
}
extension StockSubscriptionViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: StockSubscriptionTableViewCell.identifier, for: indexPath) as? StockSubscriptionTableViewCell,
let info = model.getSubscriptionInfo(at: indexPath) else {
return UITableViewCell()
}
let state = "申购状态"
let firstSection = "\(info.stockName) - (\(info.stockCode))"
let secondSection = "申购股数: \(info.stockCountString)"
let thirdSection = "申购价: \(info.actualPrice)"
let forthSection = "中签率: \(info.subscriptionRateString) %"
cell.stateLabel.text = state
cell.firstSectionLabel.text = firstSection
cell.secondSectionLabel.text = secondSection
cell.thirdSectionLabel.text = thirdSection
cell.forthSectionLabel.text = forthSection
modify(cell, with: info)
return cell
}
private func modify(_ cell: StockSubscriptionTableViewCell, with info: StockSubscriptionInfo) {
let state = model.getSubscriptionState(info: info)
if info.subscriptionRateString == "0" {
cell.forthSectionLabel.text = "-- %"
}
switch state {
case .beforeSubscription:
setBeforeSubscriptionUI(cell)
case .duringSubscription:
setDuringSubscriptionUI(cell)
case .finishedSubscription:
setFinishedSubscriptionUI(cell)
case .notDefined:
setNotDefinedUI(cell)
}
}
private func setBeforeSubscriptionUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "申购未开始"
cell.stateLabel.textColor = .black
cell.stateLabel.backgroundColor = .clear
}
private func setDuringSubscriptionUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "可申购"
cell.stateLabel.textColor = .systemGreen
cell.stateLabel.backgroundColor = .clear
}
private func setFinishedSubscriptionUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "申购结束"
cell.stateLabel.textColor = .white
cell.stateLabel.backgroundColor = .systemRed
}
private func setNotDefinedUI(_ cell: StockSubscriptionTableViewCell) {
cell.stateLabel.text = "申购状态未定"
cell.stateLabel.textColor = .systemGray2
cell.stateLabel.backgroundColor = .clear
}
}
extension StockSubscriptionViewController: StockSubscriptionModelDelegate {
func didRecieveList(_ subscriptionList: [StockSubscriptionInfo], error: Error?) {
if let error = error {
print("you got error during subscriptions request: \(error.localizedDescription)")
return
}
tableView.reloadData()
}
}
而 UI 状态如下。
写到这里,连一根 K 线都没有,是不是和一般投资理财用的软件不同?
没问题的,下一篇开始,就会进入 K 线的制作。
<<: D20-(9/20)-康普(4739)-特斯拉的电池正极材料供应商
>>: [Day06] TS:整合前几天所学,来写个 Generic Functions 吧!
今天想要分享的是这个, 记得我当时看到这个效果的时候觉得真的是炫炮过头了, 马上整个影片看完做练习,...
管理员在网站後台,需有商品上、下架操作功能。 新建BookAdd.jsp <form acti...
STM32晶片内部有一个Flash记忆体,主要用於储存我们所打的程序,我们在软件上打好程序编译完成後...
昨天已经把大部分的 GUI 弄完了,之前也已经写好了服务器的程序,今天我们把两边拼起来吧! 搬移 s...
以下可以将 www.a.com/jkhdajlf23 後缀jkhdajlf23补捉到 往後传递 而不...