导读:本期聚焦于Amelis创作的《如何使用Core NFC在iOS端读取NFC温度记录仪并解析NDEF与绘制温度曲线?》,敬请观看详情。NFC温度记录仪会将采样数据写入NDEF文本或自定义记录,iPhone借助Core NFC框架可直接读取。不少团队在解析时卡在负载前缀字节与字节序处理。本文说明如何从NFCNDEFMessage提取记录,按规范剥离语言码或媒体类型头,把十六进制负载转为有符号温度值,并用采样间隔还原时间戳。随后给出基于UIKit的折线绘制思路,包括坐标映射与离屏渲染缓存,帮助开发者在原生应用中稳定呈现温度随时间变化的过程。

在冷链监控、疫苗运输和食品仓储场景中,NFC温度记录仪凭借无源和低功耗特性被广泛采用。这类设备通常把一段时间内的温度采样结果写入NDEF记录,iOS应用通过Core NFC的读取会话拿到原始字节后,需要完成记录定位、负载解析、时间推算以及可视化展示。下面从工程实现角度拆解整套链路。

如何使用Core NFC在iOS端读取NFC温度记录仪并解析NDEF与绘制温度曲线?

NDEF记录结构与iOS端读取入口

NDEF是NFC论坛定义的数据交换格式,一个NFCNDEFMessage由一个或多个NFCNDEFRecord组成。温度记录仪常见的做法是将数据放在MIME类型记录或者外部类型记录中,负载开头往往带有少量元数据。iOS的Core NFC在iOS 11之后提供NFCTagReaderSession,当扫描到ISO 15693或NTAG系列标签时,可以通过queryNDEFStatus拿到消息对象。

在代码层面,首先需要确认会话代理正确实现,并在获取到NFCNDEFMessage后遍历records数组。每个record的type和payload属性都是Data类型,开发者必须根据设备协议文档判断哪一条记录承载温度数据。很多记录使用RTD_TEXT时,第一个字节的高位表示编码与语言码长度,真正内容从偏移之后开始,忽略这一步会导致解析出乱码。

以下示例展示如何开启会话并安全提取第一条看起来像温度数据的记录。注意真机需要配置NFC权限描述,并且在Info.plist里声明com.apple.developer.nfc.readersession.formats。

import CoreNFC

class TempReader: NSObject, NFCTagReaderSessionDelegate {
    var session: NFCTagReaderSession?

    func start() {
        session = NFCTagReaderSession(pollingOption: .iso15693, delegate: self)
        session?.alertMessage = "请将手机靠近温度记录仪"
        session?.begin()
    }

    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        guard let tag = tags.first else { return }
        session.connect(to: tag) { error in
            if error != nil { session.invalidate(errorMessage: "连接失败"); return }
            if case let .iso15693(isoTag) = tag {
                isoTag.readNDEF { message, err in
                    if let msg = message {
                        self.parse(message: msg)
                    }
                    session.invalidate()
                }
            }
        }
    }

    func parse(message: NFCNDEFMessage) {
        for record in message.records {
            let typeStr = String(data: record.type, encoding: .utf8) ?? ""
            if typeStr.contains("temp") || record.type.count == 0 {
                let payload = record.payload
                print("找到疑似温度记录,长度(payload.count)")
            }
        }
    }

    func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {}
    func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {}
}

温度负载解析与时间戳重建

拿到payload之后,核心难点在于理解厂商自定义的二进制布局。典型方案是用前两个字节表示采样间隔秒数,随后每两个字节为一个有符号小端整数,放大十倍代表摄氏温度。例如数值258表示25.8度。如果设备采用大端序,直接使用Data的withUnsafeBytes按UInt16读取会得到错误结果,必须明确使用littleEndian属性转换。

时间戳重建依赖记录写入时的起始时间。部分记录仪在NDEF文本记录里用ISO8601字符串给出开始时刻,也有设备把起始UNIX时间塞进负载头四个字节。工程上建议优先读取专用头,若缺失则退化为使用手机读取时刻减去总时长来估算。下面的函数演示如何从一个假设布局中提取温度数组和对应的时间点。

解析过程应当做边界保护,当payload长度不是预期倍数时直接丢弃,避免后续绘制越界。同时温度值如果超出合理物理范围,比如低于零下五十或高于零上一百,可以视为坏点予以剔除,保证曲线可信度。

struct Sample {
    let time: Date
    let temp: Double
}

func decode(payload: Data, start: Date, interval: Int) -> [Sample] {
    guard payload.count >= 2 else { return [] }
    let intervalSec = Int(payload[0]) | (Int(payload[1]) << 8)
    var result: [Sample] = []
    var offset = 2
    var index = 0
    while offset + 1 < payload.count {
        let raw = Int(payload[offset]) | (Int(payload[offset+1]) << 8)
        let signed = Int16(bitPattern: UInt16(raw)).littleEndian
        let temp = Double(signed) / 10.0
        let time = start.addingTimeInterval(Double(index * (intervalSec > 0 ? intervalSec : interval)))
        result.append(Sample(time: time, temp: temp))
        offset += 2
        index += 1
    }
    return result
}

基于UIKit的温度曲线绘制方案

将解析出的采样点画成曲线,最直观的方式是用UIView子类配合Core Graphics。横轴映射时间差,纵轴映射温度范围。为了支持缩放查看,可以记录最小最大温度,将数值线性变换到视图高度内。绘制时先填背景网格,再描折线,最后在两端标注极值,这样现场人员一眼能看出是否超温。

如果采样点非常多,比如上千个,每次layoutSubviews都重画会卡顿。此时可以用UIGraphicsImageRenderer把曲线先渲染成UIImage并缓存,只有数据更新时才重绘。另一个细节是用户可能在深色模式下来看,绘制代码应读取traitCollection.userInterfaceStyle切换线条颜色,避免白线白底看不见。

下面给出一个简化的绘制片段,它接收Sample数组并画出自适应折线。实际项目里还可以叠加阈值线,当某段温度高于八度就标红,满足冷链报警需求。

class TempChartView: UIView {
    var samples: [Sample] = []

    override func draw(_ rect: CGRect) {
        guard let ctx = UIGraphicsGetCurrentContext(), samples.count > 1 else { return }
        let temps = samples.map { $0.temp }
        guard let minT = temps.min(), let maxT = temps.max() else { return }
        let range = (maxT - minT) == 0 ? 1 : (maxT - minT)
        let stepX = rect.width / CGFloat(samples.count - 1)

        ctx.setStrokeColor(UIColor.systemBlue.cgColor)
        ctx.setLineWidth(2)
        ctx.beginPath()
        for (i, s) in samples.enumerated() {
            let x = CGFloat(i) * stepX
            let y = rect.height - CGFloat((s.temp - minT) / range) * rect.height
            if i == 0 { ctx.move(to: CGPoint(x: x, y: y)) }
            else { ctx.addLine(to: CGPoint(x: x, y: y)) }
        }
        ctx.strokePath()
    }
}

整体来看,Core NFC读取只是入口,真正的价值在NDEF负载的严谨解析与面向业务的可视化。只要处理好字节序、时间基准和绘制性能,iPhone就能替代专用手持机,成为现场温度审计的轻量工具。

Core_NFCNDEF温度曲线修改时间:2026-08-18 13:54:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。