前面都是在做资料处理,所以只有程序码,没有 UI 画面,谢谢看到今天的朋友
台股光是上市的家数是超过1000 家,是不可能在一个手机上显示所有公司的基本资料的,在手机上,我们常使用 UITableView 来呈现大数量,且格式相近的资料。
依照 Apple MVC 的框架,每个人的角色分配如下
Model: 负责处理逻辑,不会直接和 View 进行沟通
View: 负责呈现资料,不会直接和 Model 进行沟通
Controller: 成为 Model 和 View 的中间人,当有 View 需要资料的时候,负责提供资料。如果 View 被点击,则处理後续的点击事件。当 Model 收到资料时,Controller 成为 Model 通知的对象。**
这个页面现在要呈现 上市/上柜/兴柜 的公司基本资料,而这些基本资料需要从云端下载。那这个页面的 MVC 职责大概是这样。
**Model: 负责下载资料,并储存下载後的资料。在实际专案的时候,通常还会针对这种不会马上变化的资料,进行快取。但这边因为不是 key feature,所以不进行快取的实作。但如果要做的话,把资料放在 UserDefaults 或是 CoreData 里面就可以做到了。
View: 在 VC 的 RootView 下,主要呈现公司基本资料的列表。但设计上,我不想要在 VC 的生命周期中直接发动 URLRequest,这一段,我希望用 button 的 action 来发动。第一阶段希望做成用 button 来发动下载,这样比较容易说明每个动作,也比较容易解说每一个 response 後做的行为。而 TableViewCell 里,想呈现的资讯是,股票名,股票代号,资本额。
Controller: 在生命周期中,并不特别做什麽。但是当 button 按下的时候,会去呼叫 model 进行对应的资料下载。当 model 资料有变更的时候,会呼叫 tableView.reloadData(),去更新列表。
基本的 data model 设计,这边会需要 conform Hashable,是因为我习惯用 Set 在更新的时候进行资料的合并。
import Foundation
struct StockBasicInfo: Hashable {
let stockCode: String
let stockName: String
let companyName: String
let capital: String
}
基本的 UI layout 设计如下。
创建一个 RequestBasicInfoViewController
import UIKit
class RequestBasicInfoViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
先在一开始的 LandingViewController 拉一个 button,会发动 navigationController push 转场
@IBAction func pushRequestBasicInfoVC(_ sender: Any) {
let storyboard = UIStoryboard(name: "RequestBasicInfo", bundle: nil)
if let vc = storyboard.instantiateViewController(withIdentifier: "RequestBasicInfoViewController") as? RequestBasicInfoViewController {
navigationController?.pushViewController(vc, animated: true)
}
}
接下来,进行 Model 的实作,这边我选择使用 delegate pattern 来通知 VC 资料已经下载好了。就前面所述,Model 要处理逻辑,需要处理的部分如下。
enum MarketType: String {
case twStock = "上市"
case otc = "上柜"
case emerging = "兴柜"
}
import Foundation
protocol RequestBasicInfoModelDelegate: AnyObject {
func didRecieveCompanyInfo(_ companyList: [StockBasicInfo], error: Error?)
}
class RequestBasicInfoModel {
weak var delegate: RequestBasicInfoModelDelegate?
private var recievedInfo = [MarketType]()
private var companyList = [StockBasicInfo]()
var count: Int {
return companyList.count
}
private lazy var stockInfoManager: StockInfoManager = {
let manager = StockInfoManager()
return manager
}()
func getStockInfo(at indexPath: IndexPath) -> StockBasicInfo? {
let index = indexPath.row
if companyList.indices.contains(index) {
return companyList[index]
}
return nil
}
func requestTwStock() {
if recievedInfo.contains(.twStock) {
print("已经拿过资料")
return
}
stockInfoManager.requestTwStockCodeAndName { [weak self] list, error in
self?.updateStockInfo(from: list, marketType: .twStock)
self?.delegate?.didRecieveCompanyInfo(list, error: error)
}
}
func requestOTCStock() {
if recievedInfo.contains(.otc) {
print("已经拿过资料")
return
}
stockInfoManager.requestOTCCodeAndName { [weak self] list, error in
self?.updateStockInfo(from: list, marketType: .otc)
self?.delegate?.didRecieveCompanyInfo(list, error: error)
}
}
func requestEmergingStock() {
if recievedInfo.contains(.emerging) {
print("已经拿过资料")
return
}
stockInfoManager.requestEmerginCodeAndName { [weak self] list, error in
self?.updateStockInfo(from: list, marketType: .emerging)
self?.delegate?.didRecieveCompanyInfo(list, error: error)
}
}
private func updateStockInfo(from list: [StockBasicInfo], marketType: MarketType) {
recievedInfo.append(marketType)
let recievedList = Set(list)
let updatedList = Set(companyList).union(recievedList)
companyList = Array(updatedList).sorted { $0.stockCode < $1.stockCode }
}
}
UITableView 的程序码,这边 custom 一个 TableViewCell,CompanyBasicInfoTableViewCell
class CompanyBasicInfoTableViewCell: UITableViewCell {
static let identifier = "CompanyBasicInfoTableViewCell"
@IBOutlet weak var codeAndNameLabel: UILabel!
@IBOutlet weak var capitalLabel: UILabel!
}
UITableView 如果设置上有遇到困难,这边有 Apple 文件
Apple 对於 UITableView 的说明文件
ViewController 部分的程序码
import UIKit
class RequestBasicInfoViewController: UIViewController {
@IBOutlet weak var stateLabel: UILabel!
@IBOutlet weak var tableView: UITableView!
private lazy var model: RequestBasicInfoModel = {
let model = RequestBasicInfoModel()
model.delegate = self
return model
}()
// MARK: - life cycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
// MARK: - private methods
private func setupUI() {
tableView.delegate = self
tableView.dataSource = self
}
// MARK: - IBAction
@IBAction func requestTwStockButtonDidTap(_ sender: Any) {
model.requestTwStock()
}
@IBAction func requestOTCButtonDidTap(_ sender: Any) {
model.requestOTCStock()
}
@IBAction func requestEmergingButtonDidTap(_ sender: Any) {
model.requestEmergingStock()
}
}
extension RequestBasicInfoViewController: 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: CompanyBasicInfoTableViewCell.identifier, for: indexPath) as? CompanyBasicInfoTableViewCell,
let info = model.getStockInfo(at: indexPath) else {
return UITableViewCell()
}
let codeName = "\(info.stockName) - (\(info.stockCode))\n\(info.companyName)"
let capital = "资本额: \(info.capital) 元"
cell.codeAndNameLabel.text = codeName
cell.capitalLabel.text = capital
return cell
}
}
extension RequestBasicInfoViewController: RequestBasicInfoModelDelegate {
func didRecieveCompanyInfo(_ companyList: [StockBasicInfo], error: Error?) {
if let error = error {
print("basic info reqeust got error: \(error.localizedDescription)")
return
}
updateStateUI()
tableView.reloadData()
}
private func updateStateUI() {
var recievedMarketsText = ""
for market in model.recievedInfo {
recievedMarketsText += "\(market.rawValue) "
}
stateLabel.text = "已取得 \(recievedMarketsText) 资料 - 数量 \(model.count) 笔"
}
}
完成後的状态
延伸功能:
UITableView 如果设置上有遇到困难,这边有 Apple 文件
Apple 对於 UITableView 的说明文件
通常在列表的 UI 上,点击後,会再推入一个 VC 去呈现这个格子内容的详细资料。但这边的 demo 只会做到这里,详细页的 UI 在实作并不是难度很大的功能,比较难的是资料来源。
如果要实作,就在 UITableViewDelegate 的 tableView(_:didSelectRowAt:) 实作拿出 info model,然後把 info model 传入下一个 vc 即可。
D5 程序码可以在 GitHub 上下载的到
GitHub Repo
>>: Kneron - 在Raspberry Pi 4(Raspbian Buster)上安装 OpenCV 参考笔记
前言 什麽是Git,刚开始认识它的时候,以为他跟GitHub 有着什麽关系或是某个简称,结果两着是各...
在网路上很少关於这个科目的介绍,虽然第一次盲考就通过,但是整个过程还是如履薄冰。因此留下这次考试的过...
函式功能 函式在 JavaScript 中为物件型别,以下列出它一般的物件差别 被呼叫的能力 {} ...
今年的疫情蛮严重的,希望大家都过得安好,希望疫情快点过去,能回到一些线下技术聚会的时光~ 今天目标:...
-主引导记录(MBR)和引导扇区(来源:Syed Fahad) .该多态病毒沉思修改整个系统,这样...