DetailView/货币详情页 的实现

1. 创建货币详情数据模型类 CoinDetailModel.swift

import Foundation// JSON Data
/*URL:https://api.coingecko.com/api/v3/coins/bitcoin?localization=false&tickers=false&market_data=false&community_data=false&developer_data=false&sparkline=falseResponse:{"id": "bitcoin","symbol": "btc","name": "Bitcoin","asset_platform_id": null,"platforms": {"": ""},"detail_platforms": {"": {"decimal_place": null,"contract_address": ""}},"block_time_in_minutes": 10,"hashing_algorithm": "SHA-256","categories": ["Cryptocurrency","Layer 1 (L1)"],"public_notice": null,"additional_notices": [],"description": {"en": "Bitcoin is the first successful internet money based on peer-to-peer technology; whereby no central bank or authority is involved in the transaction and production of the Bitcoin currency. It was created by an anonymous individual/group under the name, Satoshi Nakamoto. The source code is available publicly as an open source project, anybody can look at it and be part of the developmental process.\r\n\r\nBitcoin is changing the way we see money as we speak. The idea was to produce a means of exchange, independent of any central authority, that could be transferred electronically in a secure, verifiable and immutable way. It is a decentralized peer-to-peer internet currency making mobile payment easy, very low transaction fees, protects your identity, and it works anywhere all the time with no central authority and banks.\r\n\r\nBitcoin is designed to have only 21 million BTC ever created, thus making it a deflationary currency. Bitcoin uses the <a href=\"https://www.coingecko.com/en?hashing_algorithm=SHA-256\">SHA-256</a> hashing algorithm with an average transaction confirmation time of 10 minutes. Miners today are mining Bitcoin using ASIC chip dedicated to only mining Bitcoin, and the hash rate has shot up to peta hashes.\r\n\r\nBeing the first successful online cryptography currency, Bitcoin has inspired other alternative currencies such as <a href=\"https://www.coingecko.com/en/coins/litecoin\">Litecoin</a>, <a href=\"https://www.coingecko.com/en/coins/peercoin\">Peercoin</a>, <a href=\"https://www.coingecko.com/en/coins/primecoin\">Primecoin</a>, and so on.\r\n\r\nThe cryptocurrency then took off with the innovation of the turing-complete smart contract by <a href=\"https://www.coingecko.com/en/coins/ethereum\">Ethereum</a> which led to the development of other amazing projects such as <a href=\"https://www.coingecko.com/en/coins/eos\">EOS</a>, <a href=\"https://www.coingecko.com/en/coins/tron\">Tron</a>, and even crypto-collectibles such as <a href=\"https://www.coingecko.com/buzz/ethereum-still-king-dapps-cryptokitties-need-1-billion-on-eos\">CryptoKitties</a>."},"links": {"homepage": ["http://www.bitcoin.org","",""],"blockchain_site": ["https://blockchair.com/bitcoin/","https://btc.com/","https://btc.tokenview.io/","https://www.oklink.com/btc","https://3xpl.com/bitcoin","","","","",""],"official_forum_url": ["https://bitcointalk.org/","",""],"chat_url": ["","",""],"announcement_url": ["",""],"twitter_screen_name": "bitcoin","facebook_username": "bitcoins","bitcointalk_thread_identifier": null,"telegram_channel_identifier": "","subreddit_url": "https://www.reddit.com/r/Bitcoin/","repos_url": {"github": ["https://github.com/bitcoin/bitcoin","https://github.com/bitcoin/bips"],"bitbucket": []}},"image": {"thumb": "https://assets.coingecko.com/coins/images/1/thumb/bitcoin.png?1547033579","small": "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579","large": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579"},"country_origin": "","genesis_date": "2009-01-03","sentiment_votes_up_percentage": 73.21,"sentiment_votes_down_percentage": 26.79,"watchlist_portfolio_users": 1326950,"market_cap_rank": 1,"coingecko_rank": 1,"coingecko_score": 83.151,"developer_score": 99.241,"community_score": 83.341,"liquidity_score": 100.011,"public_interest_score": 0.073,"public_interest_stats": {"alexa_rank": 9440,"bing_matches": null},"status_updates": [],"last_updated": "2023-08-11T08:43:13.856Z"}*//// 交易货币详情模型
struct CoinDetailModel: Codable {let id, symbol, name: String?let blockTimeInMinutes: Int?let hashingAlgorithm: String?let description: Description?let links: Links?enum CodingKeys: String, CodingKey {case id, symbol, namecase blockTimeInMinutes = "block_time_in_minutes"case hashingAlgorithm = "hashing_algorithm"case description, links}/// 去掉 HTML 链接的描述var readableDescription: String? {return description?.en?.removingHTMLOccurances}
}struct Description: Codable {let en: String?
}struct Links: Codable {let homepage: [String]?let subredditURL: String?enum CodingKeys: String, CodingKey {case homepagecase subredditURL = "subreddit_url"}
}

2. 创建货币详情数据服务类 CoinDetailDataService.swift

import Foundation
import Combine/// 交易货币详情服务
class CoinDetailDataService{// 交易货币详情模型数组 Published: 可以拥有订阅者@Published var coinDetails: CoinDetailModel? = nil// 随时取消操作var coinDetailSubscription: AnyCancellable?// 传入的货币模型let coin: CoinModelinit(coin: CoinModel) {self.coin = coingetCoinDetails()}/// 获取交易硬币详情func getCoinDetails(){guard let url = URL(string: "https://api.coingecko.com/api/v3/coins/\(coin.id)?localization=false&tickers=false&market_data=false&community_data=false&developer_data=false&sparkline=false")else { return }coinDetailSubscription = NetworkingManager.downLoad(url: url).decode(type: CoinDetailModel.self, decoder: JSONDecoder()).receive(on: DispatchQueue.main).sink(receiveCompletion: NetworkingManager.handleCompletion,receiveValue: { [weak self] returnCoinDetails in// 解除强引用 (注意)self?.coinDetails = returnCoinDetails// 取消订阅者self?.coinDetailSubscription?.cancel()})}
}

3. 创建货币详情 ViewModel 类,DetailViewModel.swift

import Foundation
import Combine/// 交易货币详情 ViewModel
class DetailViewModel: ObservableObject {/// 概述统计模型数组@Published var overviewStatistics: [StatisticModel] = []/// 附加统计数据数组@Published var additionalStatistics: [StatisticModel] = []/// 货币描述@Published var description: String? = nil/// 货币官网网站@Published var websiteURL: String? = nil/// 货币社区网站@Published var redditURL: String? = nil/// 交易货币模型@Published var coin: CoinModel/// 交易货币详情请求服务private let coinDetailService: CoinDetailDataService/// 随时取消订阅private var cancellables = Set<AnyCancellable>()init(coin: CoinModel) {self.coin = coinself.coinDetailService = CoinDetailDataService(coin: coin)self.addSubscribers()}/// 添加订阅者private func addSubscribers(){// 订阅货币详情数据coinDetailService.$coinDetails.combineLatest($coin)// 数据的转换.map(mapDataToStatistics).sink {[weak self] returnedArrays inself?.overviewStatistics = returnedArrays.overviewself?.additionalStatistics = returnedArrays.additional}.store(in: &cancellables)// 订阅货币详情数据coinDetailService.$coinDetails.sink {[weak self] returnedCoinDetails inself?.description = returnedCoinDetails?.readableDescriptionself?.websiteURL = returnedCoinDetails?.links?.homepage?.firstself?.redditURL = returnedCoinDetails?.links?.subredditURL}.store(in: &cancellables)}/// 数据转换为统计信息数据private func mapDataToStatistics(coinDetailModel: CoinDetailModel?, coinModel: CoinModel) -> (overview: [StatisticModel], additional: [StatisticModel]){// 概述信息// 当前货币概述信息let overviewArray = createOvervierArray(coinModel: coinModel)// 附加信息// 当前货币附加信息let additionalArray = createAdditionalArray(coinModel: coinModel, coinDetailModel: coinDetailModel)// 返回数组return (overviewArray, additionalArray)}/// 创建概述信息数组private func createOvervierArray(coinModel: CoinModel) -> [StatisticModel]{// 当前交易货币价格let price = coinModel.currentPrice.asCurrencyWith6Decimals()// 当前交易货币价格 24 小时的变化百分比let pricePercentChange = coinModel.priceChangePercentage24H// 当前交易货币价格 统计信息let priceStat = StatisticModel(title: "Current Price", value: price, percentageChange: pricePercentChange)// 市值 价格let marketCap = "$" + (coinModel.marketCap?.formattedWithAbbreviations() ?? "")// 市值 24 小时变化百分比let marketCapPercentChange = coinModel.marketCapChangePercentage24H// 市值 统计信息let marketCapStat = StatisticModel(title: "Market Capitalization", value: marketCap, percentageChange: marketCapPercentChange)// 当前交易货币的排名let rank = "\(coinModel.rank)"// 当前货币排名 统计信息let rankStat = StatisticModel(title: "Rank", value: rank)// 交易总量let volume = coinModel.totalVolume?.formattedWithAbbreviations() ?? ""// 交易 统计信息let volumeStat = StatisticModel(title: "Volume", value: volume)// 当前货币概述信息return [priceStat, marketCapStat, rankStat, volumeStat]}/// 创建附加信息数组private func createAdditionalArray(coinModel: CoinModel, coinDetailModel: CoinDetailModel?) -> [StatisticModel]{// 24 小时内最高点let high = coinModel.high24H?.asCurrencyWith6Decimals() ?? "n/a"// 最高点 统计信息let highStat = StatisticModel(title: "24h High", value: high)// 24 小时内最低点let  low = coinModel.low24H?.asCurrencyWith6Decimals() ?? "n/a"// 最低点 统计信息let lowStat = StatisticModel(title: "24h Low", value: low)// 24 小时内价格变化let priceChange = coinModel.priceChange24H?.asCurrencyWith6Decimals() ?? "n/a"// 当前交易货币 24 小时的价格变化百分比let pricePercentChange2 = coinModel.priceChangePercentage24H// 24 小时内价格变化 统计信息let priceChangeStat = StatisticModel(title: "24h Price Change", value: priceChange, percentageChange: pricePercentChange2)// 24 小时内市值变化值let marketCapChange = "$" + (coinModel.marketCapChange24H?.formattedWithAbbreviations() ?? "")// 市值 24 小时变化百分比let marketCapPercentChange2 = coinModel.marketCapChangePercentage24H// 24 小时内市值变换 统计信息let marketCapChangeStat = StatisticModel(title: "24h Market Cap Change", value: marketCapChange, percentageChange: marketCapPercentChange2)// 区块时间 (分钟为单位)let blockTime = coinDetailModel?.blockTimeInMinutes ?? 0let blockTimeString = blockTime == 0 ? "n/a" : "\(blockTime)"// 统计信息let blockTimeStat = StatisticModel(title: "Block Time", value: blockTimeString)// 哈希/散列 算法let hashing = coinDetailModel?.hashingAlgorithm ?? "n/a"let hashingStat = StatisticModel(title: "Hashing Algorithm", value: hashing)// 当前货币附加信息return [highStat, lowStat, priceChangeStat, marketCapChangeStat, blockTimeStat, hashingStat]}
}

4. 货币详情 View/视图 层

  4.1 创建折线视图 ChartView.swift

import SwiftUI/// 折线视图
struct ChartView: View {// 7 天价格中的交易货币数据private let data: [Double]// Y 最大值private let maxY: Double// Y 最小值private let minY: Double// 线条颜色private let lineColor: Color// 开始日期private let startingDate: Date// 结束日期private let endingDate: Date// 绘制折线进度的百分比@State private var percentage: CGFloat = 0init(coin: CoinModel) {data = coin.sparklineIn7D?.price ?? []maxY = data.max() ?? 0minY = data.min() ?? 0// 最后一个价格减去第一个价格,为当前的价格变化量let priceChange = (data.last ?? 0) - (data.first ?? 0)// 线条颜色lineColor = priceChange > 0 ? Color.theme.green : Color.theme.red// 转换开始结束时间格式endingDate = Date(coinGeckoString: coin.lastUpdated ?? "")// 没有返回开始时间,根据他是结束日期的前七天,所以定义为结束时间之前间隔为 -7 天startingDate = endingDate.addingTimeInterval(-7 * 24 * 60 * 60)}// 计算 X 点的位置:// 300 : Viw 的宽// 100 : 数据个数// 3   : 得到的增量 300 / 100// 1 * 3 = 3  : x 位置 的计算// 2 * 3 = 6// 3 * 3 = 9// 100 * 3 = 300// 计算 Y 点的位置// 60,000 -> 最大值// 50,000 -> 最小值// 60,000 - 50,000 = 10,000 -> yAxis / Y轴// 52,000 - data point// 52,000 - 50,000 = 2,000 / 10,000 = 20%var body: some View {VStack {// 折线视图chartView.frame(height: 200).background(chartBackground).overlay(chartYAxis.padding(.horizontal, 4), alignment: .leading)// X 轴日期文字chartDateLabels.padding(.horizontal, 4)}.font(.caption).foregroundColor(Color.theme.secondaryText).onAppear {// 线程DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {// 线程具有动画效果withAnimation(.linear(duration: 2.0)) {percentage = 1.0}}}}
}struct ChartView_Previews: PreviewProvider {static var previews: some View {ChartView(coin: dev.coin)}
}extension ChartView{/// 折线视图private var chartView: some View{// GeometryReader: 根据试图大小自动布局页面GeometryReader{ geometry inPath { path infor index in data.indices{// x 点的位置  宽度 / 总数 * 增量let xPosition =  geometry.size.width / CGFloat(data.count) * CGFloat(index + 1)// Y 轴let yAxis = maxY - minY// y 点的位置let yPosition = (1 - CGFloat((data[index] - minY) / yAxis)) * geometry.size.heightif index == 0 {// 移至起始点左上角(0,0)path.move(to: CGPoint(x: xPosition, y: yPosition))}// 添加一条线path.addLine(to: CGPoint(x: xPosition, y: yPosition))}}// 修剪绘制线条的进度.trim(from: 0, to: percentage).stroke(lineColor, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round)).shadow(color: lineColor, radius: 10, x: 0.0, y: 10).shadow(color: lineColor.opacity(0.5), radius: 10, x: 0.0, y: 20).shadow(color: lineColor.opacity(0.2), radius: 10, x: 0.0, y: 30).shadow(color: lineColor.opacity(0.1), radius: 10, x: 0.0, y: 40)}}/// 背景private var chartBackground: some View{VStack{Divider()Spacer()Divider()Spacer()Divider()}}/// Y 轴坐标点文字private var chartYAxis: some View{VStack{Text(maxY.formattedWithAbbreviations())Spacer()Text(((maxY + minY) * 0.5).formattedWithAbbreviations())Spacer()Text(minY.formattedWithAbbreviations())}}/// X 轴日期文字private var chartDateLabels: some View{HStack {Text(startingDate.asShortDateString())Spacer()Text(endingDate.asShortDateString())}}
}

  4.2 创建货币详情视图,DetailView.swift

import SwiftUI/// 加载交易货币详情页
struct DetailLoadingView: View{/// 交易货币模型@Binding var coin: CoinModel?var body: some View {ZStack {if let coin = coin{DetailView(coin: coin)}}}
}/// 交易货币详情页
struct DetailView: View {@StateObject private var viewModel: DetailViewModel/// 是否展开概述内容@State private var showFullDescription: Bool = false// 网格样式private let colums: [GridItem] = [// flexible: 自动调整大小GridItem(.flexible()),GridItem(.flexible())]// 网格间隔private let spacing: CGFloat = 30/// 交易货币模型init(coin: CoinModel) {_viewModel = StateObject(wrappedValue: DetailViewModel(coin: coin))}var body: some View {ScrollView {VStack {// 交易货币折线图ChartView(coin: viewModel.coin)// 间隔.padding(.vertical)VStack(spacing: 20) {// 概述信息标题overviewTitleDivider()// 概述信息内容descriptionSection// 概述信息网格 ViewoverviewGrid// 附加信息标题additionalTitleDivider()// 附加信息网格 ViewadditionalGrid//  网站地址websiteSection}.padding()}}.background(Color.theme.background.ignoresSafeArea()).navigationTitle(viewModel.coin.name).toolbar {ToolbarItem(placement: .navigationBarTrailing) {navigationBarTrailing}}}
}extension DetailView{/// 导航栏右边 Item,建议使用 Toolbarprivate var navigationBarTrailing: some View{HStack {Text(viewModel.coin.symbol.uppercased()).font(.headline).foregroundColor(Color.theme.secondaryText)CoinImageView(coin: viewModel.coin).frame(width: 25, height: 25)}}/// 概述信息标题private var overviewTitle: some View{Text("Overview").font(.title).bold().foregroundColor(Color.theme.accent).frame(maxWidth: .infinity, alignment: .leading)}///  附加信息标题private var additionalTitle: some View{Text("Additional Details").font(.title).bold().foregroundColor(Color.theme.accent).frame(maxWidth: .infinity, alignment: .leading)}/// 概述信息内容private var descriptionSection: some View{ZStack {if let description = viewModel.description,!description.isEmpty {VStack(alignment: .leading) {// 描述文本Text(description).lineLimit(showFullDescription ? nil : 3).font(.callout).foregroundColor(Color.theme.secondaryText)// 更多按钮Button {withAnimation(.easeInOut) {showFullDescription.toggle()}} label: {Text(showFullDescription ? "Less" : "Read more...").font(.caption).fontWeight(.bold).padding(.vertical, 4)}.accentColor(.blue)}.frame(maxWidth: .infinity, alignment: .leading)}}}/// 概述信息网格 Viewprivate var overviewGrid: some View{LazyVGrid(columns: colums,alignment: .leading,spacing: spacing,pinnedViews: []) {ForEach(viewModel.overviewStatistics) { stat inStatisticView(stat:stat)}}}/// 附加信息网格 Viewprivate var additionalGrid: some View{LazyVGrid(columns: colums,alignment: .leading,spacing: spacing,pinnedViews: []) {ForEach(viewModel.additionalStatistics) { stat inStatisticView(stat: stat)}}}/// 网站地址private var websiteSection: some View{VStack(alignment: .leading, spacing: 12){// 官方网站if let websiteString = viewModel.websiteURL,let url = URL(string: websiteString){Link("Website", destination: url)}Spacer()// 论坛网站if let redditString = viewModel.redditURL,let url = URL(string: redditString){Link("Reddit", destination: url)}}.accentColor(.blue).frame(maxWidth: .infinity, alignment: .leading).font(.headline)}
}struct DetailView_Previews: PreviewProvider {static var previews: some View {NavigationView {DetailView(coin: dev.coin)}}
}

5. 效果图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/132828.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本文整理了Debian 11在国内的几个软件源。

1&#xff0e;使用说明 一般情况下&#xff0c;将/etc/apt/sources.list文件中Debian默认的软件仓库地址和安全更新仓库地址修改为国内的镜像地址即可&#xff0c;比如将deb.debian.org和security.debian.org改为mirrors.xxx.com&#xff0c;并使用https访问&#xff0c;可使用…

保护互联网数据安全:关键方法与最佳实践

在当今数字化时代&#xff0c;互联网数据安全已经成为个人、企业和组织的首要任务之一。随着信息技术的迅猛发展&#xff0c;网络威胁也不断演进&#xff0c;因此保护互联网数据安全变得尤为关键。本文将介绍一些关键方法和最佳实践&#xff0c;帮助您确保互联网数据的安全性。…

webstorm自定义文件模板(Vue + Scss)

最终效果如下&#xff1a; 具体配置如下&#xff1a; 新增文件代码如下&#xff1a; <!--* Description: ${COMPONENT_NAME} 页面* Author: mhf* Date: ${DATE} --> <template><div>${COMPONENT_NAME} </div> </template><script&g…

JVM源码剖析之线程的创建过程

说在前面&#xff1a; 对于Java线程的创建这个话题&#xff0c;似乎已经被"八股文"带偏&#xff5e; 大部分Java程序员从"八股文"得知创建Java线程有N种方式&#xff0c;比如new Thread、new Runnable、Callable、线程池等等&#xff5e; 而笔者写下这篇文…

Maven 构建配置文件

目录 构建配置文件的类型 配置文件激活 配置文件激活实例 1、配置文件激活 2、通过Maven设置激活配置文件 3、通过环境变量激活配置文件 4、通过操作系统激活配置文件 5、通过文件的存在或者缺失激活配置文件 构建配置文件是一系列的配置项的值&#xff0c;可以用来设置…

【深度学习实验】卷积神经网络(七):实现深度残差神经网络ResNet

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. Residual&#xff08;残差连接&#xff09; __init__&#xff08;初始化&#xff09; forward&#xff08;前向传播&#xff09; 2. resnet_block&#xff08;残…

Kotlin函数作为参数指向不同逻辑

Kotlin函数作为参数指向不同逻辑 fun sum(): (Int, Int) -> Int {return { a, b -> (a b) } }fun multiplication(): (Int, Int) -> Int {return { a, b -> (a * b) } }fun main(args: Array<String>) {var math: (Int, Int) -> Intmath sum()println(m…

Unity可视化Shader工具ASE介绍——6、通过例子说明ASE节点的连接方式

大家好&#xff0c;我是阿赵。继续介绍Unity可视化Shader编辑插件ASE的用法。上一篇已经介绍了很多ASE常用的节点。这一篇通过几个小例子&#xff0c;来看看这些节点是怎样连接使用的。   这篇的内容可能会比较长&#xff0c;最终是做了一个遮挡X光的效果&#xff0c;不过把这…

python随手小练5

1、求1-100的累加和&#xff08;终止条件 1-100&#xff09;&#xff08;while和for两种&#xff09; #while循环 count 0 index 0 while index < 100:count indexindex 1 print(count)#for循环 sum 0 for i in range(0,101):sum i print(sum)结果&#xff1a; 5050 2…

拓扑排序求最长路

P1807 最长路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目要求我们求出第1号到第n号节点之间最长的距离。 我们想到使用拓扑排序来求最长路。 正常来讲&#xff0c;我们应该把1号节点入队列&#xff0c;再出队列&#xff0c;把一号节点能到达的所有的点的入度减一&a…

oracle connect by详解

1、作用&#xff1a; 用于存在父子&#xff0c;祖孙&#xff0c;上下级等层级关系的数据表进行层级查询。 2、语法 SELECT ... FROM .... START WITH cond1 CONNECT BY cond2 WHERE cond3;2.1、说明 start with: 指定起始节点的条件 connect by: 指定父子行的条件关系 …

PyTorch 深度学习之加载数据集Dataset and DataLoader(七)

1. Revision: Manual data feed 全部Batch&#xff1a;计算速度&#xff0c;性能有问题 1 个 &#xff1a;跨越鞍点 mini-Batch:均衡速度与性能 2. Terminology: Epoch, Batch-Size, Iteration DataLoader: batch_size2, sheffleTrue 3. How to define your Dataset 两种处…