You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS平台下如何实现NFC读取结果的持久化展示直至用户主动关闭?

iOS平台下如何实现NFC读取结果的持久化展示直至用户主动关闭?

我太懂你这个糟心的问题了——iOS自带的NFC操作栏简直是个“不听话的小家伙”,不仅没法控制它显示多久,连什么时候消失都由系统说了算,想让用户好好看完那些复杂的读取结果真的费劲。你现在遇到的弹窗被压在操作栏后面的问题,其实是因为系统操作栏的消失有延迟,而你立刻弹出的alert刚好撞在了它的显示周期里。

下面给你几个实用的解决思路,亲测能改善体验:

思路一:延迟弹窗,避开系统操作栏的显示周期

最直接的办法就是等系统的操作栏完全消失后,再弹出你的自定义弹窗。只要给个足够稳妥的延迟时间,就能避免重叠的尴尬场景。修改你的代码如下:

tag.readNDEF { message, error in
    if let message = message {
        let textMessage = self.parsePayloads(payloads: message.records) ?? "(none)"
        session.alertMessage = "Field read: \(textMessage)"
        session.invalidate()
        
        // 延迟1.2秒再弹出弹窗,给系统操作栏足够的消失时间
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
            let alertView = UIAlertController(title: "Field Read", message: textMessage, preferredStyle: .alert)
            alertView.addAction(UIAlertAction(title: "OK", style: .default))
            self.viewController?.present(alertView, animated: true)
        }
    }
}

这里选1.2秒是因为不同iOS版本的操作栏消失时间略有差异,这个数值能覆盖大部分场景,既不会让用户等太久,也不会出现弹窗被遮挡的情况。

思路二:利用Session失效回调,精准把控弹窗时机

Core NFC的NFCNDEFReaderSession有个代理方法sessionDidInvalidate,这个方法会在会话失效(也就是你调用session.invalidate()之后)触发,此时系统操作栏已经进入消失流程。我们可以在这个方法里弹出弹窗,再配合短延迟,体验会更丝滑:

首先,先在你的类里加个属性存储读取结果:

private var lastNFCReadResult: String?

然后修改读取回调的代码:

tag.readNDEF { message, error in
    if let message = message {
        let textMessage = self.parsePayloads(payloads: message.records) ?? "(none)"
        session.alertMessage = "Field read: \(textMessage)"
        // 先把结果存起来
        self.lastNFCReadResult = textMessage
        session.invalidate()
    }
}

最后实现代理方法:

func sessionDidInvalidate(_ session: NFCNDEFReaderSession, error: Error?) {
    guard let textMessage = self.lastNFCReadResult else { return }
    
    // 延迟0.5秒弹窗,刚好赶上系统操作栏消失的节点
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        let alertView = UIAlertController(title: "Field Read", message: textMessage, preferredStyle: .alert)
        alertView.addAction(UIAlertAction(title: "OK", style: .default))
        self.viewController?.present(alertView, animated: true)
        // 清空存储的结果,避免下次读取干扰
        self.lastNFCReadResult = nil
    }
}

这种方式比直接在读取回调里延迟更精准,因为它是基于会话失效的时机触发的,适配性更强。

思路三:自定义持久化展示界面(进阶方案)

如果想完全摆脱系统操作栏的限制,让展示风格和你的App统一,你可以在App界面里提前准备一个自定义的结果展示视图(比如半透明弹窗、底部固定浮层等),等系统操作栏消失后再显示它。比如:

  1. 先在你的ViewController里添加一个自定义的结果视图(初始设为隐藏):
private let resultView: UIView = {
    let view = UIView()
    view.backgroundColor = .white
    view.layer.cornerRadius = 12
    view.clipsToBounds = true
    // 这里可以添加UILabel展示结果、关闭按钮等控件
    let resultLabel = UILabel()
    resultLabel.numberOfLines = 0
    resultLabel.textAlignment = .center
    view.addSubview(resultLabel)
    // 给label设置布局约束
    resultLabel.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        resultLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
        resultLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
        resultLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
        resultLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60)
    ])
    
    let closeBtn = UIButton(type: .system)
    closeBtn.setTitle("关闭", for: .normal)
    view.addSubview(closeBtn)
    closeBtn.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        closeBtn.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
        closeBtn.centerXAnchor.constraint(equalTo: view.centerXAnchor)
    ])
    return view
}()
  1. viewDidLoad里把它添加到界面上并隐藏:
override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(resultView)
    // 设置布局约束,让视图居中显示
    resultView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        resultView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        resultView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        resultView.widthAnchor.constraint(equalToConstant: 280)
    ])
    resultView.isHidden = true
}
  1. 然后在sessionDidInvalidate的延迟回调里,更新视图内容并显示:
func sessionDidInvalidate(_ session: NFCNDEFReaderSession, error: Error?) {
    guard let textMessage = self.lastNFCReadResult else { return }
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        // 更新结果视图的文本内容
        if let resultLabel = self.resultView.subviews.first(where: { $0 is UILabel }) as? UILabel {
            resultLabel.text = textMessage
        }
        // 给关闭按钮添加点击事件
        if let closeBtn = self.resultView.subviews.first(where: { $0 is UIButton }) as? UIButton {
            closeBtn.addTarget(self, action: #selector(self.hideResultView), for: .touchUpInside)
        }
        // 显示视图
        self.resultView.isHidden = false
    }
}

@objc private func hideResultView() {
    resultView.isHidden = true
}

这种方式的好处是完全由你控制展示样式和时长,用户可以随时点击关闭,体验更连贯,也更符合App的整体设计风格。

总的来说,前两种方法是快速解决问题的方案,第三种是更定制化的进阶方案,你可以根据自己的需求选择~

备注:内容来源于stack exchange,提问作者davidgyoung

火山引擎 最新活动